Back to index

lightning-sunbird  0.9+nobinonly
mozMySpell.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Spellchecker Component.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * David Einstein.
00019  * Portions created by the Initial Developer are Copyright (C) 2001
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s): David Einstein <Deinst@world.std.com>
00023  *                 Kevin Hendricks <kevin.hendricks@sympatico.ca>
00024  *                 Michiel van Leeuwen <mvl@exedo.nl>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  *  This spellchecker is based on the MySpell spellchecker made for Open Office
00039  *  by Kevin Hendricks.  Although the algorithms and code, have changed 
00040  *  slightly, the architecture is still the same. The Mozilla implementation
00041  *  is designed to be compatible with the Open Office dictionaries.
00042  *  Please do not make changes to the affix or dictionary file formats 
00043  *  without attempting to coordinate with Kevin.  For more information 
00044  *  on the original MySpell see 
00045  *  http://whiteboard.openoffice.org/source/browse/whiteboard/lingucomponent/source/spellcheck/myspell/
00046  *
00047  *  A special thanks and credit goes to Geoff Kuenning
00048  * the creator of ispell.  MySpell's affix algorithms were
00049  * based on those of ispell which should be noted is
00050  * copyright Geoff Kuenning et.al. and now available
00051  * under a BSD style license. For more information on ispell
00052  * and affix compression in general, please see:
00053  * http://www.cs.ucla.edu/ficus-members/geoff/ispell.html
00054  * (the home page for ispell)
00055  *
00056  * ***** END LICENSE BLOCK ***** */
00057 
00058 /* based on MySpell (c) 2001 by Kevin Hendicks  */
00059 
00060 #include "mozMySpell.h"
00061 #include "nsReadableUtils.h"
00062 #include "nsXPIDLString.h"
00063 #include "nsIObserverService.h"
00064 #include "nsISimpleEnumerator.h"
00065 #include "nsIDirectoryEnumerator.h"
00066 #include "nsIFile.h"
00067 #include "nsDirectoryServiceUtils.h"
00068 #include "nsDirectoryServiceDefs.h"
00069 #include "mozISpellI18NManager.h"
00070 #include "nsICharsetConverterManager.h"
00071 #include "nsUnicharUtilCIID.h"
00072 #include "nsUnicharUtils.h"
00073 #include "nsCRT.h"
00074 #include <stdlib.h>
00075 
00076 static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);
00077 static NS_DEFINE_CID(kUnicharUtilCID, NS_UNICHARUTIL_CID);
00078 
00079 NS_IMPL_ISUPPORTS3(mozMySpell,
00080                    mozISpellCheckingEngine,
00081                    nsIObserver,
00082                    nsISupportsWeakReference)
00083 
00084 nsresult
00085 mozMySpell::Init()
00086 {
00087   if (!mDictionaries.Init())
00088     return NS_ERROR_OUT_OF_MEMORY;
00089 
00090   LoadDictionaryList();
00091 
00092   nsCOMPtr<nsIObserverService> obs =
00093     do_GetService("@mozilla.org/observer-service;1");
00094   if (obs) {
00095     obs->AddObserver(this, "profile-do-change", PR_TRUE);
00096   }
00097 
00098   return NS_OK;
00099 }
00100 
00101 mozMySpell::~mozMySpell()
00102 {
00103   mPersonalDictionary = nsnull;
00104   delete mMySpell;
00105 }
00106 
00107 /* attribute wstring dictionary; */
00108 NS_IMETHODIMP mozMySpell::GetDictionary(PRUnichar **aDictionary)
00109 {
00110   NS_ENSURE_ARG_POINTER(aDictionary);
00111 
00112   if (mDictionary.IsEmpty())
00113     return NS_ERROR_NOT_INITIALIZED;
00114 
00115   *aDictionary = ToNewUnicode(mDictionary);
00116   return *aDictionary ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00117 }
00118 
00119 /* set the Dictionary.
00120  * This also Loads the dictionary and initializes the converter using the dictionaries converter
00121  */
00122 NS_IMETHODIMP mozMySpell::SetDictionary(const PRUnichar *aDictionary)
00123 {
00124   NS_ENSURE_ARG_POINTER(aDictionary);
00125 
00126   if (mDictionary.Equals(aDictionary))
00127     return NS_OK;
00128 
00129   nsIFile* affFile = mDictionaries.GetWeak(nsDependentString(aDictionary));
00130   if (!affFile)
00131     return NS_ERROR_FILE_NOT_FOUND;
00132 
00133   nsCAutoString dictFileName, affFileName;
00134 
00135   // XXX This isn't really good. nsIFile->NativePath isn't safe for all
00136   // character sets on Windows.
00137   // A better way would be to QI to nsILocalFile, and get a filehandle
00138   // from there. Only problem is that myspell wants a path
00139 
00140   nsresult rv = affFile->GetNativePath(affFileName);
00141   NS_ENSURE_SUCCESS(rv, rv);
00142 
00143   dictFileName = affFileName;
00144   PRInt32 dotPos = dictFileName.RFindChar('.');
00145   if (dotPos == -1)
00146     return NS_ERROR_FAILURE;
00147 
00148   dictFileName.SetLength(dotPos);
00149   dictFileName.AppendLiteral(".dic");
00150 
00151   // SetDictionary can be called multiple times, so we might have a
00152   // valid mMySpell instance which needs cleaned up.
00153   delete mMySpell;
00154 
00155   mDictionary = aDictionary;
00156 
00157   mMySpell = new MySpell(affFileName.get(),
00158                          dictFileName.get());
00159   if (!mMySpell)
00160     return NS_ERROR_OUT_OF_MEMORY;
00161 
00162   nsCOMPtr<nsICharsetConverterManager> ccm =
00163     do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
00164   NS_ENSURE_SUCCESS(rv, rv);
00165 
00166   rv = ccm->GetUnicodeDecoder(mMySpell->get_dic_encoding(),
00167                               getter_AddRefs(mDecoder));
00168   NS_ENSURE_SUCCESS(rv, rv);
00169 
00170   rv = ccm->GetUnicodeEncoder(mMySpell->get_dic_encoding(),
00171                               getter_AddRefs(mEncoder));
00172   NS_ENSURE_SUCCESS(rv, rv);
00173 
00174 
00175   if (mEncoder)
00176     mEncoder->SetOutputErrorBehavior(mEncoder->kOnError_Signal, nsnull, '?');
00177 
00178   PRInt32 pos = mDictionary.FindChar('-');
00179   if (pos == -1)
00180     pos = mDictionary.FindChar('_');
00181 
00182   if (pos == -1)
00183     mLanguage.Assign(mDictionary);
00184   else
00185     mLanguage = Substring(mDictionary, 0, pos);
00186 
00187   return NS_OK;
00188 }
00189 
00190 /* readonly attribute wstring language; */
00191 NS_IMETHODIMP mozMySpell::GetLanguage(PRUnichar **aLanguage)
00192 {
00193   NS_ENSURE_ARG_POINTER(aLanguage);
00194 
00195   if (mDictionary.IsEmpty())
00196     return NS_ERROR_NOT_INITIALIZED;
00197 
00198   *aLanguage = ToNewUnicode(mLanguage);
00199   return *aLanguage ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00200 }
00201 
00202 /* readonly attribute boolean providesPersonalDictionary; */
00203 NS_IMETHODIMP mozMySpell::GetProvidesPersonalDictionary(PRBool *aProvidesPersonalDictionary)
00204 {
00205   NS_ENSURE_ARG_POINTER(aProvidesPersonalDictionary);
00206 
00207   *aProvidesPersonalDictionary = PR_FALSE;
00208   return NS_OK;
00209 }
00210 
00211 /* readonly attribute boolean providesWordUtils; */
00212 NS_IMETHODIMP mozMySpell::GetProvidesWordUtils(PRBool *aProvidesWordUtils)
00213 {
00214   NS_ENSURE_ARG_POINTER(aProvidesWordUtils);
00215 
00216   *aProvidesWordUtils = PR_FALSE;
00217   return NS_OK;
00218 }
00219 
00220 /* readonly attribute wstring name; */
00221 NS_IMETHODIMP mozMySpell::GetName(PRUnichar * *aName)
00222 {
00223   return NS_ERROR_NOT_IMPLEMENTED;
00224 }
00225 
00226 /* readonly attribute wstring copyright; */
00227 NS_IMETHODIMP mozMySpell::GetCopyright(PRUnichar * *aCopyright)
00228 {
00229   return NS_ERROR_NOT_IMPLEMENTED;
00230 }
00231 
00232 /* attribute mozIPersonalDictionary personalDictionary; */
00233 NS_IMETHODIMP mozMySpell::GetPersonalDictionary(mozIPersonalDictionary * *aPersonalDictionary)
00234 {
00235   *aPersonalDictionary = mPersonalDictionary;
00236   NS_IF_ADDREF(*aPersonalDictionary);
00237   return NS_OK;
00238 }
00239 
00240 NS_IMETHODIMP mozMySpell::SetPersonalDictionary(mozIPersonalDictionary * aPersonalDictionary)
00241 {
00242   mPersonalDictionary = aPersonalDictionary;
00243   return NS_OK;
00244 }
00245 
00246 struct AppendNewStruct
00247 {
00248   PRUnichar **dics;
00249   PRUint32 count;
00250   PRBool failed;
00251 };
00252 
00253 static PLDHashOperator
00254 AppendNewString(const nsAString& aString, nsIFile* aFile, void* aClosure)
00255 {
00256   AppendNewStruct *ans = (AppendNewStruct*) aClosure;
00257   ans->dics[ans->count] = ToNewUnicode(aString);
00258   if (!ans->dics[ans->count]) {
00259     ans->failed = PR_TRUE;
00260     return PL_DHASH_STOP;
00261   }
00262 
00263   ++ans->count;
00264   return PL_DHASH_NEXT;
00265 }
00266 
00267 /* void GetDictionaryList ([array, size_is (count)] out wstring dictionaries, out PRUint32 count); */
00268 NS_IMETHODIMP mozMySpell::GetDictionaryList(PRUnichar ***aDictionaries,
00269                                             PRUint32 *aCount)
00270 {
00271   if (!aDictionaries || !aCount)
00272     return NS_ERROR_NULL_POINTER;
00273 
00274   AppendNewStruct ans = {
00275     (PRUnichar**) NS_Alloc(sizeof(PRUnichar*) * mDictionaries.Count()),
00276     0,
00277     PR_FALSE
00278   };
00279 
00280   // This pointer is used during enumeration
00281   mDictionaries.EnumerateRead(AppendNewString, &ans);
00282 
00283   if (ans.failed) {
00284     while (ans.count) {
00285       --ans.count;
00286       NS_Free(ans.dics[ans.count]);
00287     }
00288     NS_Free(ans.dics);
00289     return NS_ERROR_OUT_OF_MEMORY;
00290   }
00291 
00292   *aDictionaries = ans.dics;
00293   *aCount = ans.count;
00294 
00295   return NS_OK;
00296 }
00297 
00298 void
00299 mozMySpell::LoadDictionaryList()
00300 {
00301   mDictionaries.Clear();
00302 
00303   nsresult rv;
00304 
00305   nsCOMPtr<nsIProperties> dirSvc =
00306     do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
00307   if (!dirSvc)
00308     return;
00309 
00310   nsCOMPtr<nsIFile> dictDir;
00311   rv = dirSvc->Get(DICTIONARY_SEARCH_DIRECTORY,
00312                    NS_GET_IID(nsIFile), getter_AddRefs(dictDir));
00313   if (NS_FAILED(rv)) {
00314     // default to appdir/dictionaries
00315     rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR,
00316                      NS_GET_IID(nsIFile), getter_AddRefs(dictDir));
00317     if (NS_FAILED(rv))
00318       return;
00319 
00320     dictDir->AppendNative(NS_LITERAL_CSTRING("dictionaries"));
00321   }
00322 
00323   LoadDictionariesFromDir(dictDir);
00324 
00325   nsCOMPtr<nsISimpleEnumerator> dictDirs;
00326   rv = dirSvc->Get(DICTIONARY_SEARCH_DIRECTORY_LIST,
00327                    NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(dictDirs));
00328   if (NS_FAILED(rv))
00329     return;
00330 
00331   PRBool hasMore;
00332   while (NS_SUCCEEDED(dictDirs->HasMoreElements(&hasMore)) && hasMore) {
00333     nsCOMPtr<nsISupports> elem;
00334     dictDirs->GetNext(getter_AddRefs(elem));
00335 
00336     dictDir = do_QueryInterface(elem);
00337     if (dictDir)
00338       LoadDictionariesFromDir(dictDir);
00339   }
00340 }
00341 
00342 void
00343 mozMySpell::LoadDictionariesFromDir(nsIFile* aDir)
00344 {
00345   nsresult rv;
00346 
00347   PRBool check = PR_FALSE;
00348   rv = aDir->Exists(&check);
00349   if (NS_FAILED(rv) || !check)
00350     return;
00351 
00352   rv = aDir->IsDirectory(&check);
00353   if (NS_FAILED(rv) || !check)
00354     return;
00355 
00356   nsCOMPtr<nsISimpleEnumerator> e;
00357   rv = aDir->GetDirectoryEntries(getter_AddRefs(e));
00358   if (NS_FAILED(rv))
00359     return;
00360 
00361   nsCOMPtr<nsIDirectoryEnumerator> files(do_QueryInterface(e));
00362   if (!files)
00363     return;
00364 
00365   nsCOMPtr<nsIFile> file;
00366   while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
00367     nsAutoString leafName;
00368     file->GetLeafName(leafName);
00369     if (!StringEndsWith(leafName, NS_LITERAL_STRING(".dic")))
00370       continue;
00371 
00372     nsAutoString dict(leafName);
00373     dict.SetLength(dict.Length() - 4); // magic length of ".dic"
00374 
00375     // check for the presence of the .aff file
00376     leafName = dict;
00377     leafName.AppendLiteral(".aff");
00378     file->SetLeafName(leafName);
00379     rv = file->Exists(&check);
00380     if (NS_FAILED(rv) || !check)
00381       continue;
00382 
00383 #ifdef DEBUG_bsmedberg
00384     printf("Adding dictionary: %s\n", NS_ConvertUTF16toUTF8(dict).get());
00385 #endif
00386 
00387     mDictionaries.Put(dict, file);
00388   }
00389 }
00390 
00391 nsresult mozMySpell::ConvertCharset(const PRUnichar* aStr, char ** aDst)
00392 {
00393   NS_ENSURE_ARG_POINTER(aDst);
00394   NS_ENSURE_TRUE(mEncoder, NS_ERROR_NULL_POINTER);
00395 
00396   PRInt32 outLength;
00397   PRInt32 inLength = nsCRT::strlen(aStr);
00398   nsresult rv = mEncoder->GetMaxLength(aStr, inLength, &outLength);
00399   NS_ENSURE_SUCCESS(rv, rv);
00400 
00401   *aDst = (char *) nsMemory::Alloc(sizeof(char) * (outLength+1));
00402   NS_ENSURE_TRUE(*aDst, NS_ERROR_OUT_OF_MEMORY);
00403 
00404   rv = mEncoder->Convert(aStr, &inLength, *aDst, &outLength);
00405   if (NS_SUCCEEDED(rv))
00406     (*aDst)[outLength] = '\0'; 
00407 
00408   return rv;
00409 }
00410 
00411 /* boolean Check (in wstring word); */
00412 NS_IMETHODIMP mozMySpell::Check(const PRUnichar *aWord, PRBool *aResult)
00413 {
00414   NS_ENSURE_ARG_POINTER(aWord);
00415   NS_ENSURE_ARG_POINTER(aResult);
00416   NS_ENSURE_TRUE(mMySpell, NS_ERROR_FAILURE);
00417 
00418   nsXPIDLCString charsetWord;
00419   nsresult rv = ConvertCharset(aWord, getter_Copies(charsetWord));
00420   NS_ENSURE_SUCCESS(rv, rv);
00421 
00422   *aResult = mMySpell->spell(charsetWord);
00423 
00424 
00425   if (!*aResult && mPersonalDictionary) 
00426     rv = mPersonalDictionary->Check(aWord, mLanguage.get(), aResult);
00427   
00428   return rv;
00429 }
00430 
00431 /* void Suggest (in wstring word, [array, size_is (count)] out wstring suggestions, out PRUint32 count); */
00432 NS_IMETHODIMP mozMySpell::Suggest(const PRUnichar *aWord, PRUnichar ***aSuggestions, PRUint32 *aSuggestionCount)
00433 {
00434   NS_ENSURE_ARG_POINTER(aSuggestions);
00435   NS_ENSURE_ARG_POINTER(aSuggestionCount);
00436   NS_ENSURE_TRUE(mMySpell, NS_ERROR_FAILURE);
00437 
00438   nsresult rv;
00439   *aSuggestionCount = 0;
00440   
00441   nsXPIDLCString charsetWord;
00442   rv = ConvertCharset(aWord, getter_Copies(charsetWord));
00443   NS_ENSURE_SUCCESS(rv, rv);
00444 
00445   char ** wlst;
00446   *aSuggestionCount = mMySpell->suggest(&wlst, charsetWord);
00447 
00448   if (*aSuggestionCount) {    
00449     *aSuggestions  = (PRUnichar **)nsMemory::Alloc(*aSuggestionCount * sizeof(PRUnichar *));    
00450     if (*aSuggestions) {
00451       PRUint32 index = 0;
00452       for (index = 0; index < *aSuggestionCount && NS_SUCCEEDED(rv); ++index) {
00453         // Convert the suggestion to utf16     
00454         PRInt32 inLength = nsCRT::strlen(wlst[index]);
00455         PRInt32 outLength;
00456         rv = mDecoder->GetMaxLength(wlst[index], inLength, &outLength);
00457         if (NS_SUCCEEDED(rv))
00458         {
00459           (*aSuggestions)[index] = (PRUnichar *) nsMemory::Alloc(sizeof(PRUnichar) * (outLength+1));
00460           if ((*aSuggestions)[index])
00461           {
00462             rv = mDecoder->Convert(wlst[index], &inLength, (*aSuggestions)[index], &outLength);
00463             if (NS_SUCCEEDED(rv))
00464               (*aSuggestions)[index][outLength] = 0;
00465           } 
00466           else
00467             rv = NS_ERROR_OUT_OF_MEMORY;
00468         }
00469       }
00470 
00471       if (NS_FAILED(rv))
00472         NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(index, *aSuggestions); // free the PRUnichar strings up to the point at which the error occurred
00473     }
00474     else // if (*aSuggestions)
00475       rv = NS_ERROR_OUT_OF_MEMORY;
00476   }
00477   
00478   NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(*aSuggestionCount, wlst);
00479   return rv;
00480 }
00481 
00482 NS_IMETHODIMP
00483 mozMySpell::Observe(nsISupports* aSubj, const char *aTopic,
00484                     const PRUnichar *aData)
00485 {
00486   NS_ASSERTION(!strcmp(aTopic, "profile-do-change"),
00487                "Unexpected observer topic");
00488 
00489   LoadDictionaryList();
00490 
00491   return NS_OK;
00492 }