sw/qa/extras/ooxmlexport/ooxmlexport22.cxx |   17 ++++++
 sw/source/filter/ww8/wrtw8num.cxx          |   71 +++++++++++++++++++++++------
 2 files changed, 73 insertions(+), 15 deletions(-)

New commits:
commit f5becffbfdd83d121cef7efc1aad3f7d58e2a317
Author:     Michael Stahl <michael.st...@collabora.com>
AuthorDate: Wed Jul 9 15:09:54 2025 +0200
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Wed Jul 30 10:08:50 2025 +0200

    tdf#166975 sw: fix DOCX export of list level format with repeated levels
    
    MSWordExportBase::NumberingLevel() would only replace each level's
    placeholder once in the format string; rework this so it iterates the
    whole format string.
    
    Change-Id: I7f375ac1f96f4fcbac51f7cba1cc67378f4edbb8
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187580
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>

diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
index e268c47eb2c1..34fe0302cef7 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx
@@ -86,7 +86,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf166201_simplePosCM)
 
 CPPUNIT_TEST_FIXTURE(Test, testTdf166975)
 {
-    loadAndSave("WordOK.docx");
+    createSwDoc("WordOK.docx");
 
     CPPUNIT_ASSERT_EQUAL(u"a)"_ustr,
                          getProperty<OUString>(getParagraph(2), 
u"ListLabelString"_ustr));
@@ -100,6 +100,21 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf166975)
                          getProperty<OUString>(getParagraph(5), 
u"ListLabelString"_ustr));
     CPPUNIT_ASSERT_EQUAL(u"ccc)"_ustr,
                          getProperty<OUString>(getParagraph(6), 
u"ListLabelString"_ustr));
+
+    saveAndReload(mpFilter);
+
+    CPPUNIT_ASSERT_EQUAL(u"a)"_ustr,
+                         getProperty<OUString>(getParagraph(2), 
u"ListLabelString"_ustr));
+    // this was aa%)
+    CPPUNIT_ASSERT_EQUAL(u"aa)"_ustr,
+                         getProperty<OUString>(getParagraph(3), 
u"ListLabelString"_ustr));
+    // this was aa%a%)
+    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)
diff --git a/sw/source/filter/ww8/wrtw8num.cxx 
b/sw/source/filter/ww8/wrtw8num.cxx
index e8f7b197ed59..9067327cf4f1 100644
--- a/sw/source/filter/ww8/wrtw8num.cxx
+++ b/sw/source/filter/ww8/wrtw8num.cxx
@@ -480,29 +480,72 @@ void MSWordExportBase::NumberingLevel(
         if (rFormat.HasListFormat())
         {
             sal_uInt8* pLvlPos = aNumLvlPos;
-            sNumStr = rFormat.GetListFormat();
+            OUString const sLevelFormat{rFormat.GetListFormat()};
+            OUStringBuffer buf;
+            ::std::optional<sal_Int32> oEraseFrom;
 
-            // now search the nums in the string
-            for (sal_uInt8 i = 0; i <= nLvl; ++i)
+            for (sal_Int32 nPosition{0}; nPosition < sLevelFormat.getLength(); 
)
             {
-                OUString sSrch("%" + OUString::number(i+1) + "%");
-                sal_Int32 nFnd = sNumStr.indexOf(sSrch);
-                if (-1 != nFnd)
+                if (sLevelFormat[nPosition] == '%'
+                    && (nPosition+3) < sLevelFormat.getLength()
+                    && sLevelFormat[nPosition] == '%'
+                    && sLevelFormat[nPosition+1] == '1'
+                    && sLevelFormat[nPosition+2] == '0'
+                    && sLevelFormat[nPosition+3] == '%')
                 {
-                    sal_Int32 nLen = sSrch.getLength();
+                    // WW8 does not support this level and breaks => erase it
+                    nPosition = nPosition + 4;
+                    oEraseFrom.emplace(nPosition);
+                }
+                else if (sLevelFormat[nPosition] == '%'
+                    && (nPosition+2) < sLevelFormat.getLength()
+                    && '1' <= sLevelFormat[nPosition+1]
+                    && sLevelFormat[nPosition+1] <= '9'
+                    && sLevelFormat[nPosition+2] == '%')
+                {
+                    sal_uInt8 const i(sLevelFormat[nPosition+1] - '1'); // 
need to subtract 1
+                    // because the result here is for RTF/DOC which is 0 based
+                    // not DOCX which is 1 based so it's converted there again
+                    nPosition = nPosition + 3;
+                    if (pLvlPos != ::std::end(aNumLvlPos) && i <= nLvl)
+                    {
+                        // this just contains the positions in order, level
+                        // doesn't matter here.
+                        // and yes, this index is 1-based in RTF/DOC!
+                        *pLvlPos = static_cast<sal_uInt8>(buf.getLength() + 1);
+                        ++pLvlPos;
+                    }
                     if (i < nLvl && rRule.Get(i).GetNumberingType() == 
SVX_NUM_NUMBER_NONE)
                     {
                         // LO doesn't show this level, so don't export its 
separator
-                        const OUString sSrch2("%" + OUString::number(i + 2) + 
"%");
-                        const sal_Int32 nFnd2 = sNumStr.indexOf(sSrch2, nFnd);
-                        if (-1 != nFnd2)
-                            nLen = nFnd2 - nFnd;
+                        oEraseFrom.emplace(nPosition);
+                        buf.append(static_cast<sal_Unicode>(i));
+                    }
+                    else if (nLvl < i)
+                    { // Word 2013 won't show label at all => erase completely
+                        oEraseFrom.emplace(nPosition);
+                    }
+                    else
+                    {
+                        oEraseFrom.reset();
+                        buf.append(static_cast<sal_Unicode>(i));
                     }
-                    *pLvlPos = static_cast<sal_uInt8>(nFnd + 1);
-                    ++pLvlPos;
-                    sNumStr = sNumStr.replaceAt(nFnd, nLen, 
OUStringChar(static_cast<char>(i)));
                 }
+                else
+                {
+                    if (!oEraseFrom)
+                    {
+                        buf.append(sLevelFormat[nPosition]);
+                    }
+                    ++nPosition;
+                }
+            }
+            if (oEraseFrom)
+            {
+                // a suffix is *not* erased, only in the middle!
+                buf.append(sLevelFormat.subView(*oEraseFrom));
             }
+            sNumStr = buf.makeStringAndClear();
         }
         else if (rFormat.GetNumberingType() != SVX_NUM_NUMBER_NONE)
             assert(false && "deprecated format still exists and is unhandled. 
Inform Vasily or Justin");

Reply via email to