news 2026/9/19 22:49:00

Pandoc DocBook 读取器如何解析有序列表的编号样式与内嵌标题:以 test/command/10594.md 命令测试为例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Pandoc DocBook 读取器如何解析有序列表的编号样式与内嵌标题:以 test/command/10594.md 命令测试为例

Pandoc DocBook 读取器如何解析有序列表的编号样式与内嵌标题:以 test/command/10594.md 命令测试为例

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

test/command/10594.md是 pandoc 项目中的一个命令行级(command-level)回归测试:它以一段包含内嵌<title>的 DocBook<orderedlist>为输入,通过pandoc -f docbook -t native输出内部 AST,验证 DocBook 读取器对有序列表numeration编号样式的映射、列表内<title>元素的 Div 化处理,以及<listitem>/<simpara>的块级转换行为。读完本文,你将能看懂这类test/command/*.md测试文件的格式约定,掌握 DocBook 列表在 pandoc 中的 AST 表示,并能对照 DocBook 读取器源码 复现与扩展验证。

一、测试文件全景:一个最小可复现的命令测试

test/command/10594.md全文是一个用反引号包裹的代码块,其内容遵循 pandoc 命令测试(command tests)的标准格式,包含三部分:

% pandoc -f docbook -t native <orderedlist numeration="loweralpha"> <title>header inside listing</title> // not rendered in any output format! <listitem> <simpara>first step</simpara> </listitem> </orderedlist> ^D [ Div ( "" , [] , [] ) ... ]
  • 第一行%:声明要执行的 pandoc 命令行,这里是pandoc -f docbook -t native,即把输入当作 DocBook 解析,并以 native 格式(pandoc 内部 AST 的文本表示)输出;
  • ^D之前:喂给命令的标准输入(heredoc 形式,^D模拟 EOF);
  • ^D之后:期望的标准输出,即 golden 结果,测试框架会把实际输出与它逐字节比对。

这类测试由 test/Tests/Command.hs 驱动:测试发现器会扫描test/command/目录下所有以.md结尾的文件(test/Tests/Command.hs),逐个解析出命令、输入与期望输出,并通过goldenTest做 golden 比对。值得注意的实现细节是,实际执行时命令中的pandoc会被替换为test-pandoc --emulate(test/Tests/Command.hs),从而使用与发布版 pandoc 行为一致的测试专用二进制。从命名规律可以推断,这类编号文件通常对应 pandoc 历史上的 issue/PR 编号,10594即为此用例的回归编号。

二、输入 DocBook 片段逐段拆解

测试输入是一段结构清晰的 DocBook 5 文档片段:

<orderedlist numeration="loweralpha"> <title>header inside listing</title> // not rendered in any output format! <listitem> <simpara>first step</simpara> </listitem> </orderedlist>

各元素含义如下:

元素/属性含义
<orderedlist>DocBook 有序列表,numeration="loweralpha"指定编号样式为小写字母
<title>列表的可选标题;测试中用//注释注明它在各输出格式中通常不会被渲染
<listitem>列表项容器
<simpara>"simple paragraph",只含文本与内联标记、不含块级元素的段落

注意<title>是直接嵌在<orderedlist>内部、而非位于listinfo中——这正是本用例的焦点:读取器需要把列表标题作为一种"可选的块级前置内容"捕获下来。

三、native 输出解读:Div 嵌套与 OrderedList 属性

期望输出揭示了 DocBook 读取器生成的内部 AST:

[ Div ( "" , [] , [] ) [ Div ( "" , [ "title" ] , [] ) [ Plain [ Str "header", Space, Str "inside", Space, Str "listing" ] ] , OrderedList ( 1 , LowerAlpha , DefaultDelim ) [ [ Para [ Str "first", Space, Str "step" ] ] ] ] ]

对照源码可以逐层还原:

  1. 外层Div ("" , [] , []):整个<orderedlist>被包装为 Div,id、class、键值属性均为空(本例未设置id,也没有role等属性);
  2. 内层Div ("" , ["title"] , [])<title>的文本 "header inside listing" 被解析为行内内容并以Plain块呈现,随后被打包成带titleclass 的 Div;
  3. OrderedList (1 , LowerAlpha , DefaultDelim):元组三个分量分别是起始编号、编号样式、分隔符类型——起始号为 1,样式为LowerAlpha(小写字母),分隔符为DefaultDelim(DocBook 本身不编码分隔符信息,因此固定取默认值);
  4. [ [ Para [ Str "first", Space, Str "step" ] ] ]:唯一的<listitem>解析为一个列表项,其内部的<simpara>被转换为Para块。

测试注释说该<title>"not rendered in any output format"(在输出格式中通常不渲染),但 native 输出恰恰证明了它在 AST 层面是被保留的——这是"保留信息、渲染交给 writer"的典型设计。

四、源码实现(一):orderedlist 分支与 numeration 映射

<orderedlist>的解析逻辑位于 src/Text/Pandoc/Readers/DocBook.hs:

"orderedlist" -> withOptionalTitle $ do let listStyle = case attrValue "numeration" e of "arabic" -> Decimal "loweralpha" -> LowerAlpha "upperalpha" -> UpperAlpha "lowerroman" -> LowerRoman "upperroman" -> UpperRoman _ -> Decimal let start = fromMaybe 1 $ safeRead $ attrValue "startingnumber" e orderedListWith (start,listStyle,DefaultDelim) . handleCompact <$> listitems

这段代码揭示了完整的编号样式映射关系:

DocBooknumeration属性值pandoc 列表样式
arabic(及未识别值,默认)Decimal(十进制数字)
loweralphaLowerAlpha(小写字母 a, b, c…)
upperalphaUpperAlpha(大写字母 A, B, C…)
lowerromanLowerRoman(小写罗马数字 i, ii, iii…)
upperromanUpperRoman(大写罗马数字 I, II, III…)

起始编号则读取startingnumber属性,缺省时回退为 1;分隔符固定为DefaultDelim。测试用例中的numeration="loweralpha"因此精确命中LowerAlpha分支,验证了这条映射链。

与之相邻的列表类元素解析同样值得对照:itemizedlistbulletListvariablelistdefinitionListproceduresubsteps直接使用默认样式的orderedList(src/Text/Pandoc/Readers/DocBook.hs)。这些标签以及titlelistitemsimpara等都会先经过读取器的元素白名单检查(参见 src/Text/Pandoc/Readers/DocBook.hs),未列入白名单的标签将被跳过。

五、源码实现(二):withOptionalTitle 与 title 的 Div 化

列表项内容的收集很直观:listitems = mapM getBlocks $ filterChildren (named "listitem") e(src/Text/Pandoc/Readers/DocBook.hs),即把每个<listitem>子元素递归解析成块列表;而<simpara>通过parseMixed para被解析为Para块(src/Text/Pandoc/Readers/DocBook.hs)。

真正有意思的是<title>的处理。在getBlocks的分支表中,顶层出现<title>时直接返回mempty,注释写明"handled in parent element"(由父元素处理,src/Text/Pandoc/Readers/DocBook.hs)。也就是说,<title>是否被消费,完全取决于父元素是否调用withOptionalTitle。其实现如下(src/Text/Pandoc/Readers/DocBook.hs):

withOptionalTitle p = do mbt <- getTitle b <- p case mbt of Nothing -> return b Just t -> return $ divWith (attrValue "id" e, [], getRoleAttr e) (divWith ("", ["title"], []) (plain t) <> b)
  • getTitlefilterChild (named "title") e查找直接子级<title>,取其行内内容;
  • 若存在,则把标题包装为divWith ("", ["title"], []) (plain t),再与列表主体b拼接,外包一层带元素id与 role 属性的 Div;
  • 若不存在,则原样返回列表内容,不产生额外 Div。

这正是 native 输出中两层 Div 的由来:外层 Div 的 id 取自<orderedlist>id属性(本测试未设置,故为空),内层titleDiv 则是标题的固定容器。同一个withOptionalTitle也被calloutlistitemizedlist等复用,而表格与图表的标题走的是另一条title/caption处理路径。

六、补充机制:compact 紧凑列表

orderedListWith ... . handleCompact中的handleCompactspacing属性控制(src/Text/Pandoc/Readers/DocBook.hs):

compactSpacing = case attrValue "spacing" e of "compact" -> True _ -> False handleCompact = if compactSpacing then map (fmap paraToPlain) else id

当列表声明spacing="compact"时,每个列表项内的Para会被降级为Plain(紧凑呈现);否则保持Para不变。10594 用例未设置spacing,因此<simpara>生成的Para原样保留——这也解释了为什么期望输出中列表项内容是Para而非Plain

七、如何本地复现与验证

在已构建 pandoc 的环境中,可以直接用 heredoc 复现该测试(结果应与^D后的 golden 输出完全一致):

pandoc -f docbook -t native <<'EOF' <orderedlist numeration="loweralpha"> <title>header inside listing</title> <listitem> <simpara>first step</simpara> </listitem> </orderedlist> EOF

也可以运行整个命令测试套件来验证该用例:

cabal test --test-options='-p "#10594"'

-p的匹配串来自 test/Tests/Command.hs 中的testname = "#" <> show num,即每个.md文件名去掉扩展名后即为测试名。若实际输出与 golden 不一致,测试框架会给出--- test/command/10594.md+++ pandoc -f docbook -t native形式的 diff,方便定位读取器行为变化(test/Tests/Command.hs)。

八、小结:从一条测试看 pandoc 的回归测试方法论

test/command/10594.md虽只有二十余行,却浓缩了 pandoc 三个层面的工程实践:

  1. 读取器语义numerationListNumberStyle的六路映射、startingnumber→ 起始编号、spacing="compact"Para/Plain切换,以及<title>由父元素按需消费的"handled in parent element"设计;
  2. AST 约定:可选列表标题被编码为带titleclass 的 Div,这一约定被withOptionalTitle统一实现,并被 itemizedlist、calloutlist 等列表类元素共享;
  3. 测试基建.md即用例、%/^D即输入边界、golden 比对与test-pandoc --emulate替身机制,构成了覆盖读者与写者行为的低成本回归体系。

理解这一条测试,等于掌握了阅读test/command/目录下数百个用例的通用钥匙——每个文件都是一段可直接复现的"命令 + 输入 + 期望输出"三元组,随时可以对照 DocBook 读取器 或 命令测试驱动 深入验证。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 22:48:35

Windows更新文件清理指南:安全释放C盘空间

1. 项目概述&#xff1a;为什么你总在“删更新文件”这件事上反复折腾&#xff1f;Windows更新文件删除&#xff0c;不是个技术问题&#xff0c;而是一个系统与用户之间持续博弈的日常现场。我做IT支持和系统运维十多年&#xff0c;几乎每天都会遇到三类人&#xff1a;一类是C盘…

作者头像 李华
网站建设 2026/9/19 22:42:34

2026年PyCharm安装配置全攻略:从下载到跑通第一个项目

1. 为什么2026年还要认真装一次PyCharm很多人看到"安装教程"四个字就划走了&#xff0c;觉得装个软件有什么好讲的&#xff0c;下一步下一步不就完了。但我这些年帮人看环境问题&#xff0c;十次里有七次出在第一步——装的时候随手点&#xff0c;用的时候到处报错。…

作者头像 李华
网站建设 2026/9/19 22:41:38

Branch节点深度解析:读取节点图分支逻辑与性能优化实战

在节点式开发环境里待久了&#xff0c;你会发现一个特别有意思的现象&#xff1a;Branch节点是被用得最多、但最被低估的节点之一。很多人拖一个Branch节点出来&#xff0c;接上布尔值&#xff0c;两条输出一接&#xff0c;就认为大功告成。但等到项目复杂起来&#xff0c;分支…

作者头像 李华
网站建设 2026/9/19 22:40:40

Ant Design Popover 实战:悬停与点击双触发交互的完整实现方案

Ant Design Popover 实战&#xff1a;悬停与点击双触发交互的完整实现方案 【免费下载链接】ant-design An enterprise-class UI design language and React UI library 项目地址: https://gitcode.com/gh_mirrors/ant/ant-design 气泡卡片&#xff08;Popover&#xff…

作者头像 李华
网站建设 2026/9/19 22:39:32

SIMATIC Safety V19组态编程:从F-CPU到安全程序的关键细节

简介&#xff1a;西门子SIMATIC Safety系统的最新组态与编程指南&#xff0c;面向工业自动化中负责故障安全控制项目设计、调试和维护的工程师。资源包内为1个PDF文档&#xff0c;大小6.96MB&#xff0c;已有157人学习。指南覆盖从硬件组态、安全管理编辑器、访问保护&#xff…

作者头像 李华