Pandoc Texinfo 输出中的链接文本转义:从命令测试 11758 看 `@uref` 与 `@comma{}` 的底层实现
2026/9/19 12:26:26 网站建设 项目流程

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'),不一致即判定测试失败。

该用例验证了两个事实:

  1. 句点无需特殊处理:链接文本a.b原样进入@uref的第二个参数,输出为@uref{url,a.b}
  2. 逗号必须转义:链接文本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 a
  • NormalContext:普通正文文本;
  • NodeContext:用于生成 Texinfo 节点(@node)名称,节点名对.,:(),等字符有限制;
  • ArgumentContext:用于生成 Texinfo 命令的参数(如@uref{url,文本}中逗号之后的显示文本)。

withContext通过修改写入器状态stContext并在执行完毕后恢复旧值,保证不同上下文下的转义规则互不串扰。链接文本正是通过withContext ArgumentContext $ inlineListToTexinfo txt渲染的(见inlineToTexinfoLink分支),因此链接中的逗号会命中 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 指令:

  1. 内部链接(#ident)→@ref:目标是文档内部的节标识符。写入器会先在stHeadings映射中查找该标识符对应的节点名(节点名由addNodeText在标题处理阶段生成并登记),找到则直接引用节点名,未找到则对原始目标做去除非法字符处理;显示文本通过ArgumentContext渲染,若与节点名相同则省略第二参数。
  2. 自动链接(autolink)→@url:当链接文本恰为单个Str且其escapeURI结果与目标地址完全一致时,输出不带显示文本的@url{...}(例如test/writer.texinfo中的@url{http://example.com/})。
  3. 普通链接 →@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完全一致。
  • 普通段落与行内文本StrCode等内容经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 变量章节(versionfilename等)做进一步定制。

【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc

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

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

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

立即咨询