- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
DokuWiki 本身并不原生支持数学公式排版,常见的做法是通过 latex 插件用<latex>...</latex>标签包裹 TeX 代码。Pandoc 的 DokuWiki 阅读器(Reader)针对这类内容提供了两种互补的解析路径:一是将<latex>标签内的内容作为RawInline 原始 LaTeX保留,二是通过tex_math_dollars扩展让$...$美元符包裹的公式被识别为Math 数学元素。本文以仓库中的回归测试 test/command/8178.md 为核心骨架,逐条剖析三个测试用例,并结合 DokuWiki 阅读器源码、数学解析器 与 扩展定义,讲清这一机制的完整原理与实战用法。读完本文,你将掌握:默认与启用扩展两种模式下 DokuWiki 数学内容分别如何被解析、<latex>与<LATEX>行内/块级语法的区分,以及如何在自己的转换管线中正确选用这些特性。
一、命令测试:Pandoc 回归测试的一种组织方式
test/command/8178.md 不是普通的说明文档,而是一个command test(命令测试)。Pandoc 将大量历史 bug 修复与特性行为固化为这类测试文件,由 test/Tests/Command.hs 模块统一驱动执行。
其格式约定(见 test/Tests/Command.hs)非常简洁:
- 代码块第一行以
%开头,后面是待执行的完整命令行; - 随后若干行是作为 stdin 传给该命令的输入文本;
- 输入以单独一行的
^D结束; ^D之后的内容为期望的 stdout 输出,若程序有非零退出码,则在末尾附加=> 退出码。
8178 这个编号对应 GitHub issue/PR 编号(#8178)。在 changelog.md 中可以找到这次变更的官方记录:
- The
tex_math_dollarsextension is now supported fordokuwiki(but off by default) (#8178).- Content inside
<latex>...</latex>is parsed as raw LaTeX inline, and inside<LATEX>..</LATEX>as raw LaTeX block (#8178).- The behavior of
<php>...</php>is changed, so that instead of producing a code block, it produces raw HTML with<?php ... ?>.
也就是说,这次变更同时做了三件事:为 DokuWiki 阅读器引入tex_math_dollars扩展支持(默认关闭)、支持<latex>/<LATEX>原始内容解析、调整<php>的处理方式。下面三个测试用例正是对前两点的逐一验证。
二、用例一:<latex>...</latex>被解析为原始 LaTeX 内联内容
测试文件中的第一个用例原文如下:
% pandoc -f dokuwiki -t native <latex>$\sum_{\substack{(i,j) \in I^2 \i \neq j}}$</latex> ^D [ Para [ RawInline (Format "latex") "$\\sum_{\\substack{(i,j) \\in I^2 \\i \\neq j}}$" ] ]这条命令在默认设置(未启用任何数学扩展)下,把整段<latex>...</latex>内容转换成了RawInline (Format "latex")——即一个携带格式标签latex的原始内联元素,内容被原样保留(native 输出中反斜杠按 Pandoc 的字符串转义规则显示为\\)。
背后的实现:inlineRaw
这一行为由 DokuWiki 阅读器 中的inlineRaw解析器实现:
inlineRaw :: PandocMonad m => DWParser m B.Inlines inlineRaw = try $ do char '<' fmt <- oneOfStrings ["html", "php", "latex"] -- LaTeX via https://www.dokuwiki.org/plugin:latex char '>' contents <- manyTillChar anyChar (try $ string "</" *> string (T.unpack fmt) *> char '>') return $ case T.toLower fmt of "php" -> B.rawInline "html" $ "<?php " <> contents <> " ?>" f -> B.rawInline f contents关键逻辑一目了然:解析器识别<后跟html、php或latex三种标签之一,读取直到对应的</xxx>结束标签,然后构造RawInline。其中php被特判为输出<?php ... ?>形式的 raw HTML(这正是 changelog 中提到的第三处行为变更),而html与latex则按原格式输出。因此,<latex>...</latex>中的 TeX 代码不会经历任何词法或语义解析,而是作为不透明字符串进入 Pandoc 文档树。
行内与块级的对应:<LATEX>(大写)
与inlineRaw对应,同一文件中的 blockRaw 负责块级原始内容,识别<HTML>、<PHP>、<LATEX>(大写)三种标签:
blockRaw :: PandocMonad m => DWParser m B.Blocks blockRaw = try $ do char '<' fmt <- oneOfStrings ["HTML", "PHP", "LATEX"] char '>' optional (manyTill spaceChar eol) contents <- manyTillChar anyChar (try $ string "</" *> string (T.unpack fmt) *> char '>') return $ case T.toLower fmt of "php" -> B.rawBlock "html" $ "<?php " <> contents <> " ?>" f -> B.rawBlock f contents通过T.toLower归一化后,无论源标签大小写如何,输出格式统一为小写的latex/html。按 DokuWiki 插件的惯例与 changelog 的描述:小写<latex>对应行内公式(RawInline),大写<LATEX>对应块级公式(RawBlock)。这为文档作者提供了一套直观的约定:公式较短时用行内形式,独立成行的复杂公式(如求和、多行方程组)用块级形式。
三、用例二:启用 tex_math_dollars 后$mc^2$解析为数学公式
第二个用例展示了扩展启用的效果:
% pandoc -f dokuwiki+tex_math_dollars -t native $mc^2$ ^D [ Para [ Math InlineMath "mc^2" ] ]命令行中-f dokuwiki+tex_math_dollars的+语法表示在 DokuWiki 输入格式的基础上追加启用tex_math_dollars扩展。此时$mc^2$不再被当作普通文本,而是被识别为Math InlineMath "mc^2"——即 Pandoc 文档模型中的数学元素(行内公式)。
实现链路:math 解析器与通用数学解析库
DokuWiki 阅读器在 inline 解析器列表 中同时挂载了inlineRaw与math两个候选,其中math的定义非常简短:
-- see https://www.dokuwiki.org/plugin:latex math :: PandocMonad m => DWParser m B.Inlines math = (B.displayMath <$> mathDisplay) <|> (B.math <$> mathInline)它把实际工作委托给了 Pandoc 通用数学解析库 src/Text/Pandoc/Parsing/Math.hs 中的mathDisplay(显示公式,对应$$...$$)与mathInline(行内公式,对应$...$)。这两个函数本身又是"扩展门控"的:只有相应扩展被启用时,对应的分隔符才会生效(Math.hs):
mathDisplay :: (HasReaderOptions st, Stream s m Char, UpdateSourcePos s Char) => ParsecT s st m Text mathDisplay = (guardEnabled Ext_tex_math_dollars >> mathDisplayWith "$$" "$$") <|> (guardEnabled Ext_tex_math_single_backslash >> mathDisplayWith "\\[" "\\]") <|> (guardEnabled Ext_tex_math_double_backslash >> mathDisplayWith "\\\\[" "\\\\]") mathInline :: (HasReaderOptions st, Stream s m Char, UpdateSourcePos s Char) => ParsecT s st m Text mathInline = (guardEnabled Ext_tex_math_dollars >> mathInlineWith "$" "$") <|> (guardEnabled Ext_tex_math_single_backslash >> mathInlineWith "\\(" "\\)") <|> (guardEnabled Ext_tex_math_double_backslash >> mathInlineWith "\\\\(" "\\\\)")可以看到,tex_math_dollars控制的是$...$(行内)与$$...$$(显示)两种美元符形式;此外还有tex_math_single_backslash(\(...\)与\[...\])和tex_math_double_backslash(\\(...\\)与\\[...\\])两种风格可选用。guardEnabled保证了扩展未启用时这些解析分支直接失败,从而回退到普通文本解析。
解析细节:mathInlineWith的边界规则
mathInlineWith(Math.hs)对$定界符施加了细致的约束,这些规则在 MANUAL.txt 的tex_math_dollars章节中有明确说明:
- 开头约束:开头的
$右侧必须紧跟非空格字符(when (op == "$") $ notFollowedBy space); - 结尾约束:结尾
$左侧不能是空格,且其后不能紧跟数字(notFollowedBy digit),这是为了防止把$20,000 and $30,000这样的价格误判为数学公式; \text{}特例:公式内的\text{...}允许包含$、\(、\)等特殊字符(inBalancedBraces处理花括号配对);- 换行与空白:行内公式中不允许出现空行分隔,空白序列后面不能紧跟
$,避免跨行误判; - 转义:如果确实需要字面
$,可用反斜杠转义,转义后的\$不会被当作数学定界符。
这些规则解释了为什么测试用例三中的处理与用例二截然不同——区别完全来自扩展是否启用,而不是内容本身。
四、用例三:默认情况下$保持为普通文本
第三个用例与第二个形成鲜明对照:
% pandoc -f dokuwiki -t native $mc^2$ ^D [ Para [ Str "$mc^2$" ] ]不带+tex_math_dollars时,同一行$mc^2$被解析为Str "$mc^2$"——一个普通的字符串节点,美元符号原样保留。这说明 DokuWiki 阅读器默认不启用tex_math_dollars,$在该格式下只是普通字符。
默认扩展从何而来:getDefaultExtensions
这一默认行为由 src/Text/Pandoc/Extensions.hs 决定:
getDefaultExtensions "dokuwiki" = extensionsFromList [Ext_smart]DokuWiki 输入格式的默认扩展仅包含Ext_smart(智能标点),数学相关的tex_math_dollars并未包含在内。作为对比,同文件稍后的getAll "dokuwiki"(Extensions.hs)列出了该格式"可能支持"的全部扩展(含Ext_tex_math_dollars、Ext_raw_html、Ext_smart),但getAll只是能力集合,实际生效与否取决于默认集合与用户显式开关。因此:
-f dokuwiki:$mc^2$→ 普通文本;-f dokuwiki+tex_math_dollars:$mc^2$→Math InlineMath "mc^2";- 若想彻底关闭某个默认开启的扩展,可用
-f dokuwiki-ext_name的-语法。
这样设计的好处是向后兼容:早期 DokuWiki 文档中大量存在被$包裹的普通货币金额或符号串,贸然默认开启数学解析会破坏这类文档的语义;而需要数学能力时,显式启用扩展即可获得与 Markdown 等格式一致的体验。
五、原始内容(Raw)与数学(Math)两条路径的取舍
综合三个用例,DokuWiki 阅读器提供了两条互不冲突的 LaTeX 处理路径,可以总结如下:
| 输入语法 | 是否需扩展 | 解析结果 | 适用场景 |
|---|---|---|---|
<latex>...</latex>(小写) | 否 | RawInline (Format "latex") | 行内 TeX,原样透传 |
<LATEX>...</LATEX>(大写) | 否 | RawBlock (Format "latex") | 块级 TeX,原样透传 |
$...$ | 是(tex_math_dollars) | Math InlineMath | 行内公式,语义化 |
$$...$$ | 是(tex_math_dollars) | Math DisplayMath | 显示公式,语义化 |
两者的核心差异在于语义层次:
- Raw 元素是 Pandoc 文档模型中的"旁路"节点,内容不参与语义分析,仅在写回支持该格式(如 LaTeX)的输出时被原样输出,在 HTML、Markdown 等目标中默认会被丢弃(除非启用
raw_attribute等机制)。它适合"我只要原样搬运 TeX 代码"的场景。 - Math 元素是 Pandoc 一等公民,具有明确的行内/显示语义,可以被各格式 writer 按自己的数学渲染方案处理——例如 DokuWiki 输出时渲染为
<math>标签(见 MANUAL.txt),LaTeX 输出时渲染为\(...\)/\[...\],Markdown 输出时还原为$...$/$$...$$。
从解析优先级看,inline 解析器列表 中inlineRaw排在math之前,且均使用try进行回溯,因此<latex>前缀会优先被原始内容分支捕获,不会被误判为数学公式;$...$则完全走math分支。两条路径互不干扰,文档作者可以按需混用:老插件语法(<latex>)继续用 Raw 语义,新内容则推荐用$...$+ 扩展获得完整语义化处理。
六、测试的价值与验证方式
test/command/8178.md 这类命令测试是 Pandoc 防止回归的"活文档":它将一次特性变更(#8178)拆解为三个可重复验证的行为断言,任何后续代码改动若破坏了上述任一行为,测试套件都会立即报错。测试中-t native的输出格式让断言直指 Pandoc 内部文档模型(RawInline/Math/Str),从而不依赖具体输出格式的渲染细节。
如需本地验证,可以在项目根目录通过测试套件运行这些命令测试(测试驱动逻辑见 test/Tests/Command.hs,入口为 test/test-pandoc.hs);也可以直接使用构建出的pandoc可执行文件手动复现文中三条命令,观察 native 输出与测试断言是否一致。DokuWiki 阅读器单元测试位于 test/Tests/Readers/DokuWiki.hs,其中对**bold**、//italic//、<nowiki>等基础语法也有一一对应的断言,可作为理解该阅读器整体能力的补充材料。
相关资源
- 回归测试用例:test/command/8178.md
- DokuWiki 阅读器实现:src/Text/Pandoc/Readers/DokuWiki.hs
- 通用数学解析库:src/Text/Pandoc/Parsing/Math.hs
- 扩展默认值与能力集:src/Text/Pandoc/Extensions.hs
- 扩展官方说明(含
tex_math_dollars规则):MANUAL.txt - 变更记录:changelog.md
- 命令测试框架:test/Tests/Command.hs
- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
相关推荐
Pandoc 原始 LaTeX 解析与宏展开机制详解——从回归测试 test/command/7434.md 看 raw LaTeX 处理原理
Pandoc 原始 LaTeX 解析与宏展开机制详解——从回归测试 test/command/7434.md 看 raw LaTeX 处理原理 本文以仓库中的回
文档开发工具CLIPandoc latex_macros 扩展实战:LaTeX 宏定义的解析与展开机制详解
Pandoc latex_macros 扩展实战:LaTeX 宏定义的解析与展开机制详解 本文以 pandoc 仓库中的黄金测试 test/command/58
文档开发工具CLIParceler 开源项目教程:Android Parcelable 序列化的终极解决方案
Parceler 开源项目教程:Android Parcelable 序列化的终极解决方案 引言:Android 开发者的序列化痛点 你是否还在为 Androi
文档开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考