sw/qa/extras/ooxmlexport/data/WordOK.docx  |binary
 sw/qa/extras/ooxmlexport/ooxmlexport22.cxx |   18 ++++++
 sw/source/core/doc/number.cxx              |   75 ++++++++++++++++++++---------
 3 files changed, 71 insertions(+), 22 deletions(-)

New commits:
commit 1db81743594f7b858ff4d17c80a7366adee2b3fe
Author:     Michael Stahl <michael.st...@collabora.com>
AuthorDate: Wed Jun 25 14:00:22 2025 +0200
Commit:     Michael Stahl <michael.st...@collabora.com>
CommitDate: Thu Jul 10 14:51:02 2025 +0200

    tdf#166975 sw: fix expansion of list level format with repeated levels
    
    SwNumRule::MakeNumString() would only replace each level's placeholder
    once in the format string; rework this so it iterates the whole format
    string.
    
    Change-Id: I1d0c5bf5c60c3003cc0257acfb69b37d209be617
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186977
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    (cherry picked from commit e200c729797e7f3c19e958f9796a1d0fbae829d1)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187020
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <michael.st...@collabora.com>

diff --git a/sw/qa/extras/ooxmlexport/data/WordOK.docx 
b/sw/qa/extras/ooxmlexport/data/WordOK.docx
new file mode 100644
index 000000000000..19fb0c9f5665
Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/WordOK.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
index 08e086856381..fedacb31d54b 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
@@ -73,6 +73,24 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf166201_simplePosCM)
                          getProperty<sal_Int32>(getShape(1), 
u"VertOrientPosition"_ustr));
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf166975)
+{
+    loadAndSave("WordOK.docx");
+
+    CPPUNIT_ASSERT_EQUAL(u"a)"_ustr,
+                         getProperty<OUString>(getParagraph(2), 
u"ListLabelString"_ustr));
+    // this was a%6%)
+    CPPUNIT_ASSERT_EQUAL(u"aa)"_ustr,
+                         getProperty<OUString>(getParagraph(3), 
u"ListLabelString"_ustr));
+    // this was a%7%%7%)
+    CPPUNIT_ASSERT_EQUAL(u"aaa)"_ustr,
+                         getProperty<OUString>(getParagraph(4), 
u"ListLabelString"_ustr));
+    CPPUNIT_ASSERT_EQUAL(u"bbb)"_ustr,
+                         getProperty<OUString>(getParagraph(5), 
u"ListLabelString"_ustr));
+    CPPUNIT_ASSERT_EQUAL(u"ccc)"_ustr,
+                         getProperty<OUString>(getParagraph(6), 
u"ListLabelString"_ustr));
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testTdf165492_exactWithBottomSpacing)
 {
     // Given a document with "exact row height" of 2cm
diff --git a/sw/source/core/doc/number.cxx b/sw/source/core/doc/number.cxx
index 4eec424907a4..61ae291a536a 100644
--- a/sw/source/core/doc/number.cxx
+++ b/sw/source/core/doc/number.cxx
@@ -773,13 +773,43 @@ OUString SwNumRule::MakeNumString( const 
SwNumberTree::tNumberVector & rNumVecto
 
         // In this case we are ignoring GetIncludeUpperLevels: we put all
         // level numbers requested by level format
-        for (SwNumberTree::tNumberVector::size_type i=0; i <= nLevel; ++i)
+        for (sal_Int32 nPosition{0}; nPosition < sLevelFormat.getLength() - 2;)
         {
-            OUString sReplacement;
-            const SwNumFormat& rNFormat = Get(i);
+            if (sLevelFormat[nPosition] != '%')
+            {
+                ++nPosition;
+                continue;
+            }
+            SwNumberTree::tNumberVector::size_type nReplaceLevel;
+            decltype(nPosition) nEndPosition;
+            if (sLevelFormat[nPosition+1] == '1'
+                && sLevelFormat[nPosition+2] == '0'
+                && (nPosition+3) < sLevelFormat.getLength()
+                && sLevelFormat[nPosition+3] == '%')
+            {
+                nReplaceLevel = 9; // special case %10%
+                nEndPosition = nPosition + 4;
+            }
+            else if (sLevelFormat[nPosition+2] == '%'
+                && '1' <= sLevelFormat[nPosition+1]
+                && sLevelFormat[nPosition+1] <= '9')
+            {
+                nReplaceLevel = sLevelFormat[nPosition+1] - '1'; // need to 
subtract 1
+                nEndPosition = nPosition + 3;
+            }
+            else
+            {
+                ++nPosition;
+                continue; // ignore it
+            }
+            if (nLevel < nReplaceLevel)
+            {
+                nPosition = nEndPosition;
+                // there is no number to insert - in this case Word 2013
+                continue; // shows no label at all, we just skip it
+            }
 
-            OUString sFind("%" + OUString::number(i + 1) + "%");
-            sal_Int32 nPosition = sLevelFormat.indexOf(sFind);
+            SwNumFormat const& rNFormat{Get(nReplaceLevel)};
 
             if (rNFormat.GetNumberingType() == SVX_NUM_NUMBER_NONE)
             {
@@ -789,37 +819,38 @@ OUString SwNumRule::MakeNumString( const 
SwNumberTree::tNumberVector & rNumVecto
 
                 // NOTE: if changed, fix MSWordExportBase::NumberingLevel to 
match new behaviour.
 
-                sal_Int32 nPositionNext = sLevelFormat.indexOf('%', nPosition 
+ sFind.getLength());
-                if (nPosition >= 0 && nPositionNext > nPosition)
+                sal_Int32 const nPositionNext{sLevelFormat.indexOf('%', 
nEndPosition)};
+                if (nPositionNext > nPosition)
                 {
                     sLevelFormat = sLevelFormat.replaceAt(nPosition, 
nPositionNext - nPosition, u"");
                 }
                 continue;
             }
-            else if (rNumVector[i])
-                sReplacement = Get(i).GetNumStr(rNumVector[i], aLocale, 
rMyNFormat.GetIsLegal());
+
+            OUString sReplacement;
+            if (rNumVector[nReplaceLevel])
+                sReplacement = rNFormat.GetNumStr(rNumVector[nReplaceLevel], 
aLocale, rMyNFormat.GetIsLegal());
             else
                 sReplacement = "0";        // all 0 level are a 0
 
-            if (nPosition >= 0)
+            sLevelFormat = sLevelFormat.replaceAt(nPosition, nEndPosition - 
nPosition, sReplacement);
+            nPosition += sReplacement.getLength();
+
+            if (bHideNonNumerical)
             {
-                if (bHideNonNumerical)
-                {
-                    sal_Int32 nPositionNext = sLevelFormat.indexOf('%', 
nPosition + sFind.getLength());
+                sal_Int32 const nPositionNext{sLevelFormat.indexOf('%', 
nPosition)};
 
-                    if (nPositionNext >= nPosition) {
-                        sal_Int32 nReplaceStart = nPosition + 
sFind.getLength();
-                        sal_Int32 nReplaceCount = nPositionNext - 
nReplaceStart;
+                if (nPosition < nPositionNext)
+                {
+                    sal_Int32 nReplaceCount = nPositionNext - nPosition;
 
-                        OUString sSeparator = sLevelFormat.copy(nReplaceStart, 
nReplaceCount);
-                        StripNonDelimiter(sSeparator);
+                    OUString sSeparator = sLevelFormat.copy(nPosition, 
nReplaceCount);
+                    StripNonDelimiter(sSeparator);
 
-                        sLevelFormat = sLevelFormat.replaceAt(nReplaceStart, 
nReplaceCount, sSeparator);
-                    }
+                    sLevelFormat = sLevelFormat.replaceAt(nPosition, 
nReplaceCount, sSeparator);
                 }
-
-                sLevelFormat = sLevelFormat.replaceAt(nPosition, 
sFind.getLength(), sReplacement);
             }
+
         }
 
         aStr = sLevelFormat;

Reply via email to