Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / extensions / spellcheck / hunspell / src / mozHunspell.cpp
blob92f0f02a13bf94a9843528dbc622063a06ede07a
1 /******* BEGIN LICENSE BLOCK *******
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
14 * The Initial Developers of the Original Code are Kevin Hendricks (MySpell)
15 * and László Németh (Hunspell). Portions created by the Initial Developers
16 * are Copyright (C) 2002-2005 the Initial Developers. All Rights Reserved.
18 * Contributor(s): Kevin Hendricks (kevin.hendricks@sympatico.ca)
19 * David Einstein (deinst@world.std.com)
20 * Michiel van Leeuwen (mvl@exedo.nl)
21 * Caolan McNamara (cmc@openoffice.org)
22 * László Németh (nemethl@gyorsposta.hu)
23 * Davide Prina
24 * Giuseppe Modugno
25 * Gianluca Turconi
26 * Simon Brouwer
27 * Noll Janos
28 * Biro Arpad
29 * Goldman Eleonora
30 * Sarlos Tamas
31 * Bencsath Boldizsar
32 * Halacsy Peter
33 * Dvornik Laszlo
34 * Gefferth Andras
35 * Nagy Viktor
36 * Varga Daniel
37 * Chris Halls
38 * Rene Engelhard
39 * Bram Moolenaar
40 * Dafydd Jones
41 * Harri Pitkanen
42 * Andras Timar
43 * Tor Lillqvist
45 * Alternatively, the contents of this file may be used under the terms of
46 * either the GNU General Public License Version 2 or later (the "GPL"), or
47 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
48 * in which case the provisions of the GPL or the LGPL are applicable instead
49 * of those above. If you wish to allow use of your version of this file only
50 * under the terms of either the GPL or the LGPL, and not to allow others to
51 * use your version of this file under the terms of the MPL, indicate your
52 * decision by deleting the provisions above and replace them with the notice
53 * and other provisions required by the GPL or the LGPL. If you do not delete
54 * the provisions above, a recipient may use your version of this file under
55 * the terms of any one of the MPL, the GPL or the LGPL.
57 ******* END LICENSE BLOCK *******/
59 #include "mozHunspell.h"
60 #include "nsReadableUtils.h"
61 #include "nsXPIDLString.h"
62 #include "nsIObserverService.h"
63 #include "nsISimpleEnumerator.h"
64 #include "nsIDirectoryEnumerator.h"
65 #include "nsIFile.h"
66 #include "nsDirectoryServiceUtils.h"
67 #include "nsDirectoryServiceDefs.h"
68 #include "mozISpellI18NManager.h"
69 #include "nsICharsetConverterManager.h"
70 #include "nsUnicharUtilCIID.h"
71 #include "nsUnicharUtils.h"
72 #include "nsCRT.h"
73 #include <stdlib.h>
75 static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);
76 static NS_DEFINE_CID(kUnicharUtilCID, NS_UNICHARUTIL_CID);
78 NS_IMPL_ISUPPORTS3(mozHunspell,
79 mozISpellCheckingEngine,
80 nsIObserver,
81 nsISupportsWeakReference)
83 nsresult
84 mozHunspell::Init()
86 if (!mDictionaries.Init())
87 return NS_ERROR_OUT_OF_MEMORY;
89 LoadDictionaryList();
91 nsCOMPtr<nsIObserverService> obs =
92 do_GetService("@mozilla.org/observer-service;1");
93 if (obs) {
94 obs->AddObserver(this, "profile-do-change", PR_TRUE);
97 return NS_OK;
100 mozHunspell::~mozHunspell()
102 mPersonalDictionary = nsnull;
103 delete mHunspell;
106 /* attribute wstring dictionary; */
107 NS_IMETHODIMP mozHunspell::GetDictionary(PRUnichar **aDictionary)
109 NS_ENSURE_ARG_POINTER(aDictionary);
111 if (mDictionary.IsEmpty())
112 return NS_ERROR_NOT_INITIALIZED;
114 *aDictionary = ToNewUnicode(mDictionary);
115 return *aDictionary ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
118 /* set the Dictionary.
119 * This also Loads the dictionary and initializes the converter using the dictionaries converter
121 NS_IMETHODIMP mozHunspell::SetDictionary(const PRUnichar *aDictionary)
123 NS_ENSURE_ARG_POINTER(aDictionary);
125 if (mDictionary.Equals(aDictionary))
126 return NS_OK;
128 nsIFile* affFile = mDictionaries.GetWeak(nsDependentString(aDictionary));
129 if (!affFile)
130 return NS_ERROR_FILE_NOT_FOUND;
132 nsCAutoString dictFileName, affFileName;
134 // XXX This isn't really good. nsIFile->NativePath isn't safe for all
135 // character sets on Windows.
136 // A better way would be to QI to nsILocalFile, and get a filehandle
137 // from there. Only problem is that hunspell wants a path
139 nsresult rv = affFile->GetNativePath(affFileName);
140 NS_ENSURE_SUCCESS(rv, rv);
142 dictFileName = affFileName;
143 PRInt32 dotPos = dictFileName.RFindChar('.');
144 if (dotPos == -1)
145 return NS_ERROR_FAILURE;
147 dictFileName.SetLength(dotPos);
148 dictFileName.AppendLiteral(".dic");
150 // SetDictionary can be called multiple times, so we might have a
151 // valid mHunspell instance which needs cleaned up.
152 delete mHunspell;
154 mDictionary = aDictionary;
156 mHunspell = new Hunspell(affFileName.get(),
157 dictFileName.get());
158 if (!mHunspell)
159 return NS_ERROR_OUT_OF_MEMORY;
161 nsCOMPtr<nsICharsetConverterManager> ccm =
162 do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
163 NS_ENSURE_SUCCESS(rv, rv);
165 rv = ccm->GetUnicodeDecoder(mHunspell->get_dic_encoding(),
166 getter_AddRefs(mDecoder));
167 NS_ENSURE_SUCCESS(rv, rv);
169 rv = ccm->GetUnicodeEncoder(mHunspell->get_dic_encoding(),
170 getter_AddRefs(mEncoder));
171 NS_ENSURE_SUCCESS(rv, rv);
174 if (mEncoder)
175 mEncoder->SetOutputErrorBehavior(mEncoder->kOnError_Signal, nsnull, '?');
177 PRInt32 pos = mDictionary.FindChar('-');
178 if (pos == -1)
179 pos = mDictionary.FindChar('_');
181 if (pos == -1)
182 mLanguage.Assign(mDictionary);
183 else
184 mLanguage = Substring(mDictionary, 0, pos);
186 return NS_OK;
189 /* readonly attribute wstring language; */
190 NS_IMETHODIMP mozHunspell::GetLanguage(PRUnichar **aLanguage)
192 NS_ENSURE_ARG_POINTER(aLanguage);
194 if (mDictionary.IsEmpty())
195 return NS_ERROR_NOT_INITIALIZED;
197 *aLanguage = ToNewUnicode(mLanguage);
198 return *aLanguage ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
201 /* readonly attribute boolean providesPersonalDictionary; */
202 NS_IMETHODIMP mozHunspell::GetProvidesPersonalDictionary(PRBool *aProvidesPersonalDictionary)
204 NS_ENSURE_ARG_POINTER(aProvidesPersonalDictionary);
206 *aProvidesPersonalDictionary = PR_FALSE;
207 return NS_OK;
210 /* readonly attribute boolean providesWordUtils; */
211 NS_IMETHODIMP mozHunspell::GetProvidesWordUtils(PRBool *aProvidesWordUtils)
213 NS_ENSURE_ARG_POINTER(aProvidesWordUtils);
215 *aProvidesWordUtils = PR_FALSE;
216 return NS_OK;
219 /* readonly attribute wstring name; */
220 NS_IMETHODIMP mozHunspell::GetName(PRUnichar * *aName)
222 return NS_ERROR_NOT_IMPLEMENTED;
225 /* readonly attribute wstring copyright; */
226 NS_IMETHODIMP mozHunspell::GetCopyright(PRUnichar * *aCopyright)
228 return NS_ERROR_NOT_IMPLEMENTED;
231 /* attribute mozIPersonalDictionary personalDictionary; */
232 NS_IMETHODIMP mozHunspell::GetPersonalDictionary(mozIPersonalDictionary * *aPersonalDictionary)
234 *aPersonalDictionary = mPersonalDictionary;
235 NS_IF_ADDREF(*aPersonalDictionary);
236 return NS_OK;
239 NS_IMETHODIMP mozHunspell::SetPersonalDictionary(mozIPersonalDictionary * aPersonalDictionary)
241 mPersonalDictionary = aPersonalDictionary;
242 return NS_OK;
245 struct AppendNewStruct
247 PRUnichar **dics;
248 PRUint32 count;
249 PRBool failed;
252 static PLDHashOperator
253 AppendNewString(const nsAString& aString, nsIFile* aFile, void* aClosure)
255 AppendNewStruct *ans = (AppendNewStruct*) aClosure;
256 ans->dics[ans->count] = ToNewUnicode(aString);
257 if (!ans->dics[ans->count]) {
258 ans->failed = PR_TRUE;
259 return PL_DHASH_STOP;
262 ++ans->count;
263 return PL_DHASH_NEXT;
266 /* void GetDictionaryList ([array, size_is (count)] out wstring dictionaries, out PRUint32 count); */
267 NS_IMETHODIMP mozHunspell::GetDictionaryList(PRUnichar ***aDictionaries,
268 PRUint32 *aCount)
270 if (!aDictionaries || !aCount)
271 return NS_ERROR_NULL_POINTER;
273 AppendNewStruct ans = {
274 (PRUnichar**) NS_Alloc(sizeof(PRUnichar*) * mDictionaries.Count()),
276 PR_FALSE
279 // This pointer is used during enumeration
280 mDictionaries.EnumerateRead(AppendNewString, &ans);
282 if (ans.failed) {
283 while (ans.count) {
284 --ans.count;
285 NS_Free(ans.dics[ans.count]);
287 NS_Free(ans.dics);
288 return NS_ERROR_OUT_OF_MEMORY;
291 *aDictionaries = ans.dics;
292 *aCount = ans.count;
294 return NS_OK;
297 void
298 mozHunspell::LoadDictionaryList()
300 mDictionaries.Clear();
302 nsresult rv;
304 nsCOMPtr<nsIProperties> dirSvc =
305 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
306 if (!dirSvc)
307 return;
309 nsCOMPtr<nsIFile> dictDir;
310 rv = dirSvc->Get(DICTIONARY_SEARCH_DIRECTORY,
311 NS_GET_IID(nsIFile), getter_AddRefs(dictDir));
312 if (NS_SUCCEEDED(rv)) {
313 LoadDictionariesFromDir(dictDir);
315 else {
316 // try to load gredir/dictionaries
317 nsCOMPtr<nsIFile> greDir;
318 rv = dirSvc->Get(NS_GRE_DIR,
319 NS_GET_IID(nsIFile), getter_AddRefs(greDir));
320 if (NS_SUCCEEDED(rv)) {
321 greDir->AppendNative(NS_LITERAL_CSTRING("dictionaries"));
322 LoadDictionariesFromDir(greDir);
325 // try to load appdir/dictionaries only if different than gredir
326 nsCOMPtr<nsIFile> appDir;
327 rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR,
328 NS_GET_IID(nsIFile), getter_AddRefs(appDir));
329 PRBool equals;
330 if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(appDir->Equals(greDir, &equals)) && !equals) {
331 appDir->AppendNative(NS_LITERAL_CSTRING("dictionaries"));
332 LoadDictionariesFromDir(appDir);
336 nsCOMPtr<nsISimpleEnumerator> dictDirs;
337 rv = dirSvc->Get(DICTIONARY_SEARCH_DIRECTORY_LIST,
338 NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(dictDirs));
339 if (NS_FAILED(rv))
340 return;
342 PRBool hasMore;
343 while (NS_SUCCEEDED(dictDirs->HasMoreElements(&hasMore)) && hasMore) {
344 nsCOMPtr<nsISupports> elem;
345 dictDirs->GetNext(getter_AddRefs(elem));
347 dictDir = do_QueryInterface(elem);
348 if (dictDir)
349 LoadDictionariesFromDir(dictDir);
353 void
354 mozHunspell::LoadDictionariesFromDir(nsIFile* aDir)
356 nsresult rv;
358 PRBool check = PR_FALSE;
359 rv = aDir->Exists(&check);
360 if (NS_FAILED(rv) || !check)
361 return;
363 rv = aDir->IsDirectory(&check);
364 if (NS_FAILED(rv) || !check)
365 return;
367 nsCOMPtr<nsISimpleEnumerator> e;
368 rv = aDir->GetDirectoryEntries(getter_AddRefs(e));
369 if (NS_FAILED(rv))
370 return;
372 nsCOMPtr<nsIDirectoryEnumerator> files(do_QueryInterface(e));
373 if (!files)
374 return;
376 nsCOMPtr<nsIFile> file;
377 while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
378 nsAutoString leafName;
379 file->GetLeafName(leafName);
380 if (!StringEndsWith(leafName, NS_LITERAL_STRING(".dic")))
381 continue;
383 nsAutoString dict(leafName);
384 dict.SetLength(dict.Length() - 4); // magic length of ".dic"
386 // check for the presence of the .aff file
387 leafName = dict;
388 leafName.AppendLiteral(".aff");
389 file->SetLeafName(leafName);
390 rv = file->Exists(&check);
391 if (NS_FAILED(rv) || !check)
392 continue;
394 #ifdef DEBUG_bsmedberg
395 printf("Adding dictionary: %s\n", NS_ConvertUTF16toUTF8(dict).get());
396 #endif
398 mDictionaries.Put(dict, file);
402 nsresult mozHunspell::ConvertCharset(const PRUnichar* aStr, char ** aDst)
404 NS_ENSURE_ARG_POINTER(aDst);
405 NS_ENSURE_TRUE(mEncoder, NS_ERROR_NULL_POINTER);
407 PRInt32 outLength;
408 PRInt32 inLength = nsCRT::strlen(aStr);
409 nsresult rv = mEncoder->GetMaxLength(aStr, inLength, &outLength);
410 NS_ENSURE_SUCCESS(rv, rv);
412 *aDst = (char *) nsMemory::Alloc(sizeof(char) * (outLength+1));
413 NS_ENSURE_TRUE(*aDst, NS_ERROR_OUT_OF_MEMORY);
415 rv = mEncoder->Convert(aStr, &inLength, *aDst, &outLength);
416 if (NS_SUCCEEDED(rv))
417 (*aDst)[outLength] = '\0';
419 return rv;
422 /* boolean Check (in wstring word); */
423 NS_IMETHODIMP mozHunspell::Check(const PRUnichar *aWord, PRBool *aResult)
425 NS_ENSURE_ARG_POINTER(aWord);
426 NS_ENSURE_ARG_POINTER(aResult);
427 NS_ENSURE_TRUE(mHunspell, NS_ERROR_FAILURE);
429 nsXPIDLCString charsetWord;
430 nsresult rv = ConvertCharset(aWord, getter_Copies(charsetWord));
431 NS_ENSURE_SUCCESS(rv, rv);
433 *aResult = !!mHunspell->spell(charsetWord);
436 if (!*aResult && mPersonalDictionary)
437 rv = mPersonalDictionary->Check(aWord, mLanguage.get(), aResult);
439 return rv;
442 /* void Suggest (in wstring word, [array, size_is (count)] out wstring suggestions, out PRUint32 count); */
443 NS_IMETHODIMP mozHunspell::Suggest(const PRUnichar *aWord, PRUnichar ***aSuggestions, PRUint32 *aSuggestionCount)
445 NS_ENSURE_ARG_POINTER(aSuggestions);
446 NS_ENSURE_ARG_POINTER(aSuggestionCount);
447 NS_ENSURE_TRUE(mHunspell, NS_ERROR_FAILURE);
449 nsresult rv;
450 *aSuggestionCount = 0;
452 nsXPIDLCString charsetWord;
453 rv = ConvertCharset(aWord, getter_Copies(charsetWord));
454 NS_ENSURE_SUCCESS(rv, rv);
456 char ** wlst;
457 *aSuggestionCount = mHunspell->suggest(&wlst, charsetWord);
459 if (*aSuggestionCount) {
460 *aSuggestions = (PRUnichar **)nsMemory::Alloc(*aSuggestionCount * sizeof(PRUnichar *));
461 if (*aSuggestions) {
462 PRUint32 index = 0;
463 for (index = 0; index < *aSuggestionCount && NS_SUCCEEDED(rv); ++index) {
464 // Convert the suggestion to utf16
465 PRInt32 inLength = nsCRT::strlen(wlst[index]);
466 PRInt32 outLength;
467 rv = mDecoder->GetMaxLength(wlst[index], inLength, &outLength);
468 if (NS_SUCCEEDED(rv))
470 (*aSuggestions)[index] = (PRUnichar *) nsMemory::Alloc(sizeof(PRUnichar) * (outLength+1));
471 if ((*aSuggestions)[index])
473 rv = mDecoder->Convert(wlst[index], &inLength, (*aSuggestions)[index], &outLength);
474 if (NS_SUCCEEDED(rv))
475 (*aSuggestions)[index][outLength] = 0;
477 else
478 rv = NS_ERROR_OUT_OF_MEMORY;
482 if (NS_FAILED(rv))
483 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(index, *aSuggestions); // free the PRUnichar strings up to the point at which the error occurred
485 else // if (*aSuggestions)
486 rv = NS_ERROR_OUT_OF_MEMORY;
489 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(*aSuggestionCount, wlst);
490 return rv;
493 NS_IMETHODIMP
494 mozHunspell::Observe(nsISupports* aSubj, const char *aTopic,
495 const PRUnichar *aData)
497 NS_ASSERTION(!strcmp(aTopic, "profile-do-change"),
498 "Unexpected observer topic");
500 LoadDictionaryList();
502 return NS_OK;