Pandoc Texinfo 输出中的链接文本转义:从命令测试 11758 看@uref与@comma{}的底层实现
【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc
导读
本文围绕 Pandoc 仓库中的命令测试 test/command/11758.md 展开,剖析 Markdown 文档转换为 Texinfo(-t texinfo)时,链接文本中的特殊字符(重点是英文句点.与逗号,)是如何被安全转义的。读完本文,你将理解 Pandoc Texinfo 写入器(src/Text/Pandoc/Writers/Texinfo.hs)中三种转义上下文(Normal / Node / Argument)的设计差异、@uref/@url/@ref三种链接形态的判定逻辑,以及如何通过命令测试框架验证转换结果。
一、测试用例速览:一行命令,两个关键断言
test/command/11758.md全文是一个标准 Pandoc 命令测试块:
% pandoc -t texinfo a.b a,b ^D @node Top @top Top @uref{url,a.b} @uref{url,a@comma{}b}这个测试块遵循 test/Tests/Command.hs 中定义的命令测试格式:
- 首行以
%开头,后面是要执行的命令(这里为pandoc -t texinfo); %之后、^D之前的内容作为标准输入(stdin)传给该命令,即两段 Markdown 链接;^D之后的内容是期望在 stdout 上得到的输出;- 测试运行器(
execTest)会以test-pandoc --emulate方式实际执行命令,并将实际输出与期望输出做逐行对比(compareValues'),不一致即判定测试失败。
该用例验证了两个事实:
- 句点无需特殊处理:链接文本
a.b原样进入@uref的第二个参数,输出为@uref{url,a.b}; - 逗号必须转义:链接文本
a,b中的逗号被替换为@comma{},输出为@uref{url,a@comma{}b}。
两者的差异并非偶然,而是由 Texinfo 语法本身决定的:@uref{url,displayed-text}以逗号分隔命令参数,如果链接文本中直接出现裸逗号,会让 Texinfo 解析器把文本误判为第三个参数,产生语法错误。因此写入器必须在“命令参数”这一上下文中将逗号转义为@comma{}。
二、转义的三个上下文:Normal / Node / Argument
在 src/Text/Pandoc/Writers/Texinfo.hs 中定义了写入器的上下文状态:
data Context = NormalContext | NodeContext | ArgumentContext deriving (Eq, Show) withContext :: PandocMonad m => Context -> TI m a -> TI m aNormalContext:普通正文文本;NodeContext:用于生成 Texinfo 节点(@node)名称,节点名对.,:(),等字符有限制;ArgumentContext:用于生成 Texinfo 命令的参数(如@uref{url,文本}中逗号之后的显示文本)。
withContext通过修改写入器状态stContext并在执行完毕后恢复旧值,保证不同上下文下的转义规则互不串扰。链接文本正是通过withContext ArgumentContext $ inlineListToTexinfo txt渲染的(见inlineToTexinfo的Link分支),因此链接中的逗号会命中 Argument 上下文规则。
三、stringToTexinfo:字符级转义的核心实现
所有文本最终都会经过 stringToTexinfo 做逐字符转义,其完整规则如下表:
| 字符 | 转义结果 | 生效上下文 |
|---|---|---|
{ | @{ | 全部 |
} | @} | 全部 |
@ | @@ | 全部 |
| U+00A0(不间断空格) | @ | 全部 |
| U+2014(em dash) | --- | 全部 |
| U+2013(en dash) | -- | 全部 |
| U+2026(省略号) | @dots{} | 全部 |
| U+2019(右单引号) | ' | 全部 |
, | 删除 | NodeContext |
, | @comma{} | ArgumentContext |
:.() | 删除 | NodeContext |
| 其他字符 | 原样保留 | 全部 |
对应源码中的关键分支:
escChar ',' | context == NodeContext = "" escChar ',' | context == ArgumentContext = "@comma{}" escChar ':' | context == NodeContext = "" escChar '.' | context == NodeContext = "" escChar '(' | context == NodeContext = "" escChar ')' | context == NodeContext = "" escChar c = T.singleton c由此可以清晰解释 11758 测试的两个输出:
a.b:句点.不在 ArgumentContext 的转义名单中,所以a.b原样输出为@uref{url,a.b};a,b:逗号,在 ArgumentContext 中被替换为@comma{},所以输出为@uref{url,a@comma{}b}。
注意逗号在 NodeContext 中反而会被删除(不是转义),这是因为 Texinfo 节点名不允许包含逗号(参见disallowedInNode:['.',':',',','(',')']),节点名中若含这些字符会导致@node指令无法解析。同一个字符在不同上下文中有着完全不同的命运,这正是该写入器设计最精妙之处。
四、Link的完整渲染逻辑:@ref/@url/@uref
转义只是Link渲染的一环。inlineToTexinfo 的 Link 分支 根据目标地址形态选择三种 Texinfo 指令:
- 内部链接(
#ident)→@ref:目标是文档内部的节标识符。写入器会先在stHeadings映射中查找该标识符对应的节点名(节点名由addNodeText在标题处理阶段生成并登记),找到则直接引用节点名,未找到则对原始目标做去除非法字符处理;显示文本通过ArgumentContext渲染,若与节点名相同则省略第二参数。 - 自动链接(autolink)→
@url:当链接文本恰为单个Str且其escapeURI结果与目标地址完全一致时,输出不带显示文本的@url{...}(例如test/writer.texinfo中的@url{http://example.com/})。 - 普通链接 →
@uref{url,文本}:其余情况统一走@uref,目标地址先经stringToTexinfo转义(所以mailto:nobody@nowhere.net会输出为mailto:nobody@@nowhere.net),显示文本在 ArgumentContext 下渲染。11758 测试覆盖的正是这一最常见分支。
在仓库的 texinfo 写入器综合测试 test/writer.texinfo 中,可以找到大量@uref形态的佐证,例如:
Just a @uref{/url/,URL}. @uref{mailto:nobody@@nowhere.net,Email link} @uref{,Empty}.其中@uref{,Empty}.表示空地址、空显示文本的边界情况也能被正常生成。
五、转义规则的完整配套:标题节点与段落文本
转义机制不仅作用于链接,还贯穿整个写入流程:
- 标题节点生成:
addNodeText(Texinfo.hs#L87-L101)在 NodeContext 下渲染 1~4 级标题文本作为@node名,重复的节点名会被自动追加序号(如name 2),避免 Texinfo 节点重名冲突;5 级及以上标题(Header 5+)则降级为普通段落输出。 - 菜单条目:
makeMenuLine(Texinfo.hs#L417-L426)同样以 NodeContext 渲染菜单项文本,保证@menu中引用的节点名与实际@node完全一致。 - 普通段落与行内文本:
Str、Code等内容经stringToTexinfo处理(Code额外套上@code{...},带variable类的代码则输出@code{@var{...}})。 - 脚注与引用:
Note输出为@footnote{...},内部同样经过完整转义流程。
六、如何运行与验证
在 Pandoc 开发环境中,命令测试由测试入口 test/test-pandoc.hs 驱动,测试套件通过 test/Tests/Command.hs 扫描test/command/目录下所有.md文件并逐个执行其中的命令块。本地验证方式:
cabal test pandoc-tests或单独跑 texinfo 相关用例:
cabal test pandoc-tests --test-options='--pattern Command:11758'也可以不依赖测试框架,直接用构建出的 pandoc 复现该测试:
printf 'a.b\n\na,b\n' | pandoc -t texinfo预期输出与test/command/11758.md中^D之后的内容一致:
@node Top @top Top @uref{url,a.b} @uref{url,a@comma{}b}@node Top/@top Top是写入器通过wrapTop(Texinfo.hs#L83-L85)自动添加的文档外壳——Texinfo 格式要求每个文档必须有一个 Top 节点,这一行为在所有 texinfo 输出中都恒定存在,因此也出现在本测试的期望输出中。
七、小结
test/command/11758.md用最短的篇幅精确锁定了 Pandoc Texinfo 写入器的一条关键行为:链接文本(Texinfo 命令参数)中的逗号必须转义为@comma{},而句点可以原样保留。这一行为由 src/Text/Pandoc/Writers/Texinfo.hs 中基于上下文的逐字符转义函数stringToTexinfo保证,并且与节点名中逗号被直接删除的规则形成鲜明对照。理解这套上下文转义模型,也就掌握了 Pandoc 输出合法、可被makeinfo正常编译的 Texinfo 文档的核心原理。如果你需要为 texinfo 输出配置元数据,可参考 MANUAL.txt 的 Texinfo 变量章节(version、filename等)做进一步定制。
【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考