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_, <angle brackets></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())