sw/qa/extras/ooxmlexport/data/mixednumberings.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport13.cxx | 15 ------ sw/qa/extras/ooxmlexport/ooxmlexport25.cxx | 13 +++++ sw/source/filter/ww8/docxattributeoutput.cxx | 35 -------------- sw/source/filter/ww8/wrtw8num.cxx | 7 +- sw/source/filter/ww8/ww8atr.cxx | 9 ++- sw/source/filter/ww8/ww8par.cxx | 51 +++++++++++++++++++++ sw/source/filter/ww8/ww8par.hxx | 2 8 files changed, 78 insertions(+), 54 deletions(-)
New commits: commit fed5426a3a0058f630a7c418bb1ba0cb95caadd8 Author: Jaume Pujantell <[email protected]> AuthorDate: Wed Oct 29 20:03:24 2025 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Mon Nov 3 08:39:50 2025 +0100 sw: reduce duplication of w:num items When saving to .docx format we create new w:num entries for lists even if equivalent entries already exist. This creates a lot of bloat in big documents that have heavy use of numbered lists. A document that can be saved with ~150 num entries can end up with ~2000. This causes compatibility issues with other software that doesn't expect such high number of numbering styles. Change-Id: Ie8667c0a6d98a77c7c429a9a2925d31ee19fd31c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193164 Reviewed-by: Miklos Vajna <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> (cherry picked from commit aa0c3d7eab9a611f915b4262b37d34860f527bc7) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193248 Reviewed-by: Jaume Pujantell <[email protected]> Tested-by: Jenkins Signed-off-by: Xisco Fauli <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193278 diff --git a/sw/qa/extras/ooxmlexport/data/mixednumberings.docx b/sw/qa/extras/ooxmlexport/data/mixednumberings.docx new file mode 100644 index 000000000000..fbbe7dc50ee0 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/mixednumberings.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx index 0b6459f9f5d2..f6871744f6ab 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx @@ -184,11 +184,6 @@ DECLARE_OOXMLEXPORT_TEST(testTdf95848, "tdf95848.docx") { uno::Reference<beans::XPropertySet> xPara(getParagraph(3), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(2), getProperty<sal_Int16>(xPara, u"NumberingLevel"_ustr)); - // different numbering style - OUString listStyle3; - CPPUNIT_ASSERT(xPara->getPropertyValue(u"NumberingStyleName"_ustr) >>= listStyle3); - CPPUNIT_ASSERT(listStyle3.startsWith("WWNum")); - CPPUNIT_ASSERT(listStyle3 != listStyle); // but same list CPPUNIT_ASSERT_EQUAL(u"1.1.3"_ustr, getProperty<OUString>(xPara, u"ListLabelString"_ustr)); CPPUNIT_ASSERT_EQUAL(listId, getProperty<OUString>(xPara, u"ListId"_ustr)); @@ -280,11 +275,6 @@ DECLARE_OOXMLEXPORT_TEST(testTdf108496, "tdf108496.docx") } { uno::Reference<beans::XPropertySet> xPara(getParagraph(5), uno::UNO_QUERY); - // different numbering style - OUString listStyle2; - CPPUNIT_ASSERT(xPara->getPropertyValue(u"NumberingStyleName"_ustr) >>= listStyle2); - CPPUNIT_ASSERT(listStyle2.startsWith("WWNum")); - CPPUNIT_ASSERT(listStyle2 != listStyle); // restarted numeration due to override CPPUNIT_ASSERT_EQUAL(u"1"_ustr, getProperty<OUString>(xPara, u"ListLabelString"_ustr)); CPPUNIT_ASSERT_EQUAL(listId, getProperty<OUString>(xPara, u"ListId"_ustr)); @@ -316,11 +306,6 @@ DECLARE_OOXMLEXPORT_TEST(testTdf108496, "tdf108496.docx") } { uno::Reference<beans::XPropertySet> xPara(getParagraph(11), uno::UNO_QUERY); - // different numbering style - OUString listStyle2; - CPPUNIT_ASSERT(xPara->getPropertyValue(u"NumberingStyleName"_ustr) >>= listStyle2); - CPPUNIT_ASSERT(listStyle2.startsWith("WWNum")); - CPPUNIT_ASSERT(listStyle2 != listStyle); // numeration is continued CPPUNIT_ASSERT_EQUAL(u"3"_ustr, getProperty<OUString>(xPara, u"ListLabelString"_ustr)); CPPUNIT_ASSERT_EQUAL(listId, getProperty<OUString>(xPara, u"ListId"_ustr)); diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx index 2f16a4eac671..79ccfd138041 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx @@ -201,6 +201,19 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167297) fnVerify(); } +CPPUNIT_TEST_FIXTURE(Test, testWNumDuplication) +{ + // Given a document that changes a lot between a few numbering styles and overrides: + loadAndSave("mixednumberings.docx"); + + // Then make sure that we export a reasonable number of "w:num" elements: + xmlDocUniquePtr pXmlNum = parseExport(u"word/numbering.xml"_ustr); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 10 + // - Actual : 61 + CPPUNIT_ASSERT_EQUAL(10, countXPathNodes(pXmlNum, "//w:numbering/w:num")); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index df80ef3581f4..0565a321d20b 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -7702,38 +7702,6 @@ void DocxAttributeOutput::NumberingDefinition( sal_uInt16 nId, const SwNumRule & m_pSerializer->endElementNS( XML_w, XML_num ); } -// Not all attributes of SwNumFormat are important for export, so can't just use embedded in -// that classes comparison. -static bool lcl_ListLevelsAreDifferentForExport(const SwNumFormat & rFormat1, const SwNumFormat & rFormat2) -{ - if (rFormat1 == rFormat2) - // They are equal, nothing to do - return false; - - if (!rFormat1.GetCharFormat() != !rFormat2.GetCharFormat()) - // One has charformat, other not. they are different - return true; - - if (rFormat1.GetCharFormat() && rFormat2.GetCharFormat()) - { - const SwAttrSet & a1 = rFormat1.GetCharFormat()->GetAttrSet(); - const SwAttrSet & a2 = rFormat2.GetCharFormat()->GetAttrSet(); - - if (!(a1 == a2)) - // Difference in charformat: they are different - return true; - } - - // Compare numformats with empty charformats - SwNumFormat modified1 = rFormat1; - SwNumFormat modified2 = rFormat2; - modified1.SetCharFormatName(OUString()); - modified2.SetCharFormatName(OUString()); - modified1.SetCharFormat(nullptr); - modified2.SetCharFormat(nullptr); - return modified1 != modified2; -} - void DocxAttributeOutput::OverrideNumberingDefinition( SwNumRule const& rRule, sal_uInt16 const nNum, sal_uInt16 const nAbstractNum, const std::map< size_t, size_t > & rLevelOverrides ) @@ -7749,7 +7717,8 @@ void DocxAttributeOutput::OverrideNumberingDefinition( for (sal_uInt8 nLevel = 0; nLevel < nLevels; ++nLevel) { const auto levelOverride = rLevelOverrides.find(nLevel); - bool bListsAreDifferent = lcl_ListLevelsAreDifferentForExport(rRule.Get(nLevel), rAbstractRule.Get(nLevel)); + bool bListsAreDifferent + = AreListLevelsDifferentForExport(rRule.Get(nLevel), rAbstractRule.Get(nLevel)); // Export list override only if it is different to abstract one // or we have a level numbering override diff --git a/sw/source/filter/ww8/wrtw8num.cxx b/sw/source/filter/ww8/wrtw8num.cxx index 58a027b5bf4a..e72789615419 100644 --- a/sw/source/filter/ww8/wrtw8num.cxx +++ b/sw/source/filter/ww8/wrtw8num.cxx @@ -152,9 +152,10 @@ sal_uInt16 MSWordExportBase::GetNumberingId( const SwNumRule& rNumRule ) { EnsureUsedNumberingTable(); SwNumRule* p = const_cast<SwNumRule*>(&rNumRule); - sal_uInt16 nRet = o3tl::narrowing<sal_uInt16>(m_pUsedNumTable->GetPos(p)); - - return nRet; + size_t pos = m_pUsedNumTable->GetPos(p); + if (pos == SIZE_MAX) + return SAL_MAX_UINT16; + return o3tl::narrowing<sal_uInt16>(pos); } // GetFirstLineOffset should problem never appear unadorned apart from diff --git a/sw/source/filter/ww8/ww8atr.cxx b/sw/source/filter/ww8/ww8atr.cxx index 36c3f08655a1..77b531d0e14a 100644 --- a/sw/source/filter/ww8/ww8atr.cxx +++ b/sw/source/filter/ww8/ww8atr.cxx @@ -3933,10 +3933,13 @@ void AttributeOutputBase::ParaNumRule( const SwNumRuleItem& rNumRule ) GetExport().m_rDoc.FindNumRulePtr( pList->GetDefaultListStyleName())); assert(pAbstractRule); - if (pAbstractRule == pRule && !bListRestart) + if (!AreListsDifferentForExport(*pAbstractRule, *pRule) && !bListRestart) { - // different list, but no override - nNumId = GetExport().DuplicateAbsNum(listId, *pAbstractRule) + 1; + nNumId = GetExport().GetNumberingId(*pAbstractRule); + if (nNumId == SAL_MAX_UINT16) + nNumId = GetExport().DuplicateAbsNum(listId, *pAbstractRule) + 1; + else + nNumId += 1; } else { diff --git a/sw/source/filter/ww8/ww8par.cxx b/sw/source/filter/ww8/ww8par.cxx index 9476e055c954..1d3261b4a431 100644 --- a/sw/source/filter/ww8/ww8par.cxx +++ b/sw/source/filter/ww8/ww8par.cxx @@ -1231,6 +1231,57 @@ tools::Long GetListFirstLineIndent(const SwNumFormat &rFormat) return nReverseListIndented; } +bool AreListsDifferentForExport(SwNumRule const& rRule1, SwNumRule const& rRule2) +{ + if (rRule1 == rRule2) + // They are equal, nothing to do + return false; + + if (rRule1.IsContinusNum() != rRule2.IsContinusNum()) + return true; + + sal_uInt8 const nLevels = static_cast<sal_uInt8>( + rRule1.IsContinusNum() ? WW8ListManager::nMinLevel : WW8ListManager::nMaxLevel); + for (sal_uInt8 nLevel = 0; nLevel < nLevels; ++nLevel) + { + if (AreListLevelsDifferentForExport(rRule1.Get(nLevel), rRule2.Get(nLevel))) + return true; + } + return false; +} + +// Not all attributes of SwNumFormat are important for export, so can't just use embedded in +// that classes comparison. +bool AreListLevelsDifferentForExport(SwNumFormat const& rFormat1, SwNumFormat const& rFormat2) +{ + if (rFormat1 == rFormat2) + // They are equal, nothing to do + return false; + + if (!rFormat1.GetCharFormat() != !rFormat2.GetCharFormat()) + // One has charformat, other not. they are different + return true; + + if (rFormat1.GetCharFormat() && rFormat2.GetCharFormat()) + { + const SwAttrSet& a1 = rFormat1.GetCharFormat()->GetAttrSet(); + const SwAttrSet& a2 = rFormat2.GetCharFormat()->GetAttrSet(); + + if (!(a1 == a2)) + // Difference in charformat: they are different + return true; + } + + // Compare numformats with empty charformats + SwNumFormat modified1 = rFormat1; + SwNumFormat modified2 = rFormat2; + modified1.SetCharFormatName(OUString()); + modified2.SetCharFormatName(OUString()); + modified1.SetCharFormat(nullptr); + modified2.SetCharFormat(nullptr); + return modified1 != modified2; +} + static tools::Long lcl_GetTrueMargin(SvxFirstLineIndentItem const& rFirstLine, SvxTextLeftMarginItem const& rLeftMargin, const SwNumFormat &rFormat, tools::Long &rFirstLinePos) diff --git a/sw/source/filter/ww8/ww8par.hxx b/sw/source/filter/ww8/ww8par.hxx index 7176c6bc9dc9..caf93b45060a 100644 --- a/sw/source/filter/ww8/ww8par.hxx +++ b/sw/source/filter/ww8/ww8par.hxx @@ -1963,6 +1963,8 @@ void SyncIndentWithList( SvxFirstLineIndentItem & rFirstLine, const bool bFirstLineOfStSet, const bool bLeftIndentSet ); tools::Long GetListFirstLineIndent(const SwNumFormat &rFormat); +bool AreListsDifferentForExport(SwNumRule const& rRule1, SwNumRule const& rRule2); +bool AreListLevelsDifferentForExport(SwNumFormat const& rFormat1, SwNumFormat const& rFormat2); OUString BookmarkToWriter(std::u16string_view rBookmark); bool RTLGraphicsHack(SwTwips &rLeft, SwTwips nWidth, sal_Int16 eHoriOri, sal_Int16 eHoriRel, SwTwips nPageLeft,
