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 77b52ba8c7cca2f8ca6bb6c51d6f192aa03e1ab6
Author:     Miklos Vajna <[email protected]>
AuthorDate: Mon Nov 10 10:20:02 2025 +0100
Commit:     Caolán McNamara <[email protected]>
CommitDate: Mon Nov 10 13:21:41 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/+/193715
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/sw/CppunitTest_sw_filter_md.mk b/sw/CppunitTest_sw_filter_md.mk
index 66ff788c18e7..bbd55fd9cb98 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 a970a270cae4..d24ee7a8ba9b 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>
@@ -912,6 +914,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 c25e7fecd7aa..59123bb93ea9 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();
 

Reply via email to