- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
导读
本文以 pandoc 仓库中的命令行回归测试用例 test/command/5121.md 为切入点,完整剖析 pandoc 在Markdown → HTML5转换过程中如何将"单独成段的图片 + 标题"自动识别为语义化的<figure>/<figcaption>结构,并正确处理width=500px这类图片尺寸属性。读完本文,你将掌握 pandoc 的implicit_figures与link_attributes扩展的工作机制、Figure块在 Pandoc AST 中的形态,以及如何用测试框架验证这类转换行为。
一、先看测试用例:一个典型的 golden test 长什么样
test/command/5121.md文件内容极简,却完整呈现了 pandoc 命令行测试(command test)的标准格式:
% pandoc -f markdown -t markdown_strict My caption{width=500px} ## Header 2 ^D <figure> <img src="./my-figure.jpg" width="500" alt="My caption" /> <figcaption aria-hidden="true">My caption</figcaption> </figure> ## Header 2这个文件的四段结构恰好对应 test/Tests/Command.hs 中定义的解析规则:
- 命令行:首行以
%开头,之后是要执行的命令,这里是pandoc -f markdown -t markdown_strict——从 pandoc 的 Markdown 方言读取,输出为markdown_strict(严格 Markdown,即不启用任何 pandoc 扩展)。 - 标准输入:
%行之后到^D行之前的全部内容作为命令的 stdin。本例输入是一个带标题的图片段落和一个二级标题。 - 终止符:单独一行
^D标记 stdin 结束。 - 期望输出:
^D之后的内容是 stdout 的期望结果,与真实运行输出逐行比对。
测试框架 test/Tests/Command.hs#L101-L129 通过goldenTest将实际输出与期望输出做 diff;若不一致,会在失败信息中给出--- test/command/5121.md与具体 diff,方便开发者定位。整个test/command/目录下的所有*.md文件会被 test/Tests/Command.hs#L82-L87 自动扫描为一个个测试组,而 test/test-pandoc.hs 则把这些命令测试与各 Reader/Writer 单元测试一起聚合进 tasty 测试树。
值得注意的是,命令中输出格式写的是markdown_strict,但期望输出却是 HTML5。这说明该用例实际验证的路径是:Markdown 读取器解析出图片段落 → 转换为 Pandoc 内部表示(Figure块)→ Markdown 严格模式写入器在无法用隐式图片语法表达时,降级输出为原始 HTML 的<figure>结构。这正是理解这个用例的关键。
二、输入侧:implicit_figures如何把"图片段落"变成Figure块
测试输入的图片行My caption{width=500px}包含了三个要素:
![My caption]:图片的替代文本(alt text),同时也被用作图注(caption);(./my-figure.jpg):图片源地址;{width=500px}:图片属性,声明渲染宽度为 500px。
在 pandoc 的 Markdown 读取器中,这个图片段落由para解析函数处理。源码 src/Text/Pandoc/Readers/Markdown.hs#L1050-L1106 展示了其核心逻辑:
let figureOr constr inlns = case B.toList inlns of [Image attr figCaption (src, tit)] | extensionEnabled Ext_implicit_figures exts , not (null figCaption) -> do implicitFigure attr (B.fromList figCaption) src tit _ -> constr inlns即:当一个段落只包含一张图片、且**图片有非空标题(figCaption)**时,若启用了Ext_implicit_figures扩展,则把该段落构造为一个Figure块;否则退回普通的Plain/Para段落。这就是 pandoc 中"隐式图片(implicit figure)"的语义:图片单独成段 + 带标题 → 自动成为图形。
随后implicitFigure函数(src/Text/Pandoc/Readers/Markdown.hs#L1093-L1106)进一步处理属性:
implicitFigure (ident, classes, attribs) capt url title = let alt = case "alt" `lookup` attribs of Just alt' -> B.text alt' _ -> capt ... figbody = B.plain $ B.imageWith ("", classes, attribs') url title alt in B.figureWith figattr caption figbody要点有二:
alt与caption分离:{alt=...}属性可以单独指定替代文本,未指定时用标题文本充当 alt;- 属性传递:除
alt、latex-placement外的其余属性(如width=500px)会保留在图片节点上,ident与latex-placement则上移到Figure块的属性中。
在 Pandoc AST 层面,输入最终表现为:
Figure ("", [], []) (Caption Nothing [Plain [Str "My caption"]]) [Plain [Image ("", [], [("width","500px")]) [Str "My caption"] ("./my-figure.jpg","")]]width=500px中的px单位在解析时会被规范化,属性值最终以500px的键值对形式携带(这也是期望输出中width="500"的来源)。
三、输出侧:Markdown 严格模式为何输出 HTML5<figure>
问题来了:输出目标是markdown_strict,为什么期望输出是 HTML?
答案在 Markdown 写入器 src/Text/Pandoc/Writers/Markdown.hs#L733-L769 的blockToMarkdown'对Figure分支的处理中。写入逻辑按优先级逐级降级:
- 优先还原为隐式图片语法:如果图片体是
[Plain [Image ...]]、启用了implicit_figures,且图注与替代文本一致、图片属性满足要求,就输出回[](https://link.gitcode.com/i/f94088d9f5a3aa0f25e126a13d9b13a6){...}形式; - 其次输出原始 HTML:当无法用隐式语法表达(例如图片带
width等属性,而link_attributes扩展未启用时),若启用了raw_html扩展,则调用figureToMarkdown(src/Text/Pandoc/Writers/Markdown.hs#L790-L795),直接用writeHtml5String把Figure渲染成 HTML 片段——这就是本例输出<figure>的原因; - 最终兜底:既不支持 raw HTML 又不支持 div,则退化为输出图片本身。
这里有一个极易混淆的点需要澄清:本例期望输出中的 HTML 并非"从 HTML 写入器直接输出",而是 Markdown 严格模式写入器内部的 HTML 兜底路径。因此,要看懂这段<figure>输出的生成细节,需要回到 HTML 写入器。
四、HTML 写入器:<figure>、<figcaption>与aria-hidden的生成细节
markdown_strict写入器在兜底时调用的是 HTML5 渲染逻辑,位于 src/Text/Pandoc/Writers/HTML.hs#L1086-L1110:
blockToHtmlInner opts (Figure attrs (Caption _ captBody) body) = do ... let figCaption = mconcat $ if html5 then let fcattr = if captionIsAlt captBody body then H5.customAttribute (textTag "aria-hidden") (toValue @Text "true") else mempty in [ H5.figcaption ! fcattr $ captCont ] else [ (H.div ! A.class_ "figcaption") captCont ] ... return $ if html5 then foldl (!) H5.figure figAttrs innards else foldl (!) H.div (A.class_ "float" : figAttrs) innards对照期望输出可以逐项印证:
| 期望输出片段 | 源码依据 |
|---|---|
<figure> | HTML5 模式下用H5.figure包裹(HTML4 模式则用div.float) |
<img src="./my-figure.jpg" width="500" alt="My caption" /> | 图片节点保留的width=500px属性被序列化为width="500";alt 取图注文本 |
<figcaption aria-hidden="true">My caption</figcaption> | captionIsAlt captBody body判断图注与图片 alt 一致时为<figcaption>加上aria-hidden="true",避免屏幕阅读器重复朗读 |
其中captionIsAlt的实现(src/Text/Pandoc/Writers/HTML.hs#L1112-L1115)值得注意:它把图注文本与图片alt属性(缺省时用图片描述文本)做字符串比较,两者相等才加aria-hidden。本用例中图注 "My caption" 与图片 alt 完全一致,因此输出带aria-hidden="true"——这是无障碍(accessibility)层面的细节优化:图注与 alt 重复时,将图注对辅助技术隐藏。
此外,<figcaption>在图注为空时不会输出(源码null captBody分支),图注位置(上方/下方)由writerFigureCaptionPosition选项控制,默认位于图片下方,与期望输出一致。
五、扩展开关与前置条件:何时生效、何时失效
implicit_figures与link_attributes都是可通过-f/+/-开关的 pandoc 扩展,定义于 src/Text/Pandoc/Extensions.hs:
Ext_implicit_figures(src/Text/Pandoc/Extensions.hs#L89):注释为"A paragraph with just an image is a figure",在 pandoc 的 Markdown 扩展集中默认启用(src/Text/Pandoc/Extensions.hs#L269);Ext_link_attributes(src/Text/Pandoc/Extensions.hs#L96):允许{width=500px}这类属性语法,在 pandoc Markdown 中也默认启用(src/Text/Pandoc/Extensions.hs#L295)。
由于本例使用了markdown_strict(关闭全部 pandoc 扩展)作为输出格式,link_attributes在写入侧不可用,图片的width属性无法再写回 Markdown 属性语法,于是触发 HTML 兜底路径——这正是该测试用例设计的验证意图:确认"带属性的图片 + 标题"在受限输出格式下不会丢失信息,而是降级为语义化 HTML。
作为对照,如果输出为完整的 pandoc Markdown(-t markdown),写入器会优先还原为隐式图片语法,得到My caption{width=500px}形式的原始输入。同理,直接输出到html5格式时,HTML 写入器会以figure/figcaption结构为主干输出同样的结果。从源码结构可以推断:Figure块是 pandoc AST 中的一等公民(一等块类型),各写入器对其有独立的序列化策略,这也是 pandoc 能在各格式间保持图片语义一致性的基础。
六、如何复现与运行该测试
本用例属于命令行测试套件的一部分,可直接在仓库中复现:
手工复现:用 pandoc 二进制执行
%后的命令,并输入相同 stdin:printf 'My caption{width=500px}\n\n## Header 2\n' \ | pandoc -f markdown -t markdown_strict输出即应为测试文件中的期望内容。
运行测试套件:该用例由 test/test-pandoc.hs 统一驱动,测试框架会将命令中的
pandoc替换为test-pandoc --emulate(见 test/Tests/Command.hs#L72-L78),以确保测试用可执行文件与源码保持一致。构建并运行:cabal test pandoc:test-pandoc --test-options='-p "5121"'-p "5121"过滤出本用例(测试组按文件名命名,用例编号为#1)。失败时的行为:若输出与期望不一致,测试会报出类似
--- test/command/5121.md的 diff 提示(test/Tests/Command.hs#L117-L121)。由于goldenTest支持自动更新期望值,开发者也可在确认新行为正确后刷新 golden 文件——但注意仓库为只读参考,实际贡献时按项目 CONTRIBUTING.md 流程操作。
七、小结:从一条测试用例读懂 pandoc 的图片语义管线
test/command/5121.md虽然只有十余行,却覆盖了 pandoc 图片处理的一条完整链路:
- 读取:
implicit_figures扩展把"带标题的图片段落"提升为Figure块,width=500px等属性由link_attributes扩展解析并随图片节点保留; - AST:
Figure是 pandoc 内部表示的一等块类型,图注(Caption)与图片体分离存储; - 写出:Markdown 写入器优先还原隐式图片语法,条件不满足时经
raw_html兜底调用 HTML5 渲染; - HTML 细节:
figure/figcaption结构按 HTML5 规范输出,图注与 alt 重复时自动加aria-hidden="true"以优化无障碍体验。
对于需要在各类文档管线中处理图片语义(图注、尺寸、alt、无障碍)的开发者而言,这条用例既是可复现的行为样例,也是理解 pandocFigure模型与扩展开关体系的最佳入口。相关代码可继续深入阅读:
- 读取器实现:src/Text/Pandoc/Readers/Markdown.hs#L1050-L1106
- Markdown 写入器降级逻辑:src/Text/Pandoc/Writers/Markdown.hs#L733-L800
- HTML 写入器 figure 渲染:src/Text/Pandoc/Writers/HTML.hs#L1086-L1115
- 扩展定义与默认集:src/Text/Pandoc/Extensions.hs
- 命令测试框架:test/Tests/Command.hs
- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
相关推荐
Pandoc LaTeX 图片环境转换指南:figure 与 subfigure 到 HTML5 的完整链路
Pandoc LaTeX 图片环境转换指南:figure 与 subfigure 到 HTML5 的完整链路 导读 本文以仓库中的命令测试用例 test/com
文档开发工具CLIPandoc JATS 图片与图形解析实战:`<fig>`/`<graphic>` 到原生 Figure/Image 的转换详解
Pandoc JATS 图片与图形解析实战: <fig / <graphic 到原生 Figure/Image 的转换详解 Pandoc 作为通用标记格式转换器
文档开发工具CLIKOReader 电纸书阅读器快速上手指南:Kindle、Kobo 安装与扫描 PDF 重排完整实操
KOReader 电纸书阅读器快速上手指南:Kindle、Kobo 安装与扫描 PDF 重排完整实操 扫描版 PDF 在电纸书上打开,字小到眯眼也费劲。KORe
文档开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考