sw/CppunitTest_sw_filter_md.mk | 1 sw/inc/shellio.hxx | 4 ++ sw/qa/filter/md/data/template.md | 3 + sw/qa/filter/md/data/template.ott |binary sw/qa/filter/md/md.cxx | 31 ++++++++++++++++ sw/source/filter/md/swmd.cxx | 71 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+)
New commits: commit 7462639341c043bf72560e0ddfff06e1e8879859 Author: Miklos Vajna <[email protected]> AuthorDate: Mon Nov 10 10:20:02 2025 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Mon Nov 10 17:44:47 2025 +0100 tdf#169316 sw markdown import: add a TemplateURL parameter Writer markdown import creates a document with built-in Writer styles, and it would be nice to instead use styles from a second template document instead. It is possible to use the File -> Templates functionality to manually assign a template to the resulting document, then later update styles from that, but it's far from straightforward when you want to simply do a template + markdown -> PDF conversion. Fix the problem by adding a new TemplateURL parameter for the Writer markdown import: if that's provided, it can be a URL relative to the markdown file and we'll load styles from that document before doing the actual import. This is similar to how SfxObjectShell::UpdateFromTemplate_Impl() works. Example desktop cmdline: soffice --infilter='Markdown:{"TemplateURL":{"type":"string","value":"./template.ott"}}' test.md Change-Id: I1763d0e18a116056427a6d57bbefe3e29a8514d4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193752 Reviewed-by: Miklos Vajna <[email protected]> Tested-by: Jenkins diff --git a/sw/CppunitTest_sw_filter_md.mk b/sw/CppunitTest_sw_filter_md.mk index c16777849709..117d9c7260e6 100644 --- a/sw/CppunitTest_sw_filter_md.mk +++ b/sw/CppunitTest_sw_filter_md.mk @@ -21,6 +21,7 @@ $(eval $(call gb_CppunitTest_use_libraries,sw_filter_md, \ editeng \ sal \ subsequenttest \ + svxcore \ sw \ swqahelper \ test \ diff --git a/sw/inc/shellio.hxx b/sw/inc/shellio.hxx index fcf80c6377bc..f7c7cb3b292b 100644 --- a/sw/inc/shellio.hxx +++ b/sw/inc/shellio.hxx @@ -307,6 +307,10 @@ class MarkdownReader final : public Reader { friend class SwReader; virtual ErrCodeMsg Read( SwDoc &, const OUString& rBaseURL, SwPaM &, const OUString &) override; + + /// Parse FilterOptions passed to the importer. + void SetupFilterOptions(SwDoc& rDoc); + public: MarkdownReader(): Reader() {} }; diff --git a/sw/qa/filter/md/data/template.md b/sw/qa/filter/md/data/template.md new file mode 100644 index 000000000000..c493bc3492b4 --- /dev/null +++ b/sw/qa/filter/md/data/template.md @@ -0,0 +1,3 @@ +# heading 1 + +body text diff --git a/sw/qa/filter/md/data/template.ott b/sw/qa/filter/md/data/template.ott new file mode 100644 index 000000000000..a43d91d8a871 Binary files /dev/null and b/sw/qa/filter/md/data/template.ott differ diff --git a/sw/qa/filter/md/md.cxx b/sw/qa/filter/md/md.cxx index d698082f28f4..147759ed4214 100644 --- a/sw/qa/filter/md/md.cxx +++ b/sw/qa/filter/md/md.cxx @@ -14,6 +14,8 @@ #include <com/sun/star/style/ParagraphAdjust.hpp> #include <vcl/graphicfilter.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> #include <docsh.hxx> #include <wrtsh.hxx> @@ -922,6 +924,35 @@ CPPUNIT_TEST_FIXTURE(Test, testEmbeddedAnchoredImageMdExport) CPPUNIT_ASSERT(aActual.ends_with(") B" SAL_NEWLINE_STRING)); } +CPPUNIT_TEST_FIXTURE(Test, testTemplateMdImport) +{ + // Given a document with a template: + setImportFilterOptions(uR"json({ + "TemplateURL": { + "type": "string", + "value": "./template.ott" + } +})json"_ustr); + + // When importing that markdown: + createSwDoc("template.md"); + + // Then make sure the styles are taken from the template: + SwDocShell* pDocShell = getSwDocShell(); + SwWrtShell* pWrtShell = pDocShell->GetWrtShell(); + SwCursor* pCursor = pWrtShell->GetCursor(); + SwTextNode* pTextNode = pCursor->GetPointNode().GetTextNode(); + SwFormatColl* pStyle = pTextNode->GetFormatColl(); + auto pXFillStyleItem = pStyle->GetAttrSet().GetItem<XFillStyleItem>(XATTR_FILLSTYLE); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 (drawing::FillStyle_SOLID) + // - Actual : 0 (drawing::FillStyle_NONE) + // i.e. the heading 1 style had the default black color instead of ~blue. + CPPUNIT_ASSERT_EQUAL(drawing::FillStyle_SOLID, pXFillStyleItem->GetValue()); + auto pXFillColorItem = pStyle->GetAttrSet().GetItem<XFillColorItem>(XATTR_FILLCOLOR); + CPPUNIT_ASSERT_EQUAL(Color(0x156082), pXFillColorItem->GetColorValue()); +} + 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/swmd.cxx b/sw/source/filter/md/swmd.cxx index 6455a1cc0415..f96114b7319a 100644 --- a/sw/source/filter/md/swmd.cxx +++ b/sw/source/filter/md/swmd.cxx @@ -17,6 +17,8 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ +#include <boost/property_tree/json_parser/error.hpp> + #include <osl/diagnose.h> #include <list.hxx> #include <numrule.hxx> @@ -44,11 +46,16 @@ #include <vcl/graph.hxx> #include <vcl/graphicfilter.hxx> #include <comphelper/random.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <rtl/uri.hxx> #include <ndgrf.hxx> #include <fmtcntnt.hxx> #include <swtypes.hxx> #include <fmturl.hxx> #include <formatcontentcontrol.hxx> +#include <docsh.hxx> #include "swmd.hxx" @@ -742,10 +749,74 @@ SwMarkdownParser::SwMarkdownParser(SwDoc& rD, SwPaM& rCursor, SvStream& rIn, OUS m_pArr.reset(new char[m_nFilesize + 1]); } +void MarkdownReader::SetupFilterOptions(SwDoc& rDoc) +{ + // See if any import options are provided: if so, collect them into a map. + if (!m_pMedium) + { + return; + } + + auto pItem = m_pMedium->GetItemSet().GetItem(SID_FILE_FILTEROPTIONS); + if (!pItem) + { + return; + } + + OUString aFilterOptions = pItem->GetValue(); + if (!aFilterOptions.startsWith("{")) + { + return; + } + + uno::Sequence<beans::PropertyValue> aFilterData; + try + { + std::vector<beans::PropertyValue> aData + = comphelper::JsonToPropertyValues(aFilterOptions.toUtf8()); + aFilterData = comphelper::containerToSequence(aData); + } + catch (const boost::property_tree::json_parser::json_parser_error& e) + { + SAL_WARN("sw.md", "failed to parse FilterOptions as JSON: " << e.message()); + } + comphelper::SequenceAsHashMap aMap(aFilterData); + OUString aTemplateURL; + aMap[u"TemplateURL"_ustr] >>= aTemplateURL; + if (aTemplateURL.isEmpty()) + { + return; + } + + // Have a TemplateURL: open it in a new object shell. + if (!m_pMedium->GetName().isEmpty()) + { + aTemplateURL = rtl::Uri::convertRelToAbs(m_pMedium->GetName(), aTemplateURL); + } + SwDocShell* pDocShell = rDoc.GetDocShell(); + if (!pDocShell) + { + return; + } + + SfxObjectShellLock xTemplateDoc = SfxObjectShell::CreateObjectByFactoryName( + pDocShell->GetFactory().GetFactoryName(), SfxObjectCreateMode::ORGANIZER); + xTemplateDoc->DoInitNew(); + SfxMedium aTemplateMedium(aTemplateURL, StreamMode::STD_READ); + if (!xTemplateDoc->LoadFrom(aTemplateMedium)) + { + return; + } + + // Copy the styles from the template doc to our document. + pDocShell->LoadStyles(*xTemplateDoc); +} + ErrCodeMsg MarkdownReader::Read(SwDoc& rDoc, const OUString& rBaseURL, SwPaM& rPam, const OUString&) { ErrCode nRet; + SetupFilterOptions(rDoc); SwMarkdownParser parser(rDoc, rPam, *m_pStream, rBaseURL, !m_bInsertMode); nRet = parser.CallParser();
