xref: /trunk/main/linguistic/source/spelldsp.cxx (revision 3b8558fd)
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