sw/qa/extras/ooxmlexport/ooxmlexport22.cxx | 17 ++++++ sw/source/filter/ww8/wrtw8num.cxx | 73 +++++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 10 deletions(-)
New commits: commit f3256426f4ba618b993757a923c2196be077493b Author: Michael Stahl <michael.st...@collabora.com> AuthorDate: Wed Jul 9 15:09:54 2025 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Thu Jul 10 15:41:31 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> (cherry picked from commit 58db687fd96fcbb6a6b2909142df2fa01427add9) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187637 diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx index ae3527297214..d92984a56abd 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx @@ -72,7 +72,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf165933_noDelTextOnMove) CPPUNIT_TEST_FIXTURE(Test, testTdf166975) { - loadAndSave("WordOK.docx"); + createSwDoc("WordOK.docx"); CPPUNIT_ASSERT_EQUAL(u"a)"_ustr, getProperty<OUString>(getParagraph(2), u"ListLabelString"_ustr)); @@ -86,6 +86,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, testAnnotationRef) diff --git a/sw/source/filter/ww8/wrtw8num.cxx b/sw/source/filter/ww8/wrtw8num.cxx index 681961a3770c..ed46eeb7da9a 100644 --- a/sw/source/filter/ww8/wrtw8num.cxx +++ b/sw/source/filter/ww8/wrtw8num.cxx @@ -482,20 +482,75 @@ 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] == '%') { - *pLvlPos = static_cast<sal_uInt8>(nFnd + 1); - ++pLvlPos; - sNumStr = sNumStr.replaceAt(nFnd, sSrch.getLength(), rtl::OUStringChar(static_cast<char>(i))); + // 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 0 + if (i < nLvl && rRule.Get(i).GetNumberingType() == SVX_NUM_NUMBER_NONE) + { + // LO doesn't show this level, so don't export its separator + oEraseFrom.emplace(nPosition); + buf.append(static_cast<sal_Unicode>(i)); + } + else +#endif + 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)); + } + } + 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");