- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
导读
本篇技术指南围绕 Pandoc 官方命令测试用例 test/command/6026.md 展开,系统讲解 Markdown 读者(reader)中花括号包裹的引用键语法(curly-brace citation key syntax,即@{...}形式)。你不仅会掌握该语法在-t native与-t markdown两种输出下的精确行为,还会从 Text.Pandoc.Parsing.Citations 的citeKey实现和 Markdown 读者的引用解析器 两个层面理解它为何存在、如何解析,以及如何利用它承载 URL 等含特殊字符的引用键。
测试用例背景:什么是命令测试(command test)
在深入研究语法之前,先明确 test/command/6026.md 在整个测试体系中的角色。Pandoc 采用"命令测试"(command test)机制来验证命令行行为,其定义位于 test/Tests/Command.hs:
- 测试文件是以
.md结尾的 Markdown 文件,存放于 test/command 目录; - 文件中的每个代码块(code block)即一个独立测试用例;
- 代码块第一行以
%开头,后面是要执行的 shell 命令; - 接下来的若干行是作为 stdin 传给该命令的输入;
- 输入以单独一行
^D结束; ^D之后的内容是期望的 stdout 输出;若期望 stderr 输出,则每行需以2>前缀标记;- 若期望非零退出码,最后一行应为
=>后接退出码。
测试运行时,Tests.Command 的测试树 会遍历command目录下所有.md文件,extractCommandTest读取文件并用 Pandoc 解析出其中的代码块,随后逐块执行命令并与期望输出比对(执行与比对逻辑)。6026 号测试正是针对"花括号引用键语法"的回归测试(regression test),由 changelog.md 可以确认该语法是在对应版本引入的:Implement curly-brace syntax for Markdown citation keys (#6026)——测试文件编号 6026 正是 GitHub issue 编号,这也是 Pandoc 测试文件命名的惯例。
花括号引用键语法是什么
Pandoc 的 Markdown 引用语法以@开头,紧跟引用键(citation key)。标准引用键有严格约束,而花括号语法@{...}提供了一种把任意字符串作为引用键的方式。从 MANUAL.txt 的权威说明可知:
- 引用键必须以字母、数字或
_开头; - 只能包含字母数字和单个内部标点字符(
:.#$%&-+?<>~/); - 若不满足上述约束,就必须用花括号包裹,且花括号本身不属于键的一部分;
- 例如
@Foo_bar.baz.的键是Foo_bar.baz(末尾句点不是内部标点,故不纳入键);而@{Foo_bar.baz.}的键则是完整的Foo_bar.baz.; @Foo_bar--baz的键只有Foo_bar,因为重复的内部标点会终止键;- 当你使用 URL 作为引用键时,花括号语法是推荐做法:
[@{https://example.com/bib?name=foobar&date=2000}, p. 33]。
这正是 6026 测试的用意:用@{https://openreview.net/forum?id=HkwoSDPgg}来验证以完整 URL 作为引用键的可行性。
逐行拆解测试用例
第一个用例:-t native输出
% pandoc -t native @{https://openreview.net/forum?id=HkwoSDPgg} @https://openreview.net/forum?id=HkwoSDPgg ^D输入包含两段:第一段使用花括号语法,第二段直接裸写 URL。期望输出如下两个Para块:
- 花括号形式解析为一个
Cite,其中包含citationId = "https://openreview.net/forum?id=HkwoSDPgg"(完整 URL),citationMode = AuthorInText,说明@{...}被视为文内引用(author-in-text),同时保留了原文@https://openreview.net/forum?id=HkwoSDPgg作为引用的文字内容。 - 裸 URL 形式则被解析为键
https://openreview.net/forum?id——注意?与=的截断——剩下的=HkwoSDPgg变成了普通文本Str。
这一对比清晰地表明:裸 URL 中?不是合法键字符(不在内部标点集合内),因此解析器在?处截断键;而花括号形式能完整保留整个 URL 作为键。
第二个用例:-t markdown输出
% pandoc -t markdown @{https://openreview.net/forum?id=HkwoSDPgg} @https://openreview.net/forum?id=HkwoSDPgg ^D @{https://openreview.net/forum?id=HkwoSDPgg} @https://openreview.net/forum?id=HkwoSDPgg第二用例验证往返一致性(round-trip):Markdown 读者解析后再由 Markdown 写出器(writer)输出,输入与输出完全一致——说明写出器能正确地把含特殊字符的引用键重新用花括号包裹输出。这保证了"解析→写出"不会破坏引用键的完整性。
源码级解析原理
citeKey:花括号键的入口
两种形式的键解析都汇聚在Text.Pandoc.Parsing.Citations模块的citeKey函数:
citeKey :: ... => Bool -> ParsecT s st m (Bool, Text) citeKey allowBraced = try $ do guard =<< notAfterString suppress_author <- option False (True <$ char '-') char '@' key <- simpleCiteIdentifier <|> if allowBraced then charsInBalanced '{' '}' (T.singleton <$> (satisfy (not . isSpace))) else mzero return (suppress_author, key)关键点:
- 布尔参数
allowBraced控制是否启用@{...}扩展语法。在 Markdown 读者的textualCite与citation调用处 均传入True,因此 Markdown 读者支持花括号形式; charsInBalanced '{' '}'会匹配花括号内任意非空白字符(satisfy (not . isSpace)),支持嵌套花括号的平衡匹配,这也是为什么 changelog 示例中@{foo_bar{x}'}能解析出键foo_bar{x};- 花括号内的空白会被排除,因此 URL 中的
?、=、&等字符都能被完整保留; - 同时返回的布尔值表示
-前缀(@-key表示 SuppressAuthor,即压制作者)。
与之相对,simpleCiteIdentifier实现标准键的约束:首字符必须是字母数字或_(或*,用于@*通配 nocite),后续字符仅允许字母数字、_以及单个内部标点:.#$%&-+?<>~/,重复的内部标点会终止键。这解释了裸 URL 为何在?处截断。
Markdown 读者中的引用解析流程
cite是 Markdown 读者中引用解析的顶层入口,受Ext_citations扩展开关控制(guardEnabled Ext_citations),并且只有在非脚注上下文(stateInNote)才递增stateNoteNumber(该值仅用于citationNoteNum赋值,不影响非脚注样式):
textualCite处理@key文内引用:通过citeKey True取得键;若后续跟[...]括号内容,则与normalCite组合;最终构建Citation记录时,citationMode依据suppressAuthor取SuppressAuthor或AuthorInText,citationId即花括号解析出的完整键。citation处理方括号列表中的单条引用(如[see @doe99, pp. 33]),同样通过citeKey True获取键,并支持-前缀压制作者(SuppressAuthor),否则为NormalCitation。
这两条路径产出的Citation记录都包含citationHash = 0与递增的citationNoteNum,与 6026 测试的 native 输出完全吻合。
为什么需要花括号语法
从 6026 测试的输出差异可以看到问题的本质:裸引用键语法与 URL 字符集冲突。URL 包含?、=、&、%等字符,其中?和=不是simpleCiteIdentifier允许的内部标点,会导致键被截断甚至产生歧义。而引用数据库(如 BibTeX、CSL JSON)允许任意字符串作为键,尤其许多工作流直接用 DOI 或 URL 作为键。
花括号语法在保持原有@语法习惯的同时,做到了两件事:
- 允许键包含任意特殊字符:
?、=、&、.、{}等均可安全地出现在键中; - 消除键与紧随文本的歧义:如 changelog 所注,
@{foo}A能将foo与紧随的A明确分离,因为@fooA会被整体当作键的一部分;同样地,@{key}.中的末尾句点不再被当作键的结束标志而被吞掉,而是明确保留在键内。
这两点正是 changelog.md 中对该特性的官方描述:提供一种使用包含特殊字符(标准引用键语法无法使用)的引用键的途径,同时支持将引用键与紧跟其后的文本分隔开。
完整语法示例与实战建议
结合 MANUAL.txt 的规范、changelog 的示例与 6026 测试的验证,汇总可复制、可运行的用法:
| 输入 | 解析出的引用键 | 说明 |
|---|---|---|
@doe99 | doe99 | 标准键,最常用 |
@Foo_bar.baz. | Foo_bar.baz | 末尾句点被排除(非内部标点) |
@{Foo_bar.baz.} | Foo_bar.baz. | 花括号保留末尾句点 |
@Foo_bar--baz | Foo_bar | 重复内部标点终止键 |
@{foo_bar{x}'} | foo_bar{x} | 键可含嵌套花括号与引号(changelog 示例) |
@{foo}A | foo | 键与紧随文本明确分离 |
@{https://example.com/bib?name=foobar&date=2000} | 完整 URL | 官方推荐 URL 作键的写法 |
@{https://openreview.net/forum?id=HkwoSDPgg} | 完整 URL | 6026 测试验证 |
实战建议:
- 以 URL/DOI 为引用键时务必使用花括号,这是 MANUAL.txt 的明确推荐,也是 6026 测试的核心场景;
- 花括号内不能包含空白字符(解析器使用
satisfy (not . isSpace)),因此键内部不要留空格; - 花括号支持嵌套与平衡匹配,但请保持键可读性,避免过度复杂;
- 文内引用
@{key}会得到AuthorInText模式,方括号列表中的@{key}为NormalCitation模式,压制作者时使用@-{key}; - 在 MANUAL.txt 中描述的引用键解析规则适用于所有支持
citations扩展的 Markdown 方言。
如何验证与运行该测试
如果你想在本地验证 6026 测试的行为,可以直接运行两条命令:
# 方式一:按测试原样运行(stdin 输入,^D 结束) pandoc -t native # 输入: # @{https://openreview.net/forum?id=HkwoSDPgg} # # @https://openreview.net/forum?id=HkwoSDPgg # ^D # 方式二:单行验证花括号键解析 printf '@{https://openreview.net/forum?id=HkwoSDPgg}\n' | pandoc -t native # 方式三:验证 markdown 写出器的往返一致性 printf '@{https://openreview.net/forum?id=HkwoSDPgg}\n' | pandoc -t markdown若在源码构建环境中运行,也可通过测试框架执行全部命令测试(test/Tests/Command.hs会自动收集 test/command 目录下的用例),6026 用例即作为其中一个回归项被验证。
结语
test/command/6026.md 虽只是一个十余行的命令测试文件,却精准刻画了 Pandoc 花括号引用键语法的完整行为边界:-t native用例证明了含 URL 特殊字符的键能被完整解析并保持AuthorInText模式,-t markdown用例证明了写出的往返一致性。结合 citeKey 的平衡括号匹配实现、Markdown 读者的引用解析器 的调用链,以及 MANUAL.txt 的官方语法规范,你现在已具备在 Markdown 文档中安全使用 URL/DOI 等特殊字符引用键的完整知识与验证手段。
- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
相关推荐
Pandoc DokuWiki 读取器中的花括号(`{`/`{{`)转义处理:命令测试 5416 源码级解析
Pandoc DokuWiki 读取器中的花括号( { / {{ )转义处理:命令测试 5416 源码级解析 导读 DokuWiki 使用 {{...}} 双花
文档开发工具CLIPandoc 定义列表(Definition Lists)语法详解:从命令行测试用例到 Markdown 解析器实现
Pandoc 定义列表(Definition Lists)语法详解:从命令行测试用例到 Markdown 解析器实现 导读 test/command/10889
文档开发工具CLIPandoc Org 读取器 `+INCLUDE` 与 `:lines` 行号过滤深度解析:从命令测试 6466 到源码实现
Pandoc Org 读取器 +INCLUDE 与 :lines 行号过滤深度解析:从命令测试 6466 到源码实现 本篇技术指南以 pandoc 仓库中的命令
文档开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考