sw/qa/filter/md/md.cxx | 52 ++++++++++++++++++++++++++++++++++++++++++ sw/source/filter/md/wrtmd.cxx | 14 +++++++---- 2 files changed, 61 insertions(+), 5 deletions(-)
New commits: commit 3d68d7c9f885f156c923dba2ebf869385b0d2e43 Author: Miklos Vajna <[email protected]> AuthorDate: Wed Sep 24 08:26:41 2025 +0200 Commit: Caolán McNamara <[email protected]> CommitDate: Wed Sep 24 10:38:05 2025 +0200 tdf#167564 sw markdown export: handle nested table cells The bugdoc contains a nested table; export it to markdown, the result is invalid. This happens because the markdown table cell is only allowed to contain inlines, says <https://github.github.com/gfm/#tables-extension->; while Writer tabe cells can contain inner tables. Fix the problem by exporting the content of inner tables as flat paragraphs, so at least the content is preserved and the result is valid. Note that this is not a problem when just re-exporting from-markdown documents' table cells, which always contain a single paragraph only. Change-Id: I39079118463c65e64b6a174a5adb2f3b6d80b84a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191420 Tested-by: Jenkins CollaboraOffice <[email protected]> Tested-by: Caolán McNamara <[email protected]> Reviewed-by: Caolán McNamara <[email protected]> diff --git a/sw/qa/filter/md/md.cxx b/sw/qa/filter/md/md.cxx index e18ed1a50f17..05ee24f00841 100644 --- a/sw/qa/filter/md/md.cxx +++ b/sw/qa/filter/md/md.cxx @@ -679,6 +679,58 @@ CPPUNIT_TEST_FIXTURE(Test, testMultiParaTableMdExport) CPPUNIT_ASSERT_EQUAL(aExpected, aActual); } +CPPUNIT_TEST_FIXTURE(Test, testNestedTableMdExport) +{ + // Given a document with a nested table: + createSwDoc(); + SwDocShell* pDocShell = getSwDocShell(); + SwWrtShell* pWrtShell = pDocShell->GetWrtShell(); + SwInsertTableOptions aInsertTableOptions(SwInsertTableFlags::DefaultBorder, + /*nRowsToRepeat=*/0); + pWrtShell->InsertTable(aInsertTableOptions, /*nRows=*/2, /*nCols=*/2); + pWrtShell->SttPara(); + pWrtShell->MoveTable(GotoPrevTable, fnTableStart); + pWrtShell->Insert(u"A1 before"_ustr); + pWrtShell->InsertTable(aInsertTableOptions, /*nRows=*/2, /*nCols=*/2); + pWrtShell->Insert(u"A1 after"_ustr); + pWrtShell->SttPara(); + pWrtShell->MoveTable(GotoPrevTable, fnTableStart); + pWrtShell->Insert(u"A1 inner"_ustr); + pWrtShell->GoNextCell(); + pWrtShell->Insert(u"B1 inner"_ustr); + pWrtShell->GoNextCell(); + pWrtShell->Insert(u"A2 inner"_ustr); + pWrtShell->GoNextCell(); + pWrtShell->Insert(u"B2 inner"_ustr); + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->GoNextCell(); + pWrtShell->Insert(u"B1 outer"_ustr); + pWrtShell->GoNextCell(); + pWrtShell->Insert(u"A2 outer"_ustr); + pWrtShell->GoNextCell(); + pWrtShell->Insert(u"B2 outer"_ustr); + + // When saving that to markdown: + save(mpFilter); + + // Then make sure that the inner table is exported as flat paragraphs: + std::string aActual = TempFileToString(); + std::string aExpected( + // clang-format off + SAL_NEWLINE_STRING + "| A1 before A1 inner B1 inner A2 inner B2 inner A1 after | B1 outer |" SAL_NEWLINE_STRING + "| - | - |" SAL_NEWLINE_STRING + "| A2 outer | B2 outer |" SAL_NEWLINE_STRING + SAL_NEWLINE_STRING + // clang-format on + ); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: | A1 before A1 inner B1 inner A2 inner B2 inner A1 after | + // - Actual : | A1 before | A1 inner | B1 inner | | - | - | | A2 inner | B2 inner | A1 after | + // i.e. the outer table cell had block elements, while it is only allowed to have inlines. + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/filter/md/wrtmd.cxx b/sw/source/filter/md/wrtmd.cxx index 67f02bf926d3..3f4fde819a58 100644 --- a/sw/source/filter/md/wrtmd.cxx +++ b/sw/source/filter/md/wrtmd.cxx @@ -485,7 +485,8 @@ void OutMarkdown_SwTextNode(SwMDWriter& rWrt, const SwTextNode& rNode, bool bFir } } - if (oCellInfo && oCellInfo->bCellStart) + bool bInNestedTable = rTableInfos.size() > 1; + if (oCellInfo && oCellInfo->bCellStart && !bInNestedTable) { // Cell start, separate by " | " from the previous cell, see // <https://github.github.com/gfm/#tables-extension->. @@ -695,7 +696,7 @@ void OutMarkdown_SwTextNode(SwMDWriter& rWrt, const SwTextNode& rNode, bool bFir } bool bRowEnd = oCellInfo && oCellInfo->bRowEnd; - if (bRowEnd) + if (bRowEnd && !bInNestedTable) { // Cell ends are implicit, but row end has its own marker. rWrt.Strm().WriteUnicodeOrByteText(u" |"); @@ -727,7 +728,7 @@ void OutMarkdown_SwTextNode(SwMDWriter& rWrt, const SwTextNode& rNode, bool bFir } bool bCellEnd = oCellInfo && oCellInfo->bCellEnd; - if (bInTable && !bCellEnd) + if (bInTable && (!bCellEnd || bInNestedTable)) { // Separator is a space between two in-table-cell paragraphs. rWrt.Strm().WriteUnicodeOrByteText(u" "); @@ -792,8 +793,11 @@ void OutMarkdown_SwTableNode(SwMDWriter& rWrt, const SwTableNode& rTableNode) aTableInfo.pEndNode = rTableNode.EndOfSectionNode(); rWrt.GetTableInfos().push(aTableInfo); - // Separator between the table and the previous content. - rWrt.Strm().WriteUnicodeOrByteText(u"" SAL_NEWLINE_STRING); + if (rWrt.GetTableInfos().size() == 1) + { + // Separator between the table and the previous content. + rWrt.Strm().WriteUnicodeOrByteText(u"" SAL_NEWLINE_STRING); + } } }
