tdf#146269: don't set modified when connecting frame, model and controller
[LibreOffice.git] / linguistic / source / spelldsp.cxx
blobeb15cf3be923d04ba5220b687b69afea1cfa124d
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
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>
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>
35 #include <vector>
37 #include "spelldsp.hxx"
38 #include <linguistic/spelldta.hxx>
39 #include "lngsvcmgr.hxx"
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;
49 namespace {
51 // ProposalList: list of proposals for misspelled words
52 // The order of strings in the array should be left unchanged because the
53 // spellchecker should have put the more likely suggestions at the top.
54 // New entries will be added to the end but duplicates are to be avoided.
55 // Removing entries is done by assigning the empty string.
56 // The sequence is constructed from all non empty strings in the original
57 // while maintaining the order.
58 class ProposalList
60 std::vector< OUString > aVec;
62 public:
63 ProposalList() {}
64 ProposalList(const ProposalList&) = delete;
65 ProposalList& operator=(const ProposalList&) = delete;
67 size_t Count() const;
68 void Prepend( const OUString &rText );
69 void Append( const OUString &rNew, bool bPrepend = false );
70 void Append( const std::vector< OUString > &rNew );
71 void Append( const Sequence< OUString > &rNew );
72 std::vector< OUString > GetVector() const;
77 void ProposalList::Prepend( const OUString &rText )
79 Append( rText, /*bPrepend=*/true );
82 void ProposalList::Append( const OUString &rOrig, bool bPrepend )
84 bool bFound = false;
85 // convert ASCII apostrophe to the typographic one
86 const OUString aText( rOrig.indexOf( '\'' ) > -1 ? rOrig.replace('\'', u'’') : rOrig );
87 size_t nCnt = aVec.size();
88 for (size_t i = 0; !bFound && i < nCnt; ++i)
90 if (aVec[i] == aText)
91 bFound = true;
93 if (!bFound)
95 if ( bPrepend )
96 aVec.insert( aVec.begin(), aText );
97 else
98 aVec.push_back( aText );
102 void ProposalList::Append( const std::vector< OUString > &rNew )
104 size_t nLen = rNew.size();
105 for ( size_t i = 0; i < nLen; ++i)
107 const OUString &rText = rNew[i];
108 Append( rText );
112 void ProposalList::Append( const Sequence< OUString > &rNew )
114 for (const OUString& rText : rNew)
115 Append( rText );
118 size_t ProposalList::Count() const
120 // returns the number of non-empty strings in the vector
122 size_t nRes = 0;
123 size_t nLen = aVec.size();
124 for (size_t i = 0; i < nLen; ++i)
126 if (!aVec[i].isEmpty())
127 ++nRes;
129 return nRes;
132 std::vector< OUString > ProposalList::GetVector() const
134 sal_Int32 nCount = Count();
135 sal_Int32 nIdx = 0;
136 std::vector< OUString > aRes( nCount );
137 sal_Int32 nLen = aVec.size();
138 for (sal_Int32 i = 0; i < nLen; ++i)
140 const OUString &rText = aVec[i];
141 DBG_ASSERT( nIdx < nCount, "index out of range" );
142 if (nIdx < nCount && !rText.isEmpty())
143 aRes[ nIdx++ ] = rText;
145 return aRes;
148 static bool SvcListHasLanguage(
149 const LangSvcEntries_Spell &rEntry,
150 LanguageType nLanguage )
152 Locale aTmpLocale = LanguageTag::convertToLocale( nLanguage );
154 return std::any_of(rEntry.aSvcRefs.begin(), rEntry.aSvcRefs.end(),
155 [&aTmpLocale](const Reference<XSpellChecker>& rRef) {
156 return rRef.is() && rRef->hasLocale( aTmpLocale ); });
159 SpellCheckerDispatcher::SpellCheckerDispatcher( LngSvcMgr &rLngSvcMgr ) :
160 m_rMgr (rLngSvcMgr)
165 SpellCheckerDispatcher::~SpellCheckerDispatcher()
170 Sequence< Locale > SAL_CALL SpellCheckerDispatcher::getLocales()
172 MutexGuard aGuard( GetLinguMutex() );
174 std::vector<Locale> aLocales;
175 aLocales.reserve(m_aSvcMap.size());
177 std::transform(m_aSvcMap.begin(), m_aSvcMap.end(), std::back_inserter(aLocales),
178 [](SpellSvcByLangMap_t::const_reference elem) { return LanguageTag::convertToLocale(elem.first); });
180 return comphelper::containerToSequence(aLocales);
184 sal_Bool SAL_CALL SpellCheckerDispatcher::hasLocale( const Locale& rLocale )
186 MutexGuard aGuard( GetLinguMutex() );
187 SpellSvcByLangMap_t::const_iterator aIt( m_aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
188 return aIt != m_aSvcMap.end();
192 sal_Bool SAL_CALL
193 SpellCheckerDispatcher::isValid( const OUString& rWord, const Locale& rLocale,
194 const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
196 MutexGuard aGuard( GetLinguMutex() );
197 // for historical reasons, the word can be only with ASCII apostrophe in the dictionaries,
198 // so as a fallback, convert typographical apostrophes to avoid annoying users, if they
199 // have old (user) dictionaries only with the obsolete ASCII apostrophe.
200 bool bConvert = false;
201 bool bRet = isValid_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties, bConvert );
202 if (!bRet && bConvert)
204 // fallback: convert the apostrophes
205 bRet = isValid_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties, bConvert );
207 return bRet;
210 Reference< XSpellAlternatives > SAL_CALL
211 SpellCheckerDispatcher::spell( const OUString& rWord, const Locale& rLocale,
212 const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
214 MutexGuard aGuard( GetLinguMutex() );
215 return spell_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties );
219 // returns the overall result of cross-checking with all user-dictionaries
220 // including the IgnoreAll list
221 static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry(
222 const OUString &rWord,
223 LanguageType nLanguage )
225 Reference< XDictionaryEntry > xRes;
227 // the order of winning from top to bottom is:
228 // 1) IgnoreAll list will always win
229 // 2) Negative dictionaries will win over positive dictionaries
230 Reference< XDictionary > xIgnoreAll( GetIgnoreAllList() );
231 if (xIgnoreAll.is())
232 xRes = xIgnoreAll->getEntry( rWord );
233 if (!xRes.is())
235 Reference< XSearchableDictionaryList > xDList( GetDictionaryList() );
236 Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList,
237 rWord, nLanguage, false, true ) );
238 if (xNegEntry.is())
239 xRes = std::move(xNegEntry);
240 else
242 Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList,
243 rWord, nLanguage, true, true ) );
244 if (xPosEntry.is())
245 xRes = std::move(xPosEntry);
249 return xRes;
253 bool SpellCheckerDispatcher::isValid_Impl(
254 const OUString& rWord,
255 LanguageType nLanguage,
256 const PropertyValues& rProperties,
257 bool& rConvertApostrophe)
259 MutexGuard aGuard( GetLinguMutex() );
261 bool bRes = true;
263 if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
264 return bRes;
266 // search for entry with that language
267 SpellSvcByLangMap_t::iterator aIt( m_aSvcMap.find( nLanguage ) );
268 LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
270 if (pEntry)
272 OUString aChkWord( rWord );
273 Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
275 // replace typographical apostrophe by ASCII apostrophe only as a fallback
276 // for old user dictionaries before the time of the default typographical apostrophe
277 // (Note: otherwise also no problem with non-Unicode Hunspell dictionaries, because
278 // the character conversion converts also the typographical apostrophe to the ASCII one)
279 OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
280 DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
281 if (!aSingleQuote.isEmpty() && aChkWord.indexOf(aSingleQuote[0]) > -1)
283 // tdf#150582 first check with the original typographical apostrophe,
284 // and convert it only on the second try
285 if (rConvertApostrophe)
286 aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
287 else
288 rConvertApostrophe = true;
291 RemoveHyphens( aChkWord );
292 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
293 RemoveControlChars( aChkWord );
295 sal_Int32 nLen = pEntry->aSvcRefs.getLength();
296 DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
297 "lng : sequence length mismatch");
298 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
299 "lng : index out of range");
301 sal_Int32 i = 0;
302 bool bTmpRes = true;
303 bool bTmpResValid = false;
305 // try already instantiated services first
307 const Reference< XSpellChecker > *pRef =
308 pEntry->aSvcRefs.getConstArray();
309 while (i <= pEntry->nLastTriedSvcIndex
310 && (!bTmpResValid || !bTmpRes))
312 bTmpResValid = true;
313 if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
315 bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
316 if (!bTmpRes)
318 bTmpRes = pRef[i]->isValid( aChkWord, aLocale, rProperties );
320 // Add correct words to the cache.
321 // But not those that are correct only because of
322 // the temporary supplied settings.
323 if (bTmpRes && !rProperties.hasElements())
324 GetCache().AddWord( aChkWord, nLanguage );
327 else
328 bTmpResValid = false;
330 if (bTmpResValid)
331 bRes = bTmpRes;
333 ++i;
337 // if still no result instantiate new services and try those
338 if ((!bTmpResValid || !bTmpRes)
339 && pEntry->nLastTriedSvcIndex < nLen - 1)
341 const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
342 Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
344 const Reference< XComponentContext >& xContext(
345 comphelper::getProcessComponentContext() );
347 // build service initialization argument
348 Sequence< Any > aArgs(2);
349 aArgs.getArray()[0] <<= GetPropSet();
351 while (i < nLen && (!bTmpResValid || !bTmpRes))
353 // create specific service via it's implementation name
354 Reference< XSpellChecker > xSpell;
357 xSpell.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
358 pImplNames[i], aArgs, xContext ),
359 UNO_QUERY );
361 catch (uno::Exception &)
363 SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
365 pRef [i] = xSpell;
367 Reference< XLinguServiceEventBroadcaster >
368 xBroadcaster( xSpell, UNO_QUERY );
369 if (xBroadcaster.is())
370 m_rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
372 bTmpResValid = true;
373 if (xSpell.is() && xSpell->hasLocale( aLocale ))
375 bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
376 if (!bTmpRes)
378 bTmpRes = xSpell->isValid( aChkWord, aLocale, rProperties );
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 && !rProperties.hasElements())
383 GetCache().AddWord( aChkWord, nLanguage );
386 else
387 bTmpResValid = false;
388 if (bTmpResValid)
389 bRes = bTmpRes;
391 pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
392 ++i;
395 // if language is not supported by any of the services
396 // remove it from the list.
397 if (i == nLen)
399 if (!SvcListHasLanguage( *pEntry, nLanguage ))
400 m_aSvcMap.erase( nLanguage );
404 // cross-check against results from dictionaries which have precedence!
405 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
407 Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
408 if (xTmp.is()) {
409 bRes = !xTmp->isNegative();
410 } else {
411 setCharClass(LanguageTag(nLanguage));
412 CapType ct = capitalType(aChkWord, m_oCharClass ? &*m_oCharClass : nullptr);
413 if (ct == CapType::INITCAP || ct == CapType::ALLCAP) {
414 Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage ) );
415 if (xTmp2.is()) {
416 bRes = !xTmp2->isNegative();
423 return bRes;
427 Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl(
428 const OUString& rWord,
429 LanguageType nLanguage,
430 const PropertyValues& rProperties )
432 MutexGuard aGuard( GetLinguMutex() );
434 Reference< XSpellAlternatives > xRes;
436 if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
437 return xRes;
439 // search for entry with that language
440 SpellSvcByLangMap_t::iterator aIt( m_aSvcMap.find( nLanguage ) );
441 LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
443 if (pEntry)
445 OUString aChkWord( rWord );
446 Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
448 // replace typographical apostroph by ascii apostroph
449 OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
450 DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
451 if (!aSingleQuote.isEmpty())
452 aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
454 RemoveHyphens( aChkWord );
455 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
456 RemoveControlChars( aChkWord );
458 sal_Int32 nLen = pEntry->aSvcRefs.getLength();
459 DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
460 "lng : sequence length mismatch");
461 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
462 "lng : index out of range");
464 sal_Int32 i = 0;
465 Reference< XSpellAlternatives > xTmpRes;
466 bool bTmpResValid = false;
468 // try already instantiated services first
470 const Reference< XSpellChecker > *pRef = pEntry->aSvcRefs.getConstArray();
471 sal_Int32 nNumSuggestions = -1;
472 while (i <= pEntry->nLastTriedSvcIndex
473 && (!bTmpResValid || xTmpRes.is()) )
475 bTmpResValid = true;
476 if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
478 bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
479 if (bOK)
480 xTmpRes = nullptr;
481 else
483 xTmpRes = pRef[i]->spell( aChkWord, aLocale, rProperties );
485 // Add correct words to the cache.
486 // But not those that are correct only because of
487 // the temporary supplied settings.
488 if (!xTmpRes.is() && !rProperties.hasElements())
489 GetCache().AddWord( aChkWord, nLanguage );
492 else
493 bTmpResValid = false;
495 // return first found result if the word is not known by any checker.
496 // But if that result has no suggestions use the first one that does
497 // provide suggestions for the misspelled word.
498 if (!xRes.is() && bTmpResValid)
500 xRes = xTmpRes;
501 nNumSuggestions = 0;
502 if (xRes.is())
503 nNumSuggestions = xRes->getAlternatives().getLength();
505 sal_Int32 nTmpNumSuggestions = 0;
506 if (xTmpRes.is() && bTmpResValid)
507 nTmpNumSuggestions = xTmpRes->getAlternatives().getLength();
508 if (xRes.is() && nNumSuggestions == 0 && nTmpNumSuggestions > 0)
510 xRes = xTmpRes;
511 nNumSuggestions = nTmpNumSuggestions;
514 ++i;
518 // if still no result instantiate new services and try those
519 if ((!bTmpResValid || xTmpRes.is())
520 && pEntry->nLastTriedSvcIndex < nLen - 1)
522 const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
523 Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
525 const Reference< XComponentContext >& xContext(
526 comphelper::getProcessComponentContext() );
528 // build service initialization argument
529 Sequence< Any > aArgs(2);
530 aArgs.getArray()[0] <<= GetPropSet();
532 sal_Int32 nNumSuggestions = -1;
533 while (i < nLen && (!bTmpResValid || xTmpRes.is()))
535 // create specific service via it's implementation name
536 Reference< XSpellChecker > xSpell;
539 xSpell.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
540 pImplNames[i], aArgs, xContext ),
541 UNO_QUERY );
543 catch (uno::Exception &)
545 SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
547 pRef [i] = xSpell;
549 Reference< XLinguServiceEventBroadcaster >
550 xBroadcaster( xSpell, UNO_QUERY );
551 if (xBroadcaster.is())
552 m_rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
554 bTmpResValid = true;
555 if (xSpell.is() && xSpell->hasLocale( aLocale ))
557 bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
558 if (bOK)
559 xTmpRes = nullptr;
560 else
562 xTmpRes = xSpell->spell( aChkWord, aLocale, rProperties );
564 // Add correct words to the cache.
565 // But not those that are correct only because of
566 // the temporary supplied settings.
567 if (!xTmpRes.is() && !rProperties.hasElements())
568 GetCache().AddWord( aChkWord, nLanguage );
571 else
572 bTmpResValid = false;
574 // return first found result if the word is not known by any checker.
575 // But if that result has no suggestions use the first one that does
576 // provide suggestions for the misspelled word.
577 if (!xRes.is() && bTmpResValid)
579 xRes = xTmpRes;
580 nNumSuggestions = 0;
581 if (xRes.is())
582 nNumSuggestions = xRes->getAlternatives().getLength();
584 sal_Int32 nTmpNumSuggestions = 0;
585 if (xTmpRes.is() && bTmpResValid)
586 nTmpNumSuggestions = xTmpRes->getAlternatives().getLength();
587 if (xRes.is() && nNumSuggestions == 0 && nTmpNumSuggestions > 0)
589 xRes = xTmpRes;
590 nNumSuggestions = nTmpNumSuggestions;
593 pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
594 ++i;
597 // if language is not supported by any of the services
598 // remove it from the list.
599 if (i == nLen)
601 if (!SvcListHasLanguage( *pEntry, nLanguage ))
602 m_aSvcMap.erase( nLanguage );
606 // if word is finally found to be correct
607 // clear previously remembered alternatives
608 if (bTmpResValid && !xTmpRes.is())
609 xRes = nullptr;
611 // list of proposals found (to be checked against entries of
612 // negative dictionaries)
613 ProposalList aProposalList;
614 sal_Int16 eFailureType = -1; // no failure
615 if (xRes.is())
617 aProposalList.Append( xRes->getAlternatives() );
618 eFailureType = xRes->getFailureType();
620 Reference< XSearchableDictionaryList > xDList;
621 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
622 xDList = GetDicList();
624 // cross-check against results from user-dictionaries which have precedence!
625 if (xDList.is())
627 Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
628 if (xTmp.is())
630 if (xTmp->isNegative()) // negative entry found
632 eFailureType = SpellFailure::IS_NEGATIVE_WORD;
634 // replacement text to be added to suggestions, if not empty
635 OUString aAddRplcTxt( xTmp->getReplacementText() );
637 // replacement text must not be in negative dictionary itself
638 if (!aAddRplcTxt.isEmpty() &&
639 !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
641 aProposalList.Prepend( aAddRplcTxt );
644 else // positive entry found
646 xRes = nullptr;
647 eFailureType = -1; // no failure
650 else
652 setCharClass(LanguageTag(nLanguage));
653 CapType ct = capitalType(aChkWord, m_oCharClass ? &*m_oCharClass : nullptr);
654 if (ct == CapType::INITCAP || ct == CapType::ALLCAP)
656 Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage ) );
657 if (xTmp2.is())
659 if (xTmp2->isNegative()) // negative entry found
661 eFailureType = SpellFailure::IS_NEGATIVE_WORD;
663 // replacement text to be added to suggestions, if not empty
664 OUString aAddRplcTxt( xTmp2->getReplacementText() );
666 // replacement text must not be in negative dictionary itself
667 if (!aAddRplcTxt.isEmpty() &&
668 !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
670 switch ( ct )
672 case CapType::INITCAP:
673 aProposalList.Prepend( m_oCharClass->titlecase(aAddRplcTxt) );
674 break;
675 case CapType::ALLCAP:
676 aProposalList.Prepend( m_oCharClass->uppercase(aAddRplcTxt) );
677 break;
678 default:
679 /* can't happen because of if ct == above */
680 break;
684 else // positive entry found
686 xRes = nullptr;
687 eFailureType = -1; // no failure
694 if (eFailureType != -1) // word misspelled or found in negative user-dictionary
696 // search suitable user-dictionaries for suggestions that are
697 // similar to the misspelled word
698 std::vector< OUString > aDicListProps; // list of proposals from user-dictionaries
699 SearchSimilarText( aChkWord, nLanguage, xDList, aDicListProps );
700 aProposalList.Append( aDicListProps );
701 std::vector< OUString > aProposals = aProposalList.GetVector();
703 // remove entries listed in negative dictionaries
704 // (we don't want to display suggestions that will be regarded as misspelled later on)
705 if (xDList.is())
706 SeqRemoveNegEntries( aProposals, xDList, nLanguage );
708 uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, uno::UNO_QUERY );
709 if (xSetAlt.is())
711 xSetAlt->setAlternatives( comphelper::containerToSequence(aProposals) );
712 xSetAlt->setFailureType( eFailureType );
714 else
716 if (xRes.is())
718 SAL_WARN( "linguistic", "XSetSpellAlternatives not implemented!" );
720 else if (!aProposals.empty())
722 // no xRes but Proposals found from the user-dictionaries.
723 // Thus we need to create an xRes...
724 xRes = new linguistic::SpellAlternatives( rWord, nLanguage,
725 comphelper::containerToSequence(aProposals) );
731 return xRes;
734 uno::Sequence< sal_Int16 > SAL_CALL SpellCheckerDispatcher::getLanguages( )
736 MutexGuard aGuard( GetLinguMutex() );
737 uno::Sequence< Locale > aTmp( getLocales() );
738 uno::Sequence< sal_Int16 > aRes( LocaleSeqToLangSeq( aTmp ) );
739 return aRes;
743 sal_Bool SAL_CALL SpellCheckerDispatcher::hasLanguage(
744 sal_Int16 nLanguage )
746 MutexGuard aGuard( GetLinguMutex() );
747 return hasLocale( LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))));
751 sal_Bool SAL_CALL SpellCheckerDispatcher::isValid(
752 const OUString& rWord,
753 sal_Int16 nLanguage,
754 const uno::Sequence< beans::PropertyValue >& rProperties )
756 MutexGuard aGuard( GetLinguMutex() );
757 return isValid( rWord, LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))), rProperties);
761 uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL SpellCheckerDispatcher::spell(
762 const OUString& rWord,
763 sal_Int16 nLanguage,
764 const uno::Sequence< beans::PropertyValue >& rProperties )
766 MutexGuard aGuard( GetLinguMutex() );
767 return spell(rWord, LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))), rProperties);
771 void SpellCheckerDispatcher::SetServiceList( const Locale &rLocale,
772 const Sequence< OUString > &rSvcImplNames )
774 MutexGuard aGuard( GetLinguMutex() );
776 if (m_pCache)
777 m_pCache->Flush(); // new services may spell differently...
779 LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
781 sal_Int32 nLen = rSvcImplNames.getLength();
782 if (0 == nLen)
783 // remove entry
784 m_aSvcMap.erase( nLanguage );
785 else
787 // modify/add entry
788 LangSvcEntries_Spell *pEntry = m_aSvcMap[ nLanguage ].get();
789 if (pEntry)
791 pEntry->Clear();
792 pEntry->aSvcImplNames = rSvcImplNames;
793 pEntry->aSvcRefs = Sequence< Reference < XSpellChecker > > ( nLen );
795 else
797 auto pTmpEntry = std::make_shared<LangSvcEntries_Spell>( rSvcImplNames );
798 pTmpEntry->aSvcRefs = Sequence< Reference < XSpellChecker > >( nLen );
799 m_aSvcMap[ nLanguage ] = std::move(pTmpEntry);
805 Sequence< OUString >
806 SpellCheckerDispatcher::GetServiceList( const Locale &rLocale ) const
808 MutexGuard aGuard( GetLinguMutex() );
810 Sequence< OUString > aRes;
812 // search for entry with that language and use data from that
813 LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
814 const SpellSvcByLangMap_t::const_iterator aIt( m_aSvcMap.find( nLanguage ) );
815 const LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
816 if (pEntry)
817 aRes = pEntry->aSvcImplNames;
819 return aRes;
823 void SpellCheckerDispatcher::FlushSpellCache()
825 if (m_pCache)
826 m_pCache->Flush();
829 void SpellCheckerDispatcher::setCharClass(const LanguageTag& rLanguageTag)
831 if (m_oCharClass && m_oCharClass->getLanguageTag() == rLanguageTag)
832 return;
833 m_oCharClass.emplace( rLanguageTag );
837 OUString SpellCheckerDispatcher::makeLowerCase(const OUString& aTerm, const std::optional<CharClass> & pCC)
839 if (pCC)
840 return pCC->lowercase(aTerm);
841 return aTerm;
844 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */