sw/CppunitTest_sw_filter_md.mk           |   52 +++++
 sw/Module_sw.mk                          |    1 
 sw/qa/filter/md/data/basic-elements.fodt |   65 ++++++
 sw/qa/filter/md/md.cxx                   |   67 ++++++
 sw/source/filter/md/wrtmd.cxx            |  306 ++++++++++++++++++++++++++++++-
 5 files changed, 487 insertions(+), 4 deletions(-)

New commits:
commit a76482fa6908153a1ae2483a4df8c37fe4f6b5a1
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Wed Jul 9 21:21:09 2025 +0500
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Tue Jul 15 14:21:42 2025 +0200

    tdf#160734: Markdown export: basic elements
    
    Handle headings and some formatting: bold, italic, hyperlinks.
    Escape special characters.
    
    Unit test framework is created by Ujjawal Kumar.
    
    Co-authored-by: Ujjawal Kumar <randomfores...@gmail.com>
    
    Change-Id: I378f8a349c4df2c93598acb4e792080e5b410c71
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187589
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    (cherry picked from commit c808b728525c3069dd910e9242449b9ce7fc06bf)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187775
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/sw/CppunitTest_sw_filter_md.mk b/sw/CppunitTest_sw_filter_md.mk
new file mode 100644
index 000000000000..94f029838eba
--- /dev/null
+++ b/sw/CppunitTest_sw_filter_md.mk
@@ -0,0 +1,52 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 
100 -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,sw_filter_md))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,sw_filter_md, \
+    sw/qa/filter/md/md \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sw_filter_md, \
+    cppu \
+    cppuhelper \
+    sal \
+    subsequenttest \
+    sw \
+    swqahelper \
+    test \
+    tl \
+    unotest \
+    utl \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,sw_filter_md,\
+    boost_headers \
+    libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_set_include,sw_filter_md,\
+    -I$(SRCDIR)/sw/inc \
+    -I$(SRCDIR)/sw/qa/inc \
+    $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_api,sw_filter_md,\
+        offapi \
+        udkapi \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,sw_filter_md))
+$(eval $(call gb_CppunitTest_use_vcl,sw_filter_md))
+
+$(eval $(call gb_CppunitTest_use_rdb,sw_filter_md,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,sw_filter_md))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sw/Module_sw.mk b/sw/Module_sw.mk
index 71117fd246af..aa4d5fe5de3c 100644
--- a/sw/Module_sw.mk
+++ b/sw/Module_sw.mk
@@ -174,6 +174,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sw,\
     CppunitTest_sw_filter_html \
     CppunitTest_sw_filter_xml \
     CppunitTest_sw_filter_ascii \
+    CppunitTest_sw_filter_md \
     CppunitTest_sw_a11y \
     CppunitTest_sw_core_theme \
     CppunitTest_sw_pdf_test \
diff --git a/sw/qa/filter/md/data/basic-elements.fodt 
b/sw/qa/filter/md/data/basic-elements.fodt
new file mode 100644
index 000000000000..0c5c7a29e59a
--- /dev/null
+++ b/sw/qa/filter/md/data/basic-elements.fodt
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:xlink="http://www.w3.org/1999/xlink"; 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:styles>
+  <style:style style:name="Standard" style:family="paragraph" 
style:class="text"/>
+  <style:style style:name="Heading" style:family="paragraph" 
style:parent-style-name="Standard" style:next-style-name="Text_20_body" 
style:class="chapter"/>
+  <style:style style:name="Heading_20_1" style:display-name="Heading 1" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="1" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="4.23mm" 
fo:margin-bottom="2.12mm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="18pt" fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_2" style:display-name="Heading 2" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="2" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="3.53mm" 
fo:margin-bottom="2.12mm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="16pt" fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_3" style:display-name="Heading 3" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="3" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="2.47mm" 
fo:margin-bottom="2.12mm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="14pt" fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_4" style:display-name="Heading 4" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="4" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="2.12mm" 
fo:margin-bottom="2.12mm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="13pt" fo:font-style="italic" 
fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_5" style:display-name="Heading 5" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="5" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="2.12mm" 
fo:margin-bottom="1.06mm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="12pt" fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_6" style:display-name="Heading 6" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="6" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="1.06mm" 
fo:margin-bottom="1.06mm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="12pt" fo:font-style="italic" 
fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Internet_20_link" style:display-name="Internet 
link" style:family="text">
+   <style:text-properties fo:color="#000080" 
style:text-underline-style="solid" style:text-underline-width="auto" 
style:text-underline-color="font-color"/>
+  </style:style>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="T2" style:family="text">
+   <style:text-properties fo:font-style="italic"/>
+  </style:style>
+  <style:style style:name="T3" style:family="text">
+   <style:text-properties style:text-underline-style="solid"/>
+  </style:style>
+  <style:style style:name="T4" style:family="text">
+   <style:text-properties style:text-line-through-style="solid" 
style:text-line-through-type="single"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:body>
+  <office:text>
+   <text:h text:style-name="Heading_20_1" text:outline-level="1">Heading 
1</text:h>
+   <text:h text:style-name="Heading_20_2" text:outline-level="2">Heading 
2</text:h>
+   <text:h text:style-name="Heading_20_3" text:outline-level="3">Heading 
3</text:h>
+   <text:h text:style-name="Heading_20_4" text:outline-level="4">Heading 
4</text:h>
+   <text:h text:style-name="Heading_20_5" text:outline-level="5">Heading 
5</text:h>
+   <text:h text:style-name="Heading_20_6" text:outline-level="6">Heading 
6</text:h>
+   <text:p><text:span text:style-name="T1">Bold</text:span> text</text:p>
+   <text:p>Text in <text:span text:style-name="T2">italics</text:span></text:p>
+   <text:p>This is a <text:a xlink:type="simple" 
xlink:href="http://www.libreoffice.org/"; text:style-name="Internet_20_link" 
text:visited-style-name="Visited_20_Internet_20_Link">hyperlink</text:a></text:p>
+   <text:p># Leading hash</text:p>
+   <text:p>Some {braces}, [square brackets], *asterisks*, `backticks`, 
ackslashes\, _underscores_, &lt;angle brackets&gt;</text:p>
+   <text:p/>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/filter/md/md.cxx b/sw/qa/filter/md/md.cxx
new file mode 100644
index 000000000000..01ac455d3c06
--- /dev/null
+++ b/sw/qa/filter/md/md.cxx
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <swmodeltestbase.hxx>
+
+namespace
+{
+/**
+ * Covers sw/source/filter/md/ fixes.
+ *
+ * Note that these tests are meant to be simple: either load a file and assert 
some result or build
+ * a document model with code, export and assert that result.
+ *
+ * Keep using the various sw_<format>import/export suites for multiple filter 
calls inside a single
+ * test.
+ */
+
+class Test : public SwModelTestBase
+{
+public:
+    Test()
+        : SwModelTestBase(u"/sw/qa/filter/md/data/"_ustr, u"Markdown"_ustr)
+    {
+    }
+};
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testExportingBasicElements)
+{
+    createSwDoc("basic-elements.fodt");
+
+    save(mpFilter);
+    SvFileStream fileStream(maTempFile.GetURL(), StreamMode::READ);
+    std::vector<char> buffer(fileStream.remainingSize());
+    fileStream.ReadBytes(buffer.data(), buffer.size());
+    std::string_view md_content(buffer.data(), buffer.size());
+    std::string_view expected(
+        // clang-format off
+        "# Heading 1" SAL_NEWLINE_STRING
+        "## Heading 2" SAL_NEWLINE_STRING
+        "### Heading 3" SAL_NEWLINE_STRING
+        "#### Heading 4" SAL_NEWLINE_STRING
+        "##### Heading 5" SAL_NEWLINE_STRING
+        "###### Heading 6" SAL_NEWLINE_STRING
+        "**Bold** text" SAL_NEWLINE_STRING
+        "Text in *italics*" SAL_NEWLINE_STRING
+        "This is a [hyperlink](http://www.libreoffice.org/)" SAL_NEWLINE_STRING
+        "\# Leading hash" SAL_NEWLINE_STRING
+        "Some \{braces\}, \[square brackets\], \*asterisks\*, \`backticks\`, 
\\backslashes\\, \_underscores\_, \<angle brackets\>" SAL_NEWLINE_STRING
+        SAL_NEWLINE_STRING
+        // clang-format on
+    );
+
+    CPPUNIT_ASSERT_EQUAL(expected, md_content);
+}
+
+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 8124a1100fac..7d2a1cbfa85c 100644
--- a/sw/source/filter/md/wrtmd.cxx
+++ b/sw/source/filter/md/wrtmd.cxx
@@ -19,20 +19,316 @@
 
 #include <sal/config.h>
 
+#include <editeng/crossedoutitem.hxx>
 #include <editeng/formatbreakitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <o3tl/string_view.hxx>
 #include <sal/log.hxx>
+#include <svl/itemiter.hxx>
+
 #include <officecfg/Office/Writer.hxx>
 
 #include <fmtpdsc.hxx>
 #include <mdiexp.hxx>
 #include <ndtxt.hxx>
+#include <poolfmt.hxx>
 #include <strings.hrc>
+#include <txatbase.hxx>
 #include "wrtmd.hxx"
 
+#include <algorithm>
+#include <unordered_map>
+#include <vector>
+
 namespace
 {
+struct FormattingStatus
+{
+    int nCrossedOutChange = 0;
+    int nPostureChange = 0;
+    int nUnderlineChange = 0;
+    int nWeightChange = 0;
+    std::unordered_map<OUString, int> aHyperlinkChanges;
+};
+
+struct HintsAtPos
+{
+    using value_type = std::pair<sal_Int32, const SfxPoolItem*>;
+    std::vector<value_type> table;
+    size_t cur = 0;
+    const value_type* get(size_t n) const { return n < table.size() ? 
&table[n] : nullptr; }
+    const value_type* current() const { return get(cur); }
+    const value_type* next() { return get(++cur); }
+    void sort()
+    {
+        std::stable_sort(table.begin(), table.end(),
+                         [](auto& lh, auto& rh) { return lh.first < rh.first; 
});
+    }
+};
+
+void ApplyItem(FormattingStatus& rChange, const SfxPoolItem& rItem, int 
increment)
+{
+    auto IterateItemSet = [&rChange, increment](const SfxItemSet& set) {
+        SfxItemIter iter(set);
+        while (!iter.IsAtEnd())
+        {
+            if (const auto* pNestedItem = iter.GetCurItem())
+                ApplyItem(rChange, *pNestedItem, increment);
+            iter.NextItem();
+        }
+    };
+
+    auto HandleEnumItem = [&rItem, increment ]<class ItemType>(
+        TypedWhichId<ItemType> id, 
decltype(std::declval<ItemType>().GetValue()) noneValue)
+    {
+        auto& rItem2 = rItem.StaticWhichCast(id);
+        if (rItem2.GetValue() == noneValue)
+            return -increment;
+        else
+            return +increment;
+    };
+
+    switch (rItem.Which())
+    {
+        case RES_CHRATR_CROSSEDOUT:
+            rChange.nCrossedOutChange += HandleEnumItem(RES_CHRATR_CROSSEDOUT, 
STRIKEOUT_NONE);
+            break;
+        case RES_CHRATR_POSTURE:
+            rChange.nPostureChange += HandleEnumItem(RES_CHRATR_POSTURE, 
ITALIC_NONE);
+            break;
+        case RES_CHRATR_UNDERLINE:
+            rChange.nUnderlineChange += HandleEnumItem(RES_CHRATR_UNDERLINE, 
LINESTYLE_NONE);
+            break;
+        case RES_CHRATR_WEIGHT:
+            rChange.nWeightChange += HandleEnumItem(RES_CHRATR_WEIGHT, 
WEIGHT_DONTKNOW);
+            break;
+        case RES_TXTATR_AUTOFMT:
+            if (auto& pStyle = 
rItem.StaticWhichCast(RES_TXTATR_AUTOFMT).GetStyleHandle())
+                IterateItemSet(*pStyle);
+            break;
+        case RES_TXTATR_INETFMT:
+            if (auto& url = 
rItem.StaticWhichCast(RES_TXTATR_INETFMT).GetValue(); !url.isEmpty())
+                rChange.aHyperlinkChanges[url] += increment;
+            // TODO: can we store anchors?
+            break;
+        case RES_TXTATR_CHARFMT:
+            if (auto pStyle = 
rItem.StaticWhichCast(RES_TXTATR_CHARFMT).GetCharFormat())
+                IterateItemSet(pStyle->GetAttrSet());
+            break;
+    }
+}
+
+FormattingStatus CalculateFormattingChange(HintsAtPos& starts, HintsAtPos& 
ends, sal_Int32 pos,
+                                           const FormattingStatus& 
currentFormatting)
+{
+    FormattingStatus result(currentFormatting);
+    // 1. Output closing attributes
+    for (auto* p = ends.current(); p && p->first == pos; p = ends.next())
+        ApplyItem(result, *p->second, -1);
+
+    // 2. Output opening attributes
+    for (auto* p = starts.current(); p && p->first == pos; p = starts.next())
+        ApplyItem(result, *p->second, +1);
+
+    return result;
+}
+
+void OutFormattingChange(SwMDWriter& rWrt, HintsAtPos& starts, HintsAtPos& 
ends, sal_Int32 pos,
+                         FormattingStatus& current)
+{
+    FormattingStatus result = CalculateFormattingChange(starts, ends, pos, 
current);
+
+    // Not in CommonMark
+    if (current.nCrossedOutChange <= 0 && result.nCrossedOutChange > 0)
+        rWrt.Strm().WriteUnicodeOrByteText(u"~~");
+    else if (current.nCrossedOutChange > 0 && result.nCrossedOutChange <= 0)
+        rWrt.Strm().WriteUnicodeOrByteText(u"~~");
+
+    if ((current.nPostureChange <= 0 && result.nPostureChange > 0)
+        || (current.nPostureChange > 0 && result.nPostureChange <= 0))
+        rWrt.Strm().WriteUnicodeOrByteText(u"*"); // both to open, and to close
+
+    if (current.nUnderlineChange <= 0 && result.nUnderlineChange > 0)
+    {
+        //rWrt.Strm().WriteUnicodeOrByteText(u"[u]");
+    }
+    else if (current.nUnderlineChange > 0 && result.nUnderlineChange <= 0)
+    {
+        //rWrt.Strm().WriteUnicodeOrByteText(u"[/u]");
+    }
+
+    if ((current.nWeightChange <= 0 && result.nWeightChange > 0)
+        || (current.nWeightChange > 0 && result.nWeightChange <= 0)) // both 
to open, and to close
+        rWrt.Strm().WriteUnicodeOrByteText(u"**");
+
+    for (const auto & [ url, delta ] : result.aHyperlinkChanges)
+    {
+        if (current.aHyperlinkChanges[url] <= 0 && delta > 0)
+        {
+            rWrt.Strm().WriteUnicodeOrByteText(u"[");
+        }
+        else if (current.aHyperlinkChanges[url] > 0 && delta <= 0)
+        {
+            rWrt.Strm().WriteUnicodeOrByteText(u"](");
+            rWrt.Strm().WriteUnicodeOrByteText(url);
+            rWrt.Strm().WriteUnicodeOrByteText(u")");
+        }
+    }
+
+    current = result;
+}
+
+void OutEscapedChars(SwMDWriter& rWrt, std::u16string_view chars)
+{
+    for (size_t pos = 0; pos < chars.size();)
+    {
+        size_t oldpos = pos;
+        sal_uInt32 ch = o3tl::iterateCodePoints(chars, &pos);
+        switch (ch)
+        {
+            case '\':
+            case '`':
+            case '*':
+            case '_':
+            case '{':
+            case '}':
+            case '[':
+            case ']':
+            case '<':
+            case '>':
+            case '#':
+                // TODO: should we escape '+', '-', '.', '|'?
+                rWrt.Strm().WriteUniOrByteChar('\');
+                [[fallthrough]];
+            default:
+                rWrt.Strm().WriteUnicodeOrByteText(chars.substr(oldpos, pos - 
oldpos));
+        }
+    }
+}
+
 /* Output of the nodes*/
-void OutMarkdown_SwTextNode(SwMDWriter& /*rWrt*/, const SwTextNode& /*rNode*/) 
{}
+void OutMarkdown_SwTextNode(SwMDWriter& rWrt, const SwTextNode& rNode)
+{
+    const OUString& rNodeText = rNode.GetText();
+    if (!rNodeText.isEmpty())
+    {
+        int nHeadingLevel = 0;
+        for (const SwFormat* pFormat = &rNode.GetAnyFormatColl(); pFormat;
+             pFormat = pFormat->DerivedFrom())
+        {
+            sal_uInt16 nPoolId = pFormat->GetPoolFormatId();
+            switch (nPoolId)
+            {
+                case RES_POOLCOLL_HEADLINE1:
+                    if (!nHeadingLevel)
+                        nHeadingLevel = 1;
+                    break;
+                case RES_POOLCOLL_HEADLINE2:
+                    if (!nHeadingLevel)
+                        nHeadingLevel = 2;
+                    break;
+                case RES_POOLCOLL_HEADLINE3:
+                    if (!nHeadingLevel)
+                        nHeadingLevel = 3;
+                    break;
+                case RES_POOLCOLL_HEADLINE4:
+                    if (!nHeadingLevel)
+                        nHeadingLevel = 4;
+                    break;
+                case RES_POOLCOLL_HEADLINE5:
+                    if (!nHeadingLevel)
+                        nHeadingLevel = 5;
+                    break;
+                case RES_POOLCOLL_HEADLINE6:
+                    if (!nHeadingLevel)
+                        nHeadingLevel = 6;
+                    break;
+                case RES_POOLCOLL_HTML_HR:
+                    if (rNodeText.isEmpty())
+                    {
+                        rWrt.Strm().WriteUnicodeOrByteText(u"___
");
+                        return;
+                    }
+                    break;
+            }
+        }
+
+        if (nHeadingLevel > 0)
+        {
+            for (int i = 0; i < nHeadingLevel; ++i)
+                rWrt.Strm().WriteUniOrByteChar('#');
+            rWrt.Strm().WriteUniOrByteChar(' ');
+        }
+
+        sal_Int32 nStrPos = rWrt.m_pCurrentPam->GetPoint()->GetContentIndex();
+        sal_Int32 nEnd = rNodeText.getLength();
+        if (rWrt.m_pCurrentPam->GetPoint()->GetNode() == 
rWrt.m_pCurrentPam->GetMark()->GetNode())
+            nEnd = rWrt.m_pCurrentPam->GetMark()->GetContentIndex();
+
+        HintsAtPos aHintStarts, aHintEnds;
+
+        // Start paragraph properties
+        for (SfxItemIter iter(rNode.GetSwAttrSet()); !iter.IsAtEnd(); 
iter.NextItem())
+            aHintStarts.table.emplace_back(nStrPos, iter.GetCurItem());
+
+        // Store character formatting
+        const size_t nCntAttr = rNode.HasHints() ? rNode.GetSwpHints().Count() 
: 0;
+        for (size_t i = 0; i < nCntAttr; ++i)
+        {
+            const SwTextAttr* pHint = rNode.GetSwpHints().Get(i);
+            const sal_Int32 nHintStart = pHint->GetStart();
+            if (nHintStart >= nEnd)
+                break;
+            const sal_Int32 nHintEnd = pHint->GetAnyEnd();
+            if (nHintEnd == nHintStart || nHintEnd <= nStrPos)
+                continue; // no output of zero-length hints and hints ended 
before output started yet
+            aHintStarts.table.emplace_back(std::max(nHintStart, nStrPos), 
&pHint->GetAttr());
+            aHintEnds.table.emplace_back(std::min(nHintEnd, nEnd), 
&pHint->GetAttr());
+        }
+
+        aHintEnds.sort();
+        // End paragraph properties
+        for (SfxItemIter iter(rNode.GetSwAttrSet()); !iter.IsAtEnd(); 
iter.NextItem())
+            aHintEnds.table.emplace_back(nEnd, iter.GetCurItem());
+
+        FormattingStatus currentStatus;
+        while (nStrPos < nEnd)
+        {
+            // 1. Output attributes
+            OutFormattingChange(rWrt, aHintStarts, aHintEnds, nStrPos, 
currentStatus);
+
+            // 2. Escape and output the character. This relies on hints not 
appearing in the middle of
+            // a surrogate pair.
+            sal_Int32 nEndOfChunk = nEnd;
+            if (auto* p = aHintEnds.current(); p && p->first < nEndOfChunk)
+                nEndOfChunk = p->first;
+            if (auto* p = aHintStarts.current(); p && p->first < nEndOfChunk)
+                nEndOfChunk = p->first;
+            OutEscapedChars(rWrt, rNodeText.subView(nStrPos, nEndOfChunk - 
nStrPos));
+            nStrPos = nEndOfChunk;
+        }
+        assert(aHintStarts.current() == nullptr);
+        // Output final closing attributes
+        OutFormattingChange(rWrt, aHintStarts, aHintEnds, nEnd, currentStatus);
+    }
+    rWrt.Strm().WriteUnicodeOrByteText(u"" SAL_NEWLINE_STRING);
+}
+
+void OutMarkdown_SwTableNode(SwMDWriter& /*rWrt*/, const SwTableNode& 
/*rNode*/)
+{
+    //const SwTable& rTable = rNode.GetTable();
+
+    //WriterRef pHtmlWrt;
+    //GetHTMLWriter({}, {}, pHtmlWrt);
+    //SvMemoryStream stream;
+    //SwPaM pam(*rNode.EndOfSectionNode(), rNode);
+    //pam.End()->Adjust(SwNodeOffset(+1));
+    //pHtmlWrt->Write(pam, stream, nullptr);
+
+    //...
+}
 }
 
 SwMDWriter::SwMDWriter(const OUString& rBaseURL) { SetBaseURL(rBaseURL); }
@@ -109,14 +405,16 @@ void SwMDWriter::Out_SwDoc(SwPaM* pPam)
             }
             else if (rNd.IsTableNode())
             {
-                // TODO
+                OutMarkdown_SwTableNode(*this, *rNd.GetTableNode());
+                m_pCurrentPam->GetPoint()->Assign(*rNd.EndOfSectionNode());
             }
             else if (rNd.IsSectionNode())
             {
                 SwSectionNode* pSectionNode = rNd.GetSectionNode();
-                if (!pSectionNode->GetSection().IsHiddenFlag() || 
bIncludeHidden)
+                if (!bIncludeHidden && 
pSectionNode->GetSection().IsHiddenFlag())
                 {
-                    // TODO
+                    // Skip
+                    
m_pCurrentPam->GetPoint()->Assign(*pSectionNode->EndOfSectionNode());
                 }
             }
             else if (&rNd == &m_pDoc->GetNodes().GetEndOfContent())

Reply via email to