tdf#147298: Add a simple test case for formula cell tracking by column.
[LibreOffice.git] / linguistic / source / spelldsp.cxx
blobc1b309b00adf8415467fe4438c1da2f3e98cc469
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 bool HasEntry( std::u16string_view rText ) const;
64 public:
65 ProposalList() {}
66 ProposalList(const ProposalList&) = delete;
67 ProposalList& operator=(const ProposalList&) = delete;
69 size_t Count() const;
70 void Prepend( const OUString &rText );
71 void Append( const OUString &rNew );
72 void Append( const std::vector< OUString > &rNew );
73 void Append( const Sequence< OUString > &rNew );
74 std::vector< OUString > GetVector() const;
79 bool ProposalList::HasEntry( std::u16string_view rText ) const
81 bool bFound = false;
82 size_t nCnt = aVec.size();
83 for (size_t i = 0; !bFound && i < nCnt; ++i)
85 if (aVec[i] == rText)
86 bFound = true;
88 return bFound;
91 void ProposalList::Prepend( const OUString &rText )
93 if (!HasEntry( rText ))
94 aVec.insert( aVec.begin(), rText );
97 void ProposalList::Append( const OUString &rText )
99 if (!HasEntry( rText ))
100 aVec.push_back( rText );
103 void ProposalList::Append( const std::vector< OUString > &rNew )
105 size_t nLen = rNew.size();
106 for ( size_t i = 0; i < nLen; ++i)
108 const OUString &rText = rNew[i];
109 if (!HasEntry( rText ))
110 Append( rText );
114 void ProposalList::Append( const Sequence< OUString > &rNew )
116 for (const OUString& rText : rNew)
118 if (!HasEntry( rText ))
119 Append( rText );
123 size_t ProposalList::Count() const
125 // returns the number of non-empty strings in the vector
127 size_t nRes = 0;
128 size_t nLen = aVec.size();
129 for (size_t i = 0; i < nLen; ++i)
131 if (!aVec[i].isEmpty())
132 ++nRes;
134 return nRes;
137 std::vector< OUString > ProposalList::GetVector() const
139 sal_Int32 nCount = Count();
140 sal_Int32 nIdx = 0;
141 std::vector< OUString > aRes( nCount );
142 sal_Int32 nLen = aVec.size();
143 for (sal_Int32 i = 0; i < nLen; ++i)
145 const OUString &rText = aVec[i];
146 DBG_ASSERT( nIdx < nCount, "index out of range" );
147 if (nIdx < nCount && !rText.isEmpty())
148 aRes[ nIdx++ ] = rText;
150 return aRes;
153 static bool SvcListHasLanguage(
154 const LangSvcEntries_Spell &rEntry,
155 LanguageType nLanguage )
157 Locale aTmpLocale = LanguageTag::convertToLocale( nLanguage );
159 return std::any_of(rEntry.aSvcRefs.begin(), rEntry.aSvcRefs.end(),
160 [&aTmpLocale](const Reference<XSpellChecker>& rRef) {
161 return rRef.is() && rRef->hasLocale( aTmpLocale ); });
164 SpellCheckerDispatcher::SpellCheckerDispatcher( LngSvcMgr &rLngSvcMgr ) :
165 m_rMgr (rLngSvcMgr)
170 SpellCheckerDispatcher::~SpellCheckerDispatcher()
175 Sequence< Locale > SAL_CALL SpellCheckerDispatcher::getLocales()
177 MutexGuard aGuard( GetLinguMutex() );
179 std::vector<Locale> aLocales;
180 aLocales.reserve(m_aSvcMap.size());
182 std::transform(m_aSvcMap.begin(), m_aSvcMap.end(), std::back_inserter(aLocales),
183 [](SpellSvcByLangMap_t::const_reference elem) { return LanguageTag::convertToLocale(elem.first); });
185 return comphelper::containerToSequence(aLocales);
189 sal_Bool SAL_CALL SpellCheckerDispatcher::hasLocale( const Locale& rLocale )
191 MutexGuard aGuard( GetLinguMutex() );
192 SpellSvcByLangMap_t::const_iterator aIt( m_aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
193 return aIt != m_aSvcMap.end();
197 sal_Bool SAL_CALL
198 SpellCheckerDispatcher::isValid( const OUString& rWord, const Locale& rLocale,
199 const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
201 MutexGuard aGuard( GetLinguMutex() );
202 return isValid_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties );
206 Reference< XSpellAlternatives > SAL_CALL
207 SpellCheckerDispatcher::spell( const OUString& rWord, const Locale& rLocale,
208 const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
210 MutexGuard aGuard( GetLinguMutex() );
211 return spell_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties );
215 // returns the overall result of cross-checking with all user-dictionaries
216 // including the IgnoreAll list
217 static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry(
218 const OUString &rWord,
219 LanguageType nLanguage )
221 Reference< XDictionaryEntry > xRes;
223 // the order of winning from top to bottom is:
224 // 1) IgnoreAll list will always win
225 // 2) Negative dictionaries will win over positive dictionaries
226 Reference< XDictionary > xIgnoreAll( GetIgnoreAllList() );
227 if (xIgnoreAll.is())
228 xRes = xIgnoreAll->getEntry( rWord );
229 if (!xRes.is())
231 Reference< XSearchableDictionaryList > xDList( GetDictionaryList() );
232 Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList,
233 rWord, nLanguage, false, true ) );
234 if (xNegEntry.is())
235 xRes = xNegEntry;
236 else
238 Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList,
239 rWord, nLanguage, true, true ) );
240 if (xPosEntry.is())
241 xRes = xPosEntry;
245 return xRes;
249 bool SpellCheckerDispatcher::isValid_Impl(
250 const OUString& rWord,
251 LanguageType nLanguage,
252 const PropertyValues& rProperties)
254 MutexGuard aGuard( GetLinguMutex() );
256 bool bRes = true;
258 if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
259 return bRes;
261 // search for entry with that language
262 SpellSvcByLangMap_t::iterator aIt( m_aSvcMap.find( nLanguage ) );
263 LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
265 if (pEntry)
267 OUString aChkWord( rWord );
268 Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
270 // replace typographical apostroph by ascii apostroph
271 OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
272 DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
273 if (!aSingleQuote.isEmpty())
274 aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
276 RemoveHyphens( aChkWord );
277 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
278 RemoveControlChars( aChkWord );
280 sal_Int32 nLen = pEntry->aSvcRefs.getLength();
281 DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
282 "lng : sequence length mismatch");
283 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
284 "lng : index out of range");
286 sal_Int32 i = 0;
287 bool bTmpRes = true;
288 bool bTmpResValid = false;
290 // try already instantiated services first
292 const Reference< XSpellChecker > *pRef =
293 pEntry->aSvcRefs.getConstArray();
294 while (i <= pEntry->nLastTriedSvcIndex
295 && (!bTmpResValid || !bTmpRes))
297 bTmpResValid = true;
298 if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
300 bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
301 if (!bTmpRes)
303 bTmpRes = pRef[i]->isValid( aChkWord, aLocale, rProperties );
305 // Add correct words to the cache.
306 // But not those that are correct only because of
307 // the temporary supplied settings.
308 if (bTmpRes && !rProperties.hasElements())
309 GetCache().AddWord( aChkWord, nLanguage );
312 else
313 bTmpResValid = false;
315 if (bTmpResValid)
316 bRes = bTmpRes;
318 ++i;
322 // if still no result instantiate new services and try those
323 if ((!bTmpResValid || !bTmpRes)
324 && pEntry->nLastTriedSvcIndex < nLen - 1)
326 const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
327 Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
329 Reference< XComponentContext > xContext(
330 comphelper::getProcessComponentContext() );
332 // build service initialization argument
333 Sequence< Any > aArgs(2);
334 aArgs.getArray()[0] <<= GetPropSet();
336 while (i < nLen && (!bTmpResValid || !bTmpRes))
338 // create specific service via it's implementation name
339 Reference< XSpellChecker > xSpell;
342 xSpell.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
343 pImplNames[i], aArgs, xContext ),
344 UNO_QUERY );
346 catch (uno::Exception &)
348 SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
350 pRef [i] = xSpell;
352 Reference< XLinguServiceEventBroadcaster >
353 xBroadcaster( xSpell, UNO_QUERY );
354 if (xBroadcaster.is())
355 m_rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
357 bTmpResValid = true;
358 if (xSpell.is() && xSpell->hasLocale( aLocale ))
360 bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
361 if (!bTmpRes)
363 bTmpRes = xSpell->isValid( aChkWord, aLocale, rProperties );
364 // Add correct words to the cache.
365 // But not those that are correct only because of
366 // the temporary supplied settings.
367 if (bTmpRes && !rProperties.hasElements())
368 GetCache().AddWord( aChkWord, nLanguage );
371 else
372 bTmpResValid = false;
373 if (bTmpResValid)
374 bRes = bTmpRes;
376 pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
377 ++i;
380 // if language is not supported by any of the services
381 // remove it from the list.
382 if (i == nLen)
384 if (!SvcListHasLanguage( *pEntry, nLanguage ))
385 m_aSvcMap.erase( nLanguage );
389 // cross-check against results from dictionaries which have precedence!
390 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
392 Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
393 if (xTmp.is()) {
394 bRes = !xTmp->isNegative();
395 } else {
396 setCharClass(LanguageTag(nLanguage));
397 CapType ct = capitalType(aChkWord, m_pCharClass.get());
398 if (ct == CapType::INITCAP || ct == CapType::ALLCAP) {
399 Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_pCharClass.get()), nLanguage ) );
400 if (xTmp2.is()) {
401 bRes = !xTmp2->isNegative();
408 return bRes;
412 Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl(
413 const OUString& rWord,
414 LanguageType nLanguage,
415 const PropertyValues& rProperties )
417 MutexGuard aGuard( GetLinguMutex() );
419 Reference< XSpellAlternatives > xRes;
421 if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
422 return xRes;
424 // search for entry with that language
425 SpellSvcByLangMap_t::iterator aIt( m_aSvcMap.find( nLanguage ) );
426 LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
428 if (pEntry)
430 OUString aChkWord( rWord );
431 Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
433 // replace typographical apostroph by ascii apostroph
434 OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
435 DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
436 if (!aSingleQuote.isEmpty())
437 aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
439 RemoveHyphens( aChkWord );
440 if (IsIgnoreControlChars( rProperties, GetPropSet() ))
441 RemoveControlChars( aChkWord );
443 sal_Int32 nLen = pEntry->aSvcRefs.getLength();
444 DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
445 "lng : sequence length mismatch");
446 DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
447 "lng : index out of range");
449 sal_Int32 i = 0;
450 Reference< XSpellAlternatives > xTmpRes;
451 bool bTmpResValid = false;
453 // try already instantiated services first
455 const Reference< XSpellChecker > *pRef = pEntry->aSvcRefs.getConstArray();
456 sal_Int32 nNumSuggestions = -1;
457 while (i <= pEntry->nLastTriedSvcIndex
458 && (!bTmpResValid || xTmpRes.is()) )
460 bTmpResValid = true;
461 if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
463 bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
464 if (bOK)
465 xTmpRes = nullptr;
466 else
468 xTmpRes = pRef[i]->spell( aChkWord, aLocale, rProperties );
470 // Add correct words to the cache.
471 // But not those that are correct only because of
472 // the temporary supplied settings.
473 if (!xTmpRes.is() && !rProperties.hasElements())
474 GetCache().AddWord( aChkWord, nLanguage );
477 else
478 bTmpResValid = false;
480 // return first found result if the word is not known by any checker.
481 // But if that result has no suggestions use the first one that does
482 // provide suggestions for the misspelled word.
483 if (!xRes.is() && bTmpResValid)
485 xRes = xTmpRes;
486 nNumSuggestions = 0;
487 if (xRes.is())
488 nNumSuggestions = xRes->getAlternatives().getLength();
490 sal_Int32 nTmpNumSuggestions = 0;
491 if (xTmpRes.is() && bTmpResValid)
492 nTmpNumSuggestions = xTmpRes->getAlternatives().getLength();
493 if (xRes.is() && nNumSuggestions == 0 && nTmpNumSuggestions > 0)
495 xRes = xTmpRes;
496 nNumSuggestions = nTmpNumSuggestions;
499 ++i;
503 // if still no result instantiate new services and try those
504 if ((!bTmpResValid || xTmpRes.is())
505 && pEntry->nLastTriedSvcIndex < nLen - 1)
507 const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
508 Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
510 Reference< XComponentContext > xContext(
511 comphelper::getProcessComponentContext() );
513 // build service initialization argument
514 Sequence< Any > aArgs(2);
515 aArgs.getArray()[0] <<= GetPropSet();
517 sal_Int32 nNumSuggestions = -1;
518 while (i < nLen && (!bTmpResValid || xTmpRes.is()))
520 // create specific service via it's implementation name
521 Reference< XSpellChecker > xSpell;
524 xSpell.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
525 pImplNames[i], aArgs, xContext ),
526 UNO_QUERY );
528 catch (uno::Exception &)
530 SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
532 pRef [i] = xSpell;
534 Reference< XLinguServiceEventBroadcaster >
535 xBroadcaster( xSpell, UNO_QUERY );
536 if (xBroadcaster.is())
537 m_rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
539 bTmpResValid = true;
540 if (xSpell.is() && xSpell->hasLocale( aLocale ))
542 bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
543 if (bOK)
544 xTmpRes = nullptr;
545 else
547 xTmpRes = xSpell->spell( aChkWord, aLocale, rProperties );
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() && !rProperties.hasElements())
553 GetCache().AddWord( aChkWord, nLanguage );
556 else
557 bTmpResValid = false;
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)
564 xRes = xTmpRes;
565 nNumSuggestions = 0;
566 if (xRes.is())
567 nNumSuggestions = xRes->getAlternatives().getLength();
569 sal_Int32 nTmpNumSuggestions = 0;
570 if (xTmpRes.is() && bTmpResValid)
571 nTmpNumSuggestions = xTmpRes->getAlternatives().getLength();
572 if (xRes.is() && nNumSuggestions == 0 && nTmpNumSuggestions > 0)
574 xRes = xTmpRes;
575 nNumSuggestions = nTmpNumSuggestions;
578 pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
579 ++i;
582 // if language is not supported by any of the services
583 // remove it from the list.
584 if (i == nLen)
586 if (!SvcListHasLanguage( *pEntry, nLanguage ))
587 m_aSvcMap.erase( nLanguage );
591 // if word is finally found to be correct
592 // clear previously remembered alternatives
593 if (bTmpResValid && !xTmpRes.is())
594 xRes = nullptr;
596 // list of proposals found (to be checked against entries of
597 // negative dictionaries)
598 ProposalList aProposalList;
599 sal_Int16 eFailureType = -1; // no failure
600 if (xRes.is())
602 aProposalList.Append( xRes->getAlternatives() );
603 eFailureType = xRes->getFailureType();
605 Reference< XSearchableDictionaryList > xDList;
606 if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
607 xDList = GetDicList();
609 // cross-check against results from user-dictionaries which have precedence!
610 if (xDList.is())
612 Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
613 if (xTmp.is())
615 if (xTmp->isNegative()) // negative entry found
617 eFailureType = SpellFailure::IS_NEGATIVE_WORD;
619 // replacement text to be added to suggestions, if not empty
620 OUString aAddRplcTxt( xTmp->getReplacementText() );
622 // replacement text must not be in negative dictionary itself
623 if (!aAddRplcTxt.isEmpty() &&
624 !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
626 aProposalList.Prepend( aAddRplcTxt );
629 else // positive entry found
631 xRes = nullptr;
632 eFailureType = -1; // no failure
635 else
637 setCharClass(LanguageTag(nLanguage));
638 CapType ct = capitalType(aChkWord, m_pCharClass.get());
639 if (ct == CapType::INITCAP || ct == CapType::ALLCAP)
641 Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_pCharClass.get()), nLanguage ) );
642 if (xTmp2.is())
644 if (xTmp2->isNegative()) // negative entry found
646 eFailureType = SpellFailure::IS_NEGATIVE_WORD;
648 // replacement text to be added to suggestions, if not empty
649 OUString aAddRplcTxt( xTmp2->getReplacementText() );
651 // replacement text must not be in negative dictionary itself
652 if (!aAddRplcTxt.isEmpty() &&
653 !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
655 switch ( ct )
657 case CapType::INITCAP:
658 aProposalList.Prepend( m_pCharClass->titlecase(aAddRplcTxt) );
659 break;
660 case CapType::ALLCAP:
661 aProposalList.Prepend( m_pCharClass->uppercase(aAddRplcTxt) );
662 break;
663 default:
664 /* can't happen because of if ct == above */
665 break;
669 else // positive entry found
671 xRes = nullptr;
672 eFailureType = -1; // no failure
679 if (eFailureType != -1) // word misspelled or found in negative user-dictionary
681 // search suitable user-dictionaries for suggestions that are
682 // similar to the misspelled word
683 std::vector< OUString > aDicListProps; // list of proposals from user-dictionaries
684 SearchSimilarText( aChkWord, nLanguage, xDList, aDicListProps );
685 aProposalList.Append( aDicListProps );
686 std::vector< OUString > aProposals = aProposalList.GetVector();
688 // remove entries listed in negative dictionaries
689 // (we don't want to display suggestions that will be regarded as misspelled later on)
690 if (xDList.is())
691 SeqRemoveNegEntries( aProposals, xDList, nLanguage );
693 uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, uno::UNO_QUERY );
694 if (xSetAlt.is())
696 xSetAlt->setAlternatives( comphelper::containerToSequence(aProposals) );
697 xSetAlt->setFailureType( eFailureType );
699 else
701 if (xRes.is())
703 SAL_WARN( "linguistic", "XSetSpellAlternatives not implemented!" );
705 else if (!aProposals.empty())
707 // no xRes but Proposals found from the user-dictionaries.
708 // Thus we need to create an xRes...
709 xRes = new linguistic::SpellAlternatives( rWord, nLanguage,
710 comphelper::containerToSequence(aProposals) );
716 return xRes;
719 uno::Sequence< sal_Int16 > SAL_CALL SpellCheckerDispatcher::getLanguages( )
721 MutexGuard aGuard( GetLinguMutex() );
722 uno::Sequence< Locale > aTmp( getLocales() );
723 uno::Sequence< sal_Int16 > aRes( LocaleSeqToLangSeq( aTmp ) );
724 return aRes;
728 sal_Bool SAL_CALL SpellCheckerDispatcher::hasLanguage(
729 sal_Int16 nLanguage )
731 MutexGuard aGuard( GetLinguMutex() );
732 return hasLocale( LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))));
736 sal_Bool SAL_CALL SpellCheckerDispatcher::isValid(
737 const OUString& rWord,
738 sal_Int16 nLanguage,
739 const uno::Sequence< beans::PropertyValue >& rProperties )
741 MutexGuard aGuard( GetLinguMutex() );
742 return isValid( rWord, LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))), rProperties);
746 uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL SpellCheckerDispatcher::spell(
747 const OUString& rWord,
748 sal_Int16 nLanguage,
749 const uno::Sequence< beans::PropertyValue >& rProperties )
751 MutexGuard aGuard( GetLinguMutex() );
752 return spell(rWord, LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))), rProperties);
756 void SpellCheckerDispatcher::SetServiceList( const Locale &rLocale,
757 const Sequence< OUString > &rSvcImplNames )
759 MutexGuard aGuard( GetLinguMutex() );
761 if (m_pCache)
762 m_pCache->Flush(); // new services may spell differently...
764 LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
766 sal_Int32 nLen = rSvcImplNames.getLength();
767 if (0 == nLen)
768 // remove entry
769 m_aSvcMap.erase( nLanguage );
770 else
772 // modify/add entry
773 LangSvcEntries_Spell *pEntry = m_aSvcMap[ nLanguage ].get();
774 if (pEntry)
776 pEntry->Clear();
777 pEntry->aSvcImplNames = rSvcImplNames;
778 pEntry->aSvcRefs = Sequence< Reference < XSpellChecker > > ( nLen );
780 else
782 auto pTmpEntry = std::make_shared<LangSvcEntries_Spell>( rSvcImplNames );
783 pTmpEntry->aSvcRefs = Sequence< Reference < XSpellChecker > >( nLen );
784 m_aSvcMap[ nLanguage ] = pTmpEntry;
790 Sequence< OUString >
791 SpellCheckerDispatcher::GetServiceList( const Locale &rLocale ) const
793 MutexGuard aGuard( GetLinguMutex() );
795 Sequence< OUString > aRes;
797 // search for entry with that language and use data from that
798 LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
799 const SpellSvcByLangMap_t::const_iterator aIt( m_aSvcMap.find( nLanguage ) );
800 const LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
801 if (pEntry)
802 aRes = pEntry->aSvcImplNames;
804 return aRes;
808 void SpellCheckerDispatcher::FlushSpellCache()
810 if (m_pCache)
811 m_pCache->Flush();
814 void SpellCheckerDispatcher::setCharClass(const LanguageTag& rLanguageTag)
816 if (m_pCharClass && m_pCharClass->getLanguageTag() == rLanguageTag)
817 return;
818 m_pCharClass.reset( new CharClass(rLanguageTag) );
822 OUString SpellCheckerDispatcher::makeLowerCase(const OUString& aTerm, CharClass const * pCC)
824 if (pCC)
825 return pCC->lowercase(aTerm);
826 return aTerm;
829 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */