Parser::new 不认表格和脚注
主线位置:块主线的起点。
Parser::new_ext(text, opts)把opts交给第一遍,第一遍按位决定要不要试表格、脚注、任务框,要不要把~、|当成特殊字节。每一位对应什么扩展,总表在怎么读这一卷。
flowchart TB new["Parser::new"] --> empty["Options::empty"] empty --> fp["第一遍"] fp --> tbl["表格行进段落"] fp --> refdef["脚注定义成链接定义"] fp --> lit["~~ 和 [x] 是文字"] refdef --> a["[^1] 成了链接"]
实验在 exp-pulldown-cmark/pita-lab,bin p01_options(release),输出存在 out-p01.txt。M2 Max、macOS 27、rustc 1.99.0,pulldown-cmark 0.13.5。
现象
一段 GitHub 上看起来正常的文档:
| 名称 | 版本 |
| --- | --- |
| pulldown-cmark | 0.13.5 |
~~旧接口~~已删除[^1]。
- [x] 解析
- [ ] 渲染
[^1]: 见变更记录。
用 Parser::new 解析,交给 html::push_html:
<p>| 名称 | 版本 |
| --- | --- |
| pulldown-cmark | 0.13.5 |</p>
<p>~~旧接口~~已删除<a href="%E8%A7%81%E5%8F%98%E6%9B%B4%E8%AE%B0%E5%BD%95%E3%80%82">^1</a>。</p>
<ul>
<li>[x] 解析</li>
<li>[ ] 渲染</li>
</ul>
- 表格三行成了一个段落,竖线原样输出。
~~旧接口~~和两个任务框都是普通文字。- 脚注最奇怪:
[^1]变成了一个链接,地址是百分号编码后的见变更记录。,文末的脚注定义不见了。
没有报错,也没有警告,HTML 照样生成。
原因
Parser::new 只是一层转发:
pub fn new(text: &'input str) -> Self {
Self::new_ext(text, Options::empty())
}
Options::empty() 下只有 CommonMark。扩展在第一遍里一位一位地查:
- 表格:
parse_paragraph只在ENABLE_TABLES打开时用TableParseMode::Scan扫第一行,否则|连特殊字节都不是(special_bytes里|、~、$都按位加入)。 - 任务框:
parse_block开了列表项之后,ENABLE_TASKLISTS打开才调scan_task_list_marker。 - 脚注:同一个函数里,
ENABLE_FOOTNOTES打开才试parse_footnote。没打开时,[^1]: 见变更记录。落到链接引用定义的分支:^1是合法的链接标签,见变更记录。是合法的地址。于是它进了refdefs,正文的[^1]是一个快捷引用链接。定义怎么收、标签怎么比较,见表格与脚注。
这是有意的设计:扩展全是 Options 的位,默认什么都不开(设计取舍);Options::empty() 下 CommonMark 的 652 条例子全过(为什么是 pull parser)。
修法
用 new_ext,把要的扩展一个个写出来:
let opts = Options::ENABLE_TABLES
| Options::ENABLE_FOOTNOTES
| Options::ENABLE_STRIKETHROUGH
| Options::ENABLE_TASKLISTS;
html::push_html(&mut out, Parser::new_ext(doc, opts));
<table><thead><tr><th>名称</th><th>版本</th></tr></thead><tbody>
<tr><td>pulldown-cmark</td><td>0.13.5</td></tr>
</tbody></table>
<p><del>旧接口</del>已删除<sup class="footnote-reference"><a href="#1">1</a></sup>。</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
解析</li>
<li><input disabled="" type="checkbox"/>
渲染</li>
</ul>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>见变更记录。</p>
</div>
不要图省事写 Options::all()。它把所有位都打开,包括几个会改变普通文字的:
<p>正文<sup class="footnote-reference"><a href="#9">1</a></sup>。</p>
<p>原价<span class="math math-inline">5,现价</span>3</p>
<p>“quoted” – ok…</p>
三行输入是 正文[^9]。、原价$5,现价$3、"quoted" -- ok...。ENABLE_OLD_FOOTNOTES 也在里面,它同时置位 ENABLE_FOOTNOTES,于是脚注是旧风格,没有定义的 [^9] 也成了引用(见脚注的两种风格);ENABLE_MATH 把两个价格之间的文字变成公式(见价格变成了公式);ENABLE_SMART_PUNCTUATION 换掉了引号、连字符和省略号。以后的版本加了新位,all() 还会悄悄多开几样。
怎么检查
- 在代码里搜
Parser::new(。渲染用户写的 Markdown 时,它几乎总是错的;只想要纯 CommonMark 的地方,写成Parser::new_ext(text, Options::empty()),读代码的人就知道这是有意的。 - 给渲染函数写一条测试:输入里每种要用的扩展各放一个,断言输出里有
<table>、<del>、type="checkbox"、footnote-reference。上面那份文档就能当测试输入。 - 选项集中定义成一个常量,所有调用点共用,避免一处开了、另一处没开。
小结
Parser::new等于new_ext(text, Options::empty()),只认 CommonMark,表格、删除线、任务框、脚注都按普通文字处理。- 没开
ENABLE_FOOTNOTES时,[^1]: …是一条链接引用定义,[^1]成了链接,表面上看不出错。 - 修法是
new_ext加上明确列出的位;Options::all()会带进旧式脚注、数学和智能标点。