Pandoc fenced_divs 扩展实战:用 `:::` 围栏语法编写可嵌套、带属性的 Div 块
发布时间:2026/9/19 4:52:51来源:尧图网络
Pandoc fenced_divs 扩展实战用:::围栏语法编写可嵌套、带属性的 Div 块【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc导读fenced_divs是 Pandoc Markdown 中一个非常实用的扩展extension它允许你使用连续的冒号:作为围栏在 Markdown 源文件中直接书写带标识符id、类名class和键值属性key-value的原生Div块并支持任意层级嵌套。本篇文章基于仓库中的官方回归测试用例 test/command/168.md配合 MANUAL.txt 的语法规范与 src/Text/Pandoc/Readers/Markdown.hs 的解析器实现完整讲解fenced_divs的围栏规则、属性语法、嵌套机制、与段落的关系、AST 输出形态以及它与native_divs、bracketed_spans等兄弟扩展的配合使用。读完本文你将能够熟练运用:::围栏在 Pandoc 工作流中组织提示框、侧边栏、区块容器等结构化内容并能读懂对应的原生 ASTnative AST输出。一、从回归测试看功能全貌test/command/168.md是 Pandoc 官方命令测试集command test中的一份用例文件。这类测试的格式为一组以包裹的 shell 会话先给出% pandoc ...命令随后是标准输入以^D结束再之后是该命令的预期标准输出。任何对解析器或写出器行为的改动都必须保证这些输出仍然匹配因此它们既是回归测试也是极佳的功能示例文档。该文件包含 3 个用例恰好覆盖了fenced_divs的三类典型场景用例输入特点验证要点用例一10 个冒号的开场围栏:::::::::: warning ::::::::::::围栏可含前后冒号与类名内容可包含有序列表与嵌套 Div用例二独立的一行:::不带属性的围栏不会被视为 Div保持为普通文本段落用例三5 个冒号的围栏::::: Warning无花括号的单词按类名处理Div 内容为多个段落下面逐一解读。二、基础围栏语法开场围栏、属性与闭场围栏2.1 用例一属性 嵌套 列表混合内容test/command/168.md的第一个用例输入如下:::::::::: warning :::::::::::: This is the warning! 1. list 2. another ::: {#myid .class keyval} nested div ::: :::::::::::::::::::::::::::::::对应的pandoc -t native输出为[ Div ( , [ warning ] , [] ) [ Para [ Str This , Space , Str is , Space , Str the , Space , Str warning! ] , OrderedList ( 1 , Decimal , Period ) [ [ Plain [ Str list ] ] , [ Plain [ Str another ] ] ] , Div ( myid , [ class ] , [ ( key , val ) ] ) [ Para [ Str nested , Space , Str div ] ] ] ]从中可以提炼出fenced_divs的核心语法规则这些规则在 MANUAL.txt 的 Extension: fenced_divs 一节 中有完整定义围栏由至少 3 个连续冒号构成。开场围栏的冒号串之后必须带有属性属性之后可以再跟一串连续的冒号即:::::::::: warning ::::::::::::这种前后夹击的写法。属性的写法与 fenced code blocks 完全一致既可以使用花括号{...}写完整属性也可以只写一个不带花括号的单词该单词会被当作**类名class**处理。用例一中的warning正是这种裸单词即类名的写法最终输出中Div的属性为(, [warning], [])——标识符为空、类名为[warning]、键值对为空。闭场围栏是一行至少 3 个连续冒号且不带属性。与 fenced code blocks 不同闭场围栏的冒号数量不需要与开场围栏一致——这正是用例一里10 个冒号开场、24 个冒号收场依然合法的原因。MANUAL 建议用不同长度的围栏区分嵌套层级便于视觉阅读。Div 内部是完整的块级内容用例一内部既有段落Para也有有序列表OrderedList还有嵌套的Div说明 Div 可以容纳任何块元素。2.2 用例三裸单词类名与多段落内容第三个用例验证了无花括号单词 类名以及 Div 内多个段落的情况::::: Warning Here is a paragraph. And another. :::::输出[ Div ( , [ Warning ] , [] ) [ Para [ Str Here , Space , Str is , Space , Str a , Space , Str paragraph. ] , Para [ Str And , Space , Str another. ] ] ]注意Warning被原样保留为类名大小写不转换Div 内的两个段落之间用空行分隔最终成为两个独立的Para块。这就是 Pandoc 中实现提示框 / admonition最常用的方式——给类名起一个语义化名称如warning、note、tip再在输出模板或 CSS 中针对该 class 定制样式。三、关键边界不带属性的围栏永远不是开场围栏第二个用例是最容易踩坑、也最值得深究的一个foo ::: bar输出并不是一个 Div而是[ Para [ Str foo , SoftBreak , Str ::: , SoftBreak , Str bar ] ]原因在 MANUAL.txt 中写得很清楚没有属性的围栏永远是闭场围栏。:::单独成行时没有属性因此不会被识别为开场围栏也就不会触发 Div 解析三个:::被当作普通行内文本中间以SoftBreak软换行连接整体构成一个段落。3.1 源码中的实现逻辑解析器 src/Text/Pandoc/Readers/Markdown.hs 中对应的核心函数是divFenced第 2195-2215 行divFenced :: PandocMonad m MarkdownParser m (F Blocks) divFenced do guardEnabled Ext_fenced_divs try $ do openpos - getPosition string ::: skipMany (char :) skipMany spaceChar attribs - attributes | ((\x - (,[x],[])) $ takeWhile1P (\x - x / x / \t x / \n x / \r)) ... blankline updateState $ \st - st{ stateFencedDivLevel stateFencedDivLevel st 1 } bs - mconcat $ many (notFollowedBy divFenceEnd block) divFenceEnd | (getPosition report . UnclosedDiv openpos) updateState $ \st - st{ stateFencedDivLevel stateFencedDivLevel st - 1 } return $ B.divWith attribs $ bs从这段源码可以确认三个关键事实开场围栏的属性是强制的。解析器在吃掉:::和后续冒号、空白后通过attributes | ...解析属性若花括号属性解析失败则退而取takeWhile1P抓取一个非空白单词作为类名。两者都失败即围栏后直接换行、没有任何属性时整个try分支回退:::行就不会被当作 Div 围栏——这与用例二的输出完全一致。属性语法与 fenced code blocks 共用。attributes与attribute第 644-653 行支持#id、.class、keyval与特殊属性四种形式因此 Div 属性天然支持{#myid .class keyval}这种混合写法见用例一中的嵌套 Div。嵌套通过围栏层级计数器实现。解析器用stateFencedDivLevel记录当前 Div 嵌套深度每进入一个开场围栏1每遇到闭场围栏-1。同时段落解析器para第 1083-1087 行会在divLevel 0时向前探测divFenceEnd从而在不要求空行的情况下也能正确地把闭场围栏之前的文本收进 Div 末尾段落配套的blanklines与notFollowedByDivCloser第 942-955 行则保证列表、段落等块元素在 Div 内部不会越界吞掉闭场围栏。此外若 Div 围栏打开后一直没有遇到闭场围栏解析器会报告UnclosedDiv错误——这也解释了为什么闭场围栏是不可省略的。四、嵌套 Div层级围栏的匹配规则fenced_divs支持任意层级的嵌套。MANUAL 给出的嵌套示例如下::: Warning :::::: This is a warning. ::: Danger This is a warning within a warning. ::: ::::::::::::::::::规则总结为两点开场围栏必须带属性如::: Warning因此带属性的围栏一定是开无属性的围栏一定是关二者永远不会混淆——这是嵌套能够成立的根本保证。闭场围栏的数量与开场不必相等。内层用:::3 个冒号关闭Danger外层用 18 个冒号关闭Warning解析器只看是否为一行连续的冒号不看长度匹配。这与 fenced code blocks 要求闭场冒号数 ≥ 开场数完全不同。用例一中的嵌套同样印证了这一点外层:::::::::: warning ::::::::::::内套了一个::: {#myid .class keyval}内层 Div 用:::关闭外层再用长冒号串关闭互不干扰。五、与其他扩展的配合fenced_divs在扩展生态中的位置fenced_divs只是 Pandoc 处理Div/Span的机制之一MANUAL.txt 的 Divs and Spans 一节 将其与另外两个扩展并列介绍native_divs允许在 Markdown 中直接书写divHTML 标签来创建原生Div。源码中对应的divHtml函数第 2173-2193 行会解析div id... class...的属性并生成B.divWith。与fenced_divs相比它更HTML 味而:::围栏更简洁、对 Markdown 结构更友好。bracketed_spans允许用[文本]{.class keyval}的形式创建带属性的Span行内容器语法与fenced_divs的属性写法一脉相承可以看作行内版的带属性容器。实际使用时可以这样分工需要包裹多个块级元素段落、列表、表格、甚至另一个 Div时用fenced_divs需要给一段行内文字加样式标记时用bracketed_spans需要在兼容 HTML 语义的场合直接用native_divs。六、动手验证如何在本仓库复现上述行为如果你本地已经能够构建 Pandoc构建方式见 INSTALL.md可以直接复现这份回归测试的全部输出# 复现用例一属性围栏 嵌套 Div 有序列表 pandoc -t native EOF :::::::::: warning :::::::::::: This is the warning! 1. list 2. another ::: {#myid .class keyval} nested div ::: ::::::::::::::::::::::::::::::: EOF # 复现用例二不带属性的 :: 不会被当作 Div pandoc -t native EOF foo ::: bar EOF # 复现用例三裸单词类名 多段落 pandoc -t native EOF ::::: Warning Here is a paragraph. And another. ::::: EOF也可以直接在仓库根目录运行命令测试套件用官方测试框架验证168.md中的全部 3 个用例cabal test pandoc-tests --test-options-p command # 运行全部 command 测试具体测试入口与选项以 test/test-pandoc.hs 和 Makefile 中的说明为准。七、实用技巧与常见陷阱基于前文的语法规则与源码行为整理出以下实践要点用裸单词类名做语义化容器::: warning生成的 AST 是Div (, [warning], [])在输出为 HTML 时对应div classwarning配合 CSS 即可实现提示框、引用框等视觉效果无需任何 HTML 侵入。需要 id 或键值属性时用花括号::: {#myid .class keyval}分别对应 AST 中的标识符、类名与键值对三部分在后续 Lua filter 或模板中可通过attr字段读取。闭场围栏长度随意但建议分层语法上不要求冒号数量匹配但为可读性建议外层用更长的冒号串。注意commonmark格式的差异MANUAL 明确提示commonmark 解析器不允许属性之后再跟冒号串::::: Warning ::::这种写法在 commonmark 下不合法仅 pandoc 自家 Markdown 支持。忘写闭场围栏会报错解析器会报告UnclosedDiv因此生成内容时务必成对书写。结语fenced_divs用极简的:::围栏把 HTML 中div的块级分组能力完整带进了 Markdown带属性开场、无属性收场、任意嵌套、长度自由匹配配合native_divs与bracketed_spans可覆盖从块级容器到行内标记的全部场景。本文以官方回归测试 test/command/168.md 为骨架、以 MANUAL.txt 为语法规范、以 Markdown 读取器源码 为底层证据完整还原了该扩展的解析行为与边界情况。下次编写结构化 Markdown 时不妨用::: note或::: {#important .box}替代手写 HTML让源文件更干净、AST 更可控。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网