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);
+    }
 }
 }
 

Reply via email to