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
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)
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"
66 #include "nsDirectoryServiceUtils.h"
67 #include "nsDirectoryServiceDefs.h"
68 #include "mozISpellI18NManager.h"
69 #include "nsICharsetConverterManager.h"
70 #include "nsUnicharUtilCIID.h"
71 #include "nsUnicharUtils.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
,
81 nsISupportsWeakReference
)
86 if (!mDictionaries
.Init())
87 return NS_ERROR_OUT_OF_MEMORY
;
91 nsCOMPtr
<nsIObserverService
> obs
=
92 do_GetService("@mozilla.org/observer-service;1");
94 obs
->AddObserver(this, "profile-do-change", PR_TRUE
);
100 mozHunspell::~mozHunspell()
102 mPersonalDictionary
= nsnull
;
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
))
128 nsIFile
* affFile
= mDictionaries
.GetWeak(nsDependentString(aDictionary
));
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('.');
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.
154 mDictionary
= aDictionary
;
156 mHunspell
= new Hunspell(affFileName
.get(),
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
);
175 mEncoder
->SetOutputErrorBehavior(mEncoder
->kOnError_Signal
, nsnull
, '?');
177 PRInt32 pos
= mDictionary
.FindChar('-');
179 pos
= mDictionary
.FindChar('_');
182 mLanguage
.Assign(mDictionary
);
184 mLanguage
= Substring(mDictionary
, 0, pos
);
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
;
210 /* readonly attribute boolean providesWordUtils; */
211 NS_IMETHODIMP
mozHunspell::GetProvidesWordUtils(PRBool
*aProvidesWordUtils
)
213 NS_ENSURE_ARG_POINTER(aProvidesWordUtils
);
215 *aProvidesWordUtils
= PR_FALSE
;
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
);
239 NS_IMETHODIMP
mozHunspell::SetPersonalDictionary(mozIPersonalDictionary
* aPersonalDictionary
)
241 mPersonalDictionary
= aPersonalDictionary
;
245 struct AppendNewStruct
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
;
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
,
270 if (!aDictionaries
|| !aCount
)
271 return NS_ERROR_NULL_POINTER
;
273 AppendNewStruct ans
= {
274 (PRUnichar
**) NS_Alloc(sizeof(PRUnichar
*) * mDictionaries
.Count()),
279 // This pointer is used during enumeration
280 mDictionaries
.EnumerateRead(AppendNewString
, &ans
);
285 NS_Free(ans
.dics
[ans
.count
]);
288 return NS_ERROR_OUT_OF_MEMORY
;
291 *aDictionaries
= ans
.dics
;
298 mozHunspell::LoadDictionaryList()
300 mDictionaries
.Clear();
304 nsCOMPtr
<nsIProperties
> dirSvc
=
305 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID
);
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
);
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
));
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
));
343 while (NS_SUCCEEDED(dictDirs
->HasMoreElements(&hasMore
)) && hasMore
) {
344 nsCOMPtr
<nsISupports
> elem
;
345 dictDirs
->GetNext(getter_AddRefs(elem
));
347 dictDir
= do_QueryInterface(elem
);
349 LoadDictionariesFromDir(dictDir
);
354 mozHunspell::LoadDictionariesFromDir(nsIFile
* aDir
)
358 PRBool check
= PR_FALSE
;
359 rv
= aDir
->Exists(&check
);
360 if (NS_FAILED(rv
) || !check
)
363 rv
= aDir
->IsDirectory(&check
);
364 if (NS_FAILED(rv
) || !check
)
367 nsCOMPtr
<nsISimpleEnumerator
> e
;
368 rv
= aDir
->GetDirectoryEntries(getter_AddRefs(e
));
372 nsCOMPtr
<nsIDirectoryEnumerator
> files(do_QueryInterface(e
));
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")))
383 nsAutoString
dict(leafName
);
384 dict
.SetLength(dict
.Length() - 4); // magic length of ".dic"
386 // check for the presence of the .aff file
388 leafName
.AppendLiteral(".aff");
389 file
->SetLeafName(leafName
);
390 rv
= file
->Exists(&check
);
391 if (NS_FAILED(rv
) || !check
)
394 #ifdef DEBUG_bsmedberg
395 printf("Adding dictionary: %s\n", NS_ConvertUTF16toUTF8(dict
).get());
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
);
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';
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
);
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
);
450 *aSuggestionCount
= 0;
452 nsXPIDLCString charsetWord
;
453 rv
= ConvertCharset(aWord
, getter_Copies(charsetWord
));
454 NS_ENSURE_SUCCESS(rv
, rv
);
457 *aSuggestionCount
= mHunspell
->suggest(&wlst
, charsetWord
);
459 if (*aSuggestionCount
) {
460 *aSuggestions
= (PRUnichar
**)nsMemory::Alloc(*aSuggestionCount
* sizeof(PRUnichar
*));
463 for (index
= 0; index
< *aSuggestionCount
&& NS_SUCCEEDED(rv
); ++index
) {
464 // Convert the suggestion to utf16
465 PRInt32 inLength
= nsCRT::strlen(wlst
[index
]);
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;
478 rv
= NS_ERROR_OUT_OF_MEMORY
;
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
);
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();