☰
mdBook 列表渲染全解析:从有序/无序列表到嵌套与起始序号
2026/10/5 3:58:58 网站建设 项目流程
  • 开发工具
  • 文档

【免费下载链接】mdBook

Create book from markdown files. Like Gitbook but implemented in Rust

项目地址:https://gitcode.com/gh_mirrors/md/mdBook
点击查看免费下载

mdBook 使用 Rust 生态成熟的pulldown-cmark解析器将 Markdown 章节转换为 HTML。本文以仓库测试套件中的 lists.md 测试文档为样本,逐段剖析有序列表、无序列表、嵌套列表、起始序号与混合嵌套在 mdBook 中的实际渲染行为,并结合源码级实现(解析选项、事件流、树构建、HTML 序列化)讲清每一个输出细节,帮助你在编写书籍章节时写出可预期、可验证的列表语法。

一、测试样本与验证方式

lists.md是 mdBook 基础 Markdown 渲染测试的一部分,位于tests/testsuite/markdown/basic_markdown/src/目录,同一目录下还包含blank.md、blockquotes.md、code-blocks.md、inlines.md、links.md、images.md、html.md、svg.md等兄弟用例,由SUMMARY.md汇总为名为basic_markdown的测试书。

该测试书通过 tests/testsuite/markdown.rs 中的basic_markdown测试驱动:

#[test] fn basic_markdown() { BookTest::from_dir("markdown/basic_markdown").check_all_main_files(); }

check_all_main_files()会把src/下每个章节经 mdBook 完整渲染得到的book/*.html与 expected 目录中的期望输出逐字节比对。也就是说,本文讲解的每一条列表行为都有"输入 Markdown → 期望 HTML"的成对证据,你可以直接在仓库中复现验证。

二、渲染链路:从 Markdown 到 HTML 的三步管线

要理解列表为何渲染成这样,先看清 mdBook 的 HTML 渲染流程。在 crates/mdbook-html/src/html/mod.rs 的模块文档中明确写明了三步走:

  1. 使用pulldown_cmark解析 Markdown,产生事件(Event)流;
  2. tree模块把这些事件转换为中间树结构(Tree<Node>),期间还会做增加标题锚点链接等变换;
  3. serialize模块把树序列化为最终 HTML。

核心入口是render_markdown,它内部先调用build_tree生成树,再交给serialize:

pub(crate) fn render_markdown(text: &str, options: &HtmlRenderOptions<'_>) -> String { let tree = build_tree(text, options); let mut output = String::new(); serialize::serialize(&tree, &mut output); output }

build_tree的关键在于构造解析器(见 crates/mdbook-html/src/html/mod.rs):

fn build_tree(text: &str, options: &HtmlRenderOptions<'_>) -> Tree<Node> { let events = new_cmark_parser(text, &options.markdown_options); tree::MarkdownTreeBuilder::build(options, events) }

而new_cmark_parser来自独立的 crates/mdbook-markdown/src/lib.rs,它配置了 pulldown-cmark 的全部扩展选项:

pub fn new_cmark_parser<'text>(text: &'text str, options: &MarkdownOptions) -> Parser<'text> { let mut opts = Options::empty(); opts.insert(Options::ENABLE_TABLES); opts.insert(Options::ENABLE_FOOTNOTES); opts.insert(Options::ENABLE_STRIKETHROUGH); opts.insert(Options::ENABLE_TASKLISTS); opts.insert(Options::ENABLE_HEADING_ATTRIBUTES); if options.smart_punctuation { opts.insert(Options::ENABLE_SMART_PUNCTUATION); } if options.definition_lists { opts.insert(Options::ENABLE_DEFINITION_LIST); } if options.admonitions { opts.insert(Options::ENABLE_GFM); } Parser::new_ext(text, opts) }

列表本身是 CommonMark 规范的核心语法,由pulldown_cmark基础能力保证;上述扩展选项只影响表格、脚注、任务列表等外围特性。随后MarkdownTreeBuilder(见 crates/mdbook-html/src/html/tree.rs)将解析事件组装成中间树,序列化阶段再把<ol>、<ul>、<li>等节点输出为 HTML。

三、有序列表:自动编号与嵌套

lists.md的前两个用例验证有序列表(ordered list)的两种形态。

3.1 普通有序列表

输入:

1. A 2. Normal 3. Ordered 4. List

期望输出(expected/lists.html):

<ol> <li>A</li> <li>Normal</li> <li>Ordered</li> <li>List</li> </ol>

注意输出中每个<li>之间没有额外的<p>包裹——这是 mdBook(更准确地说是 pulldown-cmark)对"紧凑列表"(tight list)的处理方式:当列表项内容不含空行分隔的段落时,直接输出裸文本节点,不生成段落标签。这是与许多所见即所得编辑器不同的细节,值得在比对渲染结果时留意。

3.2 嵌套有序列表

输入:

1. A 1. Nested 2. List 2. But 3. Still 4. Normal

期望输出:

<ol> <li>A <ol> <li>Nested</li> <li>List</li> </ol> </li> <li>But</li> <li>Still</li> <li>Normal</li> </ol>

这里展示了两个关键行为:

  • 缩进即嵌套:子列表项以 3 个空格缩进,pulldown-cmark 依据缩进量(CommonMark 要求列表嵌套缩进至少 2 个空格,实际写法常为 4 或与父项内容对齐)识别出这是一个嵌套<ol>;
  • 嵌套不影响编号延续:外层列表的计数从嵌套子列表结束后继续,But、Still、Normal依次为第 2、3、4 项,编号不受子列表"插队"干扰。

四、起始序号:尊重你写的第一个数字

第三个用例是lists.md中最具特色的部分,输入为:

7. Start list 7. with a different number.

期望输出:

<ol start="7"> <li>Start list</li> <li>with a different number.</li> </ol>

这是 CommonMark 的一个经典行为:有序列表的起始序号取列表第一项的数字,后续项的编号自动递增,而非逐项采用你写下的字面数字。因此第二行即使也写7.,渲染结果仍是第 8 项,且整个列表元素被赋予start="7"属性。HTML 规范规定<ol>的start属性用于声明起始值,浏览器据此从 7 开始显示编号。

这个用例的存在意味着:如果你在书籍中手写序号错乱(例如从 3 开始、下一项写成 5),mdBook 不会保留这种"错位",而是以首项为基准连续编号。想让列表从特定数字开始,只需保证第一项的编号正确即可。

五、无序列表:-标记与嵌套

5.1 普通无序列表

输入:

- An - Unordered - Normal - List

期望输出(expected/lists.html):

<ul> <li>An</li> <li>Unordered</li> <li>Normal</li> <li>List</li> </ul>

pulldown-cmark 支持-、*、+三种无序列表标记符,这里统一使用-。与有序列表相同,紧凑列表不产生<p>包裹。

5.2 嵌套无序列表

输入:

- Nested - Unordered - List

期望输出:

<ul> <li>Nested <ul> <li>Unordered</li> </ul> </li> <li>List</li> </ul>

子项- Unordered缩进 2 个空格,被识别为内层<ul>,且嵌套在<li>Nested内部而非作为独立的兄弟列表输出——这正是"列表嵌套"与"列表分段"的差别:只有缩进足够的行才会被并入上层列表项的子树。

六、混合嵌套:无序包有序

最后一个用例验证列表类型可以自由混用:

- This 1. Is 2. Normal - ?!

期望输出:

<ul> <li>This <ol> <li>Is</li> <li>Normal</li> </ol> </li> <li>?!</li> </ul>

无序列表项内部嵌入有序子列表,marker(-vs1.)决定了内层是<ul>还是<ol>。这在编写操作步骤时非常实用:外层用-组织一组相关任务,内层用1./2.表达任务内部的执行次序。注意子列表依然没有<p>包裹,整体保持紧凑输出。

七、MDBook 列表渲染的行为总结

综合六个用例,可以归纳 mdBook 列表渲染的几条确定规则:

输入特征渲染结果证据
1./2.逐项编号<ol>连续编号,忽略字面数字错位lists.md
首项从 7 开始<ol start="7">,后续自动 +1lists.html
子项缩进 2~4 空格嵌套<ol>/<ul>,挂在父<li>内部lists.md
-/*/+标记<ul>无序列表lists.md
无序内嵌有序外层<ul>、内层<ol>lists.md
紧凑列表(无空行)<li>内不产生<p>包裹全部期望输出

八、列表之外的关联能力:扩展列表语法

掌握基础列表后,mdBook 还提供了两个与列表相关的扩展,它们同样由new_cmark_parser的选项开关控制,在 crates/mdbook-core/src/config.rs 中有对应配置字段:

  • 任务列表(Task lists):通过Options::ENABLE_TASKLISTS启用,- [ ]/- [x]会被渲染为带复选框的列表项(<input disabled="" type="checkbox">),适合做待办清单;
  • 定义列表(Definition lists):通过Options::ENABLE_DEFINITION_LIST启用,术语独占一行、定义以:开头,可生成<dl>词汇表结构,具体语法参见 guide/src/format/markdown.md。

两者默认开启(smart_punctuation、definition_lists、admonitions在 crates/mdbook-markdown/src/lib.rs 中均为true),并可通过book.toml中output.html的对应配置项关闭。你可以像下面这样显式控制:

[output.html] definition-lists = true smart-punctuation = true

九、如何在本地复现验证

若想亲自动手验证上述渲染结果,可以按以下步骤操作(仓库只读,以下均为查看/构建/运行命令):

  1. 在仓库根目录构建 mdBook:cargo build;
  2. 将tests/testsuite/markdown/basic_markdown目录复制到临时目录,或直接基于该目录运行cargo run -- build tests/testsuite/markdown/basic_markdown(构建输出默认写入book/目录,注意这会与期望文件目录并存,比对时以src/为输入即可);
  3. 用diff tests/testsuite/markdown/basic_markdown/expected/lists.html <输出目录>/lists.html对照期望 HTML;
  4. 更省事的方式是直接运行完整测试:cargo test --test testsuite basic_markdown(对应 tests/testsuite/markdown.rs 中的basic_markdown用例),它会自动完成"渲染 → 与期望文件比对"的全流程。

十、小结

通过lists.md这个精炼的测试样本,我们完整验证了 mdBook 列表渲染的六种形态:普通有序、嵌套有序、自定义起始序号、普通无序、嵌套无序、无序嵌套有序。其背后是从pulldown-cmark解析、MarkdownTreeBuilder建树到serialize输出的完整管线。对书籍作者而言,核心要点只有三条:编号以首项为准自动连续、缩进控制嵌套层级、紧凑列表不产生段落标签。掌握了这些,你在 mdBook 中书写的每一个列表都能精确预知其在最终 HTML 中的样子。

  • 开发工具
  • 文档

【免费下载链接】mdBook

Create book from markdown files. Like Gitbook but implemented in Rust

项目地址:https://gitcode.com/gh_mirrors/md/mdBook
点击查看免费下载

相关推荐

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询