sw/CppunitTest_sw_filter_md.mk |    2 +
 sw/qa/filter/md/md.cxx         |   40 +++++++++++++++++++++++++++++++++++++
 sw/source/filter/md/wrtmd.cxx  |   44 +++++++++++++++++++++++++++++++++--------
 3 files changed, 78 insertions(+), 8 deletions(-)

New commits:
commit 149a9237ccebb834448dd8cf9f9142868631578f
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Thu Aug 28 08:27:07 2025 +0200
Commit:     Caolán McNamara <caolan.mcnam...@collabora.com>
CommitDate: Thu Aug 28 12:27:31 2025 +0200

    sw markdown export: handle <code>
    
    The import side of this was added in commit
    f6411560817dd9e2c07564eef100dcb8f995804f (tdf#162153 Add missing inline
    code span, 2025-08-27), which maps MD_SPAN_CODE to the font of the
    RES_POOLCOLL_HTML_PRE style (some kind of monospace font).
    
    So do the opposite on export: if the font is the same as the monospace
    font from RES_POOLCOLL_HTML_PRE, then export that portion of text as
    `text`.
    
    To help nesting, export the start of this as the last char prop, and
    write the end of this as the first char prop.
    
    Change-Id: I7ba21502a1040b08c6b66929ceedfd1fe72f9f70
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190313
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Tested-by: Caolán McNamara <caolan.mcnam...@collabora.com>
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>

diff --git a/sw/CppunitTest_sw_filter_md.mk b/sw/CppunitTest_sw_filter_md.mk
index 1432bc5cb2b6..6ec0da4b5289 100644
--- a/sw/CppunitTest_sw_filter_md.mk
+++ b/sw/CppunitTest_sw_filter_md.mk
@@ -26,6 +26,7 @@ $(eval $(call gb_CppunitTest_use_libraries,sw_filter_md, \
     tl \
     unotest \
     utl \
+    svl \
 ))
 
 $(eval $(call gb_CppunitTest_use_externals,sw_filter_md,\
@@ -35,6 +36,7 @@ $(eval $(call gb_CppunitTest_use_externals,sw_filter_md,\
 
 $(eval $(call gb_CppunitTest_set_include,sw_filter_md,\
     -I$(SRCDIR)/sw/inc \
+    -I$(SRCDIR)/sw/source/uibase/inc \
     -I$(SRCDIR)/sw/qa/inc \
     $$(INCLUDE) \
 ))
diff --git a/sw/qa/filter/md/md.cxx b/sw/qa/filter/md/md.cxx
index ab1c30abde3d..ce747675f6b7 100644
--- a/sw/qa/filter/md/md.cxx
+++ b/sw/qa/filter/md/md.cxx
@@ -13,6 +13,13 @@
 
 #include <com/sun/star/style/ParagraphAdjust.hpp>
 
+#include <docsh.hxx>
+#include <wrtsh.hxx>
+#include <view.hxx>
+#include <IDocumentStylePoolAccess.hxx>
+#include <poolfmt.hxx>
+#include <charatr.hxx>
+
 namespace
 {
 /**
@@ -178,6 +185,39 @@ CPPUNIT_TEST_FIXTURE(Test, testTables)
     CPPUNIT_ASSERT(getCell(xtable, u"C2"_ustr, u"data3"_ustr).is());
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testExportingCodeSpan)
+{
+    // Given a document where the middle word is a code portion:
+    createSwDoc();
+    SwDocShell* pDocShell = getSwDocShell();
+    SwDoc* pDoc = pDocShell->GetDoc();
+    IDocumentStylePoolAccess& rIDSPA = pDoc->getIDocumentStylePoolAccess();
+    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
+    pWrtShell->Insert(u"A B C"_ustr);
+    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, 
/*bBasicCall=*/false);
+    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 1, 
/*bBasicCall=*/false);
+    SwView& rView = pWrtShell->GetView();
+    SwTextFormatColl* pColl = 
rIDSPA.GetTextCollFromPool(RES_POOLCOLL_HTML_PRE);
+    SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet(rView.GetPool());
+    aSet.Put(pColl->GetFont());
+    pWrtShell->SetAttrSet(aSet);
+
+    // When saving that to markdown:
+    save(mpFilter);
+
+    // Then make sure the format of B is exported:
+    SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
+    std::vector<char> aBuffer(aStream.remainingSize());
+    aStream.ReadBytes(aBuffer.data(), aBuffer.size());
+    std::string_view aActual(aBuffer.data(), aBuffer.size());
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: A `B` C
+    // - Actual  : A B C
+    // i.e. the code formatting was lost.
+    std::string_view aExpected("A `B` C" SAL_NEWLINE_STRING);
+    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 259bded5d280..4021762510df 100644
--- a/sw/source/filter/md/wrtmd.cxx
+++ b/sw/source/filter/md/wrtmd.cxx
@@ -28,18 +28,21 @@
 #include <sal/log.hxx>
 #include <sax/tools/converter.hxx>
 #include <svl/itemiter.hxx>
+#include <editeng/fontitem.hxx>
 
 #include <officecfg/Office/Writer.hxx>
 
 #include <docary.hxx>
 #include <fmtpdsc.hxx>
 #include <IDocumentRedlineAccess.hxx>
+#include <IDocumentStylePoolAccess.hxx>
 #include <mdiexp.hxx>
 #include <ndtxt.hxx>
 #include <poolfmt.hxx>
 #include <redline.hxx>
 #include <strings.hrc>
 #include <txatbase.hxx>
+#include <charatr.hxx>
 #include "wrtmd.hxx"
 
 #include <algorithm>
@@ -54,6 +57,7 @@ struct FormattingStatus
     int nPostureChange = 0;
     int nUnderlineChange = 0;
     int nWeightChange = 0;
+    int nCodeChange = 0;
     std::unordered_map<OUString, int> aHyperlinkChanges;
     std::unordered_map<const SwRangeRedline*, int> aRedlineChanges;
 };
@@ -91,14 +95,14 @@ struct NodePositions
     }
 };
 
-void ApplyItem(FormattingStatus& rChange, const SfxPoolItem& rItem, int 
increment)
+void ApplyItem(SwMDWriter& rWrt, FormattingStatus& rChange, const SfxPoolItem& 
rItem, int increment)
 {
-    auto IterateItemSet = [&rChange, increment](const SfxItemSet& set) {
+    auto IterateItemSet = [&rWrt, &rChange, increment](const SfxItemSet& set) {
         SfxItemIter iter(set);
         while (!iter.IsAtEnd())
         {
             if (const auto* pNestedItem = iter.GetCurItem())
-                ApplyItem(rChange, *pNestedItem, increment);
+                ApplyItem(rWrt, rChange, *pNestedItem, increment);
             iter.NextItem();
         }
     };
@@ -140,6 +144,20 @@ void ApplyItem(FormattingStatus& rChange, const 
SfxPoolItem& rItem, int incremen
             if (auto pStyle = 
rItem.StaticWhichCast(RES_TXTATR_CHARFMT).GetCharFormat())
                 IterateItemSet(pStyle->GetAttrSet());
             break;
+        case RES_CHRATR_FONT:
+        {
+            const SvxFontItem& rFontItem = 
rItem.StaticWhichCast(RES_CHRATR_FONT);
+            SwDoc* pDoc = rWrt.m_pDoc;
+            IDocumentStylePoolAccess& rIDSPA = 
pDoc->getIDocumentStylePoolAccess();
+            SwTextFormatColl* pColl = 
rIDSPA.GetTextCollFromPool(RES_POOLCOLL_HTML_PRE);
+            if (rFontItem == pColl->GetFont())
+            {
+                // We know the import uses this font for code spans, so map 
this back to a code
+                // span.
+                rChange.nCodeChange += increment;
+            }
+            break;
+        }
     }
 }
 
@@ -148,19 +166,19 @@ void ApplyItem(FormattingStatus& rChange, const 
SwRangeRedline* pItem, int incre
     rChange.aRedlineChanges[pItem] += increment;
 }
 
-FormattingStatus CalculateFormattingChange(NodePositions& positions, sal_Int32 
pos,
-                                           const FormattingStatus& 
currentFormatting)
+FormattingStatus CalculateFormattingChange(SwMDWriter& rWrt, NodePositions& 
positions,
+                                           sal_Int32 pos, const 
FormattingStatus& currentFormatting)
 {
     FormattingStatus result(currentFormatting);
     // 1. Output closing attributes
     for (auto* p = positions.hintEnds.current(); p && p->first == pos;
          p = positions.hintEnds.next())
-        ApplyItem(result, *p->second, -1);
+        ApplyItem(rWrt, result, *p->second, -1);
 
     // 2. Output opening attributes
     for (auto* p = positions.hintStarts.current(); p && p->first == pos;
          p = positions.hintStarts.next())
-        ApplyItem(result, *p->second, +1);
+        ApplyItem(rWrt, result, *p->second, +1);
 
     // 3. Output closing redlines
     for (auto* p = positions.redlineEnds.current(); p && p->first == pos;
@@ -183,12 +201,17 @@ bool ShouldOpenIt(int prev, int curr) { return prev != 
curr && prev <= 0 && curr
 void OutFormattingChange(SwMDWriter& rWrt, NodePositions& positions, sal_Int32 
pos,
                          FormattingStatus& current)
 {
-    FormattingStatus result = CalculateFormattingChange(positions, pos, 
current);
+    FormattingStatus result = CalculateFormattingChange(rWrt, positions, pos, 
current);
 
     // Closing stuff
 
     // TODO/FIXME: the closing characters must be right-flanking
 
+    if (ShouldCloseIt(current.nCodeChange, result.nCodeChange))
+    {
+        rWrt.Strm().WriteUnicodeOrByteText(u"`");
+    }
+
     // Not in CommonMark
     if (ShouldCloseIt(current.nCrossedOutChange, result.nCrossedOutChange))
         rWrt.Strm().WriteUnicodeOrByteText(u"~~");
@@ -276,6 +299,11 @@ void OutFormattingChange(SwMDWriter& rWrt, NodePositions& 
positions, sal_Int32 p
         }
     }
 
+    if (ShouldOpenIt(current.nCodeChange, result.nCodeChange))
+    {
+        rWrt.Strm().WriteUnicodeOrByteText(u"`");
+    }
+
     current = result;
 }
 

Reply via email to