sw/qa/extras/layout/data/tdf167526.docx |binary sw/qa/extras/layout/layout5.cxx | 38 ++++++++++++++++++ sw/source/filter/ww8/attributeoutputbase.hxx | 3 + sw/source/filter/ww8/docxattributeoutput.cxx | 57 ++++++--------------------- sw/source/filter/ww8/docxattributeoutput.hxx | 3 + sw/source/filter/ww8/docxexport.cxx | 8 --- sw/source/filter/ww8/wrtw8nds.cxx | 13 +++--- 7 files changed, 66 insertions(+), 56 deletions(-)
New commits: commit 1f5c634fb1d8028e0752a77ddf9a9a1a8445d570 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Wed Jul 16 22:23:07 2025 +0500 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Thu Jul 17 08:04:33 2025 +0200 tdf#167526: write floating tables anchored to dummy nodes immediately Commit 441aed20b95ee40dec1df72fb8e8167d0e48c0c4 (tdf#167379 sw floattable: make dummy paragraph from DOCX import less visible, 2025-07-10) introduced dummy nodes as invisible anchors for floating tables without normal anchor nodes. Commit c9851022d102a2abfc16c033c0249f24573300e7 took care to avoid their export to DOCX, not calling OutputTextNode in OutputContentNode for them, and handling their tables as soon as the next text node is handled. However, this didn't work, when the next node wasn't a text node. In that case, the dummy paragraph wasn't detected as such in CollectFloatingTables, and got exported as normal; and after reload, it appeared after its table. Fix that by making sure to write floating tables as soon as the dummy node is handled. 1. Make previously static checkAndWriteFloatingTables a virtual method of AttributeOutputBase, implemented in DocxAttributeOutput. 2. Let MSWordExportBase::OutputTextNode check if it handles a dummy node, and if so, call AttrOutput().CheckAndWriteFloatingTables, and return. 3. Revert to calling OutputTextNode in MSWordExportBase::OutputContentNode unconditionally. This simplifies the logic a bit, avoiding the need to care about postponed actions, potentially in multiple places. Change-Id: I41b57103fc8b4522e3b5b1810389db078719b9b1 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187975 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187980 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> diff --git a/sw/qa/extras/layout/data/tdf167526.docx b/sw/qa/extras/layout/data/tdf167526.docx new file mode 100644 index 000000000000..050f081ff8e3 Binary files /dev/null and b/sw/qa/extras/layout/data/tdf167526.docx differ diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx index ae90af2846b5..4714376b3f2d 100644 --- a/sw/qa/extras/layout/layout5.cxx +++ b/sw/qa/extras/layout/layout5.cxx @@ -1798,6 +1798,44 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf166978) u"A2: this row suddenly jumps to the next page"); } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf167526) +{ + // Given a document with a floating table, immediately followed by a normal table: + createSwDoc("tdf167526.docx"); + + // check layout + { + // Make sure that the second node is a dummy node, and its line has height of 0 + auto pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "//page['pass 1']", 1); + assertXPath(pXmlDoc, "//body['pass 1']/txt[2]/anchored", 1); + assertXPath(pXmlDoc, "//body['pass 1']/txt[2]/SwParaPortion/SwLineLayout", "height", u"0"); + } + + // DOCX roundtrip: + saveAndReload(u"Office Open XML Text"_ustr); + + // check layout + { + // Make sure that the second node is a dummy node, and its line has height of 0 + auto pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "//page['pass 2']", 1); + assertXPath(pXmlDoc, "//body['pass 2']/txt[2]/anchored", 1); + // Without a fix, this would fail, because the paragraph was visible after reload; + // there were two SwLineLayout elements, each with non-0 height. + assertXPath(pXmlDoc, "//body['pass 2']/txt[2]/SwParaPortion/SwLineLayout", "height", u"0"); + } + + // check DOCX export + { + auto pXmlDoc = parseExport(u"word/document.xml"_ustr); + assertXPathNodeName(pXmlDoc, "/w:document/w:body/*[1]", "p"); + assertXPathNodeName(pXmlDoc, "/w:document/w:body/*[2]", "tbl"); + // Without a fix, this would fail, because the third element was the wrongly emitted <w:p> + assertXPathNodeName(pXmlDoc, "/w:document/w:body/*[3]", "tbl"); + } +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx b/sw/source/filter/ww8/attributeoutputbase.hxx index fa679b05b835..bdc551245b49 100644 --- a/sw/source/filter/ww8/attributeoutputbase.hxx +++ b/sw/source/filter/ww8/attributeoutputbase.hxx @@ -371,6 +371,9 @@ public: const SvxBrushItem* pBrush, // #i120928 export graphic of bullet bool isLegal) = 0; + // Output the floating tables attached to the text node + virtual void CheckAndWriteFloatingTables(const SwNode& /*rNode*/) {} + protected: static void GetNumberPara( OUString& rStr, const SwField& rField ); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 891ebdd4579d..59cbead94f7c 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -456,36 +456,23 @@ void DocxAttributeOutput::WriteFloatingTable(ww8::Frame const* pParentFrame) m_rExport.SetFloatingTableFrame(nullptr); } -static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutput) +void DocxAttributeOutput::CheckAndWriteFloatingTables(const SwNode& rNode) { - const auto& rExport = rDocxAttributeOutput.GetExport(); + // floating tables in shapes are not supported: exclude this case + if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen()) + return; + // iterate though all SpzFrameFormats and check whether they are anchored to the current text node - std::vector<ww8::Frame> aFrames; - for( sal_uInt16 nCnt = rExport.m_rDoc.GetSpzFrameFormats()->size(); nCnt; ) + for( sal_uInt16 nCnt = m_rExport.m_rDoc.GetSpzFrameFormats()->size(); nCnt; ) { - const SwFrameFormat* pFrameFormat = (*rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ]; + const SwFrameFormat* pFrameFormat = (*m_rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ]; const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); const SwNode* pAnchorNode = rAnchor.GetAnchorNode(); - if (!pAnchorNode || ! rExport.m_pCurPam->GetPointNode().GetTextNode()) + if (!pAnchorNode || !rNode.GetTextNode()) continue; - bool bAnchorMatchesNode = *pAnchorNode == *rExport.m_pCurPam->GetPointNode().GetTextNode(); - bool bAnchorIsPreviousNode = false; - if (!bAnchorMatchesNode) - { - // The anchor doesn't match, but see if the previous node is a dummy anchor, we should - // emit floating tables to that anchor here, too. - SwNodeIndex aNodeIndex(rExport.m_pCurPam->GetPointNode()); - --aNodeIndex; - if (*pAnchorNode == aNodeIndex.GetNode() && rExport.IsDummyFloattableAnchor(aNodeIndex.GetNode())) - { - bAnchorMatchesNode = true; - bAnchorIsPreviousNode = true; - } - } - - if (!bAnchorMatchesNode) + if (*pAnchorNode != *rNode.GetTextNode()) continue; const SwNodeIndex* pStartNode = pFrameFormat->GetContent().GetContentIdx(); @@ -521,21 +508,9 @@ static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutpu continue; } - // write table to docx: first tables from previous node, then from this node. + // write table to docx ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor()); - if (bAnchorIsPreviousNode) - { - aFrames.insert(aFrames.begin(), aFrame); - } - else - { - aFrames.push_back(aFrame); - } - } - - for (const auto& rFrame : aFrames) - { - rDocxAttributeOutput.WriteFloatingTable(&rFrame); + WriteFloatingTable(&aFrame); } } @@ -596,13 +571,9 @@ sal_Int32 DocxAttributeOutput::StartParagraph(const ww8::WW8TableNodeInfo::Point } // look ahead for floating tables that were put into a frame during import - // floating tables in shapes are not supported: exclude this case - if (!m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen()) - { - // Do this after opening table/row/cell, so floating tables anchored at cell start go inside - // the cell, not outside. - checkAndWriteFloatingTables(*this); - } + // Do this after opening table/row/cell, so floating tables anchored at cell start go inside + // the cell, not outside. + CheckAndWriteFloatingTables(m_rExport.m_pCurPam->GetPointNode()); // Look up the "sdt end before this paragraph" property early, when it // would normally arrive, it would be too late (would be after the diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index 4cb81b0c71e4..aa8876a0cb31 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -434,6 +434,9 @@ public: const SvxBrushItem* pBrush, bool isLegal ) override; + /// Output the floating tables attached to the text node + virtual void CheckAndWriteFloatingTables(const SwNode& rNode) override; + void WriteField_Impl(const SwField* pField, ww::eField eType, const OUString& rFieldCmd, FieldFlags nMode, OUString const* pBookmarkName = nullptr); diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx index 5b72f3e5fa2b..ffe50e1519f9 100644 --- a/sw/source/filter/ww8/docxexport.cxx +++ b/sw/source/filter/ww8/docxexport.cxx @@ -556,14 +556,6 @@ void DocxExport::CollectFloatingTables() continue; } - SwNodeIndex aNodeIndex(*pTextNode); - ++aNodeIndex; - if (!aNodeIndex.GetNode().GetTextNode()) - { - // Only text nodes know to look for floating tables from previous text nodes. - continue; - } - if (!pTextNode->HasSwAttrSet()) { continue; diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx index 513e267d0275..29a19eead43b 100644 --- a/sw/source/filter/ww8/wrtw8nds.cxx +++ b/sw/source/filter/ww8/wrtw8nds.cxx @@ -2328,6 +2328,13 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode ) { SAL_INFO( "sw.ww8", "<OutWW8_SwTextNode>" ); + if (IsDummyFloattableAnchor(rNode)) + { + // Emit their floating tables + AttrOutput().CheckAndWriteFloatingTables(rNode); + return; + } + SwWW8AttrIter aWatermarkAttrIter( *this, rNode ); // export inline heading @@ -3757,11 +3764,7 @@ void MSWordExportBase::OutputContentNode( SwContentNode& rNode ) switch ( rNode.GetNodeType() ) { case SwNodeType::Text: - // Skip dummy anchors: the next node will emit their floating tables. - if (!IsDummyFloattableAnchor(*rNode.GetTextNode())) - { - OutputTextNode(*rNode.GetTextNode()); - } + OutputTextNode(*rNode.GetTextNode()); break; case SwNodeType::Grf: OutputGrfNode( *rNode.GetGrfNode() );