extensions/spellcheck/src/mozSpellChecker.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "mozSpellChecker.h"
     7 #include "nsIServiceManager.h"
     8 #include "mozISpellI18NManager.h"
     9 #include "nsIStringEnumerator.h"
    10 #include "nsICategoryManager.h"
    11 #include "nsISupportsPrimitives.h"
    12 #include "nsISimpleEnumerator.h"
    14 #define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
    16 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellChecker)
    17 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker)
    19 NS_INTERFACE_MAP_BEGIN(mozSpellChecker)
    20   NS_INTERFACE_MAP_ENTRY(nsISpellChecker)
    21   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpellChecker)
    22   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker)
    23 NS_INTERFACE_MAP_END
    25 NS_IMPL_CYCLE_COLLECTION(mozSpellChecker,
    26                          mTsDoc,
    27                          mPersonalDictionary)
    29 mozSpellChecker::mozSpellChecker()
    30 {
    31 }
    33 mozSpellChecker::~mozSpellChecker()
    34 {
    35   if(mPersonalDictionary){
    36     //    mPersonalDictionary->Save();
    37     mPersonalDictionary->EndSession();
    38   }
    39   mSpellCheckingEngine = nullptr;
    40   mPersonalDictionary = nullptr;
    41 }
    43 nsresult 
    44 mozSpellChecker::Init()
    45 {
    46   mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
    48   mSpellCheckingEngine = nullptr;
    50   return NS_OK;
    51 } 
    53 NS_IMETHODIMP 
    54 mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc)
    55 {
    56   mTsDoc = aDoc;
    57   mFromStart = aFromStartofDoc;
    58   return NS_OK;
    59 }
    62 NS_IMETHODIMP 
    63 mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions)
    64 {
    65   if(!aSuggestions||!mConverter)
    66     return NS_ERROR_NULL_POINTER;
    68   int32_t selOffset;
    69   int32_t begin,end;
    70   nsresult result;
    71   result = SetupDoc(&selOffset);
    72   bool isMisspelled,done;
    73   if (NS_FAILED(result))
    74     return result;
    76   while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
    77     {
    78       nsString str;
    79       result = mTsDoc->GetCurrentTextBlock(&str);
    81       if (NS_FAILED(result))
    82         return result;
    83       do{
    84         result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
    85         if(NS_SUCCEEDED(result)&&(begin != -1)){
    86           const nsAString &currWord = Substring(str, begin, end - begin);
    87           result = CheckWord(currWord, &isMisspelled, aSuggestions);
    88           if(isMisspelled){
    89             aWord = currWord;
    90             mTsDoc->SetSelection(begin, end-begin);
    91             // After ScrollSelectionIntoView(), the pending notifications might
    92             // be flushed and PresShell/PresContext/Frames may be dead.
    93             // See bug 418470.
    94             mTsDoc->ScrollSelectionIntoView();
    95             return NS_OK;
    96           }
    97         }
    98         selOffset = end;
    99       }while(end != -1);
   100       mTsDoc->NextBlock();
   101       selOffset=0;
   102     }
   103   return NS_OK;
   104 }
   106 NS_IMETHODIMP 
   107 mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions)
   108 {
   109   nsresult result;
   110   bool correct;
   111   if(!mSpellCheckingEngine)
   112     return NS_ERROR_NULL_POINTER;
   114   *aIsMisspelled = false;
   115   result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct);
   116   NS_ENSURE_SUCCESS(result, result);
   117   if(!correct){
   118     if(aSuggestions){
   119       uint32_t count,i;
   120       char16_t **words;
   122       result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count);
   123       NS_ENSURE_SUCCESS(result, result); 
   124       for(i=0;i<count;i++){
   125         aSuggestions->AppendElement(nsDependentString(words[i]));
   126       }
   128       if (count)
   129         NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
   130     }
   131     *aIsMisspelled = true;
   132   }
   133   return NS_OK;
   134 }
   136 NS_IMETHODIMP 
   137 mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences)
   138 {
   139   if(!mConverter)
   140     return NS_ERROR_NULL_POINTER;
   142   nsAutoString newWord(aNewWord); // sigh
   144   if(aAllOccurrences){
   145     int32_t selOffset;
   146     int32_t startBlock,currentBlock,currOffset;
   147     int32_t begin,end;
   148     bool done;
   149     nsresult result;
   150     nsAutoString str;
   152     // find out where we are
   153     result = SetupDoc(&selOffset);
   154     if(NS_FAILED(result))
   155       return result;
   156     result = GetCurrentBlockIndex(mTsDoc,&startBlock);
   157     if(NS_FAILED(result))
   158       return result;
   160     //start at the beginning
   161     result = mTsDoc->FirstBlock();
   162     currOffset=0;
   163     currentBlock = 0;
   164     while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
   165       {
   166         result = mTsDoc->GetCurrentTextBlock(&str);
   167         do{
   168           result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end);
   169           if(NS_SUCCEEDED(result)&&(begin != -1)){
   170             if (aOldWord.Equals(Substring(str, begin, end-begin))) {
   171               // if we are before the current selection point but in the same block
   172               // move the selection point forwards
   173               if((currentBlock == startBlock)&&(begin < selOffset)){
   174                 selOffset +=
   175                   int32_t(aNewWord.Length()) - int32_t(aOldWord.Length());
   176                 if(selOffset < begin) selOffset=begin;
   177               }
   178               mTsDoc->SetSelection(begin, end-begin);
   179               mTsDoc->InsertText(&newWord);
   180               mTsDoc->GetCurrentTextBlock(&str);
   181               end += (aNewWord.Length() - aOldWord.Length());  // recursion was cute in GEB, not here.
   182             }
   183           }
   184           currOffset = end;
   185         }while(currOffset != -1);
   186         mTsDoc->NextBlock();
   187         currentBlock++;
   188         currOffset=0;          
   189       }
   191     // We are done replacing.  Put the selection point back where we found  it (or equivalent);
   192     result = mTsDoc->FirstBlock();
   193     currentBlock = 0;
   194     while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){
   195       mTsDoc->NextBlock();
   196     }
   198 //After we have moved to the block where the first occurrence of replace was done, put the 
   199 //selection to the next word following it. In case there is no word following it i.e if it happens
   200 //to be the last word in that block, then move to the next block and put the selection to the 
   201 //first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock()
   202 //and the selection offset of the last occurrence of the replaced word is taken instead of the first 
   203 //occurrence and things get messed up as reported in the bug 244969
   205     if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){
   206       nsString str;                                
   207       result = mTsDoc->GetCurrentTextBlock(&str);  
   208       result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
   209             if(end == -1)
   210              {
   211                 mTsDoc->NextBlock();
   212                 selOffset=0;
   213                 result = mTsDoc->GetCurrentTextBlock(&str); 
   214                 result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
   215                 mTsDoc->SetSelection(begin, 0);
   216              }
   217          else
   218                 mTsDoc->SetSelection(begin, 0);
   219     }
   220  }
   221   else{
   222     mTsDoc->InsertText(&newWord);
   223   }
   224   return NS_OK;
   225 }
   227 NS_IMETHODIMP 
   228 mozSpellChecker::IgnoreAll(const nsAString &aWord)
   229 {
   230   if(mPersonalDictionary){
   231     mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get());
   232   }
   233   return NS_OK;
   234 }
   236 NS_IMETHODIMP 
   237 mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord)
   238 {
   239   nsresult res;
   240   char16_t empty=0;
   241   if (!mPersonalDictionary)
   242     return NS_ERROR_NULL_POINTER;
   243   res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty);
   244   return res;
   245 }
   247 NS_IMETHODIMP 
   248 mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord)
   249 {
   250   nsresult res;
   251   char16_t empty=0;
   252   if (!mPersonalDictionary)
   253     return NS_ERROR_NULL_POINTER;
   254   res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty);
   255   return res;
   256 }
   258 NS_IMETHODIMP 
   259 mozSpellChecker::GetPersonalDictionary(nsTArray<nsString> *aWordList)
   260 {
   261   if(!aWordList || !mPersonalDictionary)
   262     return NS_ERROR_NULL_POINTER;
   264   nsCOMPtr<nsIStringEnumerator> words;
   265   mPersonalDictionary->GetWordList(getter_AddRefs(words));
   267   bool hasMore;
   268   nsAutoString word;
   269   while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) {
   270     words->GetNext(word);
   271     aWordList->AppendElement(word);
   272   }
   273   return NS_OK;
   274 }
   276 NS_IMETHODIMP 
   277 mozSpellChecker::GetDictionaryList(nsTArray<nsString> *aDictionaryList)
   278 {
   279   nsresult rv;
   281   // For catching duplicates
   282   nsClassHashtable<nsStringHashKey, nsCString> dictionaries;
   284   nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
   285   rv = GetEngineList(&spellCheckingEngines);
   286   NS_ENSURE_SUCCESS(rv, rv);
   288   for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) {
   289     nsCOMPtr<mozISpellCheckingEngine> engine = spellCheckingEngines[i];
   291     uint32_t count = 0;
   292     char16_t **words = nullptr;
   293     engine->GetDictionaryList(&words, &count);
   294     for (uint32_t k = 0; k < count; k++) {
   295       nsAutoString dictName;
   297       dictName.Assign(words[k]);
   299       // Skip duplicate dictionaries. Only take the first one
   300       // for each name.
   301       if (dictionaries.Get(dictName, nullptr))
   302         continue;
   304       dictionaries.Put(dictName, nullptr);
   306       if (!aDictionaryList->AppendElement(dictName)) {
   307         NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
   308         return NS_ERROR_OUT_OF_MEMORY;
   309       }
   310     }
   312     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
   313   }
   315   return NS_OK;
   316 }
   318 NS_IMETHODIMP 
   319 mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
   320 {
   321   if (!mSpellCheckingEngine) {
   322     aDictionary.AssignLiteral("");
   323     return NS_OK;
   324   }
   326   nsXPIDLString dictname;
   327   mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
   328   aDictionary = dictname;
   329   return NS_OK;
   330 }
   332 NS_IMETHODIMP 
   333 mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
   334 {
   335   // Calls to mozISpellCheckingEngine::SetDictionary might destroy us
   336   nsRefPtr<mozSpellChecker> kungFuDeathGrip = this;
   338   mSpellCheckingEngine = nullptr;
   340   if (aDictionary.IsEmpty()) {
   341     return NS_OK;
   342   }
   344   nsresult rv;
   345   nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
   346   rv = GetEngineList(&spellCheckingEngines);
   347   NS_ENSURE_SUCCESS(rv, rv);
   349   for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) {
   350     // We must set mSpellCheckingEngine before we call SetDictionary, since
   351     // SetDictionary calls back to this spell checker to check if the
   352     // dictionary was set
   353     mSpellCheckingEngine = spellCheckingEngines[i];
   355     rv = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get());
   357     if (NS_SUCCEEDED(rv)) {
   358       nsCOMPtr<mozIPersonalDictionary> personalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
   359       mSpellCheckingEngine->SetPersonalDictionary(personalDictionary.get());
   361       nsXPIDLString language;
   362       nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &rv));
   363       NS_ENSURE_SUCCESS(rv, rv);
   364       return serv->GetUtil(language.get(),getter_AddRefs(mConverter));
   365     }
   366   }
   368   mSpellCheckingEngine = nullptr;
   370   // We could not find any engine with the requested dictionary
   371   return NS_ERROR_NOT_AVAILABLE;
   372 }
   374 NS_IMETHODIMP 
   375 mozSpellChecker::CheckCurrentDictionary()
   376 {
   377   // If the current dictionary has been uninstalled, we need to stop using it.
   378   // This happens when there is a current engine, but that engine has no
   379   // current dictionary.
   381   if (!mSpellCheckingEngine) {
   382     // We didn't have a current dictionary
   383     return NS_OK;
   384   }
   386   nsXPIDLString dictname;
   387   mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
   389   if (!dictname.IsEmpty()) {
   390     // We still have a current dictionary
   391     return NS_OK;
   392   }
   394   // We had a current dictionary, but it has gone, so we cannot use it anymore.
   395   mSpellCheckingEngine = nullptr;
   396   return NS_OK;
   397 }
   399 nsresult
   400 mozSpellChecker::SetupDoc(int32_t *outBlockOffset)
   401 {
   402   nsresult  rv;
   404   nsITextServicesDocument::TSDBlockSelectionStatus blockStatus;
   405   int32_t selOffset;
   406   int32_t selLength;
   407   *outBlockOffset = 0;
   409   if (!mFromStart) 
   410   {
   411     rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength);
   412     if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound))
   413     {
   414       switch (blockStatus)
   415       {
   416         case nsITextServicesDocument::eBlockOutside:  // No TB in S, but found one before/after S.
   417         case nsITextServicesDocument::eBlockPartial:  // S begins or ends in TB but extends outside of TB.
   418           // the TS doc points to the block we want.
   419           *outBlockOffset = selOffset + selLength;
   420           break;
   422         case nsITextServicesDocument::eBlockInside:  // S extends beyond the start and end of TB.
   423           // we want the block after this one.
   424           rv = mTsDoc->NextBlock();
   425           *outBlockOffset = 0;
   426           break;
   428         case nsITextServicesDocument::eBlockContains: // TB contains entire S.
   429           *outBlockOffset = selOffset + selLength;
   430           break;
   432         case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S).
   433         default:
   434           NS_NOTREACHED("Shouldn't ever get this status");
   435       }
   436     }
   437     else  //failed to get last sel block. Just start at beginning
   438     {
   439       rv = mTsDoc->FirstBlock();
   440       *outBlockOffset = 0;
   441     }
   443   }
   444   else // we want the first block
   445   {
   446     rv = mTsDoc->FirstBlock();
   447     mFromStart = false;
   448   }
   449   return rv;
   450 }
   453 // utility method to discover which block we're in. The TSDoc interface doesn't give
   454 // us this, because it can't assume a read-only document.
   455 // shamelessly stolen from nsTextServicesDocument
   456 nsresult
   457 mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex)
   458 {
   459   int32_t  blockIndex = 0;
   460   bool     isDone = false;
   461   nsresult result = NS_OK;
   463   do
   464   {
   465     aDoc->PrevBlock();
   467     result = aDoc->IsDone(&isDone);
   469     if (!isDone)
   470       blockIndex ++;
   472   } while (NS_SUCCEEDED(result) && !isDone);
   474   *outBlockIndex = blockIndex;
   476   return result;
   477 }
   479 nsresult
   480 mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines)
   481 {
   482   nsresult rv;
   483   bool hasMoreEngines;
   485   nsCOMPtr<nsICategoryManager> catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
   486   if (!catMgr)
   487     return NS_ERROR_NULL_POINTER;
   489   nsCOMPtr<nsISimpleEnumerator> catEntries;
   491   // Get contract IDs of registrated external spell-check engines and
   492   // append one of HunSpell at the end.
   493   rv = catMgr->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries));
   494   if (NS_FAILED(rv))
   495     return rv;
   497   while (catEntries->HasMoreElements(&hasMoreEngines), hasMoreEngines){
   498     nsCOMPtr<nsISupports> elem;
   499     rv = catEntries->GetNext(getter_AddRefs(elem));
   501     nsCOMPtr<nsISupportsCString> entry = do_QueryInterface(elem, &rv);
   502     if (NS_FAILED(rv))
   503       return rv;
   505     nsCString contractId;
   506     rv = entry->GetData(contractId);
   507     if (NS_FAILED(rv))
   508       return rv;
   510     // Try to load spellchecker engine. Ignore errors silently
   511     // except for the last one (HunSpell).
   512     nsCOMPtr<mozISpellCheckingEngine> engine =
   513       do_GetService(contractId.get(), &rv);
   514     if (NS_SUCCEEDED(rv)) {
   515       aSpellCheckingEngines->AppendObject(engine);
   516     }
   517   }
   519   // Try to load HunSpell spellchecker engine.
   520   nsCOMPtr<mozISpellCheckingEngine> engine =
   521     do_GetService(DEFAULT_SPELL_CHECKER, &rv);
   522   if (NS_FAILED(rv)) {
   523     // Fail if not succeeded to load HunSpell. Ignore errors
   524     // for external spellcheck engines.
   525     return rv;
   526   }
   527   aSpellCheckingEngines->AppendObject(engine);
   529   return NS_OK;
   530 }

mercurial