office-gobmx/linguistic/source/spelldsp.cxx
Noel Grandin 69089f1737 loplugin:reftotemp in libreofficekit..oox
Change-Id: I62d868f70e33850fa933c8f413f824a523422499
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176400
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
Tested-by: Jenkins
2024-11-11 20:11:23 +01:00

844 lines
30 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sal/config.h>
#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/linguistic2/XLinguServiceEventBroadcaster.hpp>
#include <com/sun/star/linguistic2/SpellFailure.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <unotools/localedatawrapper.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <tools/debug.hxx>
#include <svl/lngmisc.hxx>
#include <osl/mutex.hxx>
#include <sal/log.hxx>
#include <vector>
#include "spelldsp.hxx"
#include <linguistic/spelldta.hxx>
#include "lngsvcmgr.hxx"
using namespace osl;
using namespace com::sun::star;
using namespace com::sun::star::beans;
using namespace com::sun::star::lang;
using namespace com::sun::star::uno;
using namespace com::sun::star::linguistic2;
using namespace linguistic;
namespace {
// ProposalList: list of proposals for misspelled words
// The order of strings in the array should be left unchanged because the
// spellchecker should have put the more likely suggestions at the top.
// New entries will be added to the end but duplicates are to be avoided.
// Removing entries is done by assigning the empty string.
// The sequence is constructed from all non empty strings in the original
// while maintaining the order.
class ProposalList
{
std::vector< OUString > aVec;
public:
ProposalList() {}
ProposalList(const ProposalList&) = delete;
ProposalList& operator=(const ProposalList&) = delete;
size_t Count() const;
void Prepend( const OUString &rText );
void Append( const OUString &rNew, bool bPrepend = false );
void Append( const std::vector< OUString > &rNew );
void Append( const Sequence< OUString > &rNew );
std::vector< OUString > GetVector() const;
};
}
void ProposalList::Prepend( const OUString &rText )
{
Append( rText, /*bPrepend=*/true );
}
void ProposalList::Append( const OUString &rOrig, bool bPrepend )
{
bool bFound = false;
// convert ASCII apostrophe to the typographic one
const OUString aText( rOrig.indexOf( '\'' ) > -1 ? rOrig.replace('\'', u'') : rOrig );
size_t nCnt = aVec.size();
for (size_t i = 0; !bFound && i < nCnt; ++i)
{
if (aVec[i] == aText)
bFound = true;
}
if (!bFound)
{
if ( bPrepend )
aVec.insert( aVec.begin(), aText );
else
aVec.push_back( aText );
}
}
void ProposalList::Append( const std::vector< OUString > &rNew )
{
size_t nLen = rNew.size();
for ( size_t i = 0; i < nLen; ++i)
{
const OUString &rText = rNew[i];
Append( rText );
}
}
void ProposalList::Append( const Sequence< OUString > &rNew )
{
for (const OUString& rText : rNew)
Append( rText );
}
size_t ProposalList::Count() const
{
// returns the number of non-empty strings in the vector
size_t nRes = 0;
size_t nLen = aVec.size();
for (size_t i = 0; i < nLen; ++i)
{
if (!aVec[i].isEmpty())
++nRes;
}
return nRes;
}
std::vector< OUString > ProposalList::GetVector() const
{
sal_Int32 nCount = Count();
sal_Int32 nIdx = 0;
std::vector< OUString > aRes( nCount );
sal_Int32 nLen = aVec.size();
for (sal_Int32 i = 0; i < nLen; ++i)
{
const OUString &rText = aVec[i];
DBG_ASSERT( nIdx < nCount, "index out of range" );
if (nIdx < nCount && !rText.isEmpty())
aRes[ nIdx++ ] = rText;
}
return aRes;
}
static bool SvcListHasLanguage(
const LangSvcEntries_Spell &rEntry,
LanguageType nLanguage )
{
Locale aTmpLocale = LanguageTag::convertToLocale( nLanguage );
return std::any_of(rEntry.aSvcRefs.begin(), rEntry.aSvcRefs.end(),
[&aTmpLocale](const Reference<XSpellChecker>& rRef) {
return rRef.is() && rRef->hasLocale( aTmpLocale ); });
}
SpellCheckerDispatcher::SpellCheckerDispatcher( LngSvcMgr &rLngSvcMgr ) :
m_rMgr (rLngSvcMgr)
{
}
SpellCheckerDispatcher::~SpellCheckerDispatcher()
{
}
Sequence< Locale > SAL_CALL SpellCheckerDispatcher::getLocales()
{
MutexGuard aGuard( GetLinguMutex() );
std::vector<Locale> aLocales;
aLocales.reserve(m_aSvcMap.size());
std::transform(m_aSvcMap.begin(), m_aSvcMap.end(), std::back_inserter(aLocales),
[](SpellSvcByLangMap_t::const_reference elem) { return LanguageTag::convertToLocale(elem.first); });
return comphelper::containerToSequence(aLocales);
}
sal_Bool SAL_CALL SpellCheckerDispatcher::hasLocale( const Locale& rLocale )
{
MutexGuard aGuard( GetLinguMutex() );
SpellSvcByLangMap_t::const_iterator aIt( m_aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
return aIt != m_aSvcMap.end();
}
sal_Bool SAL_CALL
SpellCheckerDispatcher::isValid( const OUString& rWord, const Locale& rLocale,
const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
{
MutexGuard aGuard( GetLinguMutex() );
// for historical reasons, the word can be only with ASCII apostrophe in the dictionaries,
// so as a fallback, convert typographical apostrophes to avoid annoying users, if they
// have old (user) dictionaries only with the obsolete ASCII apostrophe.
bool bConvert = false;
bool bRet = isValid_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties, bConvert );
if (!bRet && bConvert)
{
// fallback: convert the apostrophes
bRet = isValid_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties, bConvert );
}
return bRet;
}
Reference< XSpellAlternatives > SAL_CALL
SpellCheckerDispatcher::spell( const OUString& rWord, const Locale& rLocale,
const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
{
MutexGuard aGuard( GetLinguMutex() );
return spell_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties );
}
// returns the overall result of cross-checking with all user-dictionaries
// including the IgnoreAll list
static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry(
const OUString &rWord,
LanguageType nLanguage )
{
Reference< XDictionaryEntry > xRes;
// the order of winning from top to bottom is:
// 1) IgnoreAll list will always win
// 2) Negative dictionaries will win over positive dictionaries
Reference< XDictionary > xIgnoreAll( GetIgnoreAllList() );
if (xIgnoreAll.is())
xRes = xIgnoreAll->getEntry( rWord );
if (!xRes.is())
{
Reference< XSearchableDictionaryList > xDList( GetDictionaryList() );
Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList,
rWord, nLanguage, false, true ) );
if (xNegEntry.is())
xRes = std::move(xNegEntry);
else
{
Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList,
rWord, nLanguage, true, true ) );
if (xPosEntry.is())
xRes = std::move(xPosEntry);
}
}
return xRes;
}
bool SpellCheckerDispatcher::isValid_Impl(
const OUString& rWord,
LanguageType nLanguage,
const PropertyValues& rProperties,
bool& rConvertApostrophe)
{
MutexGuard aGuard( GetLinguMutex() );
bool bRes = true;
if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
return bRes;
// search for entry with that language
SpellSvcByLangMap_t::iterator aIt( m_aSvcMap.find( nLanguage ) );
LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
if (pEntry)
{
OUString aChkWord( rWord );
Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
// replace typographical apostrophe by ASCII apostrophe only as a fallback
// for old user dictionaries before the time of the default typographical apostrophe
// (Note: otherwise also no problem with non-Unicode Hunspell dictionaries, because
// the character conversion converts also the typographical apostrophe to the ASCII one)
OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
if (!aSingleQuote.isEmpty() && aChkWord.indexOf(aSingleQuote[0]) > -1)
{
// tdf#150582 first check with the original typographical apostrophe,
// and convert it only on the second try
if (rConvertApostrophe)
aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
else
rConvertApostrophe = true;
}
RemoveHyphens( aChkWord );
if (IsIgnoreControlChars( rProperties, GetPropSet() ))
RemoveControlChars( aChkWord );
sal_Int32 nLen = pEntry->aSvcRefs.getLength();
DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
"lng : sequence length mismatch");
DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
"lng : index out of range");
sal_Int32 i = 0;
bool bTmpRes = true;
bool bTmpResValid = false;
// try already instantiated services first
{
const Reference< XSpellChecker > *pRef =
pEntry->aSvcRefs.getConstArray();
while (i <= pEntry->nLastTriedSvcIndex
&& (!bTmpResValid || !bTmpRes))
{
bTmpResValid = true;
if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
{
bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
if (!bTmpRes)
{
bTmpRes = pRef[i]->isValid( aChkWord, aLocale, rProperties );
// Add correct words to the cache.
// But not those that are correct only because of
// the temporary supplied settings.
if (bTmpRes && !rProperties.hasElements())
GetCache().AddWord( aChkWord, nLanguage );
}
}
else
bTmpResValid = false;
if (bTmpResValid)
bRes = bTmpRes;
++i;
}
}
// if still no result instantiate new services and try those
if ((!bTmpResValid || !bTmpRes)
&& pEntry->nLastTriedSvcIndex < nLen - 1)
{
const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
const Reference< XComponentContext >& xContext(
comphelper::getProcessComponentContext() );
// build service initialization argument
Sequence< Any > aArgs(2);
aArgs.getArray()[0] <<= GetPropSet();
while (i < nLen && (!bTmpResValid || !bTmpRes))
{
// create specific service via it's implementation name
Reference< XSpellChecker > xSpell;
try
{
xSpell.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
pImplNames[i], aArgs, xContext ),
UNO_QUERY );
}
catch (uno::Exception &)
{
SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
}
pRef [i] = xSpell;
Reference< XLinguServiceEventBroadcaster >
xBroadcaster( xSpell, UNO_QUERY );
if (xBroadcaster.is())
m_rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
bTmpResValid = true;
if (xSpell.is() && xSpell->hasLocale( aLocale ))
{
bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
if (!bTmpRes)
{
bTmpRes = xSpell->isValid( aChkWord, aLocale, rProperties );
// Add correct words to the cache.
// But not those that are correct only because of
// the temporary supplied settings.
if (bTmpRes && !rProperties.hasElements())
GetCache().AddWord( aChkWord, nLanguage );
}
}
else
bTmpResValid = false;
if (bTmpResValid)
bRes = bTmpRes;
pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
++i;
}
// if language is not supported by any of the services
// remove it from the list.
if (i == nLen)
{
if (!SvcListHasLanguage( *pEntry, nLanguage ))
m_aSvcMap.erase( nLanguage );
}
}
// cross-check against results from dictionaries which have precedence!
if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
{
Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
if (xTmp.is()) {
bRes = !xTmp->isNegative();
} else {
setCharClass(LanguageTag(nLanguage));
CapType ct = capitalType(aChkWord, m_oCharClass ? &*m_oCharClass : nullptr);
if (ct == CapType::INITCAP || ct == CapType::ALLCAP) {
Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage ) );
if (xTmp2.is()) {
bRes = !xTmp2->isNegative();
}
}
}
}
}
return bRes;
}
Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl(
const OUString& rWord,
LanguageType nLanguage,
const PropertyValues& rProperties )
{
MutexGuard aGuard( GetLinguMutex() );
Reference< XSpellAlternatives > xRes;
if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
return xRes;
// search for entry with that language
SpellSvcByLangMap_t::iterator aIt( m_aSvcMap.find( nLanguage ) );
LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
if (pEntry)
{
OUString aChkWord( rWord );
Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
// replace typographical apostroph by ascii apostroph
OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
if (!aSingleQuote.isEmpty())
aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
RemoveHyphens( aChkWord );
if (IsIgnoreControlChars( rProperties, GetPropSet() ))
RemoveControlChars( aChkWord );
sal_Int32 nLen = pEntry->aSvcRefs.getLength();
DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
"lng : sequence length mismatch");
DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
"lng : index out of range");
sal_Int32 i = 0;
Reference< XSpellAlternatives > xTmpRes;
bool bTmpResValid = false;
// try already instantiated services first
{
const Reference< XSpellChecker > *pRef = pEntry->aSvcRefs.getConstArray();
sal_Int32 nNumSuggestions = -1;
while (i <= pEntry->nLastTriedSvcIndex
&& (!bTmpResValid || xTmpRes.is()) )
{
bTmpResValid = true;
if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
{
bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
if (bOK)
xTmpRes = nullptr;
else
{
xTmpRes = pRef[i]->spell( aChkWord, aLocale, rProperties );
// Add correct words to the cache.
// But not those that are correct only because of
// the temporary supplied settings.
if (!xTmpRes.is() && !rProperties.hasElements())
GetCache().AddWord( aChkWord, nLanguage );
}
}
else
bTmpResValid = false;
// return first found result if the word is not known by any checker.
// But if that result has no suggestions use the first one that does
// provide suggestions for the misspelled word.
if (!xRes.is() && bTmpResValid)
{
xRes = xTmpRes;
nNumSuggestions = 0;
if (xRes.is())
nNumSuggestions = xRes->getAlternatives().getLength();
}
sal_Int32 nTmpNumSuggestions = 0;
if (xTmpRes.is() && bTmpResValid)
nTmpNumSuggestions = xTmpRes->getAlternatives().getLength();
if (xRes.is() && nNumSuggestions == 0 && nTmpNumSuggestions > 0)
{
xRes = xTmpRes;
nNumSuggestions = nTmpNumSuggestions;
}
++i;
}
}
// if still no result instantiate new services and try those
if ((!bTmpResValid || xTmpRes.is())
&& pEntry->nLastTriedSvcIndex < nLen - 1)
{
const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
const Reference< XComponentContext >& xContext(
comphelper::getProcessComponentContext() );
// build service initialization argument
Sequence< Any > aArgs(2);
aArgs.getArray()[0] <<= GetPropSet();
sal_Int32 nNumSuggestions = -1;
while (i < nLen && (!bTmpResValid || xTmpRes.is()))
{
// create specific service via it's implementation name
Reference< XSpellChecker > xSpell;
try
{
xSpell.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
pImplNames[i], aArgs, xContext ),
UNO_QUERY );
}
catch (uno::Exception &)
{
SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
}
pRef [i] = xSpell;
Reference< XLinguServiceEventBroadcaster >
xBroadcaster( xSpell, UNO_QUERY );
if (xBroadcaster.is())
m_rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
bTmpResValid = true;
if (xSpell.is() && xSpell->hasLocale( aLocale ))
{
bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
if (bOK)
xTmpRes = nullptr;
else
{
xTmpRes = xSpell->spell( aChkWord, aLocale, rProperties );
// Add correct words to the cache.
// But not those that are correct only because of
// the temporary supplied settings.
if (!xTmpRes.is() && !rProperties.hasElements())
GetCache().AddWord( aChkWord, nLanguage );
}
}
else
bTmpResValid = false;
// return first found result if the word is not known by any checker.
// But if that result has no suggestions use the first one that does
// provide suggestions for the misspelled word.
if (!xRes.is() && bTmpResValid)
{
xRes = xTmpRes;
nNumSuggestions = 0;
if (xRes.is())
nNumSuggestions = xRes->getAlternatives().getLength();
}
sal_Int32 nTmpNumSuggestions = 0;
if (xTmpRes.is() && bTmpResValid)
nTmpNumSuggestions = xTmpRes->getAlternatives().getLength();
if (xRes.is() && nNumSuggestions == 0 && nTmpNumSuggestions > 0)
{
xRes = xTmpRes;
nNumSuggestions = nTmpNumSuggestions;
}
pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
++i;
}
// if language is not supported by any of the services
// remove it from the list.
if (i == nLen)
{
if (!SvcListHasLanguage( *pEntry, nLanguage ))
m_aSvcMap.erase( nLanguage );
}
}
// if word is finally found to be correct
// clear previously remembered alternatives
if (bTmpResValid && !xTmpRes.is())
xRes = nullptr;
// list of proposals found (to be checked against entries of
// negative dictionaries)
ProposalList aProposalList;
sal_Int16 eFailureType = -1; // no failure
if (xRes.is())
{
aProposalList.Append( xRes->getAlternatives() );
eFailureType = xRes->getFailureType();
}
Reference< XSearchableDictionaryList > xDList;
if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
xDList = GetDicList();
// cross-check against results from user-dictionaries which have precedence!
if (xDList.is())
{
Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
if (xTmp.is())
{
if (xTmp->isNegative()) // negative entry found
{
eFailureType = SpellFailure::IS_NEGATIVE_WORD;
// replacement text to be added to suggestions, if not empty
OUString aAddRplcTxt( xTmp->getReplacementText() );
// replacement text must not be in negative dictionary itself
if (!aAddRplcTxt.isEmpty() &&
!SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
{
aProposalList.Prepend( aAddRplcTxt );
}
}
else // positive entry found
{
xRes = nullptr;
eFailureType = -1; // no failure
}
}
else
{
setCharClass(LanguageTag(nLanguage));
CapType ct = capitalType(aChkWord, m_oCharClass ? &*m_oCharClass : nullptr);
if (ct == CapType::INITCAP || ct == CapType::ALLCAP)
{
Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage ) );
if (xTmp2.is())
{
if (xTmp2->isNegative()) // negative entry found
{
eFailureType = SpellFailure::IS_NEGATIVE_WORD;
// replacement text to be added to suggestions, if not empty
OUString aAddRplcTxt( xTmp2->getReplacementText() );
// replacement text must not be in negative dictionary itself
if (!aAddRplcTxt.isEmpty() &&
!SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
{
switch ( ct )
{
case CapType::INITCAP:
aProposalList.Prepend( m_oCharClass->titlecase(aAddRplcTxt) );
break;
case CapType::ALLCAP:
aProposalList.Prepend( m_oCharClass->uppercase(aAddRplcTxt) );
break;
default:
/* can't happen because of if ct == above */
break;
}
}
}
else // positive entry found
{
xRes = nullptr;
eFailureType = -1; // no failure
}
}
}
}
}
if (eFailureType != -1) // word misspelled or found in negative user-dictionary
{
// search suitable user-dictionaries for suggestions that are
// similar to the misspelled word
std::vector< OUString > aDicListProps; // list of proposals from user-dictionaries
SearchSimilarText( aChkWord, nLanguage, xDList, aDicListProps );
aProposalList.Append( aDicListProps );
std::vector< OUString > aProposals = aProposalList.GetVector();
// remove entries listed in negative dictionaries
// (we don't want to display suggestions that will be regarded as misspelled later on)
if (xDList.is())
SeqRemoveNegEntries( aProposals, xDList, nLanguage );
uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, uno::UNO_QUERY );
if (xSetAlt.is())
{
xSetAlt->setAlternatives( comphelper::containerToSequence(aProposals) );
xSetAlt->setFailureType( eFailureType );
}
else
{
if (xRes.is())
{
SAL_WARN( "linguistic", "XSetSpellAlternatives not implemented!" );
}
else if (!aProposals.empty())
{
// no xRes but Proposals found from the user-dictionaries.
// Thus we need to create an xRes...
xRes = new linguistic::SpellAlternatives( rWord, nLanguage,
comphelper::containerToSequence(aProposals) );
}
}
}
}
return xRes;
}
uno::Sequence< sal_Int16 > SAL_CALL SpellCheckerDispatcher::getLanguages( )
{
MutexGuard aGuard( GetLinguMutex() );
uno::Sequence< Locale > aTmp( getLocales() );
uno::Sequence< sal_Int16 > aRes( LocaleSeqToLangSeq( aTmp ) );
return aRes;
}
sal_Bool SAL_CALL SpellCheckerDispatcher::hasLanguage(
sal_Int16 nLanguage )
{
MutexGuard aGuard( GetLinguMutex() );
return hasLocale( LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))));
}
sal_Bool SAL_CALL SpellCheckerDispatcher::isValid(
const OUString& rWord,
sal_Int16 nLanguage,
const uno::Sequence< beans::PropertyValue >& rProperties )
{
MutexGuard aGuard( GetLinguMutex() );
return isValid( rWord, LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))), rProperties);
}
uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL SpellCheckerDispatcher::spell(
const OUString& rWord,
sal_Int16 nLanguage,
const uno::Sequence< beans::PropertyValue >& rProperties )
{
MutexGuard aGuard( GetLinguMutex() );
return spell(rWord, LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))), rProperties);
}
void SpellCheckerDispatcher::SetServiceList( const Locale &rLocale,
const Sequence< OUString > &rSvcImplNames )
{
MutexGuard aGuard( GetLinguMutex() );
if (m_pCache)
m_pCache->Flush(); // new services may spell differently...
LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
sal_Int32 nLen = rSvcImplNames.getLength();
if (0 == nLen)
// remove entry
m_aSvcMap.erase( nLanguage );
else
{
// modify/add entry
LangSvcEntries_Spell *pEntry = m_aSvcMap[ nLanguage ].get();
if (pEntry)
{
pEntry->Clear();
pEntry->aSvcImplNames = rSvcImplNames;
pEntry->aSvcRefs = Sequence< Reference < XSpellChecker > > ( nLen );
}
else
{
auto pTmpEntry = std::make_shared<LangSvcEntries_Spell>( rSvcImplNames );
pTmpEntry->aSvcRefs = Sequence< Reference < XSpellChecker > >( nLen );
m_aSvcMap[ nLanguage ] = std::move(pTmpEntry);
}
}
}
Sequence< OUString >
SpellCheckerDispatcher::GetServiceList( const Locale &rLocale ) const
{
MutexGuard aGuard( GetLinguMutex() );
Sequence< OUString > aRes;
// search for entry with that language and use data from that
LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
const SpellSvcByLangMap_t::const_iterator aIt( m_aSvcMap.find( nLanguage ) );
const LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
if (pEntry)
aRes = pEntry->aSvcImplNames;
return aRes;
}
void SpellCheckerDispatcher::FlushSpellCache()
{
if (m_pCache)
m_pCache->Flush();
}
void SpellCheckerDispatcher::setCharClass(const LanguageTag& rLanguageTag)
{
if (m_oCharClass && m_oCharClass->getLanguageTag() == rLanguageTag)
return;
m_oCharClass.emplace( rLanguageTag );
}
OUString SpellCheckerDispatcher::makeLowerCase(const OUString& aTerm, const std::optional<CharClass> & pCC)
{
if (pCC)
return pCC->lowercase(aTerm);
return aTerm;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */