xref: /trunk/main/linguistic/source/spelldsp.cxx (revision cdf0e10c4e3984b49a9502b011690b615761d4a3)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_linguistic.hxx"
30 
31 #include <com/sun/star/uno/Reference.h>
32 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
33 #include <com/sun/star/linguistic2/SpellFailure.hpp>
34 #include <com/sun/star/registry/XRegistryKey.hpp>
35 
36 #include <cppuhelper/factory.hxx>   // helper for factories
37 #include <unotools/localedatawrapper.hxx>
38 #include <unotools/processfactory.hxx>
39 #include <tools/debug.hxx>
40 #include <svl/lngmisc.hxx>
41 #include <osl/mutex.hxx>
42 
43 #include <vector>
44 
45 #include "spelldsp.hxx"
46 #include "linguistic/spelldta.hxx"
47 #include "lngsvcmgr.hxx"
48 #include "linguistic/lngprops.hxx"
49 
50 
51 using namespace utl;
52 using namespace osl;
53 using namespace rtl;
54 using namespace com::sun::star;
55 using namespace com::sun::star::beans;
56 using namespace com::sun::star::lang;
57 using namespace com::sun::star::uno;
58 using namespace com::sun::star::linguistic2;
59 using namespace linguistic;
60 
61 ///////////////////////////////////////////////////////////////////////////
62 // ProposalList: list of proposals for misspelled words
63 //   The order of strings in the array should be left unchanged because the
64 // spellchecker should have put the more likely suggestions at the top.
65 // New entries will be added to the end but duplicates are to be avoided.
66 // Removing entries is done by assigning the empty string.
67 // The sequence is constructed from all non empty strings in the original
68 // while maintaining the order.
69 //
70 class ProposalList
71 {
72     std::vector< OUString > aVec;
73 
74     sal_Bool    HasEntry( const OUString &rText ) const;
75 
76     // make copy c-tor and assignment operator private
77     ProposalList( const ProposalList & );
78     ProposalList & operator = ( const ProposalList & );
79 
80 public:
81     ProposalList()  {}
82 
83     //size_t  Size() const   { return aVec.size(); }
84     size_t  Count() const;
85     void    Prepend( const OUString &rText );
86     void    Append( const OUString &rNew );
87     void    Append( const std::vector< OUString > &rNew );
88     void    Append( const Sequence< OUString > &rNew );
89     void    Remove( const OUString &rText );
90     Sequence< OUString >    GetSequence() const;
91 };
92 
93 
94 sal_Bool ProposalList::HasEntry( const OUString &rText ) const
95 {
96     sal_Bool bFound = sal_False;
97     size_t nCnt = aVec.size();
98     for (size_t i = 0;  !bFound && i < nCnt;  ++i)
99     {
100         if (aVec[i] == rText)
101             bFound = sal_True;
102     }
103     return bFound;
104 }
105 
106 void ProposalList::Prepend( const OUString &rText )
107 {
108     if (!HasEntry( rText ))
109         aVec.insert( aVec.begin(), rText );
110 }
111 
112 void ProposalList::Append( const OUString &rText )
113 {
114     if (!HasEntry( rText ))
115         aVec.push_back( rText );
116 }
117 
118 void ProposalList::Append( const std::vector< OUString > &rNew )
119 {
120     size_t nLen = rNew.size();
121     for ( size_t i = 0;  i < nLen;  ++i)
122     {
123         const OUString &rText = rNew[i];
124         if (!HasEntry( rText ))
125             Append( rText );
126     }
127 }
128 
129 void ProposalList::Append( const Sequence< OUString > &rNew )
130 {
131     sal_Int32 nLen = rNew.getLength();
132     const OUString *pNew = rNew.getConstArray();
133     for (sal_Int32 i = 0;  i < nLen;  ++i)
134     {
135         const OUString &rText = pNew[i];
136         if (!HasEntry( rText ))
137             Append( rText );
138     }
139 }
140 
141 size_t ProposalList::Count() const
142 {
143     // returns the number of non-empty strings in the vector
144 
145     size_t nRes = 0;
146     size_t nLen = aVec.size();
147     for (size_t i = 0;  i < nLen;  ++i)
148     {
149         if (aVec[i].getLength() != 0)
150             ++nRes;
151     }
152     return nRes;
153 }
154 
155 Sequence< OUString > ProposalList::GetSequence() const
156 {
157     sal_Int32 nCount = Count();
158     sal_Int32 nIdx = 0;
159     Sequence< OUString > aRes( nCount );
160     OUString *pRes = aRes.getArray();
161     sal_Int32 nLen = aVec.size();
162     for (sal_Int32 i = 0;  i < nLen;  ++i)
163     {
164         const OUString &rText = aVec[i];
165         DBG_ASSERT( nIdx < nCount, "index our of range" );
166         if (nIdx < nCount && rText.getLength() > 0)
167             pRes[ nIdx++ ] = rText;
168     }
169     return aRes;
170 }
171 
172 void ProposalList::Remove( const OUString &rText )
173 {
174     size_t nLen = aVec.size();
175     for (size_t i = 0;  i < nLen;  ++i)
176     {
177         OUString &rEntry = aVec[i];
178         if (rEntry == rText)
179         {
180             rEntry = OUString();
181             break;  // there should be only one matching entry
182         }
183     }
184 }
185 
186 
187 ///////////////////////////////////////////////////////////////////////////
188 
189 sal_Bool SvcListHasLanguage(
190         const LangSvcEntries_Spell &rEntry,
191         LanguageType nLanguage )
192 {
193     sal_Bool bHasLanguage = sal_False;
194     Locale aTmpLocale;
195 
196     const Reference< XSpellChecker >  *pRef  = rEntry.aSvcRefs .getConstArray();
197     sal_Int32 nLen = rEntry.aSvcRefs.getLength();
198     for (sal_Int32 k = 0;  k < nLen  &&  !bHasLanguage;  ++k)
199     {
200         if (pRef[k].is())
201         {
202             if (0 == aTmpLocale.Language.getLength())
203                 aTmpLocale = CreateLocale( nLanguage );
204             bHasLanguage = pRef[k]->hasLocale( aTmpLocale );
205         }
206     }
207 
208     return bHasLanguage;
209 }
210 
211 ///////////////////////////////////////////////////////////////////////////
212 
213 
214 SpellCheckerDispatcher::SpellCheckerDispatcher( LngSvcMgr &rLngSvcMgr ) :
215     rMgr    (rLngSvcMgr)
216 {
217     pCache = NULL;
218 }
219 
220 
221 SpellCheckerDispatcher::~SpellCheckerDispatcher()
222 {
223     ClearSvcList();
224     delete pCache;
225 }
226 
227 
228 void SpellCheckerDispatcher::ClearSvcList()
229 {
230     // release memory for each table entry
231     SpellSvcByLangMap_t aTmp;
232     aSvcMap.swap( aTmp );
233 }
234 
235 
236 Sequence< Locale > SAL_CALL SpellCheckerDispatcher::getLocales()
237         throw(RuntimeException)
238 {
239     MutexGuard  aGuard( GetLinguMutex() );
240 
241     Sequence< Locale > aLocales( static_cast< sal_Int32 >(aSvcMap.size()) );
242     Locale *pLocales = aLocales.getArray();
243     SpellSvcByLangMap_t::const_iterator aIt;
244     for (aIt = aSvcMap.begin();  aIt != aSvcMap.end();  ++aIt)
245     {
246         *pLocales++ = CreateLocale( aIt->first );
247     }
248     return aLocales;
249 }
250 
251 
252 sal_Bool SAL_CALL SpellCheckerDispatcher::hasLocale( const Locale& rLocale )
253         throw(RuntimeException)
254 {
255     MutexGuard  aGuard( GetLinguMutex() );
256     SpellSvcByLangMap_t::const_iterator aIt( aSvcMap.find( LocaleToLanguage( rLocale ) ) );
257     return aIt != aSvcMap.end();
258 }
259 
260 
261 sal_Bool SAL_CALL
262     SpellCheckerDispatcher::isValid( const OUString& rWord, const Locale& rLocale,
263             const PropertyValues& rProperties )
264         throw(IllegalArgumentException, RuntimeException)
265 {
266     MutexGuard  aGuard( GetLinguMutex() );
267     return isValid_Impl( rWord, LocaleToLanguage( rLocale ), rProperties, sal_True );
268 }
269 
270 
271 Reference< XSpellAlternatives > SAL_CALL
272     SpellCheckerDispatcher::spell( const OUString& rWord, const Locale& rLocale,
273             const PropertyValues& rProperties )
274         throw(IllegalArgumentException, RuntimeException)
275 {
276     MutexGuard  aGuard( GetLinguMutex() );
277     return spell_Impl( rWord, LocaleToLanguage( rLocale ), rProperties, sal_True );
278 }
279 
280 
281 // returns the overall result of cross-checking with all user-dictionaries
282 // including the IgnoreAll list
283 static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry(
284     const OUString &rWord,
285     LanguageType nLanguage )
286 {
287     Reference< XDictionaryEntry > xRes;
288 
289     // the order of winning from top to bottom is:
290     // 1) IgnoreAll list will always win
291     // 2) Negative dictionaries will win over positive dictionaries
292     Reference< XDictionary > xIgnoreAll( GetIgnoreAllList() );
293     if (xIgnoreAll.is())
294         xRes = xIgnoreAll->getEntry( rWord );
295     if (!xRes.is())
296     {
297         Reference< XDictionaryList > xDList( GetDictionaryList() );
298         Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList,
299                 rWord, nLanguage, sal_False, sal_True ) );
300         if (xNegEntry.is())
301             xRes = xNegEntry;
302         else
303         {
304             Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList,
305                     rWord, nLanguage, sal_True, sal_True ) );
306             if (xPosEntry.is())
307                 xRes = xPosEntry;
308         }
309     }
310 
311     return xRes;
312 }
313 
314 
315 sal_Bool SpellCheckerDispatcher::isValid_Impl(
316             const OUString& rWord,
317             LanguageType nLanguage,
318             const PropertyValues& rProperties,
319             sal_Bool bCheckDics)
320         throw( RuntimeException, IllegalArgumentException )
321 {
322     MutexGuard  aGuard( GetLinguMutex() );
323 
324     sal_Bool bRes = sal_True;
325 
326     if (nLanguage == LANGUAGE_NONE  || !rWord.getLength())
327         return bRes;
328 
329     // search for entry with that language
330     SpellSvcByLangMap_t::iterator    aIt( aSvcMap.find( nLanguage ) );
331     LangSvcEntries_Spell    *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
332 
333     if (!pEntry)
334     {
335 #ifdef LINGU_EXCEPTIONS
336         throw IllegalArgumentException();
337 #endif
338     }
339     else
340     {
341         OUString aChkWord( rWord );
342         Locale aLocale( CreateLocale( nLanguage ) );
343 
344         // replace typographical apostroph by ascii apostroph
345         String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
346         DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
347         if (aSingleQuote.Len())
348             aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );
349 
350         RemoveHyphens( aChkWord );
351         if (IsIgnoreControlChars( rProperties, GetPropSet() ))
352             RemoveControlChars( aChkWord );
353 
354         sal_Int32 nLen = pEntry->aSvcRefs.getLength();
355         DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
356                 "lng : sequence length mismatch");
357         DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
358                 "lng : index out of range");
359 
360         sal_Int32 i = 0;
361         sal_Bool bTmpRes = sal_True;
362         sal_Bool bTmpResValid = sal_False;
363 
364         // try already instantiated services first
365         {
366             const Reference< XSpellChecker >  *pRef  =
367                     pEntry->aSvcRefs.getConstArray();
368             while (i <= pEntry->nLastTriedSvcIndex
369                    &&  (!bTmpResValid  ||  sal_False == bTmpRes))
370             {
371                 bTmpResValid = sal_True;
372                 if (pRef[i].is()  &&  pRef[i]->hasLocale( aLocale ))
373                 {
374                     bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
375                     if (!bTmpRes)
376                     {
377                         bTmpRes = pRef[i]->isValid( aChkWord, aLocale, rProperties );
378 
379                         // Add correct words to the cache.
380                         // But not those that are correct only because of
381                         // the temporary supplied settings.
382                         if (bTmpRes  &&  0 == rProperties.getLength())
383                             GetCache().AddWord( aChkWord, nLanguage );
384                     }
385                 }
386                 else
387                     bTmpResValid = sal_False;
388 
389                 if (bTmpResValid)
390                     bRes = bTmpRes;
391 
392                 ++i;
393             }
394         }
395 
396         // if still no result instantiate new services and try those
397         if ((!bTmpResValid  ||  sal_False == bTmpRes)
398             &&  pEntry->nLastTriedSvcIndex < nLen - 1)
399         {
400             const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
401             Reference< XSpellChecker >  *pRef  = pEntry->aSvcRefs .getArray();
402 
403             Reference< XMultiServiceFactory >  xMgr( getProcessServiceFactory() );
404             if (xMgr.is())
405             {
406                 // build service initialization argument
407                 Sequence< Any > aArgs(2);
408                 aArgs.getArray()[0] <<= GetPropSet();
409                 //! The dispatcher searches the dictionary-list
410                 //! thus the service needs not to now about it
411                 //aArgs.getArray()[1] <<= GetDicList();
412 
413                 while (i < nLen  &&  (!bTmpResValid  ||  sal_False == bTmpRes))
414                 {
415                     // create specific service via it's implementation name
416                     Reference< XSpellChecker > xSpell;
417                     try
418                     {
419                         xSpell = Reference< XSpellChecker >(
420                                 xMgr->createInstanceWithArguments(
421                                 pImplNames[i], aArgs ),  UNO_QUERY );
422                     }
423                     catch (uno::Exception &)
424                     {
425                         DBG_ASSERT( 0, "createInstanceWithArguments failed" );
426                     }
427                     pRef [i] = xSpell;
428 
429                     Reference< XLinguServiceEventBroadcaster >
430                             xBroadcaster( xSpell, UNO_QUERY );
431                     if (xBroadcaster.is())
432                         rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
433 
434                     bTmpResValid = sal_True;
435                     if (xSpell.is()  &&  xSpell->hasLocale( aLocale ))
436                     {
437                         bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
438                         if (!bTmpRes)
439                         {
440                             bTmpRes = xSpell->isValid( aChkWord, aLocale, rProperties );
441 
442                             // Add correct words to the cache.
443                             // But not those that are correct only because of
444                             // the temporary supplied settings.
445                             if (bTmpRes  &&  0 == rProperties.getLength())
446                                 GetCache().AddWord( aChkWord, nLanguage );
447                         }
448                     }
449                     else
450                         bTmpResValid = sal_False;
451 
452                     if (bTmpResValid)
453                         bRes = bTmpRes;
454 
455                     pEntry->nLastTriedSvcIndex = (sal_Int16) i;
456                     ++i;
457                 }
458 
459                 // if language is not supported by any of the services
460                 // remove it from the list.
461                 if (i == nLen)
462                 {
463                     if (!SvcListHasLanguage( *pEntry, nLanguage ))
464                         aSvcMap.erase( nLanguage );
465                 }
466             }
467         }
468 
469         // cross-check against results from dictionaries which have precedence!
470         if (bCheckDics  &&
471             GetDicList().is()  &&  IsUseDicList( rProperties, GetPropSet() ))
472         {
473             Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
474             if (xTmp.is())
475                 bRes = !xTmp->isNegative();
476         }
477     }
478 
479     return bRes;
480 }
481 
482 
483 Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl(
484             const OUString& rWord,
485             LanguageType nLanguage,
486             const PropertyValues& rProperties,
487             sal_Bool bCheckDics )
488         throw(IllegalArgumentException, RuntimeException)
489 {
490     MutexGuard  aGuard( GetLinguMutex() );
491 
492     Reference< XSpellAlternatives > xRes;
493 
494     if (nLanguage == LANGUAGE_NONE  || !rWord.getLength())
495         return xRes;
496 
497     // search for entry with that language
498     SpellSvcByLangMap_t::iterator    aIt( aSvcMap.find( nLanguage ) );
499     LangSvcEntries_Spell    *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
500 
501     if (!pEntry)
502     {
503 #ifdef LINGU_EXCEPTIONS
504         throw IllegalArgumentException();
505 #endif
506     }
507     else
508     {
509         OUString aChkWord( rWord );
510         Locale aLocale( CreateLocale( nLanguage ) );
511 
512         // replace typographical apostroph by ascii apostroph
513         String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
514         DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
515         if (aSingleQuote.Len())
516             aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );
517 
518         RemoveHyphens( aChkWord );
519         if (IsIgnoreControlChars( rProperties, GetPropSet() ))
520             RemoveControlChars( aChkWord );
521 
522         sal_Int32 nLen = pEntry->aSvcRefs.getLength();
523         DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
524                 "lng : sequence length mismatch");
525         DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
526                 "lng : index out of range");
527 
528         sal_Int32 i = 0;
529         Reference< XSpellAlternatives > xTmpRes;
530         sal_Bool bTmpResValid = sal_False;
531 
532         // try already instantiated services first
533         {
534             const Reference< XSpellChecker >  *pRef  = pEntry->aSvcRefs.getConstArray();
535             sal_Int32 nNumSugestions = -1;
536             while (i <= pEntry->nLastTriedSvcIndex
537                    &&  (!bTmpResValid || xTmpRes.is()) )
538             {
539                 bTmpResValid = sal_True;
540                 if (pRef[i].is()  &&  pRef[i]->hasLocale( aLocale ))
541                 {
542                     sal_Bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
543                     if (bOK)
544                         xTmpRes = NULL;
545                     else
546                     {
547                         xTmpRes = pRef[i]->spell( aChkWord, aLocale, rProperties );
548 
549                         // Add correct words to the cache.
550                         // But not those that are correct only because of
551                         // the temporary supplied settings.
552                         if (!xTmpRes.is()  &&  0 == rProperties.getLength())
553                             GetCache().AddWord( aChkWord, nLanguage );
554                     }
555                 }
556                 else
557                     bTmpResValid = sal_False;
558 
559                 // return first found result if the word is not known by any checker.
560                 // But if that result has no suggestions use the first one that does
561                 // provide suggestions for the misspelled word.
562                 if (!xRes.is() && bTmpResValid)
563                 {
564                     xRes = xTmpRes;
565                     nNumSugestions = 0;
566                     if (xRes.is())
567                         nNumSugestions = xRes->getAlternatives().getLength();
568                 }
569                 sal_Int32 nTmpNumSugestions = 0;
570                 if (xTmpRes.is() && bTmpResValid)
571                     nTmpNumSugestions = xTmpRes->getAlternatives().getLength();
572                 if (xRes.is() && nNumSugestions == 0 && nTmpNumSugestions > 0)
573                 {
574                     xRes = xTmpRes;
575                     nNumSugestions = nTmpNumSugestions;
576                 }
577 
578                 ++i;
579             }
580         }
581 
582         // if still no result instantiate new services and try those
583         if ((!bTmpResValid || xTmpRes.is())
584             &&  pEntry->nLastTriedSvcIndex < nLen - 1)
585         {
586             const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
587             Reference< XSpellChecker >  *pRef  = pEntry->aSvcRefs .getArray();
588 
589             Reference< XMultiServiceFactory >  xMgr( getProcessServiceFactory() );
590             if (xMgr.is())
591             {
592                 // build service initialization argument
593                 Sequence< Any > aArgs(2);
594                 aArgs.getArray()[0] <<= GetPropSet();
595                 //! The dispatcher searches the dictionary-list
596                 //! thus the service needs not to now about it
597                 //aArgs.getArray()[1] <<= GetDicList();
598 
599                 sal_Int32 nNumSugestions = -1;
600                 while (i < nLen  &&  (!bTmpResValid || xTmpRes.is()))
601                 {
602                     // create specific service via it's implementation name
603                     Reference< XSpellChecker > xSpell;
604                     try
605                     {
606                         xSpell = Reference< XSpellChecker >(
607                                 xMgr->createInstanceWithArguments(
608                                 pImplNames[i], aArgs ), UNO_QUERY );
609                     }
610                     catch (uno::Exception &)
611                     {
612                         DBG_ASSERT( 0, "createInstanceWithArguments failed" );
613                     }
614                     pRef [i] = xSpell;
615 
616                     Reference< XLinguServiceEventBroadcaster >
617                             xBroadcaster( xSpell, UNO_QUERY );
618                     if (xBroadcaster.is())
619                         rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
620 
621                     bTmpResValid = sal_True;
622                     if (xSpell.is()  &&  xSpell->hasLocale( aLocale ))
623                     {
624                         sal_Bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
625                         if (bOK)
626                             xTmpRes = NULL;
627                         else
628                         {
629                             xTmpRes = xSpell->spell( aChkWord, aLocale, rProperties );
630 
631                             // Add correct words to the cache.
632                             // But not those that are correct only because of
633                             // the temporary supplied settings.
634                             if (!xTmpRes.is()  &&  0 == rProperties.getLength())
635                                 GetCache().AddWord( aChkWord, nLanguage );
636                         }
637                     }
638                     else
639                         bTmpResValid = sal_False;
640 
641                     // return first found result if the word is not known by any checker.
642                     // But if that result has no suggestions use the first one that does
643                     // provide suggestions for the misspelled word.
644                     if (!xRes.is() && bTmpResValid)
645                     {
646                         xRes = xTmpRes;
647                         nNumSugestions = 0;
648                         if (xRes.is())
649                             nNumSugestions = xRes->getAlternatives().getLength();
650                     }
651                     sal_Int32 nTmpNumSugestions = 0;
652                     if (xTmpRes.is() && bTmpResValid)
653                         nTmpNumSugestions = xTmpRes->getAlternatives().getLength();
654                     if (xRes.is() && nNumSugestions == 0 && nTmpNumSugestions > 0)
655                     {
656                         xRes = xTmpRes;
657                         nNumSugestions = nTmpNumSugestions;
658                     }
659 
660                     pEntry->nLastTriedSvcIndex = (sal_Int16) i;
661                     ++i;
662                 }
663 
664                 // if language is not supported by any of the services
665                 // remove it from the list.
666                 if (i == nLen)
667                 {
668                     if (!SvcListHasLanguage( *pEntry, nLanguage ))
669                         aSvcMap.erase( nLanguage );
670                 }
671             }
672         }
673 
674         // if word is finally found to be correct
675         // clear previously remembered alternatives
676         if (bTmpResValid  &&  !xTmpRes.is())
677             xRes = NULL;
678 
679         // list of proposals found (to be checked against entries of
680         // neagtive dictionaries)
681         ProposalList aProposalList;
682 //        Sequence< OUString > aProposals;
683         sal_Int16 eFailureType = -1;    // no failure
684         if (xRes.is())
685         {
686             aProposalList.Append( xRes->getAlternatives() );
687 //            aProposals = xRes->getAlternatives();
688             eFailureType = xRes->getFailureType();
689         }
690         Reference< XDictionaryList > xDList;
691         if (GetDicList().is()  &&  IsUseDicList( rProperties, GetPropSet() ))
692             xDList = Reference< XDictionaryList >( GetDicList(), UNO_QUERY );
693 
694         // cross-check against results from user-dictionaries which have precedence!
695         if (bCheckDics  &&  xDList.is())
696         {
697             Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
698             if (xTmp.is())
699             {
700                 if (xTmp->isNegative())    // positive entry found
701                 {
702                     eFailureType = SpellFailure::IS_NEGATIVE_WORD;
703 
704                     // replacement text to be added to suggestions, if not empty
705                     OUString aAddRplcTxt( xTmp->getReplacementText() );
706 
707                     // replacement text must not be in negative dictionary itself
708                     if (aAddRplcTxt.getLength() &&
709                         !SearchDicList( xDList, aAddRplcTxt, nLanguage, sal_False, sal_True ).is())
710                     {
711                         aProposalList.Prepend( aAddRplcTxt );
712                     }
713                 }
714                 else    // positive entry found
715                 {
716                     xRes = NULL;
717                     eFailureType = -1;  // no failure
718                 }
719             }
720         }
721 
722         if (eFailureType != -1)     // word misspelled or found in negative user-dictionary
723         {
724             // search suitable user-dictionaries for suggestions that are
725             // similar to the misspelled word
726             std::vector< OUString > aDicListProps;   // list of proposals from user-dictionaries
727             SearchSimilarText( aChkWord, nLanguage, xDList, aDicListProps );
728             aProposalList.Append( aDicListProps );
729             Sequence< OUString > aProposals = aProposalList.GetSequence();
730 
731             // remove entries listed in negative dictionaries
732             // (we don't want to display suggestions that will be regarded as misspelledlater on)
733             if (bCheckDics  &&  xDList.is())
734                 SeqRemoveNegEntries( aProposals, xDList, nLanguage );
735 
736             uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, uno::UNO_QUERY );
737             if (xSetAlt.is())
738             {
739                 xSetAlt->setAlternatives( aProposals );
740                 xSetAlt->setFailureType( eFailureType );
741             }
742             else
743             {
744                 if (xRes.is())
745                 {
746                     DBG_ASSERT( 0, "XSetSpellAlternatives not implemented!" );
747                 }
748                 else if (aProposals.getLength() > 0)
749                 {
750                     // no xRes but Proposals found from the user-dictionaries.
751                     // Thus we need to create an xRes...
752                     xRes = new linguistic::SpellAlternatives( rWord, nLanguage,
753                             SpellFailure::IS_NEGATIVE_WORD, aProposals );
754                 }
755             }
756         }
757     }
758 
759     return xRes;
760 }
761 
762 uno::Sequence< sal_Int16 > SAL_CALL SpellCheckerDispatcher::getLanguages(  )
763 throw (uno::RuntimeException)
764 {
765     MutexGuard  aGuard( GetLinguMutex() );
766     uno::Sequence< Locale > aTmp( getLocales() );
767     uno::Sequence< sal_Int16 > aRes( LocaleSeqToLangSeq( aTmp ) );
768     return aRes;
769 }
770 
771 
772 sal_Bool SAL_CALL SpellCheckerDispatcher::hasLanguage(
773     sal_Int16 nLanguage )
774 throw (uno::RuntimeException)
775 {
776     MutexGuard  aGuard( GetLinguMutex() );
777     Locale aLocale( CreateLocale( nLanguage ) );
778     return hasLocale( aLocale );
779 }
780 
781 
782 sal_Bool SAL_CALL SpellCheckerDispatcher::isValid(
783     const OUString& rWord,
784     sal_Int16 nLanguage,
785     const uno::Sequence< beans::PropertyValue >& rProperties )
786 throw (lang::IllegalArgumentException, uno::RuntimeException)
787 {
788     MutexGuard  aGuard( GetLinguMutex() );
789     Locale aLocale( CreateLocale( nLanguage ) );
790     return isValid( rWord, aLocale, rProperties);
791 }
792 
793 
794 uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL SpellCheckerDispatcher::spell(
795     const OUString& rWord,
796     sal_Int16 nLanguage,
797     const uno::Sequence< beans::PropertyValue >& rProperties )
798 throw (lang::IllegalArgumentException, uno::RuntimeException)
799 {
800     MutexGuard  aGuard( GetLinguMutex() );
801     Locale aLocale( CreateLocale( nLanguage ) );
802     return spell( rWord, aLocale, rProperties);
803 }
804 
805 
806 void SpellCheckerDispatcher::SetServiceList( const Locale &rLocale,
807         const Sequence< OUString > &rSvcImplNames )
808 {
809     MutexGuard  aGuard( GetLinguMutex() );
810 
811     if (pCache)
812         pCache->Flush();    // new services may spell differently...
813 
814     sal_Int16 nLanguage = LocaleToLanguage( rLocale );
815 
816     sal_Int32 nLen = rSvcImplNames.getLength();
817     if (0 == nLen)
818         // remove entry
819         aSvcMap.erase( nLanguage );
820     else
821     {
822         // modify/add entry
823         LangSvcEntries_Spell *pEntry = aSvcMap[ nLanguage ].get();
824         if (pEntry)
825         {
826             pEntry->Clear();
827             pEntry->aSvcImplNames = rSvcImplNames;
828             pEntry->aSvcRefs = Sequence< Reference < XSpellChecker > > ( nLen );
829         }
830         else
831         {
832             boost::shared_ptr< LangSvcEntries_Spell > pTmpEntry( new LangSvcEntries_Spell( rSvcImplNames ) );
833             pTmpEntry->aSvcRefs = Sequence< Reference < XSpellChecker > >( nLen );
834             aSvcMap[ nLanguage ] = pTmpEntry;
835         }
836     }
837 }
838 
839 
840 Sequence< OUString >
841     SpellCheckerDispatcher::GetServiceList( const Locale &rLocale ) const
842 {
843     MutexGuard  aGuard( GetLinguMutex() );
844 
845     Sequence< OUString > aRes;
846 
847     // search for entry with that language and use data from that
848     sal_Int16 nLanguage = LocaleToLanguage( rLocale );
849     SpellCheckerDispatcher          *pThis = (SpellCheckerDispatcher *) this;
850     const SpellSvcByLangMap_t::iterator aIt( pThis->aSvcMap.find( nLanguage ) );
851     const LangSvcEntries_Spell      *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
852     if (pEntry)
853         aRes = pEntry->aSvcImplNames;
854 
855     return aRes;
856 }
857 
858 
859 LinguDispatcher::DspType SpellCheckerDispatcher::GetDspType() const
860 {
861     return DSP_SPELL;
862 }
863 
864 void SpellCheckerDispatcher::FlushSpellCache()
865 {
866     if (pCache)
867         pCache->Flush();
868 }
869 
870 ///////////////////////////////////////////////////////////////////////////
871 
872