sc/qa/unit/ucalc_sort.cxx      |   61 +++++++++++++++++++++++++++++++++++++++++
 sc/source/core/data/table3.cxx |   40 ++++++++++++++++++--------
 2 files changed, 88 insertions(+), 13 deletions(-)

New commits:
commit b0732e1ba9944a07aed737dcc52ef9e7614c38df
Author:     Regina Henschel <rb.hensc...@t-online.de>
AuthorDate: Fri Sep 5 15:16:07 2025 +0200
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Thu Sep 11 08:55:52 2025 +0200

    tdf#168175 Use sort parameter language in natural sort
    
    Now the natural sort uses the language given in the sort parameter. Only
    if it does not contain a language, then the global locale is used. The
    language determines, which character is used as decimal separator.
    
    This corresponds to ODF where both the locale info and whether to use
    natural sort belong to the database range attributes.
    
    Change-Id: I52629bdff7f6a93974770d2922e80ac95df21a5d
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190622
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>

diff --git a/sc/qa/unit/ucalc_sort.cxx b/sc/qa/unit/ucalc_sort.cxx
index 0c24b4b8063b..86e44d9f9699 100644
--- a/sc/qa/unit/ucalc_sort.cxx
+++ b/sc/qa/unit/ucalc_sort.cxx
@@ -30,6 +30,7 @@
 #include <svx/svdocirc.hxx>
 #include <svx/svdpage.hxx>
 #include <rtl/math.hxx>
+#include <unotools/syslocaleoptions.hxx>
 
 class TestSort : public ScUcalcTestBase
 {
@@ -2222,6 +2223,66 @@ CPPUNIT_TEST_FIXTURE(TestSort, testQueryBinarySearch)
     m_pDoc->DeleteTab(0);
 }
 
+CPPUNIT_TEST_FIXTURE(TestSort, testLanguageDependentNaturalSort)
+{
+    // Set the system locale to "en-US" for to have different decimal 
separator than "de-EN".
+    SvtSysLocaleOptions aOptions;
+    OUString sLocaleConfigString = aOptions.GetLanguageTag().getBcp47();
+    aOptions.SetLocaleConfigString(u"en-US"_ustr);
+    aOptions.Commit();
+    comphelper::ScopeGuard g([&aOptions, &sLocaleConfigString] {
+        aOptions.SetLocaleConfigString(sLocaleConfigString);
+        aOptions.Commit();
+    });
+
+    // Generate test data
+    m_pDoc->InsertTab(0, u"NaturalSortTest"_ustr);
+    m_pDoc->SetString(ScAddress(0,0,0),u"Item"_ustr); // ScAddress(col, row, 
tab)
+    m_pDoc->SetString(ScAddress(0,1,0),u"K2,5"_ustr);
+    m_pDoc->SetString(ScAddress(0,2,0),u"K2,501"_ustr);
+    m_pDoc->SetString(ScAddress(0,3,0),u"K10"_ustr);
+    m_pDoc->SetString(ScAddress(0,4,0),u"K1,104"_ustr);
+    m_pDoc->SetString(ScAddress(0,5,0),u"K1,2"_ustr);
+    m_pDoc->SetString(ScAddress(0,6,0),u"K2,40"_ustr);
+    m_pDoc->SetAnonymousDBData(
+        0, std::unique_ptr<ScDBData>(new ScDBData(STR_DB_LOCAL_NONAME, 0, 0, 
0, 0, 6)));
+
+    // Create sort parameters
+    ScSortParam aSortParam;
+    aSortParam.nCol1 = 0;
+    aSortParam.nCol2 = 0;
+    aSortParam.nRow1 = 0;
+    aSortParam.nRow2 = 6;
+    aSortParam.bHasHeader = true;
+    aSortParam.bNaturalSort = true; // needs to be adapted when mode 'integer' 
is implemented
+    aSortParam.bInplace = false;
+    aSortParam.nDestTab = 0;
+    aSortParam.nDestCol = 2;
+    aSortParam.nDestRow = 0;
+    aSortParam.aCollatorLocale = css::lang::Locale(u"de"_ustr, u"DE"_ustr, 
u""_ustr);
+    aSortParam.maKeyState[0].bDoSort = true;
+    aSortParam.maKeyState[0].nField = 0;
+    aSortParam.maKeyState[0].bAscending = true;
+    aSortParam.maKeyState[0].aColorSortMode = ScColorSortMode::None;
+
+    // Actually sort
+    ScDBDocFunc aFunc(*m_xDocShell);
+    bool bSorted = aFunc.Sort(0, aSortParam, true, true, true);
+    CPPUNIT_ASSERT(bSorted);
+
+    // Verify sort result. Without fix the comma was treated as ordinary 
character and thus the order
+    // had been  Item | K1,2 | K1,104 | K2,5 | K2,40 | K2,501 | K10
+    const std::array<OUString, 7> aExpected
+        = { u"Item"_ustr, u"K1,104"_ustr, u"K1,2"_ustr, u"K2,40"_ustr,
+            u"K2,5"_ustr, u"K2,501"_ustr, u"K10"_ustr };
+    for (SCROW nRow = 0; nRow <= 6; nRow++)
+    {
+        CPPUNIT_ASSERT_EQUAL(aExpected[nRow], m_pDoc->GetString(ScAddress(2, 
nRow, 0)));
+    }
+
+    m_pDoc->DeleteTab(0);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
index 055617dfec14..a8eb314f935b 100644
--- a/sc/source/core/data/table3.cxx
+++ b/sc/source/core/data/table3.cxx
@@ -23,11 +23,13 @@
 #include <svl/zforlist.hxx>
 #include <svl/zformat.hxx>
 #include <unotools/collatorwrapper.hxx>
+#include <unotools/charclass.hxx>
 #include <stdlib.h>
 #include <com/sun/star/i18n/KParseTokens.hpp>
 #include <com/sun/star/i18n/KParseType.hpp>
 #include <sal/log.hxx>
 #include <osl/diagnose.h>
+#include <i18nlangtag/languagetag.hxx>
 
 #include <refdata.hxx>
 #include <table.hxx>
@@ -83,6 +85,9 @@ using namespace ::com::sun::star::i18n;
     @param sWhole
     Original string to be split into pieces
 
+    @param rLanguageTag
+    Contains the language related infos from the sort parameters
+
     @param sPrefix
     Prefix string that consists of the part before the first number token.
     If no number was found, sPrefix is unchanged.
@@ -98,14 +103,15 @@ using namespace ::com::sun::star::i18n;
     @return Returns TRUE if a numeral element is found in a given string, or
     FALSE if no numeral element is found.
 */
-static bool SplitString( const OUString &sWhole,
-    OUString &sPrefix, OUString &sSuffix, double &fNum )
+static bool SplitString(const OUString &sWhole, const LanguageTag& 
rLanguageTag, OUString &sPrefix,
+                        OUString &sSuffix, double &fNum)
 {
     // Get prefix element, search for any digit and stop.
     sal_Int32 nPos = 0;
+    const CharClass aCharClass(rLanguageTag);
     while (nPos < sWhole.getLength())
     {
-        const sal_uInt16 nType = ScGlobal::getCharClass().getCharacterType( 
sWhole, nPos);
+        const sal_uInt16 nType = aCharClass.getCharacterType( sWhole, nPos);
         if (nType & KCharacterType::DIGIT)
             break;
         sWhole.iterateCodePoints( &nPos );
@@ -116,10 +122,10 @@ static bool SplitString( const OUString &sWhole,
         return false;
 
     // Get numeral element
-    const OUString& sUser = ScGlobal::getLocaleData().getNumDecimalSep();
-    ParseResult aPRNum = ScGlobal::getCharClass().parsePredefinedToken(
-        KParseType::ANY_NUMBER, sWhole, nPos,
-        KParseTokens::ANY_NUMBER, u""_ustr, KParseTokens::ANY_NUMBER, sUser );
+    const OUString& sUser = 
LocaleDataWrapper::get(rLanguageTag)->getNumDecimalSep();
+    ParseResult aPRNum = 
aCharClass.parsePredefinedToken(KParseType::ANY_NUMBER, sWhole, nPos,
+                                                         
KParseTokens::ANY_NUMBER, u""_ustr,
+                                                         
KParseTokens::ANY_NUMBER, sUser);
 
     if ( aPRNum.EndPos == nPos )
     {
@@ -146,6 +152,10 @@ static bool SplitString( const OUString &sWhole,
     @param sInput2
     Input string 2
 
+    @param rLanguageTag
+    Contains the language related infos from the sort parameters. They are 
needed
+    in method SplitString.
+
     @param bCaseSens
     Boolean value for case sensitivity
 
@@ -158,16 +168,17 @@ static bool SplitString( const OUString &sWhole,
     @return Returns 1 if sInput1 is greater, 0 if sInput1 == sInput2, and -1 if
     sInput2 is greater.
 */
-static short Compare( const OUString &sInput1, const OUString &sInput2,
-               const bool bCaseSens, const ScUserListData* pData, const 
CollatorWrapper *pCW )
+static short Compare(const OUString &sInput1, const OUString &sInput2,
+                     const LanguageTag& rLanguageTag, const bool bCaseSens,
+                     const ScUserListData* pData, const CollatorWrapper *pCW)
 {
     OUString sStr1( sInput1 ), sStr2( sInput2 ), sPre1, sSuf1, sPre2, sSuf2;
 
     do
     {
         double nNum1, nNum2;
-        bool bNumFound1 = SplitString( sStr1, sPre1, sSuf1, nNum1 );
-        bool bNumFound2 = SplitString( sStr2, sPre2, sSuf2, nNum2 );
+        bool bNumFound1 = SplitString( sStr1, rLanguageTag, sPre1, sSuf1, 
nNum1 );
+        bool bNumFound2 = SplitString( sStr2, rLanguageTag, sPre2, sSuf2, 
nNum2 );
 
         short nPreRes; // Prefix comparison result
         if ( pData )
@@ -1493,6 +1504,7 @@ short ScTable::CompareCell(
                 bool bUserDef     = aSortParam.bUserDef;        // custom sort 
order
                 bool bNaturalSort = aSortParam.bNaturalSort;    // natural sort
                 bool bCaseSens    = aSortParam.bCaseSens;       // case 
sensitivity
+                LanguageTag aSortLanguageTag(aSortParam.aCollatorLocale);
 
                 ScUserList& rList = ScGlobal::GetUserList();
                 if (bUserDef && rList.size() > aSortParam.nUserIndex)
@@ -1500,7 +1512,8 @@ short ScTable::CompareCell(
                     const ScUserListData& rData = rList[aSortParam.nUserIndex];
 
                     if ( bNaturalSort )
-                        nRes = naturalsort::Compare( aStr1, aStr2, bCaseSens, 
&rData, pSortCollator );
+                        nRes = naturalsort::Compare(aStr1, aStr2, 
aSortLanguageTag, bCaseSens,
+                                                    &rData, pSortCollator);
                     else
                     {
                         if ( bCaseSens )
@@ -1513,7 +1526,8 @@ short ScTable::CompareCell(
                 if (!bUserDef)
                 {
                     if ( bNaturalSort )
-                        nRes = naturalsort::Compare( aStr1, aStr2, bCaseSens, 
nullptr, pSortCollator );
+                        nRes = naturalsort::Compare(aStr1, aStr2, 
aSortLanguageTag, bCaseSens,
+                                                    nullptr, pSortCollator );
                     else
                         nRes = static_cast<short>( 
pSortCollator->compareString( aStr1, aStr2 ) );
                 }

Reply via email to