ARTICLE DETAIL

资讯详情

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

Pandoc 定义列表双向转换实战:HTML `<dl>` 与 MediaWiki `;`/`:` 语法的机制与验证

Pandoc 定义列表双向转换实战:HTML `<dl>` 与 MediaWiki `;`/`:` 语法的机制与验证 Pandoc 定义列表双向转换实战HTMLdl与 MediaWiki;/:语法的机制与验证【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc导读本文以 pandoc 仓库中的命令测试 test/command/10708.md 为核心线索深入剖析 pandoc 在 HTML 与 MediaWiki 两种格式之间双向转换「定义列表definition list」的完整机制包括 MediaWiki 特有语法; 术语/: 定义的生成与解析、术语中冒号必须用nowiki:/nowiki转义的原因、简单列表与 HTMLdl标签两种输出策略的自动取舍以及如何在本地复现并扩展这类命令测试。读完本文你将能理解 pandoc 内部列表渲染的决策逻辑并掌握用命令测试驱动格式转换验证的实战方法。一、从一个命令测试说起10708 号测试在验证什么pandoc 的回归测试体系中有一类「命令测试command test」把真实命令行调用、标准输入和期望输出写进一个 Markdown 文件由测试框架自动执行并比对结果。文件 test/command/10708.md 正是其中一个用例它聚焦于定义列表在 HTML 与 MediaWiki 之间的往返转换% pandoc -f html -t mediawiki dl dtCase 1: Both subsets are non-empty/dt dd In this case, … /dd /dl ^D ; Case 1nowiki:/nowiki Both subsets are non-empty : In this case, …% pandoc -f mediawiki -t html ; term : definition ^D dl dtterm/dt dd definition /dd /dl两个用例分别验证了HTML → MediaWiki一段包含dt术语与dd定义的dl定义列表被转换为 MediaWiki 的; 术语/: 定义语法且术语中内嵌的冒号被转义为nowiki:/nowiki避免被误解析为术语与定义的分隔符。MediaWiki → HTML; term : definition这种术语与定义写在同一行的写法被正确还原为dldtterm/dtdddefinition/dd/dl。命令测试的文件格式本身在 test/Tests/Command.hs 的模块注释中有明确规定第一行以%开头是要执行的命令之后是传入标准输入的文本以单独一行^D终止再往下是期望输出。测试框架会遍历test/command/目录下所有.md文件逐一抽取并执行见 test/Tests/Command.hs。二、HTML → MediaWiki定义列表的降级输出策略2.1 MediaWiki 的定义列表语法MediaWiki 使用两种字符表达定义列表语法含义; 术语定义术语相当于 HTML 的dt: 定义术语的定义内容相当于 HTML 的dd多个定义行可以跟在同一个术语之后形成「一词多释」结构。这与 HTML 中dldt…/dtdd…/dd/dl的信息模型一一对应这正是 pandoc 能在两者之间无损转换的语义基础。2.2 术语中的冒号为何需要nowiki在 MediaWiki 语法中;开头的行里第一个冒号是术语与定义的分隔标记。因此当术语本身包含冒号如测试用例中的Case 1: Both subsets are non-empty时必须把术语内的冒号用nowiki:/nowiki包裹使解析器将其视为普通文本而不是分隔符。从源码看pandoc 的 MediaWiki 写入器在渲染定义列表术语时会专门开启一个状态位stInDefLabel并在输出Str内联元素时按冒号切分、逐一插入nowiki:/nowiki见 src/Text/Pandoc/Writers/MediaWiki.hsinlineToMediaWiki (Str str) do inDefLabel - gets stInDefLabel return $ literal $ if inDefLabel then T.intercalate nowiki:/nowiki $ map escapeText $ T.splitOn : str else ...对应地definitionListItemToMediaWiki在渲染标签label之前先设置stInDefLabel True渲染完成后再复位src/Text/Pandoc/Writers/MediaWiki.hs。于是Case 1: Both subsets are non-empty输出为; Case 1nowiki:/nowiki Both subsets are non-empty与测试期望完全一致。2.3 定义内容的行首缩进同一个函数还决定了定义的输出形态术语行以当前的列表标记顶层为;开头定义行以「去掉首字符的标记 :」开头顶层即空串 :定义内容逐行列出marker - asks listLevel return $ literal (T.pack marker) space chomp labelText cr vcat (map (\d - literal (T.pack (init marker)) literal : chomp d) contents)因此测试输出中的: In this case, …正是dd内容转换后的结果。这也解释了为什么在 MediaWiki 输出中术语与定义分行书写HTML 输入里dt与dd本身就是分块的。三、MediaWiki → HTML定义列表的读取与还原3.1 从;/:到DefinitionListMediaWiki 读取器在 src/Text/Pandoc/Readers/MediaWiki.hs 中定义了定义列表的解析definitionList :: PandocMonad m MWParser m Blocks definitionList B.definitionList $ many1 defListItem defListItem :: PandocMonad m MWParser m (Inlines, [Blocks]) defListItem try $ do terms - mconcat . intersperse B.linebreak $ many defListTerm defs - if null terms then notFollowedBy ... * many1 (listItem :) else many (listItem :) return (terms, defs)defListTermL523-L529要求行首guardColumnOne以;开头跳过空白后持续读取内联内容直到遇到冒号或换行才停止定义部分由listItem :解析而listItem中有一个关键规则L544-L546guardColumnOne | guard (c :) -- def can start on same line as term这行注释正是测试用例 2 的机制核心定义允许与术语处在同一行。所以; term : definition中defListTerm在冒号前停下得到术语term随后listItem :在同一行继续解析出定义definition最终重组为 pandoc 的DefinitionList [(term, [definition])]再经由 HTML 写入器输出dldtterm/dtdddefinition/dd/dl。3.2nowiki的读取还原反向路径同样成立读取器在 src/Text/Pandoc/Readers/MediaWiki.hs 的inlineTag分支中识别nowiki标签将其包裹的原始字符含实体解码作为纯文本内联内容返回TagOpen nowiki _ - try $ do (_,raw) - htmlTag (~ tag) if T.any ( /) raw then return mempty else B.text . fromEntities $ manyTillChar anyChar (htmlTag (~ TagClose (nowiki :: Text)))因此写入器生成的Case 1nowiki:/nowiki Both subsets are non-empty再读回 HTML 时冒号会恢复为术语中的普通字符DefinitionList语义不丢失——测试用例 1 的输出可作为 MediaWiki 输入二次往返结果依旧稳定。四、简单列表判定何时用;/:何时退回 HTML 标签细心的读者会注意到MediaWiki 写入器并非总是把定义列表输出为;/:语法。在 src/Text/Pandoc/Writers/MediaWiki.hs 中blockToMediaWiki对DefinitionList的处理是二选一tags - (|| not (isSimpleList x)) $ asks useTags if tags then ... literal dl ... literal /dl ... else ... 使用 ; 与 : 标记 ...也就是说当且仅当isSimpleList x成立时才使用简洁的 wiki 标记否则退回 HTMLdl/dt/dd标签。判定的核心在 src/Text/Pandoc/Writers/MediaWiki.hsisSimpleList (DefinitionList items)要求所有定义内容concatMap snd items都是简单列表项isSimpleListItem单项定义必须是Plain、Para或者一个本身也是简单列表的嵌套列表若定义由「段落 嵌套列表」两块组成[x, y]且x为段落也算简单其余情况如定义中含引用块、代码块、表格等复杂块一律视为非简单使用 HTML 标签输出保证信息不因语法降级而丢失。测试用例 1 的定义内容In this case, …是纯段落因此命中了简单路径输出为;/:语法读者可以自行把dd换成blockquote试一下观察输出如何自动切换到dl标签形态。五、如何在本地复现与运行这些命令测试5.1 手动复现两条转换命令如果本机已构建好 pandoc或构建目录下的test-pandoc可执行文件可以直接复现测试中的两条命令# 用例 1HTML 定义列表 → MediaWiki echo dl dtCase 1: Both subsets are non-empty/dt dd In this case, … /dd /dl | pandoc -f html -t mediawiki # 用例 2MediaWiki 定义列表 → HTML echo ; term : definition | pandoc -f mediawiki -t html预期输出分别与 test/command/10708.md 中^D之后的内容一致。5.2 通过测试套件运行命令测试由 test/Tests/Command.hs 统一驱动它读取test/command/下所有.md文件用 golden-test 框架将真实运行结果与文件中的期望输出逐字节比对L101-L129。其中execTest会把命令中的pandoc替换为test-pandoc --emulateL72-L78以纯 Haskell 模拟完整 CLI 行为。运行全部命令测试cabal test pandoc:test-pandoc --test-options-p Command # 或使用 stack stack test pandoc:test-pandoc --test-options-p Command新增类似用例也非常简单在test/command/下新建一个.md文件按「%命令 → stdin →^D→ 期望输出」的格式书写即可无需改动任何测试代码也可以仿照 test/Tests/Command.hs 的注释示例用2前缀表达期望的 stderr 输出、用 退出码表达期望的非零退出状态。六、围绕定义列表的更多边界情况围绕这一主题pandoc 的读取器与写入器还处理了若干边界情况了解它们有助于写出更健壮的转换只有定义、没有术语的条目defListItem中if null terms分支专门处理「无dt的dd」形态此时直接要求至少一个:定义行src/Text/Pandoc/Readers/MediaWiki.hs并用B.definitionList [(mempty, contents)]表达空术语的条目L563。多级列表标记混排MediaWiki 的*、#、;、:可以连续书写表示嵌套层级如*:…、;;…。listItem通过extras - many (try $ char c * lookAhead listStartChar)累积重复标记并递归解析子列表src/Text/Pandoc/Readers/MediaWiki.hs而写入器侧则用listLevel累加;前缀来还原嵌套src/Text/Pandoc/Writers/MediaWiki.hs。段首出现列表标记字符写入器在渲染Para时若内容以*、#、;、:等列表标记开头会前置nowiki/nowiki防止被误读为列表src/Text/Pandoc/Writers/MediaWiki.hs包含://的裸 URL 也会被整体nowiki包裹以免破坏链接语法L462-L464对应 issue #11834。结语通过 test/command/10708.md 这个精悍的测试文件我们可以一窥 pandoc 在「格式转换正确性」上的严谨设计写入器按isSimpleList判定选择;/:标记或dl标签并用stInDefLabel状态精确转义术语冒号读取器则通过guardColumnOne、同行动定义等规则无损还原。这种「先定信息模型DefinitionList再为每种输出语法做降级与转义」的思路正是 pandoc 作为通用标记语言转换器能够覆盖上百种格式组合的关键所在。读者若想继续深入可研读 src/Text/Pandoc/Writers/MediaWiki.hs 与 src/Text/Pandoc/Readers/MediaWiki.hs 的完整实现并参考 test/Tests/Command.hs 自行扩充属于自己的转换用例。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表