1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
37 #include "spelldsp.hxx"
38 #include <linguistic/spelldta.hxx>
39 #include "lngsvcmgr.hxx"
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
;
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.
60 std::vector
< OUString
> aVec
;
64 ProposalList(const ProposalList
&) = delete;
65 ProposalList
& operator=(const ProposalList
&) = delete;
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
)
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
)
96 aVec
.insert( aVec
.begin(), aText
);
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
];
112 void ProposalList::Append( const Sequence
< OUString
> &rNew
)
114 for (const OUString
& rText
: rNew
)
118 size_t ProposalList::Count() const
120 // returns the number of non-empty strings in the vector
123 size_t nLen
= aVec
.size();
124 for (size_t i
= 0; i
< nLen
; ++i
)
126 if (!aVec
[i
].isEmpty())
132 std::vector
< OUString
> ProposalList::GetVector() const
134 sal_Int32 nCount
= Count();
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
;
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
) :
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();
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
);
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() );
232 xRes
= xIgnoreAll
->getEntry( rWord
);
235 Reference
< XSearchableDictionaryList
> xDList( GetDictionaryList() );
236 Reference
< XDictionaryEntry
> xNegEntry( SearchDicList( xDList
,
237 rWord
, nLanguage
, false, true ) );
239 xRes
= std::move(xNegEntry
);
242 Reference
< XDictionaryEntry
> xPosEntry( SearchDicList( xDList
,
243 rWord
, nLanguage
, true, true ) );
245 xRes
= std::move(xPosEntry
);
253 bool SpellCheckerDispatcher::isValid_Impl(
254 const OUString
& rWord
,
255 LanguageType nLanguage
,
256 const PropertyValues
& rProperties
,
257 bool& rConvertApostrophe
)
259 MutexGuard
aGuard( GetLinguMutex() );
263 if (LinguIsUnspecified( nLanguage
) || rWord
.isEmpty())
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;
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], '\'' );
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");
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
))
313 if (pRef
[i
].is() && pRef
[i
]->hasLocale( aLocale
))
315 bTmpRes
= GetCache().CheckWord( aChkWord
, nLanguage
);
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
);
328 bTmpResValid
= false;
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
),
361 catch (uno::Exception
&)
363 SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
367 Reference
< XLinguServiceEventBroadcaster
>
368 xBroadcaster( xSpell
, UNO_QUERY
);
369 if (xBroadcaster
.is())
370 m_rMgr
.AddLngSvcEvtBroadcaster( xBroadcaster
);
373 if (xSpell
.is() && xSpell
->hasLocale( aLocale
))
375 bTmpRes
= GetCache().CheckWord( aChkWord
, nLanguage
);
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
);
387 bTmpResValid
= false;
391 pEntry
->nLastTriedSvcIndex
= static_cast<sal_Int16
>(i
);
395 // if language is not supported by any of the services
396 // remove it from the list.
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
) );
409 bRes
= !xTmp
->isNegative();
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
) );
416 bRes
= !xTmp2
->isNegative();
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())
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;
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");
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()) )
476 if (pRef
[i
].is() && pRef
[i
]->hasLocale( aLocale
))
478 bool bOK
= GetCache().CheckWord( aChkWord
, nLanguage
);
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
);
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
)
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)
511 nNumSuggestions
= nTmpNumSuggestions
;
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
),
543 catch (uno::Exception
&)
545 SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
549 Reference
< XLinguServiceEventBroadcaster
>
550 xBroadcaster( xSpell
, UNO_QUERY
);
551 if (xBroadcaster
.is())
552 m_rMgr
.AddLngSvcEvtBroadcaster( xBroadcaster
);
555 if (xSpell
.is() && xSpell
->hasLocale( aLocale
))
557 bool bOK
= GetCache().CheckWord( aChkWord
, nLanguage
);
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
);
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
)
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)
590 nNumSuggestions
= nTmpNumSuggestions
;
593 pEntry
->nLastTriedSvcIndex
= static_cast<sal_Int16
>(i
);
597 // if language is not supported by any of the services
598 // remove it from the list.
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())
611 // list of proposals found (to be checked against entries of
612 // negative dictionaries)
613 ProposalList aProposalList
;
614 sal_Int16 eFailureType
= -1; // no failure
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!
627 Reference
< XDictionaryEntry
> xTmp( lcl_GetRulingDictionaryEntry( aChkWord
, nLanguage
) );
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
647 eFailureType
= -1; // no failure
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
) );
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())
672 case CapType::INITCAP
:
673 aProposalList
.Prepend( m_oCharClass
->titlecase(aAddRplcTxt
) );
675 case CapType::ALLCAP
:
676 aProposalList
.Prepend( m_oCharClass
->uppercase(aAddRplcTxt
) );
679 /* can't happen because of if ct == above */
684 else // positive entry found
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)
706 SeqRemoveNegEntries( aProposals
, xDList
, nLanguage
);
708 uno::Reference
< linguistic2::XSetSpellAlternatives
> xSetAlt( xRes
, uno::UNO_QUERY
);
711 xSetAlt
->setAlternatives( comphelper::containerToSequence(aProposals
) );
712 xSetAlt
->setFailureType( eFailureType
);
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
) );
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
) );
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
,
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
,
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() );
777 m_pCache
->Flush(); // new services may spell differently...
779 LanguageType nLanguage
= LinguLocaleToLanguage( rLocale
);
781 sal_Int32 nLen
= rSvcImplNames
.getLength();
784 m_aSvcMap
.erase( nLanguage
);
788 LangSvcEntries_Spell
*pEntry
= m_aSvcMap
[ nLanguage
].get();
792 pEntry
->aSvcImplNames
= rSvcImplNames
;
793 pEntry
->aSvcRefs
= Sequence
< Reference
< XSpellChecker
> > ( nLen
);
797 auto pTmpEntry
= std::make_shared
<LangSvcEntries_Spell
>( rSvcImplNames
);
798 pTmpEntry
->aSvcRefs
= Sequence
< Reference
< XSpellChecker
> >( nLen
);
799 m_aSvcMap
[ nLanguage
] = std::move(pTmpEntry
);
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;
817 aRes
= pEntry
->aSvcImplNames
;
823 void SpellCheckerDispatcher::FlushSpellCache()
829 void SpellCheckerDispatcher::setCharClass(const LanguageTag
& rLanguageTag
)
831 if (m_oCharClass
&& m_oCharClass
->getLanguageTag() == rLanguageTag
)
833 m_oCharClass
.emplace( rLanguageTag
);
837 OUString
SpellCheckerDispatcher::makeLowerCase(const OUString
& aTerm
, const std::optional
<CharClass
> & pCC
)
840 return pCC
->lowercase(aTerm
);
844 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */