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 Original Code is Mozilla Spellchecker Component.
16 * The Initial Developer of the Original Code is David Einstein.
17 * Portions created by the Initial Developer are Copyright (C) 2001
18 * the Initial Developer. All Rights Reserved.
20 * Contributor(s): David Einstein Deinst@world.std.com
22 * Alternatively, the contents of this file may be used under the terms of
23 * either the GNU General Public License Version 2 or later (the "GPL"), or
24 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
25 * in which case the provisions of the GPL or the LGPL are applicable instead
26 * of those above. If you wish to allow use of your version of this file only
27 * under the terms of either the GPL or the LGPL, and not to allow others to
28 * use your version of this file under the terms of the MPL, indicate your
29 * decision by deleting the provisions above and replace them with the notice
30 * and other provisions required by the GPL or the LGPL. If you do not delete
31 * the provisions above, a recipient may use your version of this file under
32 * the terms of any one of the MPL, the GPL or the LGPL.
34 * ***** END LICENSE BLOCK ***** */
37 #include "mozSpellChecker.h"
38 #include "nsIServiceManager.h"
39 #include "mozISpellI18NManager.h"
40 #include "nsIStringEnumerator.h"
41 #include "nsICategoryManager.h"
42 #include "nsISupportsPrimitives.h"
44 #define UNREASONABLE_WORD_LENGTH 64
46 #define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
48 NS_IMPL_ISUPPORTS1(mozSpellChecker
, nsISpellChecker
)
50 mozSpellChecker::mozSpellChecker()
54 mozSpellChecker::~mozSpellChecker()
56 if(mPersonalDictionary
){
57 // mPersonalDictionary->Save();
58 mPersonalDictionary
->EndSession();
60 mSpellCheckingEngine
= nsnull
;
61 mPersonalDictionary
= nsnull
;
65 mozSpellChecker::Init()
67 mPersonalDictionary
= do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
69 mSpellCheckingEngine
= nsnull
;
70 mCurrentEngineContractId
= nsnull
;
71 mDictionariesMap
.Init();
72 InitSpellCheckDictionaryMap();
78 mozSpellChecker::SetDocument(nsITextServicesDocument
*aDoc
, PRBool aFromStartofDoc
)
81 mFromStart
= aFromStartofDoc
;
87 mozSpellChecker::NextMisspelledWord(nsAString
&aWord
, nsStringArray
*aSuggestions
)
89 if(!aSuggestions
||!mConverter
)
90 return NS_ERROR_NULL_POINTER
;
95 result
= SetupDoc(&selOffset
);
96 PRBool isMisspelled
,done
;
97 if (NS_FAILED(result
))
100 while( NS_SUCCEEDED(mTsDoc
->IsDone(&done
)) && !done
)
103 result
= mTsDoc
->GetCurrentTextBlock(&str
);
105 if (NS_FAILED(result
))
108 result
= mConverter
->FindNextWord(str
.get(),str
.Length(),selOffset
,&begin
,&end
);
109 if(NS_SUCCEEDED(result
)&&(begin
!= -1)){
110 const nsAString
&currWord
= Substring(str
, begin
, end
- begin
);
111 result
= CheckWord(currWord
, &isMisspelled
, aSuggestions
);
114 mTsDoc
->SetSelection(begin
, end
-begin
);
115 // After ScrollSelectionIntoView(), the pending notifications might
116 // be flushed and PresShell/PresContext/Frames may be dead.
118 mTsDoc
->ScrollSelectionIntoView();
131 mozSpellChecker::CheckWord(const nsAString
&aWord
, PRBool
*aIsMisspelled
, nsStringArray
*aSuggestions
)
135 if(!mSpellCheckingEngine
)
136 return NS_ERROR_NULL_POINTER
;
138 // don't bother to check crazy words
139 if (aWord
.Length() > UNREASONABLE_WORD_LENGTH
) {
140 *aIsMisspelled
= PR_TRUE
;
144 *aIsMisspelled
= PR_FALSE
;
145 result
= mSpellCheckingEngine
->Check(PromiseFlatString(aWord
).get(), &correct
);
146 NS_ENSURE_SUCCESS(result
, result
);
152 result
= mSpellCheckingEngine
->Suggest(PromiseFlatString(aWord
).get(), &words
, &count
);
153 NS_ENSURE_SUCCESS(result
, result
);
154 for(i
=0;i
<count
;i
++){
155 aSuggestions
->AppendString(nsDependentString(words
[i
]));
159 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count
, words
);
162 *aIsMisspelled
= PR_TRUE
;
169 mozSpellChecker::Replace(const nsAString
&aOldWord
, const nsAString
&aNewWord
, PRBool aAllOccurrences
)
172 return NS_ERROR_NULL_POINTER
;
174 nsAutoString
newWord(aNewWord
); // sigh
178 PRInt32 startBlock
,currentBlock
,currOffset
;
184 // find out where we are
185 result
= SetupDoc(&selOffset
);
186 if(NS_FAILED(result
))
188 result
= GetCurrentBlockIndex(mTsDoc
,&startBlock
);
189 if(NS_FAILED(result
))
192 //start at the beginning
193 result
= mTsDoc
->FirstBlock();
196 while( NS_SUCCEEDED(mTsDoc
->IsDone(&done
)) && !done
)
198 result
= mTsDoc
->GetCurrentTextBlock(&str
);
200 result
= mConverter
->FindNextWord(str
.get(),str
.Length(),currOffset
,&begin
,&end
);
201 if(NS_SUCCEEDED(result
)&&(begin
!= -1)){
202 if (aOldWord
.Equals(Substring(str
, begin
, end
-begin
))) {
203 // if we are before the current selection point but in the same block
204 // move the selection point forwards
205 if((currentBlock
== startBlock
)&&(begin
< (PRInt32
) selOffset
)){
206 selOffset
+= (aNewWord
.Length() - aOldWord
.Length());
207 if(selOffset
< 0) selOffset
=0;
209 mTsDoc
->SetSelection(begin
, end
-begin
);
210 mTsDoc
->InsertText(&newWord
);
211 mTsDoc
->GetCurrentTextBlock(&str
);
212 end
+= (aNewWord
.Length() - aOldWord
.Length()); // recursion was cute in GEB, not here.
216 }while(currOffset
!= -1);
222 // We are done replacing. Put the selection point back where we found it (or equivalent);
223 result
= mTsDoc
->FirstBlock();
225 while(( NS_SUCCEEDED(mTsDoc
->IsDone(&done
)) && !done
) &&(currentBlock
< startBlock
)){
229 //After we have moved to the block where the first occurrence of replace was done, put the
230 //selection to the next word following it. In case there is no word following it i.e if it happens
231 //to be the last word in that block, then move to the next block and put the selection to the
232 //first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock()
233 //and the selection offset of the last occurrence of the replaced word is taken instead of the first
234 //occurrence and things get messed up as reported in the bug 244969
236 if( NS_SUCCEEDED(mTsDoc
->IsDone(&done
)) && !done
){
238 result
= mTsDoc
->GetCurrentTextBlock(&str
);
239 result
= mConverter
->FindNextWord(str
.get(),str
.Length(),selOffset
,&begin
,&end
);
244 result
= mTsDoc
->GetCurrentTextBlock(&str
);
245 result
= mConverter
->FindNextWord(str
.get(),str
.Length(),selOffset
,&begin
,&end
);
246 mTsDoc
->SetSelection(begin
, 0);
249 mTsDoc
->SetSelection(begin
, 0);
253 mTsDoc
->InsertText(&newWord
);
259 mozSpellChecker::IgnoreAll(const nsAString
&aWord
)
261 if(mPersonalDictionary
){
262 mPersonalDictionary
->IgnoreWord(PromiseFlatString(aWord
).get());
268 mozSpellChecker::AddWordToPersonalDictionary(const nsAString
&aWord
)
272 if (!mPersonalDictionary
)
273 return NS_ERROR_NULL_POINTER
;
274 res
= mPersonalDictionary
->AddWord(PromiseFlatString(aWord
).get(),&empty
);
279 mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString
&aWord
)
283 if (!mPersonalDictionary
)
284 return NS_ERROR_NULL_POINTER
;
285 res
= mPersonalDictionary
->RemoveWord(PromiseFlatString(aWord
).get(),&empty
);
290 mozSpellChecker::GetPersonalDictionary(nsStringArray
*aWordList
)
292 if(!aWordList
|| !mPersonalDictionary
)
293 return NS_ERROR_NULL_POINTER
;
295 nsCOMPtr
<nsIStringEnumerator
> words
;
296 mPersonalDictionary
->GetWordList(getter_AddRefs(words
));
300 while (NS_SUCCEEDED(words
->HasMore(&hasMore
)) && hasMore
) {
301 words
->GetNext(word
);
302 aWordList
->AppendString(word
);
307 struct AppendNewStruct
309 nsStringArray
*dictionaryList
;
313 static PLDHashOperator
314 AppendNewString(const nsAString
& aString
, nsCString
*, void* aClosure
)
316 AppendNewStruct
*ans
= (AppendNewStruct
*) aClosure
;
318 if (!ans
->dictionaryList
->AppendString(aString
))
320 ans
->failed
= PR_TRUE
;
321 return PL_DHASH_STOP
;
324 return PL_DHASH_NEXT
;
328 mozSpellChecker::GetDictionaryList(nsStringArray
*aDictionaryList
)
330 AppendNewStruct ans
= {aDictionaryList
, PR_FALSE
};
332 mDictionariesMap
.EnumerateRead(AppendNewString
, &ans
);
335 return NS_ERROR_OUT_OF_MEMORY
;
341 mozSpellChecker::GetCurrentDictionary(nsAString
&aDictionary
)
343 nsXPIDLString dictname
;
345 if (!mSpellCheckingEngine
)
346 return NS_ERROR_NOT_INITIALIZED
;
348 mSpellCheckingEngine
->GetDictionary(getter_Copies(dictname
));
349 aDictionary
= dictname
;
354 mozSpellChecker::SetCurrentDictionary(const nsAString
&aDictionary
)
357 nsCString
*contractId
;
359 if (!mDictionariesMap
.Get(aDictionary
, &contractId
)){
360 NS_WARNING("Dictionary not found");
361 return NS_ERROR_NOT_AVAILABLE
;
364 if (!mCurrentEngineContractId
|| !mCurrentEngineContractId
->Equals(*contractId
)){
365 mSpellCheckingEngine
= do_GetService(contractId
->get(), &rv
);
369 mCurrentEngineContractId
= contractId
;
373 res
= mSpellCheckingEngine
->SetDictionary(PromiseFlatString(aDictionary
).get());
375 NS_WARNING("Dictionary load failed");
379 mSpellCheckingEngine
->SetPersonalDictionary(mPersonalDictionary
);
381 nsXPIDLString language
;
383 nsCOMPtr
<mozISpellI18NManager
> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &res
));
384 if(serv
&& NS_SUCCEEDED(res
)){
385 res
= serv
->GetUtil(language
.get(),getter_AddRefs(mConverter
));
391 mozSpellChecker::SetupDoc(PRUint32
*outBlockOffset
)
395 nsITextServicesDocument::TSDBlockSelectionStatus blockStatus
;
402 rv
= mTsDoc
->LastSelectedBlock(&blockStatus
, &selOffset
, &selLength
);
403 if (NS_SUCCEEDED(rv
) && (blockStatus
!= nsITextServicesDocument::eBlockNotFound
))
407 case nsITextServicesDocument::eBlockOutside
: // No TB in S, but found one before/after S.
408 case nsITextServicesDocument::eBlockPartial
: // S begins or ends in TB but extends outside of TB.
409 // the TS doc points to the block we want.
410 *outBlockOffset
= selOffset
+ selLength
;
413 case nsITextServicesDocument::eBlockInside
: // S extends beyond the start and end of TB.
414 // we want the block after this one.
415 rv
= mTsDoc
->NextBlock();
419 case nsITextServicesDocument::eBlockContains
: // TB contains entire S.
420 *outBlockOffset
= selOffset
+ selLength
;
423 case nsITextServicesDocument::eBlockNotFound
: // There is no text block (TB) in or before the selection (S).
425 NS_NOTREACHED("Shouldn't ever get this status");
428 else //failed to get last sel block. Just start at beginning
430 rv
= mTsDoc
->FirstBlock();
435 else // we want the first block
437 rv
= mTsDoc
->FirstBlock();
438 mFromStart
= PR_FALSE
;
444 // utility method to discover which block we're in. The TSDoc interface doesn't give
445 // us this, because it can't assume a read-only document.
446 // shamelessly stolen from nsTextServicesDocument
448 mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument
*aDoc
, PRInt32
*outBlockIndex
)
450 PRInt32 blockIndex
= 0;
451 PRBool isDone
= PR_FALSE
;
452 nsresult result
= NS_OK
;
458 result
= aDoc
->IsDone(&isDone
);
463 } while (NS_SUCCEEDED(result
) && !isDone
);
465 *outBlockIndex
= blockIndex
;
471 mozSpellChecker::InitSpellCheckDictionaryMap()
474 PRBool hasMoreEngines
;
476 nsCStringArray contractIds
;
478 nsCOMPtr
<nsICategoryManager
> catMgr
= do_GetService(NS_CATEGORYMANAGER_CONTRACTID
);
480 return NS_ERROR_NULL_POINTER
;
482 nsCOMPtr
<nsISimpleEnumerator
> catEntries
;
484 // Get contract IDs of registrated external spell-check engines and
485 // append one of HunSpell at the end.
486 rv
= catMgr
->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries
));
490 while (catEntries
->HasMoreElements(&hasMoreEngines
), hasMoreEngines
){
491 nsCOMPtr
<nsISupports
> elem
;
492 rv
= catEntries
->GetNext(getter_AddRefs(elem
));
494 nsCOMPtr
<nsISupportsCString
> entry
= do_QueryInterface(elem
, &rv
);
498 nsCString contractId
;
499 rv
= entry
->GetData(contractId
);
503 contractIds
.AppendCString(contractId
);
506 contractIds
.AppendCString(NS_LITERAL_CSTRING(DEFAULT_SPELL_CHECKER
));
508 // Retrieve dictionaries from all available spellcheckers and
509 // fill mDictionariesMap hash (only the first dictionary with the
510 // each name is used).
511 for (i
=0;i
<contractIds
.Count();i
++){
515 nsCString
*contractId
= contractIds
[i
];
517 // Try to load spellchecker engine. Ignore errors silently
518 // except for the last one (HunSpell).
519 nsCOMPtr
<mozISpellCheckingEngine
> engine
=
520 do_GetService(contractId
->get(), &rv
);
522 // Fail if not succeeded to load HunSpell. Ignore errors
523 // for external spellcheck engines.
524 if (i
==contractIds
.Count()-1){
531 engine
->GetDictionaryList(&words
,&count
);
532 for(k
=0;k
<count
;k
++){
533 nsAutoString dictName
;
535 dictName
.Assign(words
[k
]);
537 nsCString dictCName
= NS_ConvertUTF16toUTF8(dictName
);
539 // Skip duplicate dictionaries. Only take the first one
541 if (mDictionariesMap
.Get(dictName
, NULL
))
544 mDictionariesMap
.Put(dictName
, new nsCString(*contractId
));
547 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count
, words
);