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