pandoc 中 RST 解释文本角色(Interpreted Text Roles)的往返转换原理与实战
2026/9/19 21:53:36 网站建设 项目流程
  • 文档
  • 开发工具
  • CLI

【免费下载链接】pandoc

Universal markup converter

项目地址:https://gitcode.com/gh_mirrors/pa/pandoc
点击查看免费下载

本文以 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 读取器中负责解释文本角色的核心函数是interpretedRolerenderRole(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

roleBeforeroleAfter(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)对Codeinterpreted-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 asSpanwithroleattributes (#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:字段:若基础角色是codelanguage字段会作为语言类别并入(对应code高亮扩展,src/Text/Pandoc/Readers/RST.hs);
  • :raw::format::当父角色为raw时,以字段中的format为准决定 RawInline 的格式。

因此,文档作者既可以用.. role::定义语义化角色获得标准输出,也可以依赖未定义角色的兜底行为让角色信息无损地进入 AST。

实战应用:在文档流水线中保留与操纵角色

掌握了上述 AST 约定,就可以在实际工作流中利用它:

  1. 观测角色语义:将 RST 文档转为 native 格式即可查看每个角色对应的 AST 形态。例如:

    pandoc -f rst -t native input.rst

    未定义角色会显示为Code+interpreted-text类 +role键值对,与测试 3407 的期望输出完全一致。

  2. 跨格式保真:RST 中的未定义角色先进入 AST(Code/Span),再写回 RST 时仍还原为:role:\...`` 语法,这正是 3407 验证的无损往返能力。需要提醒的是,转换为其他格式时这类角色并不会自动获得特殊样式,因为其语义仅存在于 RST 层。

  3. 用 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 native

test/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

项目地址:https://gitcode.com/gh_mirrors/pa/pandoc
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询