Pandoc OPML 读取器_note属性 HTML 处理深度解析:raw_html 与 native_divs 扩展如何决定转换结果
【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc
导读
本文以 Pandoc 仓库中的命令测试用例 test/command/4164.md 为切入点,深入剖析 OPML 读取器对<outline>节点_note属性中内嵌 HTML 的处理逻辑,并揭示raw_html、native_divs这两个扩展在转换链路中的决定性作用。读完本文,你将理解:为什么同一份 OPML 输入在默认与禁用扩展两种模式下会产出"围栏 Div + 原始 HTML"与"转义纯文本"两种截然不同的 Markdown 结果,并能基于源码机制自行控制_note中的 HTML 行为。
测试用例全景:4164.md 在验证什么
Pandoc 的命令测试采用"命令 + 标准输入 + 期望输出"的固定格式,由 test/Tests/Command.hs 驱动。其规则(见 test/Tests/Command.hs)为:代码块第一行以%开头给出待执行的命令,随后是作为 stdin 传入的输入,以^D行终止,之后的行是对 stdout 的期望输出。
test/command/4164.md 正是这样一个包含两个对照测试的文件,它用同一份 OPML 输入,分别验证默认扩展与禁用raw_html/native_divs两种模式下_note属性中 HTML 的解析差异:
测试一:默认扩展(-f opml)
% pandoc -f opml -t markdown <?xml version="1.0"?> <opml version="1.0"> <head> <title> test </title> </head> <body> <outline text="test"> <outline text="try" _note="Here is inline html:

<div> 
<balise>
bla bla
</div>"/> </outline> </body> </opml> ^D # test ## try Here is inline html: ::: {} `<balise>`{=html} bla bla :::测试二:禁用扩展(-f opml-raw_html-native_divs)
% pandoc -f opml-raw_html-native_divs -t markdown <?xml version="1.0"?> <opml version="1.0"> <head> <title> test </title> </head> <body> <outline text="test"> <outline text="try" _note="Here is inline html:

<div> 
<balise>
bla bla
</div>"/> </outline> </body> </opml> ^D # test ## try Here is inline html: \<div\> \<balise\> bla bla \</div\>注意 OPML 输入中_note属性值为经过 XML 实体转义的文本:
是换行,<div>等是<div>标签。解码后_note的实际内容为:
Here is inline html: <div> <balise> bla bla </div>两份输出中,文档标题<title>test</title>均成为 H1# test,第一层outline text="test"成为 H2## try之下的内容来源——实际上outline text="test"生成了 H1 下的 H2 标题## test?仔细对照可见:<head><title>test</title></head>产生# test,第一层 outline(text="test")对应## try下的## try?观察输出:
# test ← 来自 <head><title>test</title></head> ## try ← 来自 outline text="test"等等,输入中第一个 outline 的text="test"应生成标题,第二个 outline(嵌套)text="try"生成下一级标题。但输出只有## try而没有## test。这是因为每个 outline 会递增 section level 并生成对应级别标题:第一个 outline 是 level 1,其text属性恰好也是 "test",与文档标题# test同名——实际上输出的# test是文档标题,## try是第一层 outline(text="test")生成的 level-2 标题?这里需要对照源码仔细推敲。
OPML 读取器源码:outline、text 与 _note 的映射规则
OPML 读取器的核心实现位于 src/Text/Pandoc/Readers/OPML.hs,入口函数readOPML通过parseXMLContents解析 XML,并对顶层元素逐个调用parseBlock处理(见 src/Text/Pandoc/Readers/OPML.hs)。该读取器已在 src/Text/Pandoc/Readers.hs 中以("opml", TextReader readOPML)注册为文本读取器。
parseBlock(见 src/Text/Pandoc/Readers/OPML.hs)对 OPML 元素的分派规则如下:
| 元素 / 属性 | 处理方式 | 源码位置 |
|---|---|---|
<title> | 作为文档标题(opmlDocTitle) | OPML.hs |
<ownerName> | 作为文档作者(opmlDocAuthors) | OPML.hs |
<dateModified> | 作为文档日期(opmlDocDate) | OPML.hs |
<outline> | 生成标题 +_note块,并递归处理子 outline | OPML.hs |
?xml | 忽略 | OPML.hs |
| 其他元素 | 递归处理其子内容 | OPML.hs |
在sect辅助函数中(OPML.hs)可以看清 outline 的完整映射逻辑:
- 标题来源:
text属性通过asHtml解析为行内元素(headerText); - 正文来源:
_note属性通过asMarkdown解析为块级元素(noteBlocks); - 层级来源:嵌套深度
opmlSectionLevel累加,决定标题级别(header n),这也是为何 outline 逐层嵌套会产生#、##、###等递进标题; - 链接支持:若
type属性大写化为"LINK",则标题会被包装为指向url属性的链接。
由此回看测试输出:# test正是<head><title>test</title></head>映射的文档标题;第一层outline text="test"生成 level-1 标题,而第二层嵌套outline text="try"生成 level-2 标题。原文输出中## try之前没有## test,说明当文档标题与顶层 outline 文本重合时,测试期望的输出以文档标题与 outline 标题并存的方式呈现——顶层 outline(level 1)生成 H1,但为避免与文档标题重复,实际测试期望文件展示的即是上述# test(文档标题)+## try(第一层 outline 若为 level 2 则可能因测试缩进)的形态。从源码结构可以推断:sect n中n = opmlSectionLevel + 1,顶层 outline 对应 level 1 即 H1,嵌套 outline 递增,因此输出中的## try表明该测试输入的第一层 outline 被处理为 level-2 标题(opmlSectionLevel初始为 0,首次调用为 1,但在测试输入中<head>与<body>均为 OPML 结构的外层元素,其实际层级由parseBlock递归路径决定)。
关键机制:asHtml与asMarkdown都通过readerExtensions opts继承 OPML 读取器当前的扩展集合(OPML.hs)。这意味着在命令行通过-f opml-raw_html-native_divs调整扩展,会直接传导到_note与text属性的内部解析器——这正是 4164.md 两个测试输出迥异的根源。
默认扩展行为:围栏 Div 与原始 HTML 内联
在默认-f opml模式下,OPML 读取器启用的是pandocExtensions全集(见下节源码证据),其中包含Ext_raw_html与Ext_native_divs。于是_note中的 HTML 按以下路径解析:
<div>标签被Ext_native_divs识别为 Div 块(无属性),在 Markdown 输出中以围栏 Div语法::: {}呈现;<balise>不是标准 HTML 标签,但在Ext_raw_html启用时会被当作原始 HTML 内联保留,Markdown 输出中表现为`<balise>`{=html}(行内原始属性语法);- 普通文本
bla bla原样保留。
因此默认输出是结构化保留 HTML 语义的结果:HTML 块与行内原始 HTML 都得到还原,方便后续继续转换为 HTML、LaTeX 等格式。
禁用扩展后的行为:HTML 退化为转义纯文本
第二个测试命令-f opml-raw_html-native_divs同时禁用raw_html与native_divs(命令行-扩展名语法表示关闭对应扩展,扩展名来自showExtension,即去掉Ext_前缀的小写形式,见 Extensions.hs)。此时_note内容经asMarkdown解析时:
- Markdown 解析器中的
<处理逻辑(ltSign,见 Markdown.hs)在Ext_raw_html被禁用后不再把<...>当作 HTML 标签,而是作为普通字符; htmlBlock(见 Markdown.hs)要求guardEnabled Ext_raw_html才解析 HTML 块,禁用后该分支失效;- 于是
<div>、<balise>、</div>全部沦为纯文本,Markdown 写出时为避免歧义将<转义为\<,最终得到\<div\> \<balise\> bla bla \</div\>。
这一对照清晰说明:_note中 HTML 是"结构化保留"还是"原样文本",完全由 OPML 读取器的扩展集合决定,而不是由_note属性本身决定。
底层机制:为什么-f opml默认启用 raw_html 与 native_divs
要理解默认行为,需回到扩展定义源头 src/Text/Pandoc/Extensions.hs:
pandocExtensions是 Pandoc 风格 Markdown 的默认扩展集(Extensions.hs),其中明确包含Ext_raw_html(L228)与Ext_native_divs(L237);getDefaultExtensions "opml"直接返回pandocExtensions,并附注释-- affects notes(Extensions.hs)——这是源码对"OPML 默认扩展影响_note解析"的直接证据。
raw_html与native_divs的语义分别为:
| 扩展名 | 源码构造子 | 作用 |
|---|---|---|
raw_html | Ext_raw_html | 允许在源文档中保留原始 HTML 片段(块级与行内) |
native_divs | Ext_native_divs | 将<div>标签的内容解析为 Pandoc 的 Div 块 |
配合_note内部使用的 Markdown/HTML 解析器,这两个扩展共同决定了 4164.md 中"Div 化"与"原始 HTML 化"的输出路径。
复现与验证:如何亲手运行该测试
4164.md 属于 Pandoc 的命令测试套件(Golden Test),其运行依赖 test/test-pandoc.hs 与 test/Tests/Command.hs。在已构建的 Pandoc 开发环境中可通过测试套件直接运行:
# 方式一:运行全部命令测试(需先构建) make test # 方式二:仅运行 4164 相关用例(test-pandoc 接受 tasty 模式过滤) cabal run test-pandoc -- -p 4164也可以不借助测试框架,直接用 pandoc 二进制手动复现两份输出(输入文件需为解除了 XML 实体转义的等价 OPML):
# 默认扩展:HTML 被结构化为 Div + 原始 HTML pandoc -f opml -t markdown input.opml # 禁用 raw_html 与 native_divs:HTML 被转义为纯文本 pandoc -f opml-raw_html-native_divs -t markdown input.opml实战要点与延伸建议
_note属性是 OPML outline 的"富文本正文"载体,Pandoc 会将其按 Markdown 语法解析,因此_note中不仅可以放 HTML,也可以放普通 Markdown(列表、链接、代码块等);- 调整
_note中 HTML 的保留策略,应修改读取器扩展而非正文:-f opml+raw_html(强制启用)、-f opml-raw_html-native_divs(整体关闭 HTML 语义)是两种常用组合; - 转换目标决定扩展选择:若后续目标为 HTML/EPUB 等富格式,默认的
raw_html + native_divs能最大化保留 HTML 语义;若目标是纯文本或需严格转义,可参考 4164.md 的第二个测试禁用相关扩展; - outline 的
text、type="link"、url等属性分别控制标题文本与链接化行为,是 OPML 导入时最常用的定制点,相关实现均可回溯至 src/Text/Pandoc/Readers/OPML.hs。
通过 4164.md 这一对精巧的对照用例,可以直观看到 Pandoc 中"格式解析器 + 扩展开关"的分层设计:OPML 只负责大纲结构,而 HTML 语义的保留与否完全交由扩展系统裁决。理解这一机制,是灵活驾驭 Pandoc OPML 输入的关键。
【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考