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