Back to index

lightning-sunbird  0.9+nobinonly
mozSpellChecker.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  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 
00039 #include "mozSpellChecker.h"
00040 #include "nsIServiceManager.h"
00041 #include "mozISpellI18NManager.h"
00042 #include "nsIStringEnumerator.h"
00043 
00044 #define UNREASONABLE_WORD_LENGTH 64
00045 
00046 NS_IMPL_ISUPPORTS1(mozSpellChecker, nsISpellChecker)
00047 
00048 mozSpellChecker::mozSpellChecker()
00049 {
00050 }
00051 
00052 mozSpellChecker::~mozSpellChecker()
00053 {
00054   if(mPersonalDictionary){
00055     //    mPersonalDictionary->Save();
00056     mPersonalDictionary->EndSession();
00057   }
00058   mSpellCheckingEngine = nsnull;
00059   mPersonalDictionary = nsnull;
00060 }
00061 
00062 nsresult 
00063 mozSpellChecker::Init()
00064 {
00065   mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
00066 
00067   nsresult rv;
00068   mSpellCheckingEngine = do_GetService("@mozilla.org/spellchecker/myspell;1",&rv);
00069   if (NS_FAILED(rv)) {
00070     return rv;
00071   }
00072   mSpellCheckingEngine->SetPersonalDictionary(mPersonalDictionary);
00073   return NS_OK;
00074 }
00075 
00076 NS_IMETHODIMP 
00077 mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, PRBool aFromStartofDoc)
00078 {
00079   mTsDoc = aDoc;
00080   mFromStart = aFromStartofDoc;
00081   return NS_OK;
00082 }
00083 
00084 
00085 NS_IMETHODIMP 
00086 mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsStringArray *aSuggestions)
00087 {
00088   if(!aSuggestions||!mConverter)
00089     return NS_ERROR_NULL_POINTER;
00090 
00091   PRUint32 selOffset;
00092   PRInt32 begin,end;
00093   nsresult result;
00094   result = SetupDoc(&selOffset);
00095   PRBool isMisspelled,done;
00096   if (NS_FAILED(result))
00097     return result;
00098 
00099   while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
00100     {
00101       nsString str;
00102       result = mTsDoc->GetCurrentTextBlock(&str);
00103   
00104       if (NS_FAILED(result))
00105         return result;
00106       do{
00107         result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
00108         if(NS_SUCCEEDED(result)&&(begin != -1)){
00109           const nsAString &currWord = Substring(str, begin, end - begin);
00110           result = CheckWord(currWord, &isMisspelled, aSuggestions);
00111           if(isMisspelled){
00112             aWord = currWord;
00113             mTsDoc->SetSelection(begin, end-begin);
00114             mTsDoc->ScrollSelectionIntoView();
00115             return NS_OK;
00116           }
00117         }
00118         selOffset = end;
00119       }while(end != -1);
00120       mTsDoc->NextBlock();
00121       selOffset=0;
00122     }
00123   return NS_OK;
00124 }
00125 
00126 NS_IMETHODIMP 
00127 mozSpellChecker::CheckWord(const nsAString &aWord, PRBool *aIsMisspelled, nsStringArray *aSuggestions)
00128 {
00129   nsresult result;
00130   PRBool correct;
00131   if(!mSpellCheckingEngine)
00132     return NS_ERROR_NULL_POINTER;
00133 
00134   // don't bother to check crazy words, also, myspell gets unhappy if you
00135   // give it too much data and crashes sometimes
00136   if (aWord.Length() > UNREASONABLE_WORD_LENGTH) {
00137     *aIsMisspelled = PR_TRUE;
00138     return NS_OK;
00139   }
00140 
00141   *aIsMisspelled = PR_FALSE;
00142   result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct);
00143   NS_ENSURE_SUCCESS(result, result);
00144   if(!correct){
00145     if(aSuggestions){
00146       PRUint32 count,i;
00147       PRUnichar **words;
00148       
00149       result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count);
00150       NS_ENSURE_SUCCESS(result, result); 
00151       for(i=0;i<count;i++){
00152         aSuggestions->AppendString(nsDependentString(words[i]));
00153       }
00154       
00155       if (count)
00156         NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
00157     }
00158     if(aIsMisspelled){
00159       *aIsMisspelled = PR_TRUE;
00160     }
00161   }
00162   return NS_OK;
00163 }
00164 
00165 NS_IMETHODIMP 
00166 mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, PRBool aAllOccurrences)
00167 {
00168   if(!mConverter)
00169     return NS_ERROR_NULL_POINTER;
00170 
00171   nsAutoString newWord(aNewWord); // sigh
00172 
00173   if(aAllOccurrences){
00174     PRUint32 selOffset;
00175     PRInt32 startBlock,currentBlock,currOffset;
00176     PRInt32 begin,end;
00177     PRBool done;
00178     nsresult result;
00179     nsAutoString str;
00180 
00181     // find out where we are
00182     result = SetupDoc(&selOffset);
00183     if(NS_FAILED(result))
00184       return result;
00185     result = GetCurrentBlockIndex(mTsDoc,&startBlock);
00186     if(NS_FAILED(result))
00187       return result;
00188 
00189     //start at the beginning
00190     result = mTsDoc->FirstBlock();
00191     currOffset=0;
00192     currentBlock = 0;
00193     while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
00194       {
00195         result = mTsDoc->GetCurrentTextBlock(&str);
00196         do{
00197           result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end);
00198           if(NS_SUCCEEDED(result)&&(begin != -1)){
00199             if (aOldWord.Equals(Substring(str, begin, end-begin))) {
00200               // if we are before the current selection point but in the same block
00201               // move the selection point forwards
00202               if((currentBlock == startBlock)&&(begin < (PRInt32) selOffset)){
00203                 selOffset += (aNewWord.Length() - aOldWord.Length());
00204                 if(selOffset < 0) selOffset=0;
00205               }
00206               mTsDoc->SetSelection(begin, end-begin);
00207               mTsDoc->InsertText(&newWord);
00208               mTsDoc->GetCurrentTextBlock(&str);
00209               end += (aNewWord.Length() - aOldWord.Length());  // recursion was cute in GEB, not here.
00210             }
00211           }
00212           currOffset = end;
00213         }while(currOffset != -1);
00214         mTsDoc->NextBlock();
00215         currentBlock++;
00216         currOffset=0;          
00217       }
00218 
00219     // We are done replacing.  Put the selection point back where we found  it (or equivalent);
00220     result = mTsDoc->FirstBlock();
00221     currentBlock = 0;
00222     while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){
00223       mTsDoc->NextBlock();
00224     }
00225 
00226 //After we have moved to the block where the first occurance of replace was done, put the 
00227 //selection to the next word following it. In case there is no word following it i.e if it happens
00228 //to be the last word in that block, then move to the next block and put the selection to the 
00229 //first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock()
00230 //and the selection offset of the last occurance of the replaced word is taken instead of the first 
00231 //occurance and things get messed up as reported in the bug 244969
00232 
00233     if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){
00234       nsString str;                                
00235       result = mTsDoc->GetCurrentTextBlock(&str);  
00236       result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
00237             if(end == -1)
00238              {
00239                 mTsDoc->NextBlock();
00240                 selOffset=0;
00241                 result = mTsDoc->GetCurrentTextBlock(&str); 
00242                 result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
00243                 mTsDoc->SetSelection(begin, 0);
00244              }
00245          else
00246                 mTsDoc->SetSelection(begin, 0);
00247     }
00248  }
00249   else{
00250     mTsDoc->InsertText(&newWord);
00251   }
00252   return NS_OK;
00253 }
00254 
00255 NS_IMETHODIMP 
00256 mozSpellChecker::IgnoreAll(const nsAString &aWord)
00257 {
00258   if(mPersonalDictionary){
00259     mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get());
00260   }
00261   return NS_OK;
00262 }
00263 
00264 NS_IMETHODIMP 
00265 mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord)
00266 {
00267   nsresult res;
00268   PRUnichar empty=0;
00269   if (!mPersonalDictionary)
00270     return NS_ERROR_NULL_POINTER;
00271   res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty);
00272   return res;
00273 }
00274 
00275 NS_IMETHODIMP 
00276 mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord)
00277 {
00278   nsresult res;
00279   PRUnichar empty=0;
00280   if (!mPersonalDictionary)
00281     return NS_ERROR_NULL_POINTER;
00282   res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty);
00283   return res;
00284 }
00285 
00286 NS_IMETHODIMP 
00287 mozSpellChecker::GetPersonalDictionary(nsStringArray *aWordList)
00288 {
00289   if(!aWordList || !mPersonalDictionary)
00290     return NS_ERROR_NULL_POINTER;
00291 
00292   nsCOMPtr<nsIStringEnumerator> words;
00293   mPersonalDictionary->GetWordList(getter_AddRefs(words));
00294   
00295   PRBool hasMore;
00296   nsAutoString word;
00297   while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) {
00298     words->GetNext(word);
00299     aWordList->AppendString(word);
00300   }
00301   return NS_OK;
00302 }
00303 
00304 NS_IMETHODIMP 
00305 mozSpellChecker::GetDictionaryList(nsStringArray *aDictionaryList)
00306 {
00307   nsAutoString temp;
00308   PRUint32 count,i;
00309   PRUnichar **words;
00310   
00311   if(!aDictionaryList || !mSpellCheckingEngine)
00312     return NS_ERROR_NULL_POINTER;
00313   mSpellCheckingEngine->GetDictionaryList(&words,&count);
00314   for(i=0;i<count;i++){
00315     temp.Assign(words[i]);
00316     aDictionaryList->AppendString(temp);
00317   }
00318   NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
00319 
00320   return NS_OK;
00321 }
00322 
00323 NS_IMETHODIMP 
00324 mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
00325 {
00326   nsXPIDLString dictname;
00327   mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
00328   aDictionary = dictname;
00329   return NS_OK;
00330 }
00331 
00332 NS_IMETHODIMP 
00333 mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
00334 {
00335   if(!mSpellCheckingEngine)
00336     return NS_ERROR_NULL_POINTER;
00337  
00338   nsresult res;
00339   res = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get());
00340   if(NS_FAILED(res)){
00341     NS_WARNING("Dictionary load failed");
00342     return res;
00343   }
00344   nsXPIDLString language;
00345   
00346   nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &res));
00347   if(serv && NS_SUCCEEDED(res)){
00348     res = serv->GetUtil(language.get(),getter_AddRefs(mConverter));
00349   }
00350   return res;
00351 }
00352 
00353 nsresult
00354 mozSpellChecker::SetupDoc(PRUint32 *outBlockOffset)
00355 {
00356   nsresult  rv;
00357 
00358   nsITextServicesDocument::TSDBlockSelectionStatus blockStatus;
00359   PRInt32 selOffset;
00360   PRInt32 selLength;
00361   *outBlockOffset = 0;
00362 
00363   if (!mFromStart) 
00364   {
00365     rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength);
00366     if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound))
00367     {
00368       switch (blockStatus)
00369       {
00370         case nsITextServicesDocument::eBlockOutside:  // No TB in S, but found one before/after S.
00371         case nsITextServicesDocument::eBlockPartial:  // S begins or ends in TB but extends outside of TB.
00372           // the TS doc points to the block we want.
00373           *outBlockOffset = selOffset + selLength;
00374           break;
00375                     
00376         case nsITextServicesDocument::eBlockInside:  // S extends beyond the start and end of TB.
00377           // we want the block after this one.
00378           rv = mTsDoc->NextBlock();
00379           *outBlockOffset = 0;
00380           break;
00381                 
00382         case nsITextServicesDocument::eBlockContains: // TB contains entire S.
00383           *outBlockOffset = selOffset + selLength;
00384           break;
00385         
00386         case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S).
00387         default:
00388           NS_NOTREACHED("Shouldn't ever get this status");
00389       }
00390     }
00391     else  //failed to get last sel block. Just start at beginning
00392     {
00393       rv = mTsDoc->FirstBlock();
00394       *outBlockOffset = 0;
00395     }
00396   
00397   }
00398   else // we want the first block
00399   {
00400     rv = mTsDoc->FirstBlock();
00401     mFromStart = PR_FALSE;
00402   }
00403   return rv;
00404 }
00405 
00406 
00407 // utility method to discover which block we're in. The TSDoc interface doesn't give
00408 // us this, because it can't assume a read-only document.
00409 // shamelessly stolen from nsTextServicesDocument
00410 nsresult
00411 mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, PRInt32 *outBlockIndex)
00412 {
00413   PRInt32  blockIndex = 0;
00414   PRBool   isDone = PR_FALSE;
00415   nsresult result = NS_OK;
00416 
00417   do
00418   {
00419     aDoc->PrevBlock();
00420 
00421     result = aDoc->IsDone(&isDone);
00422 
00423     if (!isDone)
00424       blockIndex ++;
00425 
00426   } while (NS_SUCCEEDED(result) && !isDone);
00427   
00428   *outBlockIndex = blockIndex;
00429 
00430   return result;
00431 }