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