Pandoc HTML 读取器解析<figure>/<figcaption>的完整指南:从命令行测试到源码实现
【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc
Pandoc 是通用的标记格式转换器(Universal markup converter),其 HTML 读取器(reader)负责把 HTML5 文档转换为 Pandoc 内部的抽象语法树(native AST)。本指南以仓库中的命令测试用例 test/command/4183.md 为骨架,逐条剖析 Pandoc 如何把<figure>与<figcaption>元素解析为Figure块,并结合 HTML 读取器源码 说明其底层实现原理。读完本文,你将掌握:Figure块在 native 输出中的精确结构、figcaption与图片alt文本各自的去向、空标题与嵌套块级内容的处理规则,以及如何自行运行与扩展此类命令测试。
一、背景:HTML5<figure>与 Pandoc 的Figure块
HTML5 引入<figure>元素用于承载插图、图表、代码片段等独立于正文的内容,通常配合<figcaption>提供标题说明。Pandoc 的 HTML 读取器专门为这一对元素提供了块级(block)映射:<figure>对应 Pandoc 的Figure块,<figcaption>中的内容被收集为图的标题(caption),<figure>内的其余块构成图的主体(body)。
命令测试 test/command/4183.md 是 Pandoc 官方测试套件(golden test)的一部分,它通过三组pandoc -f html -t native命令,用输入/期望输出对的方式锁定了该解析行为。命令测试本身由 test/command 目录下的大量.md文件驱动,运行入口为 test/Tests/Command.hs。
在 native 语法树中,Figure块的通用形态为:
Figure attr caption body其中:
attr是(id, classes, key-values)三元组,即元素的标识符、类名列表与键值对属性;caption是Caption,其结构为Caption shortCaption blocks(shortCaption一般为Nothing,blocks为标题的块列表);body是Figure内除去标题后的块列表,通常是一个Plain包裹的Image。
Image的 native 形态为Image attr inlines target,其中inlines是图片的可选文字(通常来自alt属性),target是(src, title)二元组。理解这些结构后,下面三个用例就能逐字段对上号。
二、用例一:无<figcaption>的裸<figure>
测试文件的第一组命令如下:
% pandoc -f html -t native <figure> <img src="foo" alt="bar"> </figure> ^D [ Figure ( "" , [] , [] ) (Caption Nothing []) [ Plain [ Image ( "" , [] , [] ) [ Str "bar" ] ( "foo" , "" ) ] ] ]这里<figure>只包含一个<img>,没有任何<figcaption>。解析结果为:
Figure ("", [], []):figure元素没有任何id/class/自定义属性,因此属性三元组全部为空;Caption Nothing []:没有figcaption,标题块列表为空;[ Plain [ Image ("", [], []) [Str "bar"] ("foo", "") ] ]:主体中只有一个Plain块,内含一个Image。图片的inlines是[Str "bar"],它直接来自<img>的alt="bar";target为("foo", ""),即src="foo"、无title。
值得注意的是:没有<figcaption>时,图片的alt文本会被放进Image的inlines,但不会成为Figure的标题。标题(caption)与图片的替代文本(alt text)在 Pandoc 的 AST 中是两个不同的概念,前者来自figcaption,后者来自img的alt属性。
三、用例二:带块级figcaption的<figure>
测试文件的第二组命令验证了figcaption内容为块级元素时的处理:
% pandoc -f html -t native <figure> <img src="foo" alt="bar"> <figcaption> <div> baz </div> </figcaption> </figure> ^D [ Figure ( "" , [] , [] ) (Caption Nothing [ Div ( "" , [] , [] ) [ Plain [ Str "baz" ] ] ]) [ Plain [ Image ( "" , [] , [] ) [ Str "bar" ] ( "foo" , "" ) ] ] ]关键变化在Caption部分:
figcaption里的<div>baz</div>被解析为一个块级Div ("", [], []),其内容为[ Plain [ Str "baz" ] ];- 该
Div整体进入Caption Nothing [ Div ... ],成为标题的块列表; - 主体
body仍然只有那个alt="bar"的Image,与用例一完全一致。
这揭示了源码中的一个设计:figcaption的内容是按照“块”(block)来解析的,而不是“行内”(inline)。因此figcaption内部的<div>、<p>等块级容器会原样保留在标题的块列表中。这一点与第三个用例形成对照。
四、用例三:figcaption内含段落与行内格式
测试文件的第三组命令展示了更接近真实排版的情况——figcaption内含<p>与行内强调:
% pandoc -f html -t native <figure> <img src="foo"> <figcaption><p><em>baz</em></p></figcaption> </figure> ^D [ Figure ( "" , [] , [] ) (Caption Nothing [ Para [ Emph [ Str "baz" ] ] ]) [ Plain [ Image ( "" , [] , [] ) [] ( "foo" , "" ) ] ] ] ]两个细节值得展开:
- 标题按块解析:
figcaption内的<p><em>baz</em></p>被解析为Para [ Emph [ Str "baz" ] ]。这里em标签被正确转换为行内元素Emph,而<p>则整体成为标题中的一个Para块——再次印证“块级解析”规则:标题可以容纳多个块,并保留段落与行内格式的层级关系。 alt缺失时的Image:<img src="foo">没有alt属性,因此Image的inlines为空列表[],即Image ("", [], []) [] ("foo", "")。说明 Pandoc 不会为缺失的alt注入任何占位文本。
对比用例一(alt="bar"产生[Str "bar"])可以看到:Image的inlines完全取决于 HTML 中alt属性的有无与内容。
五、源码实现剖析:pFigure解析器
上述三个用例的行为并非散落在各处,而是由 src/Text/Pandoc/Readers/HTML.hs 中单一函数统一定义。首先,块级分发表中"figure" -> pFigure(见 HTML.hs 第 258 行)把<figure>开标签路由到专用解析器。
pFigure的实现位于 HTML.hs 第 666-675 行:
pFigure :: PandocMonad m => TagParser m Blocks pFigure = do TagOpen tag attrList <- pSatisfy $ matchTagOpen "figure" [] let parser = Left <$> pInTags "figcaption" block <|> (Right <$> block) (captions, rest) <- partitionEithers <$> manyTill parser (pCloses tag <|> eof) -- Concatenate all captions together return $ B.figureWith (toAttr attrList) (B.simpleCaption (mconcat captions)) (mconcat rest)逐行解读其工作原理:
- 匹配开标签:
pSatisfy $ matchTagOpen "figure" []确认当前标签是<figure>,并取出属性列表attrList。 - 二分支解析:核心是
parser的定义——Left <$> pInTags "figcaption" block把figcaption内的块解析结果标记为Left(候选标题);Right <$> block把其他普通块标记为Right(主体内容)。这就是用例一/三中图片Image进入主体、而figcaption进入标题的结构来源。 - 收集与切分:
manyTill parser (pCloses tag <|> eof)持续按上述二分支解析,直到</figure>或文件结束;partitionEithers把Left(所有标题块)与Right(所有主体块)分到两个列表。 - 合并标题:
B.simpleCaption (mconcat captions)把所有figcaption块按出现顺序拼接为一个Caption。源码注释 "Concatenate all captions together" 表明:即使一个<figure>内出现多个<figcaption>,也会被合并进同一个标题,而不是报错或丢弃。 - 构建结果:
B.figureWith (toAttr attrList)把<figure>自身的属性(id、class、key-values)完整保留到Figure的attr字段;主体部分mconcat rest合并所有非标题块。
从源码结构还可以推断两个边界行为:其一,若figure内既有figcaption又有多个普通块,普通块会全部进入body;其二,若figcaption出现在主体块之后,解析器依然能正确将其归入标题(因为它只按“是否为 figcaption”分流,不依赖位置)。
六、simpleCaption与标题结构的关系
pFigure使用B.simpleCaption构造标题,这与 Pandoc 中表格(Table)、DocBook、JATS 等读取器构造标题的方式一致(参见 src/Text/Pandoc/Readers/DocBook.hs、src/Text/Pandoc/Readers/JATS.hs、src/Text/Pandoc/Readers/HTML/Table.hs)。
simpleCaption生成Caption Nothing blocks,这正是三个用例中Caption Nothing [...]的由来——Nothing表示“无短标题”。也就是说,从 HTML 的figcaption解析出来的标题始终是完整标题,Pandoc 不会为它自动生成短标题。
在 AST 层面,Figure作为块类型同样参与其他模块的处理,例如 src/Text/Pandoc/Shared.hs 第 904 行 的blockToInlines (Figure _ _ body)在需要把块转行内时只取Figure的主体而舍弃标题——这为理解“Figure 被内联化时的行为”提供了依据。而在写出(writer)方向,LaTeX 写出器(src/Text/Pandoc/Writers/LaTeX.hs)、Docx 写出器(src/Text/Pandoc/Writers/Docx/OpenXML.hs)等都会读取stInFigure状态来生成对应的figure环境,说明Figure块是贯穿读写两端的一等公民。
七、如何在本地复现与扩展该命令测试
test/command/4183.md是命令测试(command test)格式:每个以 ``` 包裹的代码块包含一条 shell 命令(以%开头)、通过标准输入(以^D结束)送入的输入内容,以及期望的 stdout 输出。要复现第一个用例,只需在仓库根目录执行:
pandoc -f html -t native然后粘贴:
<figure> <img src="foo" alt="bar"> </figure>并以 Ctrl-D(^D)结束输入,即可得到与用例一完全一致的 native 输出。其余两个用例同理。这种交互式验证方式正是 Pandoc 命令测试的设计初衷:测试文件中的命令与人工敲入的命令完全等价。
如果你想扩展测试,可以参考同一目录下其他.md文件的写法:新建一个test/command/<编号>.md,在其中写入% pandoc ...命令块与期望输出,然后通过测试套件入口 test/Tests/Command.hs 统一运行比对。可覆盖的场景包括:
<figure id="fig1" class="wide">带属性时的Figure ("fig1", ["wide"], [])形态;figcaption内同时出现多个段落、列表或代码块的复杂排版;figure内出现多个普通块(如图片加说明段落)时的body结构;- 嵌套
figure(HTML5 不允许,但解析器行为可通过测试固定下来)。
八、小结
通过 test/command/4183.md 这一组命令测试,可以完整掌握 Pandoc HTML 读取器对<figure>/<figcaption>的解析语义:
| 输入特征 | AST 结果 |
|---|---|
无<figcaption> | Caption Nothing [],img的alt进入Image的inlines |
<figcaption>内含<div> | Div块整体进入Caption的块列表 |
<figcaption>内含<p><em> | Para [ Emph ... ]进入Caption,行内格式保留 |
<img>无alt | Image的inlines为空[] |
<figure>带属性 | 属性经toAttr保留到Figure的attr |
其底层统一由 HTML.hs 的pFigure实现:figcaption按块解析并归入标题、其余块归入主体、多个标题块按序拼接。无论你是要理解 Pandoc 的 native AST、编写基于Figure的过滤器,还是为 HTML 转 Markdown/LaTeX 的流程排查图片标题问题,上述用例与源码都可以作为直接可复现、可引用的依据。
【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考