基于 Markdown 的 Docx 生产

概述

本文档不涉及 pandoc 安装步骤 和 markdown 语法,这两方面的内容请自行搜索。

什么是Markdown?

Markdown 是一种轻量级标记语言,创始人是约翰·格鲁伯(John Gruber)。它允许人们 “使用易读易写的纯文本格式编写文档,然后转换成有效的 HTML 文档”。 类似的标记语言还有很多,例如:asciidoc,python 的 reStructuredText, 以及各种各样的 wiki 格式。 Markdown 之所以流行也许是因为语法比较简单。但是 Markdown 缺乏一个统一的标准,各种组织对 Markdown 做了不同的扩展,形成了各种方言,对其通用性造成了影响。

编写 Markdown 并不依赖特定的软件,任意一个纯文本编辑器,大到各种 IDE,小到记事本都可以用来编写 Markdown。

通过工具,可以将 Markdown 转换成各种格式,例如 Docx, PDF,HTML 甚至是 PPT。 如今,markdown 已经不仅仅局限在技术圈,很多小说作者也开始使用 markdown 进行创作,出版社也开始支持 markdown 格式的书稿。

什么是 Word?

通常,我们管 Word 叫文字处理软件,但实际上 Word 是一个排版工具,经常被拿来和 LaTeX 做对比。虽然可以在 Word 进行文字编辑,但是往往体验比较差。

不可否认 Word 在排版方面确实强大,但是对于大多数人却太过复杂。

如何将 Markdown 文件转换成 Docx 文件?

这里要介绍一个文档格式转换的神器 -- pandoc。pandoc 是一个通用的标记语言格式转换工具,支持数十种格式直接的相互转换。例如:使用如下命令,就可以将一个 markdown 文件转换成 word 文档:

pandoc demo.md -o demo.docx

但是这样处理得到的 Word 文档往往还不能满足我们的需求,我们通常需要自定义 Word 文档中的样式,插入 目录/分页符 等对象。下面就来看看如何实现这些功能。

自定义样式

使用 pandoc 生成 docx 文件时,可以通过 “--reference-doc” 参数指定一个样式模板,pandoc 会自动将模板中定义的样式作用于新生成的 docx 文件。这类似于 css 对于 html 的效果。

使用 markdown 生成 Docx 可以很方便的在多个样式模板间切换,模板一次制作终身受益!

通常,制作一个样式模板的流程大概是这样:

  • 首先,我们获取模板

      pandoc --print-default-data-file reference.docx > custom-reference.docx
    
  • 然后,对 custom-reference.docx 中各种文本的样式进行修改。

    例如:如果要修改标题样式,则选中标题,在样式窗口中点击“修改样式”。


    图片

    如果直接通过工具栏修改了样式,需要将修改同步更新到对应的样式。


    图片
  • 最后,在生成 docx 文件时,使用 --reference-doc 参数指定样式模板即可。例如:

      pandoc --reference-doc=custom-reference.docx a.md -o a.docx
    

自定义表格样式

表格的样式修改有些特殊:pandoc 生成的 docx 中表格的默认样式是 “Table”,但是该样式在 pandoc 生成的样式模板中是无法修改的。需要一些额外的工作。

首先,编写一个 markdown 文件,假设叫 demo.md,在其中插入一个表格,类似于:

| 修订版本号    | 修订人         | 修订日期      | 修订描述 |
| ------------- | -------------- | ----          | ----     |
| 1.0           | xxx           | xxxx年xx月xx日 | 创建     |

然后使用 pandoc 将这个 markdown 文件转换成 Docx 格式

pandoc demo.md -o demo.docx

用 Word 打开 demo.docx,可以看到其中的表格样式为 “Table”。

image

表格样式

将 demo.docx 中的表格复制到模板文件(custom-reference.docx)中,然后在模板文件中修改该表格的样式。完成后保存。 然后,就可以使用模板重新生成docx了。

插入目录 & 分页符

pandoc 支持在 markdown 中插入一个 yaml 格式的“头”,可以定义一些元数据。例如,下面这段 markdown

---
title: 测试
toc: 1
toc-title: 目录
...

# 一级标题
# 一级标题
# 一级标题

title 定义了文档的标题,toc-title 定义了目录的标题,”toc: 1“ 表示需要生成目录。

pandoc 原生的“插入目录”很不灵活,如果想在标题和目录之间插入一些内容是很难实现的。而且 pandoc 也不支持分页符(pagebreak),这时需要借助一个 pandoc 的 Filter 或者叫插件 pandoc-docx-pagebreak-py。该插件提供了两个指令:

  • \newpage 插入一个分页符
  • \toc 插入目录

使用 pagebreak 需要关闭 pandoc 本身的 toc

有了这个 Filter, 我们就可以在文档的任何位置插入 目录 或者 分页符。例如下面这段 markdown 生成的 docx 会包含两页,第一页只有一个一级标题;第二页包含一个一级标题,后面跟着目录,然后又是一个一级标题。

# 一级标题

\newpage

# 一级标题

\toc

# 一级标题

pandoc-docx-pagebreak 基于 python,需要 python 环境,python 的安装这里就不赘述了。

使用 pip 安装 pandoc-docx-pagebreak

pip3 install git+https://github.com/pandocker/pandoc-docx-pagebreak-py

pandoc-docx-pagebreak, 有个问题: 目录的标题默认是英文,而且 hard code 在代码中, 而不是使用 pandoc 的 toc-title, 这个问题需要修改代码解决:

docx_pagebreak/init.py

 49                     # para = [pf.Para(pf.Str("Table"), pf.Space(), pf.Str("of"), pf.Space(), pf.Str("Contents"))]
 50                     para = [pf.Para(pf.Str("目录"))]

如果不知道模块安装路径,可以执行以下命令

python3 -mpydoc  docx_pagebreak

在输出的最下方可以看到模块路径。

总结

最后,看下最终的 markdown 到 docx 的转换命令:

pandoc -s --filter=pandoc-docx-pagebreakpy --reference-doc=/Users/Real/Documents/pandoc/template.docx -t docx -o a.docx a.md

简单说明一下:

  • --reference-doc,加载自定义样式模板
  • --filter,加载 pandoc-docx-pagebreak 插件,提供 目录 及 分页符 功能。
  • -t,指定输出格式
  • -o, 指定输出文件名

命令看上去有点长,你可以自己写个脚本来简化输入。例如我的脚本是这样的:

#!/usr/bin/env lua
-- lua 写的,仅供参考。

local file = "'" .. arg[1] .. "'"
local output, _ = string.gsub(file, ".md", ".docx", 1)

local cmd = {"pandoc"}
-- standalone
table.insert(cmd, "-s")
-- 添加目录
--table.insert(cmd, "--toc")
-- 渲染 mermaid 图形
table.insert(cmd, "-F mermaid-filter")

-- enable \newpage \toc
table.insert(cmd, "--filter=pandoc-docx-pagebreakpy")

-- 目标格式
table.insert(cmd, "-t docx --reference-doc=/Users/Real/Documents/pandoc/template.docx")

table.insert(cmd, file)
table.insert(cmd, "-o " .. output)

os.execute(table.concat(cmd, " "))

然后,我转化 markdown 时,就可以执行 md2doc a.md,就可以在输入文件的同级目录生成一个 a.docx。很方便。 如果实在不会写脚本,可以把命令记在笔记里用的时候稍微修改一下就好了。

One thing more

推荐大家学习一下 code snippet,大多数开发工具都支持这个功能,可以自定义一些代码片段,加快输入。例如下面这段,是我在 vscode 里定义的一个 snippet:

"doc header": {
    "prefix": "doc_header",
    "body": [
        "\\",
        "\\",
        "\\",
        "",
        "|            |               |            |",
        "|----------- | ------------  | ------     |",
        "|文件版本:  | V1.0          | 文件编号: |",
        "|发布日期:  | $CURRENT_YEAR年$CURRENT_MONTH月$CURRENT_DATE日 | 编    制: |",
        "|审    核:  |               | 批    准: |",
        "",
        "\\",
        "\\",
        "\\",
        "",
        "    修订记录:",
        "",
        "| 修订版本号    | 修订人         | 修订日期      | 修订描述 |",
        "| ------------- | -------------- | ----          | ----     |",
        "| 1.0           | xxx           | $CURRENT_YEAR年$CURRENT_MONTH月$CURRENT_DATE日 | 创建     |",
        "",
        "\\newpage",
        "",
        "\\toc",
        "",
        "\\newpage",
        ""
    ],
},

当我输入 doc_ 时,vscode 会自动提示 doc_header,此时按回车会自动插入我常用的一个文档表头,包含两个表格、分页符还有目录,其中的日期是自动填充的插入时的日期,非常方便。

其他注意事项

  1. 文档中第一个列表必须是无序列表,否则文档中的列表会乱掉。这应该这应该是 bug。(pandoc v2.9.2.1)

你可能感兴趣的:(基于 Markdown 的 Docx 生产)