From f6dd1a7d7e01987242b8d19e7593b2d8bd9451d2 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Mon, 19 Jan 2026 19:29:58 +0800 Subject: [PATCH 1/2] :art: Improve export to Word .docx format https://github.com/siyuan-note/siyuan/issues/14970 Signed-off-by: Daniel <845765@qq.com> --- app/electron-builder-darwin-arm64.yml | 4 +-- app/electron-builder-darwin.yml | 4 +-- app/electron-builder-linux-arm64.yml | 4 +-- app/electron-builder-linux.yml | 4 +-- app/electron-builder.yml | 4 +-- .../pandoc-template.docx | Bin .../pandoc-resources/pandoc_color_filter.lua | 25 ++++++++++++++++++ kernel/go.mod | 2 +- kernel/go.sum | 4 +-- kernel/model/export.go | 2 ++ kernel/util/pandoc.go | 23 ++++++++++++---- 11 files changed, 58 insertions(+), 18 deletions(-) rename app/pandoc/{ => pandoc-resources}/pandoc-template.docx (100%) create mode 100644 app/pandoc/pandoc-resources/pandoc_color_filter.lua diff --git a/app/electron-builder-darwin-arm64.yml b/app/electron-builder-darwin-arm64.yml index 5daa1d09f..f1fc7295c 100644 --- a/app/electron-builder-darwin-arm64.yml +++ b/app/electron-builder-darwin-arm64.yml @@ -65,5 +65,5 @@ extraResources: filter: "!**/{.DS_Store}" - from: "pandoc/pandoc-darwin-arm64.zip" to: "pandoc.zip" - - from: "pandoc/pandoc-template.docx" - to: "pandoc-template.docx" + - from: "pandoc/pandoc-resources" + to: "pandoc-resources" diff --git a/app/electron-builder-darwin.yml b/app/electron-builder-darwin.yml index c837eb3d0..55cdc229e 100644 --- a/app/electron-builder-darwin.yml +++ b/app/electron-builder-darwin.yml @@ -65,5 +65,5 @@ extraResources: filter: "!**/{.DS_Store}" - from: "pandoc/pandoc-darwin-amd64.zip" to: "pandoc.zip" - - from: "pandoc/pandoc-template.docx" - to: "pandoc-template.docx" + - from: "pandoc/pandoc-resources" + to: "pandoc-resources" diff --git a/app/electron-builder-linux-arm64.yml b/app/electron-builder-linux-arm64.yml index c92a5a009..3fbc65b23 100644 --- a/app/electron-builder-linux-arm64.yml +++ b/app/electron-builder-linux-arm64.yml @@ -69,5 +69,5 @@ extraResources: filter: "!**/{.DS_Store}" - from: "pandoc/pandoc-linux-arm64.zip" to: "pandoc.zip" - - from: "pandoc/pandoc-template.docx" - to: "pandoc-template.docx" + - from: "pandoc/pandoc-resources" + to: "pandoc-resources" diff --git a/app/electron-builder-linux.yml b/app/electron-builder-linux.yml index dc609d740..b06a4dd11 100644 --- a/app/electron-builder-linux.yml +++ b/app/electron-builder-linux.yml @@ -66,5 +66,5 @@ extraResources: filter: "!**/{.DS_Store}" - from: "pandoc/pandoc-linux-amd64.zip" to: "pandoc.zip" - - from: "pandoc/pandoc-template.docx" - to: "pandoc-template.docx" + - from: "pandoc/pandoc-resources" + to: "pandoc-resources" diff --git a/app/electron-builder.yml b/app/electron-builder.yml index 76e4d1d6d..484ae6be2 100644 --- a/app/electron-builder.yml +++ b/app/electron-builder.yml @@ -72,5 +72,5 @@ extraResources: filter: "!**/{.DS_Store}" - from: "pandoc/pandoc-windows-amd64.zip" to: "pandoc.zip" - - from: "pandoc/pandoc-template.docx" - to: "pandoc-template.docx" + - from: "pandoc/pandoc-resources" + to: "pandoc-resources" diff --git a/app/pandoc/pandoc-template.docx b/app/pandoc/pandoc-resources/pandoc-template.docx similarity index 100% rename from app/pandoc/pandoc-template.docx rename to app/pandoc/pandoc-resources/pandoc-template.docx diff --git a/app/pandoc/pandoc-resources/pandoc_color_filter.lua b/app/pandoc/pandoc-resources/pandoc_color_filter.lua new file mode 100644 index 000000000..d67bf68dd --- /dev/null +++ b/app/pandoc/pandoc-resources/pandoc_color_filter.lua @@ -0,0 +1,25 @@ +function Span(el) + local style = el.attributes.style + if not style then return nil end + + -- 1. 提取 Hex 颜色 (支持 #FFF 和 #FFFFFF) + local hex = style:match("color%s*:%s*#(%x+)") + if not hex then return nil end + if #hex == 3 then + hex = hex:sub(1,1):rep(2) .. hex:sub(2,2):rep(2) .. hex:sub(3,3):rep(2) + end + + -- 2. 提取文本内容并处理 XML 转义 + -- 使用 stringify 快速获取 span 内部所有纯文本(丢弃内部嵌套样式以换取稳定性) + local txt = pandoc.utils.stringify(el.content) + txt = txt:gsub('&', '&'):gsub('<', '<'):gsub('>', '>') + + -- 3. 构造完整的 OpenXML 运行块 (Run) + -- 这种结构 Word 识别率 100%,且不会产生标签嵌套冲突 + local run_xml = string.format( + '%s', + hex:upper(), txt + ) + + return pandoc.RawInline('openxml', run_xml) +end \ No newline at end of file diff --git a/kernel/go.mod b/kernel/go.mod index 33b692db8..a65b01ad9 100644 --- a/kernel/go.mod +++ b/kernel/go.mod @@ -8,7 +8,7 @@ require ( github.com/88250/epub v0.0.0-20230830085737-c19055cd1f48 github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7 github.com/88250/gulu v1.2.3-0.20251208021445-f93f2666eaac - github.com/88250/lute v1.7.7-0.20260114095037-49a2cce7593f + github.com/88250/lute v1.7.7-0.20260119115611-b18a050e4eae github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4 github.com/ConradIrwin/font v0.2.1 diff --git a/kernel/go.sum b/kernel/go.sum index 2dc52c264..1b154b928 100644 --- a/kernel/go.sum +++ b/kernel/go.sum @@ -14,8 +14,8 @@ github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950 h1:Pa5hMiBceT github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/88250/gulu v1.2.3-0.20251208021445-f93f2666eaac h1:EC80pY8zyR0gbL8ZLIBB4IPG/ia3ZHScrR/xt8zU8qU= github.com/88250/gulu v1.2.3-0.20251208021445-f93f2666eaac/go.mod h1:IQ5dXW9CjVmx6B7OfK1Y4ZBKTPMe9q1AkVoLGGzRbS8= -github.com/88250/lute v1.7.7-0.20260114095037-49a2cce7593f h1:KDG/ZiZJ/NMAPH/at561BSJ2ET7MlQfBPhd9uoUXXYg= -github.com/88250/lute v1.7.7-0.20260114095037-49a2cce7593f/go.mod h1:WYyUw//5yVw9BJnoVjx7rI/3szsISxNZCYGOqTIrV0o= +github.com/88250/lute v1.7.7-0.20260119115611-b18a050e4eae h1:XVdbxiLwEJQbTzkRmCNOcfshEPA060MvXvEk5j/ZPcg= +github.com/88250/lute v1.7.7-0.20260119115611-b18a050e4eae/go.mod h1:WYyUw//5yVw9BJnoVjx7rI/3szsISxNZCYGOqTIrV0o= github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46 h1:Bq1JsDfVbHKUxNL/B2JXd8cC/1h6aFjrlXpGycnh0Hk= github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46/go.mod h1:fVfOloBzs2+W2VJCCbq60XIxc3yJHAZ0Gahv1oO0gyI= github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY= diff --git a/kernel/model/export.go b/kernel/model/export.go index 7f77f86d6..270bb309b 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -773,6 +773,7 @@ func ExportDocx(id, savePath string, removeAssets, merge bool) (fullPath string, "-f", "html+tex_math_dollars", "--resource-path", tmpDir, "-o", tmpDocxPath, + "--lua-filter", util.PandocColorFilterPath, } params := util.RemoveInvalid(Conf.Export.PandocParams) @@ -946,6 +947,7 @@ func ExportMarkdownHTML(id, savePath string, docx, merge bool) (name, dom string if docx { processIFrame(tree) + fillThemeStyleVar(tree) } luteEngine := NewLute() diff --git a/kernel/util/pandoc.go b/kernel/util/pandoc.go index d0e5d2085..c6831bf4a 100644 --- a/kernel/util/pandoc.go +++ b/kernel/util/pandoc.go @@ -101,8 +101,9 @@ func Pandoc(from, to, o, content string) (err error) { } var ( - PandocBinPath string // Pandoc 可执行文件路径 - PandocTemplatePath string // Pandoc Docx 模板文件路径 + PandocBinPath string // Pandoc 可执行文件路径 + PandocTemplatePath string // Pandoc Docx 模板文件路径 + PandocColorFilterPath string // Pandoc 颜色过滤器路径 ) func InitPandoc() { @@ -129,11 +130,11 @@ func InitPandoc() { } } - PandocTemplatePath = filepath.Join(pandocDir, "pandoc-template.docx") + PandocTemplatePath = filepath.Join(pandocDir, "pandoc-resources", "pandoc-template.docx") if !gulu.File.IsExist(PandocTemplatePath) { - PandocTemplatePath = filepath.Join(WorkingDir, "pandoc-template.docx") + PandocTemplatePath = filepath.Join(WorkingDir, "pandoc-resources", "pandoc-template.docx") if "dev" == Mode || !gulu.File.IsExist(PandocTemplatePath) { - PandocTemplatePath = filepath.Join(WorkingDir, "pandoc/pandoc-template.docx") + PandocTemplatePath = filepath.Join(WorkingDir, "pandoc", "pandoc-resources", "pandoc-template.docx") } } if !gulu.File.IsExist(PandocTemplatePath) { @@ -141,6 +142,18 @@ func InitPandoc() { logging.LogWarnf("pandoc template file [%s] not found", PandocTemplatePath) } + PandocColorFilterPath = filepath.Join(pandocDir, "pandoc-resources", "pandoc_color_filter.lua") + if !gulu.File.IsExist(PandocColorFilterPath) { + PandocColorFilterPath = filepath.Join(WorkingDir, "pandoc-resources", "pandoc_color_filter.lua") + if "dev" == Mode || !gulu.File.IsExist(PandocColorFilterPath) { + PandocColorFilterPath = filepath.Join(WorkingDir, "pandoc", "pandoc-resources", "pandoc_color_filter.lua") + } + } + if !gulu.File.IsExist(PandocColorFilterPath) { + PandocColorFilterPath = "" + logging.LogWarnf("pandoc color filter file [%s] not found", PandocColorFilterPath) + } + defer eventbus.Publish(EvtConfPandocInitialized) if gulu.OS.IsWindows() { From f7d43c48b359fd3924877c448660177dced44f98 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Tue, 20 Jan 2026 09:20:22 +0800 Subject: [PATCH 2/2] :art: Supports setting Pandoc parameters for export docx https://github.com/siyuan-note/siyuan/issues/16845 Signed-off-by: Daniel <845765@qq.com> --- kernel/model/conf.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/model/conf.go b/kernel/model/conf.go index 34dd01abc..edb5cafa1 100644 --- a/kernel/model/conf.go +++ b/kernel/model/conf.go @@ -325,6 +325,9 @@ func InitConf() { if "" != docxTemplate { params := util.RemoveInvalid(Conf.Export.PandocParams) if gulu.File.IsExist(docxTemplate) && !strings.Contains(params, "--reference-doc") { + if !strings.HasPrefix(docxTemplate, "\"") { + docxTemplate = "\"" + docxTemplate + "\"" + } params += " --reference-doc " + docxTemplate Conf.Export.PandocParams = strings.TrimSpace(params) }