sw/qa/extras/ooxmlexport/data/tdf170516_drawingBeforePlainText.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport25.cxx | 38 ++++++---- sw/source/filter/ww8/wrtw8nds.cxx | 17 ++++ 3 files changed, 43 insertions(+), 12 deletions(-)
New commits: commit a8abe02aeab3722ad275fac8b9969e9f26cb958b Author: Justin Luth <[email protected]> AuthorDate: Tue Jan 27 10:04:09 2026 -0500 Commit: Miklos Vajna <[email protected]> CommitDate: Thu Feb 5 09:27:22 2026 +0100 tdf#170516 docx export: write flies before w:sdt starts If a run Pos start a runSdt content control (CH_TXTATR_BREAKWORD) AND it also anchors a floating shape, then the shape was being placed inside the sdtContent, and if the Sdt was plainText, then MS Word MIGHT consider that document to be corrupt. This patch follows plan B (see patchset notes). Plan A didn't pass CI unit tests (but it did pass locally...) For whatever reason, tdf#78333's original fix for corrupt SDTs skipped this situation when it happened in a floating table && !rNode.GetFlyFormat() ... { bPostponeWritingText = true ; } but the corruption is reported in that case too... (Floating shapes cannot anchor other flies, so all their images are inline [they have their own ]. so only in a floating table is this situation likely to arise. Well, at least natively. ODT->DOCX can also trigger this.) make CppunitTest_sw_ooxmlexport25 \ CPPUNIT_TEST_NAME=testTdf170516_drawingBeforePlainText Change-Id: I951fcef33fe54d71736f3036a43087f340927727 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198632 Reviewed-by: Justin Luth <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/sw/qa/extras/ooxmlexport/data/tdf170516_drawingBeforePlainText.docx b/sw/qa/extras/ooxmlexport/data/tdf170516_drawingBeforePlainText.docx new file mode 100644 index 000000000000..fa054c0c9ac3 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf170516_drawingBeforePlainText.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx index 9a2bf41b986a..c3c770857be3 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx @@ -147,18 +147,6 @@ DECLARE_OOXMLEXPORT_TEST(testTdf165478_bottomAligned, "tdf165478_bottomAligned.d CPPUNIT_ASSERT_EQUAL(sal_Int32(1887), nFlyTop); } -CPPUNIT_TEST_FIXTURE(Test, testTdf170438_dropdown) -{ - createSwDoc("tdf170438_dropdown.odt"); - - saveAndReload(TestFilter::DOCX); - - xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); - // MS Word reports document as corrupt if displayText is empty - assertXPath(pXmlDoc, "//w:listItem[1]", "displayText", u" "); - assertXPath(pXmlDoc, "//w:listItem[1]", "value", u""); // value may be empty -} - CPPUNIT_TEST_FIXTURE(Test, testTdf165359_SdtWithInline) { createSwDoc("tdf165359_SdtWithInline.docx"); @@ -181,6 +169,32 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf165359_SdtWithDrawing) assertXPath(pXmlDoc, "//w:sdtPr/w:text", 0); } +CPPUNIT_TEST_FIXTURE(Test, testTdf170438_dropdown) +{ + createSwDoc("tdf170438_dropdown.odt"); + + saveAndReload(TestFilter::DOCX); + + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + // MS Word reports document as corrupt if displayText is empty + assertXPath(pXmlDoc, "//w:listItem[1]", "displayText", u" "); + assertXPath(pXmlDoc, "//w:listItem[1]", "value", u""); // value may be empty +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf170516_drawingBeforePlainText) +{ + createSwDoc("tdf170516_drawingBeforePlainText.docx"); + + saveAndReload(TestFilter::DOCX); + + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + assertXPath(pXmlDoc, "//w:sdt", 1); // only one sdt + assertXPath(pXmlDoc, "//w:tc/w:p/w:sdt", 1); // and it is inside a cell + + assertXPath(pXmlDoc, "//mc:AlternateContent", 1); // only one drawing + assertXPath(pXmlDoc, "//w:tc/w:p/w:r/mc:AlternateContent", 1); // and it is not inside the sdt +} + CPPUNIT_TEST_FIXTURE(Test, testTdf170389_manyTabstops) { createSwDoc("tdf170389_manyTabstops.odt"); diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx index f795be4b6b0c..1912d25e7634 100644 --- a/sw/source/filter/ww8/wrtw8nds.cxx +++ b/sw/source/filter/ww8/wrtw8nds.cxx @@ -2512,9 +2512,11 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode ) // Don't redline content-controls--Word doesn't do them. SwTextAttr* pAttr = rNode.GetTextAttrAt(nCurrentPos, RES_TXTATR_CONTENTCONTROL, sw::GetTextAttrMode::Default); + bool bIsStartOfContentControl = false; if (pAttr && pAttr->GetStart() == nCurrentPos) { pRedlineData = nullptr; + bIsStartOfContentControl = true; } sal_Int32 nNextAttr = GetNextPos( &aAttrIter, rNode, nCurrentPos ); @@ -2578,6 +2580,21 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode ) OUString aSymbolFont; sal_Int32 nLen = nNextAttr - nCurrentPos; + + if (!bPostponeWritingText && bIsStartOfContentControl + && nStateOfFlyFrame == FLY_PROCESSED + && GetExportFormat() == MSWordExportBase::ExportFormat::DOCX) + { + // FLY_PROCESSED: there is at least 1 fly already written + + // assurance: this will not duplicate what is done for fields below... + assert(bTextAtr); // because SwTextContentControl always SetHasDummyChar(true) + + // write flys in a separate run before Sdt content control + AttrOutput().EndRun(&rNode, nCurrentPos, /*nLen=*/-1, /*bLastRun=*/false); + AttrOutput().StartRun(pRedlineData, nCurrentPos, bSingleEmptyRun); + } + if ( !bTextAtr && nLen ) { sal_Unicode ch = aStr[nCurrentPos];
