formula/source/ui/dlg/funcpage.cxx | 66 ++++++++++++++++------- formula/source/ui/dlg/funcpage.hxx | 7 ++ formula/uiconfig/ui/functionpage.ui | 30 ++++++++-- include/unotools/textsearch.hxx | 27 +++++++++ sc/source/ui/formdlg/dwfunctr.cxx | 72 ++++++++++++++++--------- sc/source/ui/inc/dwfunctr.hxx | 5 + sc/uiconfig/scalc/ui/functionpanel.ui | 22 +++++++ unotools/source/i18n/textsearch.cxx | 97 ++++++++++++++++++++++++++++++++++ 8 files changed, 278 insertions(+), 48 deletions(-)
New commits: commit d05e0be5f4ab2bdb31d6cf14528a9a8ee5ac965c Author: AhmedHamed <ahmedhamed3...@gmail.com> AuthorDate: Fri Jul 5 21:41:26 2024 +0300 Commit: Andreas Heinisch <andreas.heini...@yahoo.de> CommitDate: Tue Sep 3 09:59:16 2024 +0200 tdf#161543 Enhance the searching functionality in FD & FW Change-Id: I1a21595228f886c942ae46d90e41705443d31550 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/170073 Reviewed-by: Heiko Tietze <heiko.tie...@documentfoundation.org> Reviewed-by: Andreas Heinisch <andreas.heini...@yahoo.de> Tested-by: Jenkins diff --git a/formula/source/ui/dlg/funcpage.cxx b/formula/source/ui/dlg/funcpage.cxx index c844a29f612e..9724793928a5 100644 --- a/formula/source/ui/dlg/funcpage.cxx +++ b/formula/source/ui/dlg/funcpage.cxx @@ -25,6 +25,7 @@ #include "funcpage.hxx" #include <unotools/syslocale.hxx> #include <unotools/charclass.hxx> +#include <unotools/textsearch.hxx> namespace formula { @@ -48,10 +49,10 @@ FuncPage::FuncPage(weld::Container* pParent, const IFunctionManager* _pFunctionM , m_xLbFunction(m_xBuilder->weld_tree_view(u"function"_ustr)) , m_xScratchIter(m_xLbFunction->make_iterator()) , m_xLbFunctionSearchString(m_xBuilder->weld_entry(u"search"_ustr)) + , m_xSimilaritySearch(m_xBuilder->weld_check_button(u"similaritysearch"_ustr)) , m_xHelpButton(m_xBuilder->weld_button(u"help"_ustr)) , m_pFunctionManager(_pFunctionManager) { - m_xLbFunction->make_sorted(); m_aHelpId = m_xLbFunction->get_help_id(); m_pFunctionManager->fillLastRecentlyUsedFunctions(aLRUList); @@ -76,6 +77,7 @@ FuncPage::FuncPage(weld::Container* pParent, const IFunctionManager* _pFunctionM m_xLbFunction->connect_row_activated(LINK(this, FuncPage, DblClkHdl)); m_xLbFunction->connect_key_press(LINK(this, FuncPage, KeyInputHdl)); m_xLbFunctionSearchString->connect_changed(LINK(this, FuncPage, ModifyHdl)); + m_xSimilaritySearch->connect_toggled(LINK(this, FuncPage, SimilarityToggleHdl)); m_xHelpButton->connect_clicked(LINK(this, FuncPage, SelHelpClickHdl)); m_xHelpButton->set_sensitive(false); @@ -117,12 +119,26 @@ void FuncPage::impl_addFunctions(const IFunctionCategory* _pCategory, bool bFill } } +void FuncPage::SearchFunction(const OUString& rFuncName, const OUString& rSearchString, + TFunctionDesc pDesc, const bool bSimilaritySearch) +{ + std::pair<sal_Int32, sal_Int32> score = std::make_pair(0, 0); + if (bSimilaritySearch && !utl::TextSearch::SimilaritySearch(rFuncName, rSearchString, score)) + return; + if (!bSimilaritySearch && rFuncName.indexOf(rSearchString) < 0 + && rSearchString.indexOf(rFuncName) < 0) + return; + + sFuncScores.insert(std::make_pair(score, std::make_pair(rFuncName, pDesc))); +} + //aStr is non-empty when user types in the search box to search some function void FuncPage::UpdateFunctionList(const OUString& aStr) { m_xLbFunction->clear(); m_xLbFunction->freeze(); mCategories.clear(); + sFuncScores.clear(); const sal_Int32 nSelPos = m_xLbCategory->get_active(); bool bCollapse = nSelPos == 1; @@ -195,21 +211,23 @@ void FuncPage::UpdateFunctionList(const OUString& aStr) for (sal_uInt32 j = 0; j < nFunctionCount; ++j) { TFunctionDesc pDesc(pCategory->getFunction(j)); - // tdf#146781 - search for the desired function also in the description - if (rCharClass.uppercase(pDesc->getFunctionName()).indexOf(aSearchStr) >= 0 - || rCharClass.uppercase(pDesc->getDescription()).indexOf(aSearchStr) >= 0) - { - if (!pDesc->isHidden()) - { - OUString aFunction(pDesc->getFunctionName()); - OUString sId(weld::toId(pDesc)); - - weld::TreeIter* pCategoryIter - = FillCategoriesMap(pCategory->getName(), bCollapse); - m_xLbFunction->insert(pCategoryIter, -1, &aFunction, &sId, nullptr, nullptr, - false, m_xScratchIter.get()); - } - } + const OUString aFunction(rCharClass.uppercase(pDesc->getFunctionName())); + SearchFunction(aFunction, aSearchStr, pDesc, m_xSimilaritySearch->get_active()); + } + } + + for (const auto& func : sFuncScores) + { + TFunctionDesc pDesc(func.second.second); + if (!pDesc->isHidden()) + { + const OUString aCategory(pDesc->getCategory()->getName()); + const OUString aFunction(func.second.first); + const OUString aFuncDescId(weld::toId(pDesc)); + weld::TreeIter* pCategory = FillCategoriesMap(aCategory, bCollapse); + + m_xLbFunction->insert(pCategory, -1, &aFunction, &aFuncDescId, nullptr, nullptr, + false, m_xScratchIter.get()); } } } @@ -231,11 +249,12 @@ void FuncPage::UpdateFunctionList(const OUString& aStr) IMPL_LINK_NOARG(FuncPage, SelComboBoxHdl, weld::ComboBox&, void) { - m_xLbFunctionSearchString->set_sensitive(m_xLbCategory->get_active() > 0); + if (m_xLbCategory->get_active() == 0) + m_xLbFunctionSearchString->set_text(u""_ustr); + m_xHelpButton->set_sensitive(false); OUString searchStr = m_xLbFunctionSearchString->get_text(); m_xLbFunction->set_help_id(m_aHelpId); UpdateFunctionList(searchStr); - m_xHelpButton->set_sensitive(false); } IMPL_LINK_NOARG(FuncPage, SelTreeViewHdl, weld::TreeView&, void) @@ -270,6 +289,17 @@ IMPL_LINK_NOARG(FuncPage, DblClkHdl, weld::TreeView&, bool) } IMPL_LINK_NOARG(FuncPage, ModifyHdl, weld::Entry&, void) +{ + if (m_xLbCategory->get_active() == 0) + { + m_xLbCategory->set_active(1); + m_xHelpButton->set_sensitive(false); + } + OUString searchStr = m_xLbFunctionSearchString->get_text(); + UpdateFunctionList(searchStr); +} + +IMPL_LINK_NOARG(FuncPage, SimilarityToggleHdl, weld::Toggleable&, void) { OUString searchStr = m_xLbFunctionSearchString->get_text(); UpdateFunctionList(searchStr); diff --git a/formula/source/ui/dlg/funcpage.hxx b/formula/source/ui/dlg/funcpage.hxx index 0df77e1b156b..010bca872832 100644 --- a/formula/source/ui/dlg/funcpage.hxx +++ b/formula/source/ui/dlg/funcpage.hxx @@ -21,6 +21,7 @@ #include <vcl/weld.hxx> #include <vector> +#include <set> #include <unordered_map> namespace formula @@ -42,6 +43,7 @@ private: std::unique_ptr<weld::TreeView> m_xLbFunction; std::unique_ptr<weld::TreeIter> m_xScratchIter; std::unique_ptr<weld::Entry> m_xLbFunctionSearchString; + std::unique_ptr<weld::CheckButton> m_xSimilaritySearch; std::unique_ptr<weld::Button> m_xHelpButton; Link<FuncPage&,void> aDoubleClickLink; @@ -50,6 +52,8 @@ private: ::std::vector< TFunctionDesc > aLRUList; ::std::unordered_map<OUString, std::unique_ptr<weld::TreeIter>> mCategories; + ::std::set<std::pair<std::pair<sal_Int32, sal_Int32>, std::pair<OUString, TFunctionDesc>>> + sFuncScores; OUString m_aHelpId; // tdf#104487 - remember last used function category @@ -64,6 +68,7 @@ private: DECL_LINK(KeyInputHdl, const KeyEvent&, bool); DECL_LINK(ModifyHdl, weld::Entry&, void); DECL_LINK(SelHelpClickHdl, weld::Button&, void); + DECL_LINK(SimilarityToggleHdl, weld::Toggleable&, void); void UpdateFunctionList(const OUString&); @@ -92,6 +97,8 @@ public: void SetSelectHdl( const Link<FuncPage&,void>& rLink ) { aSelectionLink = rLink; } bool IsVisible() const { return m_xContainer->get_visible(); } + + void SearchFunction(const OUString&, const OUString&, TFunctionDesc, const bool); }; } // formula diff --git a/formula/uiconfig/ui/functionpage.ui b/formula/uiconfig/ui/functionpage.ui index 4ed38281cd1b..c48beb6918ac 100644 --- a/formula/uiconfig/ui/functionpage.ui +++ b/formula/uiconfig/ui/functionpage.ui @@ -52,6 +52,26 @@ <property name="position">1</property> </packing> </child> + <child> + <object class="GtkCheckButton" id="similaritysearch"> + <property name="label" translatable="yes" context="functionpage|similaritysearch">Similar</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="similaritysearch-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="functionpage|extended_tip|similaritysearch">Search and Sort functions by similarity</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> <child> <object class="GtkLabel" id="label1"> <property name="visible">True</property> @@ -64,7 +84,7 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">2</property> + <property name="position">3</property> </packing> </child> <child> @@ -84,7 +104,7 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">3</property> + <property name="position">4</property> </packing> </child> <child> @@ -99,7 +119,7 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">4</property> + <property name="position">5</property> </packing> </child> <child> @@ -147,7 +167,7 @@ <packing> <property name="expand">True</property> <property name="fill">True</property> - <property name="position">5</property> + <property name="position">6</property> </packing> </child> <child> @@ -163,7 +183,7 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">6</property> + <property name="position">7</property> </packing> </child> <child internal-child="accessible"> diff --git a/include/unotools/textsearch.hxx b/include/unotools/textsearch.hxx index 753534166098..3b06c93549d5 100644 --- a/include/unotools/textsearch.hxx +++ b/include/unotools/textsearch.hxx @@ -27,6 +27,9 @@ #include <ostream> +#define WLD_THRESHOLD 3 +#define SMALL_STRING_THRESHOLD 4 + class CharClass; namespace com::sun::star::lang { struct Locale; } @@ -209,6 +212,30 @@ public: /* replace back references in the replace string by the sub expressions from the search result */ static void ReplaceBackReferences( OUString& rReplaceStr, std::u16string_view rStr, const css::util::SearchResult& rResult ); + + /** + * @brief Search for a string in a another one based on similarity + * @param rString The string we compare with + * @param rSearchString The search term + * @param rSimilarityScore The similarity score (sent by reference to be filled) + * @return True if the search term is found, false otherwise + */ + static bool SimilaritySearch(const OUString& rString, const OUString& rSearchString, + ::std::pair<sal_Int32, sal_Int32>& rSimilarityScore); + /** + * @brief Get similarity score between two strings + * according to the length of the common substring and its position + * @param rString The string we compare with + * @param rSearchString The search term + * @param nInitialScore The initial score + * @param bFromStart True if the search is from the start + * @return Score if the search term is found in the text, -1 otherwise + */ + static sal_Int32 GetSubstringSimilarity(std::u16string_view rString, + std::u16string_view rSearchString, + sal_Int32& nInitialScore, const bool bFromStart); + static sal_Int32 GetWeightedLevenshteinDistance(const OUString& rString, + const OUString& rSearchString); }; } // namespace utl diff --git a/sc/source/ui/formdlg/dwfunctr.cxx b/sc/source/ui/formdlg/dwfunctr.cxx index c4cf19cabf94..3ca34e1cc118 100644 --- a/sc/source/ui/formdlg/dwfunctr.cxx +++ b/sc/source/ui/formdlg/dwfunctr.cxx @@ -22,6 +22,7 @@ #include <sfx2/viewsh.hxx> #include <formula/funcvarargs.h> #include <unotools/charclass.hxx> +#include <unotools/textsearch.hxx> #include <vcl/svapp.hxx> #include <vcl/help.hxx> @@ -55,6 +56,7 @@ ScFunctionWin::ScFunctionWin(weld::Widget* pParent) , xScratchIter(xFuncList->make_iterator()) , xInsertButton(m_xBuilder->weld_button(u"insert"_ustr)) , xHelpButton(m_xBuilder->weld_button(u"help"_ustr)) + , xSimilaritySearch(m_xBuilder->weld_check_button(u"similaritysearch"_ustr)) , xFiFuncDesc(m_xBuilder->weld_text_view(u"funcdesc"_ustr)) , m_xSearchString(m_xBuilder->weld_entry(u"search"_ustr)) , xConfigListener(new comphelper::ConfigurationListener(u"/org.openoffice.Office.Calc/Formula/Syntax"_ustr)) @@ -79,6 +81,7 @@ ScFunctionWin::ScFunctionWin(weld::Widget* pParent) xFuncList->connect_row_activated(LINK( this, ScFunctionWin, SetRowActivatedHdl)); xInsertButton->connect_clicked(LINK( this, ScFunctionWin, SetSelectionClickHdl)); xHelpButton->connect_clicked(LINK( this, ScFunctionWin, SetHelpClickHdl)); + xSimilaritySearch->connect_toggled(LINK(this, ScFunctionWin, SetSimilarityToggleHdl)); xCatBox->set_active(0); @@ -109,6 +112,7 @@ ScFunctionWin::~ScFunctionWin() xFuncList.reset(); xInsertButton.reset(); xHelpButton.reset(); + xSimilaritySearch.reset(); xFiFuncDesc.reset(); } @@ -189,6 +193,19 @@ void ScFunctionWin::UpdateLRUList() } } +void ScFunctionWin::SearchFunction(const OUString& rFuncName, const OUString& rSearchString, + const ScFuncDesc* pDesc, const bool bSimilaritySearch) +{ + std::pair<sal_Int32, sal_Int32> score = std::make_pair(0, 0); + if (bSimilaritySearch && !utl::TextSearch::SimilaritySearch(rFuncName, rSearchString, score)) + return; + if (!bSimilaritySearch && rFuncName.indexOf(rSearchString) < 0 + && rSearchString.indexOf(rFuncName) < 0) + return; + + sFuncScores.insert(std::make_pair(score, std::make_pair(rFuncName, pDesc))); +} + /************************************************************************* #* Member: SetDescription #*------------------------------------------------------------------------ @@ -252,6 +269,7 @@ void ScFunctionWin::UpdateFunctionList(const OUString& rSearchString) xFuncList->clear(); xFuncList->freeze(); mCategories.clear(); + sFuncScores.clear(); bool bCollapse = nCategory == 0; bool bFilter = !rSearchString.isEmpty(); @@ -270,38 +288,31 @@ void ScFunctionWin::UpdateFunctionList(const OUString& rSearchString) const ScFuncDesc* pDesc = pFuncMgr->First(nCategory); while (pDesc) { - OUString aCategory = pDesc->getCategory()->getName(); - OUString aFunction = pDesc->getFunctionName(); - OUString aFuncDescId = weld::toId(pDesc); + const OUString aCategory(pDesc->getCategory()->getName()); + const OUString aFunction(pCharClass->uppercase(pDesc->getFunctionName())); + const OUString aFuncDescId(weld::toId(pDesc)); - if (!bFilter || (pCharClass->uppercase(aFunction).startsWith(aSearchStr))) + if (bFilter) + SearchFunction(aFunction, aSearchStr, pDesc, xSimilaritySearch->get_active()); + else { weld::TreeIter* pCategory = FillCategoriesMap(aCategory, bCollapse); xFuncList->insert(pCategory, -1, &aFunction, &aFuncDescId, nullptr, nullptr, - false, xScratchIter.get()); + false, xScratchIter.get()); } pDesc = pFuncMgr->Next(); } - // Now add the functions that have the search string in the middle of the function name - // Note that this will only be necessary if the search string is not empty - if (bFilter) + for (const auto& func : sFuncScores) { - pDesc = pFuncMgr->First( nCategory ); - while ( pDesc ) - { - OUString aCategory = pDesc->getCategory()->getName(); - OUString aFunction = pDesc->getFunctionName(); - OUString aFuncDescId = weld::toId(pDesc); - - if (pCharClass->uppercase(aFunction).indexOf(aSearchStr) > 0) - { - weld::TreeIter* pCategory = FillCategoriesMap(aCategory, bCollapse); - xFuncList->insert(pCategory, -1, &aFunction, &aFuncDescId, nullptr, nullptr, - false, xScratchIter.get()); - } - pDesc = pFuncMgr->Next(); - } + pDesc = func.second.second; + const OUString aCategory(pDesc->getCategory()->getName()); + const OUString aFunction(func.second.first); + const OUString aFuncDescId(weld::toId(pDesc)); + weld::TreeIter* pCategory = FillCategoriesMap(aCategory, bCollapse); + + xFuncList->insert(pCategory, -1, &aFunction, &aFuncDescId, nullptr, nullptr, false, + xScratchIter.get()); } } else // LRU list @@ -477,6 +488,11 @@ void ScFunctionWin::DoEnter(bool bDoubleOrEnter) IMPL_LINK_NOARG(ScFunctionWin, ModifyHdl, weld::Entry&, void) { + if (xCatBox->get_active() == 0) + { + xCatBox->set_active(1); + xHelpButton->set_sensitive(false); + } OUString searchStr = m_xSearchString->get_text(); UpdateFunctionList(searchStr); SetDescription(); @@ -576,8 +592,9 @@ IMPL_LINK(ScFunctionWin, KeyInputHdl, const KeyEvent&, rEvent, bool) IMPL_LINK_NOARG(ScFunctionWin, SelComboHdl, weld::ComboBox&, void) { + if (xCatBox->get_active() == 0) + m_xSearchString->set_text(u""_ustr); xHelpButton->set_sensitive(xCatBox->get_active() != 1); - m_xSearchString->set_sensitive(xCatBox->get_active() > 0); OUString searchStr = m_xSearchString->get_text(); UpdateFunctionList(searchStr); SetDescription(); @@ -638,6 +655,13 @@ IMPL_LINK_NOARG( ScFunctionWin, SetHelpClickHdl, weld::Button&, void ) } } +IMPL_LINK_NOARG(ScFunctionWin, SetSimilarityToggleHdl, weld::Toggleable&, void) +{ + OUString searchStr = m_xSearchString->get_text(); + UpdateFunctionList(searchStr); + SetDescription(); +} + IMPL_LINK_NOARG( ScFunctionWin, SetRowActivatedHdl, weld::TreeView&, bool ) { DoEnter(true); // saves the input diff --git a/sc/source/ui/inc/dwfunctr.hxx b/sc/source/ui/inc/dwfunctr.hxx index e393f2a69fcb..9c05491dac2d 100644 --- a/sc/source/ui/inc/dwfunctr.hxx +++ b/sc/source/ui/inc/dwfunctr.hxx @@ -49,6 +49,7 @@ private: std::unique_ptr<weld::TreeIter> xScratchIter; std::unique_ptr<weld::Button> xInsertButton; std::unique_ptr<weld::Button> xHelpButton; + std::unique_ptr<weld::CheckButton> xSimilaritySearch; std::unique_ptr<weld::TextView> xFiFuncDesc; std::unique_ptr<weld::Entry> m_xSearchString; @@ -59,6 +60,8 @@ private: OUString m_aListHelpId; OUString m_aSearchHelpId; + ::std::set<std::pair<std::pair<sal_Int32, sal_Int32>, std::pair<OUString, const ScFuncDesc*>>> + sFuncScores; ::std::vector< const formula::IFunctionDescription*> aLRUList; ::std::unordered_map<OUString, std::unique_ptr<weld::TreeIter>> mCategories; @@ -70,6 +73,7 @@ private: DECL_LINK( SetRowActivatedHdl, weld::TreeView&, bool ); DECL_LINK( SetSelectionClickHdl, weld::Button&, void ); DECL_LINK( SetHelpClickHdl, weld::Button&, void ); + DECL_LINK( SetSimilarityToggleHdl, weld::Toggleable&, void ); DECL_LINK( SelComboHdl, weld::ComboBox&, void ); DECL_LINK( SelTreeHdl, weld::TreeView&, void ); DECL_LINK( ModifyHdl, weld::Entry&, void ); @@ -82,6 +86,7 @@ public: void InitLRUList(); void UpdateFunctionList(const OUString&); + void SearchFunction(const OUString&, const OUString&, const ScFuncDesc*, const bool); }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/uiconfig/scalc/ui/functionpanel.ui b/sc/uiconfig/scalc/ui/functionpanel.ui index 671aa7149270..f2bade14020e 100644 --- a/sc/uiconfig/scalc/ui/functionpanel.ui +++ b/sc/uiconfig/scalc/ui/functionpanel.ui @@ -145,7 +145,7 @@ <object class="GtkBox"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="orientation">vertical</property> + <property name="orientation">horizontal</property> <property name="spacing">6</property> <child> <object class="GtkEntry" id="search"> @@ -166,6 +166,26 @@ <property name="position">1</property> </packing> </child> + <child> + <object class="GtkCheckButton" id="similaritysearch"> + <property name="label" translatable="yes" context="functionpanel|similaritysearch">Similar</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="similaritysearch-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="functionpanel|extended_tip|similaritysearch">Search and Sort functions by similarity</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> </object> <packing> <property name="left-attach">0</property> diff --git a/unotools/source/i18n/textsearch.cxx b/unotools/source/i18n/textsearch.cxx index 9c4573c38538..5958240cda44 100644 --- a/unotools/source/i18n/textsearch.cxx +++ b/unotools/source/i18n/textsearch.cxx @@ -353,6 +353,103 @@ void TextSearch::ReplaceBackReferences( OUString& rReplaceStr, std::u16string_vi rReplaceStr = sBuff.makeStringAndClear(); } +bool TextSearch::SimilaritySearch(const OUString& rString, const OUString& rSearchString, + ::std::pair<sal_Int32, sal_Int32>& rSimilarityScore) +{ + sal_Int32 nScore = 0; + sal_Int32 nFirstScore = GetSubstringSimilarity(rString, rSearchString, nScore, true); + if (nFirstScore == -1) + nFirstScore = GetSubstringSimilarity(rString, rSearchString, nScore, false); + if (nFirstScore == -1) + { + if (rSearchString.getLength() == 1) + { + if (rString.startsWith(rSearchString)) + nFirstScore = nScore; + else if (rString.endsWith(rSearchString)) + nFirstScore = nScore + 1; + nScore += 2; + } + else if (rString.getLength() == 1 && rSearchString.getLength() < SMALL_STRING_THRESHOLD) + { + if (rSearchString.startsWith(rString)) + nFirstScore = nScore; + else if (rSearchString.endsWith(rString)) + nFirstScore = nScore + 1; + nScore += 2; + } + } + sal_Int32 nSecondScore = GetWeightedLevenshteinDistance(rString, rSearchString); + + if (nFirstScore == -1 && nSecondScore >= WLD_THRESHOLD) + return false; + + rSimilarityScore.first = (nFirstScore == -1) ? nScore : nFirstScore; + rSimilarityScore.second = nSecondScore; + return true; +} + +sal_Int32 TextSearch::GetSubstringSimilarity(std::u16string_view rString, + std::u16string_view rSearchString, + sal_Int32& nInitialScore, const bool bFromStart) +{ + sal_Int32 nScore = -1; + for (sal_Int32 length = rSearchString.length(); length > 1; length--) + { + sal_Int32 nStartPos = bFromStart ? 0 : rSearchString.length() - length; + std::u16string_view rSearchSubString = rSearchString.substr(nStartPos, length); + if (rString.starts_with(rSearchSubString)) + { + nScore = nInitialScore; + break; + } + else if (rString.ends_with(rSearchSubString)) + { + nScore = nInitialScore + 1; + break; + } + else if (rString.find(rSearchSubString) != std::u16string_view::npos) + { + nScore = nInitialScore + 2; + break; + } + nInitialScore += 3; + } + return nScore; +} + +sal_Int32 TextSearch::GetWeightedLevenshteinDistance(const OUString& rString, + const OUString& rSearchString) +{ + sal_Int32 n = rString.getLength(); + sal_Int32 m = rSearchString.getLength(); + std::vector<std::vector<sal_Int32>> ScoreDP(n + 1, std::vector<sal_Int32>(m + 1)); + + for (sal_Int32 i = 0; i <= n; i++) + { + ScoreDP[i][0] = i; + } + for (sal_Int32 j = 0; j <= m; j++) + { + ScoreDP[0][j] = j; + } + + for (sal_Int32 i = 1; i <= n; i++) + { + for (sal_Int32 j = 1; j <= m; j++) + { + sal_Int32& minE = ScoreDP[i][j]; + minE = ScoreDP[i - 1][j] + 1; + minE = std::min(minE, ScoreDP[i][j - 1] + 1); + if (rString[i - 1] != rSearchString[j - 1]) + minE = std::min(minE, ScoreDP[i - 1][j - 1] + 2); + else + minE = std::min(minE, ScoreDP[i - 1][j - 1]); + } + } + return ScoreDP[n][m]; +} + } // namespace utl /* vim:set shiftwidth=4 softtabstop=4 expandtab: */