sw/qa/extras/ooxmlexport/data/tdf170389_manyTabstops.odt |binary sw/qa/extras/ooxmlexport/ooxmlexport25.cxx | 12 +++++++++ sw/source/filter/ww8/docxattributeoutput.cxx | 19 ++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-)
New commits: commit f8b72d104ad32171ab637fc83c6f942a930b69e1 Author: [email protected] <[email protected]> AuthorDate: Mon Jan 19 11:34:38 2026 -0500 Commit: Justin Luth <[email protected]> CommitDate: Tue Jan 20 03:58:20 2026 +0100 tdf#170389 docx export: limit w:tabs to 64 entries Files with more than 64 tabstops are reported as corrupt by MS Word. Exposed by 6.2 commit 2bc84658cce1df5050fe788dd0c8a0906a1ca2c3 Author: Justin Luth on Wed Jul 18 07:37:41 2018 +0200 related tdf#63561 docx: styles inherit tabstops too Reviewed-on: https://gerrit.libreoffice.org/57278 Probably dedup needs to happen here, since AFAICS we accumulate the inherited tabs in each successive inherted style/paragraph. make CppunitTest_sw_ooxmlexport25 \ CPPUNIT_TEST_NAME=testTdf170389_manyTabstops Change-Id: I2fa356184e5322f9483678c9a50ae6ce96599920 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197606 Tested-by: Jenkins Reviewed-by: Justin Luth <[email protected]> diff --git a/sw/qa/extras/ooxmlexport/data/tdf170389_manyTabstops.odt b/sw/qa/extras/ooxmlexport/data/tdf170389_manyTabstops.odt new file mode 100644 index 000000000000..a54dba2ccf8b Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf170389_manyTabstops.odt differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx index f3f0f030a026..72fbe062ce04 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx @@ -150,6 +150,18 @@ DECLARE_OOXMLEXPORT_TEST(testTdf165478_bottomAligned, "tdf165478_bottomAligned.d CPPUNIT_ASSERT_EQUAL(sal_Int32(1887), nFlyTop); } +CPPUNIT_TEST_FIXTURE(Test, testTdf170389_manyTabstops) +{ + createSwDoc("tdf170389_manyTabstops.odt"); + + save(TestFilter::DOCX); + + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + // MS Word reports document as corrupt if it has more than 64 tabstops defined + // The paragraph itself defines 40, and inherits 40. Without the fix, this was 80 + assertXPath(pXmlDoc, "//w:tabs/w:tab", 64); +} + CPPUNIT_TEST_FIXTURE(Test, testInvalidDatetimeInProps) { createSwDoc("invalidDatetimeInProps.fodt"); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index af496d0764f5..df2cb11ee67a 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -9288,6 +9288,9 @@ void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop ) m_pSerializer->startElementNS(XML_w, XML_tabs); + // <w:tabs> may contain 64 <w:tab> entries at most, or else MS Word reports the file as corrupt + sal_uInt32 nWrittenTabs = 0; + // Get offset for tabs // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins. // But in ODT, zero position could be page margins or paragraph indent according to used settings. @@ -9297,6 +9300,9 @@ void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop ) sal_Int32 nCurrTab = 0; for ( sal_uInt16 i = 0; i < nInheritedTabCount; ++i ) { + if (nWrittenTabs == 64) + break; // maximum allowed number of entries reached + while ( nCurrTab < nCount && rTabStop[nCurrTab] < pInheritedTabs->At(i) ) ++nCurrTab; @@ -9305,13 +9311,20 @@ void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop ) m_pSerializer->singleElementNS( XML_w, XML_tab, FSNS( XML_w, XML_val ), "clear", FSNS( XML_w, XML_pos ), OString::number(pInheritedTabs->At(i).GetTabPos()) ); + ++nWrittenTabs; } } for (sal_uInt16 i = 0; i < nCount; i++ ) { if( rTabStop[i].GetAdjustment() != SvxTabAdjust::Default ) - impl_WriteTabElement( m_pSerializer, rTabStop[i], tabsOffset ); + { + if (nWrittenTabs < 64) + { + impl_WriteTabElement( m_pSerializer, rTabStop[i], tabsOffset ); + ++nWrittenTabs; + } + } else GetExport().setDefaultTabStop( rTabStop[i].GetTabPos()); } commit 74f914f3db9a860c60982c0e5dc5fe7d0c521a07 Author: [email protected] <[email protected]> AuthorDate: Mon Jan 19 14:46:23 2026 -0500 Commit: Justin Luth <[email protected]> CommitDate: Tue Jan 20 03:58:09 2026 +0100 tdf#170389 docx export: nCount already known to be > 0 Document why this (seemingly unnecessary) check is here. I was about to remove it, but it is VERY necessary to ensure no empty <w:tabs> is created. But the check for nCount > 0 was redundant. We have already returned if both are zero. Change-Id: I1ff4788baaad3edd00611ac37f0265f0dea3b828 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197608 Tested-by: Jenkins Reviewed-by: Justin Luth <[email protected]> diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 4090515df968..af496d0764f5 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -9280,10 +9280,10 @@ void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop ) } // do not output inherited tabs twice (inside styles and inside inline properties) - if ( nCount == nInheritedTabCount && nCount > 0 ) + if (nCount == nInheritedTabCount) { if ( *pInheritedTabs == rTabStop ) - return; + return; // <w:tabs> must contain at least one <w:tab>, so don't write it empty } m_pSerializer->startElementNS(XML_w, XML_tabs);
