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