Pandoc AsciiDoc 输出中的链接宏处理:--wrap=preserve下的隐式链接与link:前缀规则
【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc
导读
本文以 pandoc 官方回归测试用例 test/command/10105.md 为切入点,深入剖析 Pandoc 在把 Markdown 链接转换为 AsciiDoc/Asciidoctor 输出时的两条核心规则:哪些 URL 可以输出为隐式(implicit)链接宏、哪些必须显式加上link:前缀,以及--wrap=preserve对链接行输出的影响。读完本文,你将理解http:/https:与ftps:等非默认协议在 AsciiDoc 输出中的差异,掌握判断link:前缀是否需要的底层逻辑,并能结合源码在 src/Text/Pandoc/Writers/AsciiDoc.hs 中追溯这条规则的完整实现。
一、用例本体:一条命令,两组输出
回归测试 test/command/10105.md 全文如下:
% pandoc -t asciidoc --wrap=preserve [link](https://example.com) link ^D https://example.com[link] link:ftps://example.com[link]这是一个典型的 pandoc 命令行测试(command test)文件,其结构为:
% pandoc -t asciidoc --wrap=preserve:要执行的命令,输出格式为asciidoc,并开启--wrap=preserve换行模式;- 随后的普通文本:标准输入(stdin)中的 Markdown 源文档,包含两个链接
[link](https://example.com)与link; ^D:结束标准输入;- 最后两行:期望的输出,即
https://example.com[link]与link:ftps://example.com[link]。
输入与输出一一对应,测试意图非常清晰:同样是绝对 URL 链接,https:协议走隐式链接宏,而ftps:协议必须显式加link:前缀。
关于测试框架:pandoc 的命令行测试通过 test/Command.hs 驱动,测试脚本与期望输出按上述约定放在同一
.md文件中。本文用例属于 test/command 目录下众多回归测试之一,专门用于锁定 AsciiDoc 写器的链接宏行为,防止未来重构时回归。
二、从 Markdown 链接到 AsciiDoc 链接宏
2.1 AsciiDoc 的两种链接写法
AsciiDoc/Asciidoctor 中,链接有两种等价写法:
- 隐式链接宏(implicit macro):
URL[link text],例如https://example.com[link]。要求 URL 本身能被 AsciiDoc 解析器识别为可接受的链接目标。 - 显式链接宏(explicit macro):
link:URL[link text],例如link:ftps://example.com[link]。link:前缀强制把目标当作链接处理,适用于相对路径、自定义协议等无法被隐式识别的目标。
pandoc 的 AsciiDoc 写器正是根据目标的协议/形态决定采用哪种写法。
2.2 写器中的判定逻辑
核心实现在 src/Text/Pandoc/Writers/AsciiDoc.hs 的inlineToAsciiDoc对Link元素的分支中,源码注释本身就给出了两条对照示例:
-- relative: link:downloads/foo.zip[download foo.zip] -- abs: http://google.cod[Google] -- or my@email.com[email john]判定是否需要在前面加link:前缀的代码为:
let needsLinkPrefix = case parseURI (T.unpack src) of Just u -> uriScheme u `notElem` ["http:","https:", "ftp:", "irc:", "mailto:"] _ -> True逻辑可以拆解为两步:
- 用
Network.URI.parseURI解析链接目标src; - 若解析成功,检查
uriScheme(即协议名)是否在豁免列表["http:", "https:", "ftp:", "irc:", "mailto:"]中:在列表中则无需前缀(输出隐式宏),不在列表中则需要link:前缀; - 若解析失败(例如相对路径
downloads/foo.zip),则一律需要link:前缀。
这正是 10105 号用例所验证的行为:https://example.com的 scheme 是https:,命中豁免列表,输出https://example.com[link];ftps://example.com的 scheme 是ftps:,不在列表中,输出link:ftps://example.com[link]。
需要特别说明:虽然ftps:是 IANA 官方注册协议(在 src/Text/Pandoc/URI.hs 的schemes集合中可见),但 pandoc 的 AsciiDoc 写器在link:前缀判定上使用了一个独立、更窄的豁免列表,只覆盖http/https/ftp/irc/mailto五个协议。这体现了写器层面的选择:只对这些最常用、AsciiDoc 解析器可隐式识别的协议输出隐式宏,其余协议(包括ftps:)统一交给link:显式宏处理,保证在 Asciidoctor 中渲染结果可靠。
2.3 mailto 的特例与自链接优化
在上述豁免列表之外,mailto:还享受两处特例处理(src/Text/Pandoc/Writers/AsciiDoc.hs):
let srcSuffix = fromMaybe src (T.stripPrefix "mailto:" src) let useAuto = case txt of [Str s] | escapeURI s == srcSuffix -> True _ -> Falsemailto:前缀会在输出时被剥掉(srcSuffix),因为 AsciiDoc 的mailto:宏本身需要保留该前缀;- 如果链接文本恰好等于去除
mailto:后的地址(如[foo@example.com](mailto:foo@example.com)),则useAuto为真,输出纯文本foo@example.com即可,AsciiDoc 会自动识别为邮件链接,无需任何宏; escapeURI(定义于 src/Text/Pandoc/URI.hs)负责转义空白与 `<>|"{}[]^`` 等字符,保证链接文本与目标可安全比较。
类似地,当--出现在链接目标中时(needsPassthrough为真),输出会退化为link:++...++[...]的 pass-through 形式,避免双连字符触发 AsciiDoc 的替代(substitution)机制(src/Text/Pandoc/Writers/AsciiDoc.hs)。
三、--wrap=preserve在用例中的作用
10105 用例显式传入了--wrap=preserve,这一点并非无关紧要。
3.1 三种换行模式
根据 MANUAL.txt 的说明:
| 模式 | 行为 |
|---|---|
--wrap=auto(默认) | 按--columns(默认 72 列)自动折行 |
--wrap=none | 完全不换行 |
--wrap=preserve | 尽量保留源文档中的非语义换行 |
在 AsciiDoc 写器内部,换行模式由writerWrapText决定。对应实现可见 src/Text/Pandoc/Writers/AsciiDoc.hs 对SoftBreak的处理:
inlineToAsciiDoc opts SoftBreak = case writerWrapText opts of WrapAuto -> return space WrapPreserve -> return cr WrapNone -> return space即:--wrap=preserve下,源文档中的软换行(SoftBreak)会在输出中保留为换行符;而auto/none模式下软换行被折叠为空格。同时pandocToAsciiDoc在WrapAuto时才会依据--columns计算列宽(src/Text/Pandoc/Writers/AsciiDoc.hs)。
3.2 对测试稳定性的意义
用例中每行各含一个链接、源文档不含软换行,因此preserve模式的作用主要体现在输出行的确定性:每个链接独立成行、不被折行或合并,期望输出与输入一一对应,便于精确断言。若改为默认的auto模式,短行不会触发折行,结果通常相同;但使用preserve消除了列宽变化带来的潜在干扰,让测试聚焦于链接宏本身的判定逻辑——这正是该测试选择此参数的原因。
四、与源码、测试的相互印证
4.1 写器单元测试中的对应覆盖
除了命令行回归测试 10105,AsciiDoc 写器还有专门的单元测试套件 test/Tests/Writers/AsciiDoc.hs。其中:
asciidoc = unpack . purely (writeAsciiDocLegacy def) . toPandoc asciidoctor = unpack . purely (writeAsciiDoc def) . toPandocasciidoc走writeAsciiDocLegacy(对应asciidoc_legacy格式,面向asciidoc-py);asciidoctor走writeAsciiDoc(对应现代asciidoc格式,面向 Asciidoctor)。
这与 MANUAL.txt 对输出格式的划分一致:asciidoc是 Asciidoctor 解释的现代 AsciiDoc,asciidoc_legacy是asciidoc-py解释的旧版,而asciidoctor只是asciidoc的废弃别名。10105 用例使用的-t asciidoc即现代格式。
4.2 相关源码文件速查
| 关注点 | 位置 |
|---|---|
link:前缀判定与链接输出 | src/Text/Pandoc/Writers/AsciiDoc.hs |
| 换行模式对 SoftBreak 的影响 | src/Text/Pandoc/Writers/AsciiDoc.hs |
escapeURI与 IANA scheme 列表 | src/Text/Pandoc/URI.hs、src/Text/Pandoc/URI.hs |
--wrap选项说明 | MANUAL.txt |
| 命令行测试框架 | test/Tests/Command.hs |
| AsciiDoc 写器单元测试 | test/Tests/Writers/AsciiDoc.hs |
五、实战小结与自查清单
在实际使用pandoc -t asciidoc(或-t asciidoctor)输出链接时,可以对照以下规则自查:
- 目标为
http:/https:/ftp:/irc:/mailto:协议且是绝对 URL:输出隐式链接宏URL[text],不加前缀; - 目标是相对路径(如
downloads/foo.zip):parseURI解析失败,输出link:downloads/foo.zip[text]; - 目标为其他协议(如
ftps:、ssh:、doi:等):虽然可能是合法 IANA scheme,但不在写器豁免列表内,输出link:URL[text]; mailto:链接且文本等于地址本身:输出裸文本,由 AsciiDoc 自动识别为邮件链接;- 链接目标含
--:退化为link:++...++[...]pass-through 写法。
通过 test/command/10105.md 这个短小精悍的回归用例,我们可以一眼看穿 pandoc AsciiDoc 写器在链接宏选择上的设计取舍:对常用协议追求简洁的隐式宏,对非常用协议和相对路径则退回显式link:宏,从而兼顾输出可读性与渲染可靠性。当你在自己的文档转换流程中遇到link:前缀"意外"出现时,不妨回到这份豁免列表,判断目标协议是否属于那五个"白名单"协议即可。
【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考