sw/qa/filter/md/md.cxx | 52 ++++++++++++++++++++++++++++++++++++++++++ sw/source/filter/md/wrtmd.cxx | 14 +++++++---- 2 files changed, 61 insertions(+), 5 deletions(-)
New commits: commit c830c9ab824c8086b2124fec44f834a1d0ae4fa5 Author: Miklos Vajna <[email protected]> AuthorDate: Wed Sep 24 08:26:41 2025 +0200 Commit: Miklos Vajna <[email protected]> CommitDate: Wed Sep 24 12:38:14 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/+/191426 Reviewed-by: Miklos Vajna <[email protected]> Tested-by: Jenkins diff --git a/sw/qa/filter/md/md.cxx b/sw/qa/filter/md/md.cxx index 6a0bda8b0402..21c96dbf9dc9 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 5c26793d1c83..a3339f417b79 100644 --- a/sw/source/filter/md/wrtmd.cxx +++ b/sw/source/filter/md/wrtmd.cxx @@ -504,7 +504,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->. @@ -714,7 +715,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" |"); @@ -746,7 +747,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" "); @@ -811,8 +812,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); + } } }
