十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Pandoc `four_space_rule` 扩展全解析:从 3511 号回归测试看列表续行缩进规则的底层实现

Pandoc `four_space_rule` 扩展全解析:从 3511 号回归测试看列表续行缩进规则的底层实现 Pandocfour_space_rule扩展全解析从 3511 号回归测试看列表续行缩进规则的底层实现【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc在 Pandoc 的 Markdown 解析器中列表项的续行缩进continuation indent规则直接决定了嵌套列表、缩进代码块和列表内段落如何被识别。本篇文章以仓库内的回归测试 test/command/3511.md 为主体结合 MANUAL.txt 中的扩展说明与 Markdown 读取器源码 的实现细节深入讲解four_space_rule扩展开启与关闭对解析结果的具体影响。读完本文你将能够准确预判任意缩进写法在 Pandoc 中会被解析为嵌套列表还是代码块并能熟练使用-f markdownfour_space_rule与-t plainfour_space_rule等命令控制这一行为。一、测试用例 3511 的背景它验证了什么test/command/3511.md是 Pandoc 命令测试套件golden test中的一个用例其编号对应早期 GitHub issue #3511。这类测试文件的格式约定是以%开头的行是待执行的 pandoc 命令行其后是标准输入内容^D结束输入再往下是期望的标准输出。整个测试用于守护 Markdown 列表解析器在两种模式下的行为默认模式不开启four_space_rule列表续行缩进对齐到列表标记后首个非空白字符所在的列four_space_rule模式-f markdownfour_space_rule列表续行内容固定需要 4 个空格的缩进。同一份输入在两种模式下会产生截然不同的 AST 输出这正是本测试的价值所在——它同时锁定了两套解析行为防止后续代码改动时无意间破坏其中之一。二、默认模式续行缩进 内容起始列先看测试的第一段。命令行与输入为% pandoc -t native - a - b - c - code 1000. one not continuation ^D期望输出即测试文件中锁定的 AST[ BulletList [ [ Plain [ Str a ] , BulletList [ [ Plain [ Str b ] , BulletList [ [ Plain [ Str c ] ] ] ] ] ] , [ CodeBlock ( , [] , [] ) code ] ] , OrderedList ( 1000 , Decimal , Period ) [ [ Plain [ Str one ] ] ] , CodeBlock ( , [] , [] ) not continuation ]这个输出包含三个值得注意的行为1. 嵌套列表的缩进计算。列表项- a的内容起始列是第 3 列-占 1 列、空格占 1 列、a从第 3 列开始因此其续行缩进为 2 个空格。输入中的- b2 个空格恰好到达第 3 列被识别为- a的子列表同理- c成为- b的子列表。最终 AST 呈现为三层嵌套的BulletList。2. 代码块与普通文本的区分。第二个列表项- code中标记后跟了 5 个空格再跟code。在默认模式下该项内容相对项起始列的缩进超过 4 个空格被判定为缩进代码块因此 AST 中是[ CodeBlock ( , [] , [] ) code ]——一个独立的、无属性空字符串、空列表的代码块。3. 有序列表的假续行。1000. one的有序列表标记较长1000.共 6 个字符内容one从第 7 列开始续行缩进应为 6 个空格。而接下来的not continuation只有 4 个空格到达第 5 列不足 6 个因此不是one的续行段落而是被解析为一个顶层CodeBlock。注意有序列表的起始编号 1000 被完整保留在 AST 中( 1000 , Decimal , Period )这与startnum扩展的默认行为一致。三、开启four_space_rule续行缩进固定为 4 空格测试的第二段使用同样的输入仅把not code/continuation微调但显式开启扩展% pandoc -t native -f markdownfour_space_rule - a - b - c - not code 1000. one continuation ^D期望输出[ BulletList [ [ Plain [ Str a ] ] , [ Plain [ Str b ] , BulletList [ [ Plain [ Str c ] ] ] ] , [ CodeBlock ( , [] , [] ) not code ] ] , OrderedList ( 1000 , Decimal , Period ) [ [ Para [ Str one ] , Para [ Str continuation ] ] ] ]对比默认模式开启扩展后的差异一目了然1. 嵌套结构被拉平。默认模式下- b2 空格是- a的子列表而在four_space_rule模式下续行缩进固定为 4 空格2 个空格的- b不足 4 空格于是它不再作为- a的续行而是成为一个新的顶层列表项。同理- c4 空格恰好达到续行缩进要求成为- b的子列表。结果 AST 从三层嵌套 一个代码块项变成了三个顶层项a、b含子列表c、以及代码块not code。2. 代码块的归属变化。- not code在两种模式下都解析为CodeBlock not code但归属不同默认模式下它是顶层项因为它前面的 2 空格- b被嵌套进了- afour_space_rule模式下它同样是顶层项但此时它前面的项是- b。也就是说5 个空格的缩进无论哪种模式都足以构成代码块变化的只是整个列表的嵌套拓扑。3. 有序列表的续行复活。默认模式下not continuation4 空格因不足 6 空格而成为独立代码块开启扩展后续行缩进固定为 4 空格continuation恰好满足条件于是它被并入1000. one这个列表项成为第二个段落AST 显示为[ Para [ Str one ] , Para [ Str continuation ] ]。这正是four_space_rule名称的由来——固定四空格与列表标记的长度无关。四、源码级原理continuationIndent是如何计算的要真正理解上述差异需要看读取器的核心实现。在 src/Text/Pandoc/Readers/Markdown.hs 中rawListItem函数负责解析单个列表项的原始文本并计算续行缩进rawListItem :: PandocMonad m Bool -- four space rule - MarkdownParser m a - MarkdownParser m (Text, Int) rawListItem fourSpaceRule start try $ do pos1 - getPosition start pos2 - getPosition let continuationIndent if fourSpaceRule then 4 else sourceColumn pos2 - sourceColumn pos1 ...关键逻辑只有一行若fourSpaceRule为真续行缩进固定为 4否则取列表标记起点列与标记结束后列之差即标记后首个非空白字符的列号。这个continuationIndent随后被传给listContinuation由它来决定哪些行属于当前列表项的续行listContinuation :: PandocMonad m Int - MarkdownParser m Text listContinuation continuationIndent try $ do x - try $ do notFollowedBy blankline notFollowedByHtmlCloser notFollowedByDivCloser gobbleSpaces continuationIndent anyLineNewline ...凡是以continuationIndent个空格开头的行都会被吞入当前项不足该缩进的行则不属于该项。这与测试 3511 中的两种结果完全对应。fourSpaceRule这个布尔值从何而来看bulletList与orderedList的入口bulletList :: PandocMonad m MarkdownParser m (F Blocks) bulletList do fourSpaceRule - (True $ guardEnabled Ext_four_space_rule) | return False items - fmap sequence $ many1 $ listItem fourSpaceRule bulletListStart ... orderedList :: PandocMonad m MarkdownParser m (F Blocks) orderedList try $ do ... fourSpaceRule - (True $ guardEnabled Ext_four_space_rule) | return (style Example) items - fmap sequence $ many1 $ listItem fourSpaceRule (orderedListStart (Just (style, delim))) ...guardEnabled Ext_four_space_rule是 Parsec 风格的扩展守卫扩展启用时返回True否则回退。注意orderedList中还有一个特殊分支当列表样式是Example即example_lists的标记列表时即使没有开启four_space_rule也强制按四空格规则解析。其原因在 MANUAL.txt 的example_lists一节有说明示例标签往往很长若要求内容对齐到标记后首字符列会非常笨拙因此示例列表永远表现得像开启了four_space_rule一样。Ext_four_space_rule本身的定义在 src/Text/Pandoc/Extensions.hs 中| Ext_four_space_rule -- ^ Require 4-space indent for list contents它隶属于allMarkdownExtensions集合因此可以在任意 markdown 变体上用four_space_rule显式开启而 pandoc 的默认 markdown 不启用它。五、与缩进代码块、定义列表的联动four_space_rule的影响不止于列表续行段落还波及缩进代码块和定义列表缩进代码块需 8 空格。开启该扩展后列表项内的缩进代码块必须相对页边距缩进 8 个空格列表标记占位 续行 4 列 代码块 4 列。这一点在 changelog.md 的历史说明中写得很清楚四空格规则下列表项内缩进的代码总是必须从页边距缩进 8 个空格而新规则只要求相对列表标记后首个非空白字符缩进 4 个空格即可。定义列表的缩进要求。在 MANUAL.txt 的Definition lists一节中定义内容要求缩进到:或~标记后首个非空白字符所在的列或者若启用four_space_rule扩展4 个空格或一个制表符位。对应到源码definitionListItem同样通过(True $ guardEnabled Ext_four_space_rule) | pure False决定传入listItem的规则。输出端同样受控。该扩展不仅作用于读取器也作用于 Markdown 写入器。在 src/Text/Pandoc/Writers/Markdown.hs 中let start case variant of Markua - * Commonmark - - Markdown | isEnabled Ext_four_space_rule opts - - T.replicate (writerTabStop opts - 2) ...当输出端启用该扩展时列表项标记后会补齐到 4 个空格的缩进位置writerTabStop默认 4有序列表项的续行缩进ind也直接取writerTabStop。回归测试 test/command/10812.md 验证了-t plainfour_space_rule下- a会被输出为- a标记补足到 4 列同时确认默认情况下不补齐。测试 test/command/11542.md 与 test/command/7172.md 则分别覆盖了markdownfour_space_rule的往返round-trip场景。六、历史背景为什么要保留这条旧规则four_space_rule并非 Pandoc 原创而是对 pandoc 2.0 之前旧解析行为的兼容开关。从 changelog.md 的 2.0 版本条目可以看到完整的来龙去脉pandoc 2.0 改变了列表续行缩进的解析规则从固定四空格改为对齐到列表标记后首个非空白字符列以兼容日益流行的 CommonMark 习惯这一变更由一批用户报告驱动changelog 中列出的 issue 编号包括 #3125、#2367、#2575、#2210、#1990、#1137、#744、#172、#137、#128典型痛点如- a后接 4 空格缩进内容在旧规则下必须缩进 8 空格才能被识别为代码块为照顾依赖旧行为的工作流2.0 同时新增了four_space_rule扩展触发旧的列表项内容解析规则并明确建议偏好旧行为的用户可以使用-f markdownfour_space_rule。因此今天的four_space_rule本质上是 Pandoc 与 CommonMark 式列表解析之间的兼容性开关关闭默认时对齐内容列、嵌套更直观、缩进代码块更宽松开启时回归 pandoc ≤ 2.0 的四空格规则嵌套列表必须缩进 4 空格列表内代码块必须缩进 8 空格。七、实践指南何时开启、如何验证综合以上分析给出可复现的实践建议默认模式推荐用于新文档。直接使用pandoc -t native input.md。此时嵌套子列表缩进到父项内容起始列即可如- a后接 2 空格子列表列表项内代码块相对内容起始列缩进 4 空格即可识别长有序列表标记如1000.的续行段落需要较多缩进容易被误判为代码块需留意。需要回归旧行为的场景。使用pandoc -f markdownfour_space_rule -t native input.md所有列表续行含嵌套子列表统一缩进 4 空格列表项内代码块需缩进 8 空格长标记有序列表的续行只需 4 空格段落归属更稳定。输出端对称控制。生成 Markdown/plain 时用-t markdownfour_space_rule或-t plainfour_space_rule让写出内容与读取端规则保持一致便于往返一致。验证手段。本文分析的 test/command/3511.md 本身就是最直接的验证样本将其中两段输入分别用对应命令转换得到的nativeAST 应逐字匹配测试文件中的期望输出。此外 test/command/10812.mdplain 写入器、test/command/11542.md 与 test/command/7172.mdmarkdown 往返共同构成了该扩展的完整测试矩阵可作为自定义排查时的参照。结语一个看似不起眼的缩进规则背后串联了读取器续行缩进计算、扩展守卫、写入器对称处理与长达数年的兼容性设计。通过 test/command/3511.md 这一对黄金测试Pandoc 把默认对齐内容列与旧版固定四空格两套行为同时固化了下来。理解continuationIndent的计算方式src/Text/Pandoc/Readers/Markdown.hs 中rawListItem的sourceColumn差值逻辑你就能在遇到任何列表里多了一行代码块或嵌套层级莫名断裂的问题时迅速判断出是缩进列数不满足规则所致并用-f markdownfour_space_rule或调整缩进轻松解决。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表