- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
本文以 pandoc 仓库中的命令测试用例 test/command/3407.md 为核心,深入讲解 RST(reStructuredText)解释文本角色(interpreted text role)在 pandoc 中是如何被解析、如何在 AST 中表示、以及如何被写回的完整闭环。读完本文,你将理解:role:\text`` 这种语法的底层实现机制,掌握用 native 格式观测 AST、借助 Lua 过滤器操纵角色属性,以及自行运行与扩展该回归测试的方法。
测试用例 3407:一次角色语义的往返验证
test/command/3407.md是 pandoc 的 golden 命令行测试(位于test/command/目录,由 test/Tests/Command.hs 驱动),它用两个方向相反的转换验证了 RST 未知解释文本角色的无损往返:
第一个方向是native → RST,把 AST 中带interpreted-text类与role属性的Code元素输出为 RST 角色语法:
% pandoc -f native -t rst [Para [Code ("",["interpreted-text"],[("role","foo")]) "text"]] ^D :foo:`text`第二个方向是RST → native,把 RST 角色语法重新解析回同样的 AST:
% pandoc -f rst -t native :foo:`text` ^D [ Para [ Code ( "" , [ "interpreted-text" ] , [ ( "role" , "foo" ) ] ) "text" ] ]测试用例的格式约定是:%开头为命令行,随后是标准输入,^D表示输入结束,最后是期望输出。这两个用例合起来证明了一个关键结论:未注册的 RST 角色:foo:在 pandoc 中会被保留为Code内联元素,类名为interpreted-text、键值对属性为("role","foo"),并且这个表示在两次转换之间完全一致,从而可以在文档处理流水线中安全地保留角色语义。
RST 解释文本角色是什么
在 reStructuredText 中,解释文本(interpreted text)是指用单个反引号括起来的文本,可以显式或隐式地绑定一个“角色”(role)。角色决定了这段文本的语义或渲染方式。标准形式是角色名出现在反引号内容之前或之后:
:foo:`text` ; 角色在前(显式) `text`:foo: ; 角色在后(显式) `text` ; 无角色标记(使用 default-role)pandoc 的 RST 读取器从源码结构看(src/Text/Pandoc/Readers/RST.hs)同时支持角色前置与后置两种写法,并支持通过.. default-role::指令设置默认角色(对应 src/Text/Pandoc/Readers/RST.hs 中的stateRstDefaultRole状态)。
角色名本身有严格的语法约束:在 src/Text/Pandoc/Readers/RST.hs 中,roleName定义为“由字母数字以及彼此不连续的内部连字符、下划线、句点、冒号和加号组成的单词”,这也是 docutils 规范所允许的角色名形式。
Reader 端:角色如何被解析进 AST
分派逻辑 renderRole
RST 读取器中负责解释文本角色的核心函数是interpretedRole与renderRole(src/Text/Pandoc/Readers/RST.hs):
interpretedRole = try $ do (role, contents) <- roleBefore <|> roleAfter renderRole contents Nothing role nullAttr解析得到角色名与内容后,renderRole会按角色名进行分派。内置角色映射为对应的 Pandoc 内联元素:
| RST 角色 | 生成的 Pandoc 内联元素 |
|---|---|
:sup:/:superscript: | Superscript |
:sub:/:subscript: | Subscript |
:mark: | Span ("",["mark"],[]) |
:emphasis: | Emph |
:strong: | Strong |
:rfc-reference:/:RFC: | 指向 faqs.org 的Link |
:pep-reference:/:PEP: | 指向 python.org 的Link(编号补零到 4 位) |
:literal:/:code: | Code(携带属性) |
:math: | Math |
:title-reference:/:title:/:t: | Span ("",["title-ref"],[]) |
:span: | Span(携带属性) |
:raw: | RawInline |
:cite...:前缀 | 解析为Cite引文(支持:t、:ct、:year、:yearpar等模式) |
未定义角色则落到renderRole的兜底分支(src/Text/Pandoc/Readers/RST.hs):
Nothing -> -- undefined role return $ B.codeWith ("",["interpreted-text"],[("role",role)]) contents也就是说,任何未注册的角色都会被编码为Code,其属性三元组为(标识符, ["interpreted-text"], [("role", 角色名)])——这正是测试 3407 中 native 输出所展示的形态。这样设计的好处是:未知角色不会在解析阶段被丢弃,语义信息完整保留在 AST 中,后续既可以原样写回 RST,也可以被过滤器识别和处理。
词法细节:roleBefore / roleAfter / unmarkedInterpretedText
roleBefore与roleAfter(src/Text/Pandoc/Readers/RST.hs)分别处理:role:\text`与 ``text:role: `` 两种形式;后者在没有角色标记时回落到当前default-role。内容解析由unmarkedInterpretedText`(src/Text/Pandoc/Readers/RST.hs)完成,它允许:
- 内容中不含未转义的反引号与换行;
- 使用 ``` 反斜杠转义特殊字符;
- 单个换行(非空行分隔)可出现在内容中;
- 反引号后紧跟字母数字时(形如
`a`b)不会被误判为角色标记。
另外,源码注释(src/Text/Pandoc/Readers/RST.hs)明确提示:这里并未精确实现 docutils 官方的“内联标记识别规则”(inline markup recognition rules)中的复杂边界条件,但对绝大多数实际场景足够用;同时存在两个已知 TODO:addNewRole会静默丢弃:class:之外的类别信息,且允许直接使用:raw:角色(docutils 中该角色只能被继承使用)。
Writer 端:AST 如何写回 RST 角色
RST 写入器(src/Text/Pandoc/Writers/RST.hs)对Code的interpreted-text形态做了专门匹配(src/Text/Pandoc/Writers/RST.hs):
inlineToRST (Code (_,["interpreted-text"],[("role",role)]) str) = return $ ":" <> literal role <> ":`" <> literal str <> "`"这正是测试 3407 第一段所验证的行为:只要Code元素带有interpreted-text类与role键值对,输出就是:role:\内容`。类似的,Span若带有role键值对也会被写回为角色形式([src/Text/Pandoc/Writers/RST.hs](https://link.gitcode.com/i/980e40ca3f3c795bb79a79706f70f4ca#L770-L775)),并专门处理了("",["mark"],[])的 Span 输出为:mark:`...``(src/Text/Pandoc/Writers/RST.hs)。
普通的Code(没有interpreted-text类)则按常规 RST 代码语法输出:内容不含反引号时用双反引号code,含反引号时改用:literal:角色(因为:literal:支持反斜杠转义,见 src/Text/Pandoc/Writers/RST.hs 及注释引用的 #3496、#3974 两个 issue)。
这一能力在 changelog.md 中有明确记载:
RST writer: support unknown interpreted text roles by parsing them as
Spanwithroleattributes (#3407). This way they can be manipulated in the AST.
即:unknown interpreted text roles 支持(#3407)让未定义角色以带role属性的形式进入 AST,从而可被过滤器操纵——测试 3407 正是这一功能点的回归保障。
扩展机制:.. role::指令与自定义角色
除了兜底保留,pandoc 还支持通过 RST 指令正式注册自定义角色。读取器中的addNewRole(src/Text/Pandoc/Readers/RST.hs)处理.. role::指令(分派点见 src/Text/Pandoc/Readers/RST.hs),其要点包括:
- 角色继承:新角色可以指定父角色(如
.. role:: foo(code)),getBaseRole会沿继承链一直回溯到内置基础角色,从而复用父角色的渲染逻辑; :class:字段:未显式给出时,默认类别取角色名本身(见 src/Text/Pandoc/Readers/RST.hs 的注释);:language:字段:若基础角色是code,language字段会作为语言类别并入(对应code高亮扩展,src/Text/Pandoc/Readers/RST.hs);:raw:与:format::当父角色为raw时,以字段中的format为准决定 RawInline 的格式。
因此,文档作者既可以用.. role::定义语义化角色获得标准输出,也可以依赖未定义角色的兜底行为让角色信息无损地进入 AST。
实战应用:在文档流水线中保留与操纵角色
掌握了上述 AST 约定,就可以在实际工作流中利用它:
观测角色语义:将 RST 文档转为 native 格式即可查看每个角色对应的 AST 形态。例如:
pandoc -f rst -t native input.rst未定义角色会显示为
Code+interpreted-text类 +role键值对,与测试 3407 的期望输出完全一致。跨格式保真:RST 中的未定义角色先进入 AST(
Code/Span),再写回 RST 时仍还原为:role:\...`` 语法,这正是 3407 验证的无损往返能力。需要提醒的是,转换为其他格式时这类角色并不会自动获得特殊样式,因为其语义仅存在于 RST 层。用 Lua 过滤器定制渲染:由于角色信息落在 AST 的属性里(src/Text/Pandoc/Readers/RST.hs 生成的
Code带("role",role)),可以编写 Lua 过滤器匹配interpreted-text类与role属性,把特定角色转成自定义 HTML、LaTeX 或其他输出。例如将:foo:角色渲染为带 class 的<span>,即可在不改动源码的情况下扩展 RST 的角色表现力——这也是 changelog 中所说“manipulated in the AST”的典型用途。pandoc 的 Lua 过滤机制可参考 pandoc-lua-engine/src/Text 与官方文档 doc/lua-filters.md。
如何验证与扩展该测试
本用例可以直接运行验证。在项目根目录执行命令测试套件:
cabal test pandoc --test-options='-t command'或单独运行命令测试(具体入口见 test/Tests/Command.hs 与 test/test-pandoc.hs)。若需手工核对,也可直接执行用例中的两条命令并比对输出:
printf '[Para [Code ("",["interpreted-text"],[("role","foo")]) "text"]]\n' \ | pandoc -f native -t rst printf ':foo:`text`\n' | pandoc -f rst -t nativetest/command/目录下的每个*.md文件都是一个独立的 golden 用例,格式与 3407 相同;新增用例只需按%命令 + 输入 +^D+ 期望输出的格式添加文件即可被测试框架自动拾取。
小结
从测试用例 test/command/3407.md 出发,可以完整还原 pandoc 处理 RST 解释文本角色的全链路:读取器用roleBefore/roleAfter解析角色语法,内置角色经renderRole分派为对应的内联元素,未定义角色则保留为Code(类interpreted-text、属性role);写入器检测到该形态后还原:role:\...`语法,实现无损往返。配合.. role::` 指令与 Lua 过滤器,这套机制既能承载 docutils 风格的角色体系,又为下游工具链保留了充分的扩展空间。
- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
相关推荐
Pandoc RST 读取器中的解释文本角色(Interpreted Text Roles)边界行为解析:基于 test/command/4811.md 的回归测试深入解读
Pandoc RST 读取器中的解释文本角色(Interpreted Text Roles)边界行为解析:基于 test/command/4811.md 的回归
文档开发工具CLIPandoc 代码块行号:RST 与 Org 之间 `number-lines` / `-n` / `+n` 的往返转换实战
Pandoc 代码块行号:RST 与 Org 之间 number lines / n / +n 的往返转换实战 导读 本文以 test/command/5178
文档开发工具CLIPandoc 中 `<mark>` 高亮标记的 AST 表示与 HTML/原生格式往返转换实战
Pandoc 中 <mark 高亮标记的 AST 表示与 HTML/原生格式往返转换实战 导读 <mark 是 HTML5 中用于标记"与当前上下文相关的突出显
文档开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考