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 49279f6a01c4f07b723072c068a9c1eab5b7f292 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Wed Jul 16 22:23:07 2025 +0500 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Wed Jul 16 21:31:40 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> 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 f103796a9cdd..50212f60cf19 100644 --- a/sw/qa/extras/layout/layout5.cxx +++ b/sw/qa/extras/layout/layout5.cxx @@ -1831,6 +1831,44 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf166181) CPPUNIT_ASSERT_LESS(sal_Int32(500), height.toInt32()); } +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 8318470f3959..fc99648ff619 100644 --- a/sw/source/filter/ww8/attributeoutputbase.hxx +++ b/sw/source/filter/ww8/attributeoutputbase.hxx @@ -351,6 +351,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 083df7151523..482c847e6278 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -457,36 +457,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(); @@ -522,21 +509,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); } } @@ -597,13 +572,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 c09fc2f646b3..dd215d9aa7fe 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 4ee0efdf2aea..87d906b75970 100644 --- a/sw/source/filter/ww8/docxexport.cxx +++ b/sw/source/filter/ww8/docxexport.cxx @@ -557,14 +557,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 107b2e4256a6..a1cf6ad15833 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() );