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");