include/linguistic/misc.hxx | 4 +- include/linguistic/spelldta.hxx | 3 + linguistic/source/dlistimp.cxx | 3 + linguistic/source/misc.cxx | 65 +++++++++++++++++++++++++--------------- linguistic/source/spelldsp.cxx | 26 ++++++++-------- linguistic/source/spelldsp.hxx | 1 linguistic/source/spelldta.cxx | 5 +-- 7 files changed, 67 insertions(+), 40 deletions(-)
New commits: commit b2da15234473c8bda598813c707efb7038c12840 Author: Noel Grandin <[email protected]> AuthorDate: Wed Jan 28 11:27:09 2026 +0200 Commit: Noel Grandin <[email protected]> CommitDate: Thu Jan 29 12:10:54 2026 +0100 tdf#148218 reduce OString alloc in dictionary searching cache the information we need from the dictionary, so we avoid repeatedly calling LinguLocaleToLanguage in SearchDicList for every single word we spellcheck. Shaves 17% of the temporary allocations Change-Id: Ie28dab4224a35ec703ff7f698e9ea89775594c74 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198281 Reviewed-by: Noel Grandin <[email protected]> Tested-by: Jenkins diff --git a/include/linguistic/misc.hxx b/include/linguistic/misc.hxx index f2db0d3ad5ed..eda5152701de 100644 --- a/include/linguistic/misc.hxx +++ b/include/linguistic/misc.hxx @@ -32,6 +32,7 @@ #include <rtl/ref.hxx> #include <linguistic/lngdllapi.h> +#include <map> #include <vector> namespace com::sun::star::beans { class XPropertySet; } @@ -154,7 +155,8 @@ css::uno::Reference< SearchDicList( const css::uno::Reference< css::linguistic2::XSearchableDictionaryList >& rDicList, const OUString& rWord, LanguageType nLanguage, - bool bSearchPosDics, bool bSearchSpellEntry ); + bool bSearchPosDics, bool bSearchSpellEntry, + std::map<LanguageType, std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& rDictionaryMap ); LNG_DLLPUBLIC DictionaryError AddEntryToDic( css::uno::Reference< css::linguistic2::XDictionary > const &rxDic, diff --git a/include/linguistic/spelldta.hxx b/include/linguistic/spelldta.hxx index d0057ffbd426..921a832936d5 100644 --- a/include/linguistic/spelldta.hxx +++ b/include/linguistic/spelldta.hxx @@ -42,7 +42,8 @@ std::vector< OUString > void SeqRemoveNegEntries( std::vector< OUString > &rSeq, css::uno::Reference< css::linguistic2::XSearchableDictionaryList > const &rxDicList, - LanguageType nLanguage ); + LanguageType nLanguage, + std::map<LanguageType, std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& rDictionaryMap ); void SearchSimilarText( const OUString &rText, LanguageType nLanguage, css::uno::Reference< css::linguistic2::XSearchableDictionaryList > const &xDicList, diff --git a/linguistic/source/dlistimp.cxx b/linguistic/source/dlistimp.cxx index 56c24db1d4df..6157acea51b5 100644 --- a/linguistic/source/dlistimp.cxx +++ b/linguistic/source/dlistimp.cxx @@ -509,8 +509,9 @@ uno::Reference< XDictionaryEntry > SAL_CALL sal_Bool bSearchPosDics, sal_Bool bSearchSpellEntry ) { osl::MutexGuard aGuard( GetLinguMutex() ); + std::map<LanguageType, std::vector<css::uno::Reference<css::linguistic2::XDictionary>>> aDictionaryMap; return SearchDicList( this, rWord, LinguLocaleToLanguage( rLocale ), - bSearchPosDics, bSearchSpellEntry ); + bSearchPosDics, bSearchSpellEntry, aDictionaryMap ); } diff --git a/linguistic/source/misc.cxx b/linguistic/source/misc.cxx index 9b1909ca5cf7..2969ed6c5f28 100644 --- a/linguistic/source/misc.cxx +++ b/linguistic/source/misc.cxx @@ -18,6 +18,7 @@ */ #include <memory> +#include <map> #include <optional> #include <sal/log.hxx> #include <svl/lngmisc.hxx> @@ -250,39 +251,57 @@ static bool lcl_HasHyphInfo( const uno::Reference<XDictionaryEntry> &xEntry ) uno::Reference< XDictionaryEntry > SearchDicList( const uno::Reference< XSearchableDictionaryList > &xDicList, const OUString &rWord, LanguageType nLanguage, - bool bSearchPosDics, bool bSearchSpellEntry ) + bool bSearchPosDics, bool bSearchSpellEntry, + std::map<LanguageType, std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& rDictionaryMap ) { MutexGuard aGuard( GetLinguMutex() ); - uno::Reference< XDictionaryEntry > xEntry; - if (!xDicList.is()) - return xEntry; - - const uno::Sequence< uno::Reference< XDictionary > > - aDics( xDicList->getDictionaries() ); + return uno::Reference< XDictionaryEntry >(); - for (const uno::Reference<XDictionary>& axDic : aDics) + if (rDictionaryMap.empty()) { - DictionaryType eType = axDic->getDictionaryType(); - LanguageType nLang = LinguLocaleToLanguage( axDic->getLocale() ); - - if ( axDic.is() && axDic->isActive() - && (nLang == nLanguage || LinguIsUnspecified( nLang)) ) + const uno::Sequence< uno::Reference< XDictionary > > + aDics( xDicList->getDictionaries() ); + for (const uno::Reference<XDictionary>& rxDic : aDics) { - // DictionaryType_MIXED is deprecated - SAL_WARN_IF(eType == DictionaryType_MIXED, "linguistic", "unexpected dictionary type"); + assert( rxDic.is() ); + LanguageType nLang = LinguLocaleToLanguage( rxDic->getLocale() ); + rDictionaryMap[nLang].push_back(rxDic); + } + } - if ( (!bSearchPosDics && eType == DictionaryType_NEGATIVE) - || ( bSearchPosDics && eType == DictionaryType_POSITIVE)) + auto lcl_search = [&rDictionaryMap, bSearchPosDics, bSearchSpellEntry, &rWord](LanguageType nLang) + { + auto it = rDictionaryMap.find(nLang); + if (it != rDictionaryMap.end()) + for (const uno::Reference<XDictionary>& rxDic : it->second) { - xEntry = axDic->getEntry( rWord ); - if ( xEntry.is() && (bSearchSpellEntry || lcl_HasHyphInfo( xEntry )) ) - break; - xEntry = nullptr; + if ( rxDic->isActive() ) + { + DictionaryType eType = rxDic->getDictionaryType(); + if ( (!bSearchPosDics && eType == DictionaryType_NEGATIVE) + || ( bSearchPosDics && eType == DictionaryType_POSITIVE)) + { + uno::Reference< XDictionaryEntry > xEntry = rxDic->getEntry( rWord ); + if ( xEntry.is() && (bSearchSpellEntry || lcl_HasHyphInfo( xEntry )) ) + return xEntry; + } + } } - } - } + return uno::Reference< XDictionaryEntry >(); + }; + + uno::Reference< XDictionaryEntry > xEntry; + if ((xEntry = lcl_search(nLanguage))) + return xEntry; + + // check the same list as in the LinguIsUnspecified() function + if ((xEntry = lcl_search(LANGUAGE_NONE))) + return xEntry; + if ((xEntry = lcl_search(LANGUAGE_UNDETERMINED))) + return xEntry; + xEntry = lcl_search(LANGUAGE_MULTIPLE); return xEntry; } diff --git a/linguistic/source/spelldsp.cxx b/linguistic/source/spelldsp.cxx index 3ef247f6e788..6c50a4c9c65a 100644 --- a/linguistic/source/spelldsp.cxx +++ b/linguistic/source/spelldsp.cxx @@ -232,7 +232,9 @@ Reference< XSpellAlternatives > SAL_CALL // including the IgnoreAll list static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry( const OUString &rWord, - LanguageType nLanguage ) + LanguageType nLanguage, + const Reference< XSearchableDictionaryList >& xDList, + std::map<LanguageType, std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& rDictionaryMap ) { Reference< XDictionaryEntry > xRes; @@ -244,15 +246,14 @@ static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry( xRes = xIgnoreAll->getEntry( rWord ); if (!xRes.is()) { - Reference< XSearchableDictionaryList > xDList( GetDictionaryList() ); Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList, - rWord, nLanguage, false, true ) ); + rWord, nLanguage, false, true, rDictionaryMap ) ); if (xNegEntry.is()) xRes = std::move(xNegEntry); else { Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList, - rWord, nLanguage, true, true ) ); + rWord, nLanguage, true, true, rDictionaryMap ) ); if (xPosEntry.is()) xRes = std::move(xPosEntry); } @@ -413,16 +414,17 @@ bool SpellCheckerDispatcher::isValid_Impl( } // cross-check against results from dictionaries which have precedence! - if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() )) + auto xDicList = GetDicList(); + if (xDicList && IsUseDicList( rProperties, GetPropSet() )) { - Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) ); + Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage, xDicList, m_aDictionaryMap ) ); 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 ) ); + Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage, xDicList, m_aDictionaryMap ) ); if (xTmp2.is()) { bRes = !xTmp2->isNegative(); } @@ -636,7 +638,7 @@ Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl( // cross-check against results from user-dictionaries which have precedence! if (xDList.is()) { - Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) ); + Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage, xDList, m_aDictionaryMap ) ); if (xTmp.is()) { if (xTmp->isNegative()) // negative entry found @@ -648,7 +650,7 @@ Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl( // replacement text must not be in negative dictionary itself if (!aAddRplcTxt.isEmpty() && - !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is()) + !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true, m_aDictionaryMap ).is()) { aProposalList.Prepend( aAddRplcTxt ); } @@ -665,7 +667,7 @@ Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl( 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 ) ); + Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage, xDList, m_aDictionaryMap ) ); if (xTmp2.is()) { if (xTmp2->isNegative()) // negative entry found @@ -677,7 +679,7 @@ Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl( // replacement text must not be in negative dictionary itself if (!aAddRplcTxt.isEmpty() && - !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is()) + !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true, m_aDictionaryMap ).is()) { switch ( ct ) { @@ -715,7 +717,7 @@ Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl( // 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 ); + SeqRemoveNegEntries( aProposals, xDList, nLanguage, m_aDictionaryMap ); uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, uno::UNO_QUERY ); if (xSetAlt.is()) diff --git a/linguistic/source/spelldsp.hxx b/linguistic/source/spelldsp.hxx index c05676e1c9f0..a479f7bc7296 100644 --- a/linguistic/source/spelldsp.hxx +++ b/linguistic/source/spelldsp.hxx @@ -51,6 +51,7 @@ class SpellCheckerDispatcher : css::uno::Reference< css::linguistic2::XLinguProperties > m_xPropSet; css::uno::Reference< css::linguistic2::XSearchableDictionaryList > m_xDicList; + std::map<LanguageType, std::vector<css::uno::Reference<css::linguistic2::XDictionary>>> m_aDictionaryMap; LngSvcMgr &m_rMgr; mutable std::unique_ptr<linguistic::SpellCache> m_pCache; // Spell Cache (holds known words) diff --git a/linguistic/source/spelldta.cxx b/linguistic/source/spelldta.cxx index f3d6eca2be62..c1a50b22c4de 100644 --- a/linguistic/source/spelldta.cxx +++ b/linguistic/source/spelldta.cxx @@ -97,14 +97,15 @@ void SearchSimilarText( const OUString &rText, LanguageType nLanguage, void SeqRemoveNegEntries( std::vector< OUString > &rSeq, Reference< XSearchableDictionaryList > const &rxDicList, - LanguageType nLanguage ) + LanguageType nLanguage, + std::map<LanguageType, std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& rDictionaryMap ) { bool bSthRemoved = false; sal_Int32 nLen = rSeq.size(); for (sal_Int32 i = 0; i < nLen; ++i) { Reference< XDictionaryEntry > xNegEntry( SearchDicList( rxDicList, - rSeq[i], nLanguage, false, true ) ); + rSeq[i], nLanguage, false, true, rDictionaryMap ) ); if (xNegEntry.is()) { rSeq[i].clear();
