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