ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Pandoc MediaWiki 写出器如何判定外部链接与内链:从回归测试 11562 看畸形 URL 的容错处理

Pandoc MediaWiki 写出器如何判定外部链接与内链:从回归测试 11562 看畸形 URL 的容错处理 Pandoc MediaWiki 写出器如何判定外部链接与内链从回归测试 11562 看畸形 URL 的容错处理【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc导读本文以 pandoc 仓库中的回归测试 test/command/11562.md 为线索深入剖析 MediaWiki 写出器Writer对外部链接与 wiki 内链的分类判定逻辑当链接目标 URL 带有#/#、##这类技术上不合法的 fragment 时pandoc 如何保证其仍被正确输出为外部链接[url 文本]而不是被误判为[[目标|文本]]内部链接。读完本文你将掌握 pandoc 命令测试command test的格式与运行方式、MediaWiki 链接语法的底层判定源码src/Text/Pandoc/Writers/MediaWiki.hs以及 URI 合法性检查isURI的实现要点。回归测试 11562一个畸形 URL 分类问题的样本在 pandoc 的测试体系中test/command/目录下存放了大量以数字命名的命令测试文件每个文件就是一个端到端的回归用例。11562这个编号对应 pandoc 的 issue/PR 编号用于防止曾经修复过的 bug 再次出现。test/command/11562.md 的全部内容如下% pandoc -t mediawiki [example](https://example.com/#/#room) [example](https://example.com/##) ^D [https://example.com/#/#room example] [https://example.com/## example]测试意图一目了然输入两个 Markdown 链接目标地址分别是https://example.com/#/#room与https://example.com/##其 fragment#之后的部分分别是/#room和#都包含多余的#字符属于技术上不合法的 URL输出使用pandoc -t mediawiki转换后两者都应被渲染为 MediaWiki 的外部链接语法[https://example.com/#/#room example]与[https://example.com/## example]。而变更日志 changelog.md 明确记录了该测试对应的修复MediaWiki writer: Handle some technically malformed URLs (#11562).也就是说这个测试文件是那次 bug 修复的验收凭证。命令测试格式读懂测试文件的前提要理解上面的测试文件需要先了解 pandoc 命令测试的统一格式。测试运行框架定义在 test/Tests/Command.hs代码块的第一行%之后是要执行的命令如pandoc -t mediawiki之后是若干行将被作为 stdin 传给命令的输入文本stdin 以单独一行^D结束^D之后的行是期望的 stdout 输出若期望有 stderr 输出每行需以2前缀标注若期望非零退出码最后一行需以加状态码结尾。因此 11562 测试的语义是把两个 Markdown 链接送入 MediaWiki 写出器期望得到两行外部链接格式的输出。问题本质MediaWiki 外链与内链是两种语法MediaWiki 链接语法对外部与内部有严格的区分外部链接[完整URL 显示文本]如[https://example.com/page 示例]由方括号加空格分隔内部链接wikilink[[目标页面|显示文本]]或[[目标页面]]指向本 wiki 内的页面管道符|后是可选的显示文本。这两种语法在渲染行为上完全不同。如果 pandoc 把一个外部 URL 错误地输出成[[https://example.com/...|example]]MediaWiki 会把它当成一个指向同名 wiki 页面或帮组页的内部链接导致链接失效。因此写出器必须准确地判定链接目标到底是不是一个外部 URI。修复实现先截断 fragment再做 URI 判定MediaWiki 写出器的链接分支位于 src/Text/Pandoc/Writers/MediaWiki.hsinlineToMediaWiki (Link _ txt (src, _)) do -- We need to remove links from link text, because an a element is -- not allowed inside another a element. label - inlineListToMediaWiki (removeLinks txt) let label render Nothing label case txt of [Str s] | isURI src escapeURI s src - return $ literal src _ - return $ literal $ if isURI (T.takeWhile (/ #) src) -- see #11562 then [ src label ] else if src label then [[ src ]] else [[ src | label ]] -- with leading / its a link to a help page where src fromMaybe src $ T.stripPrefix / src关键的修复点正是注释标注的isURI (T.takeWhile (/ #) src)先取 URL 中第一个#之前的全部内容即 scheme 与 host/path 部分再对它做 URI 合法性检查。以https://example.com/#/#room为例若直接对完整 URL 调用isURI包含#/#的 fragment 会被 URI 解析器判定为技术上不合法的 URI检查结果为False写出器就会落入[[src|label]]内链分支产生错误输出截断后得到https://example.com/这是完全合法的 URIisURI返回True于是输出外部链接[https://example.com/#/#room example]。同理https://example.com/##截断后同样得到https://example.com/被正确归类为外链。这也是 changelog.md 所称的handle some technically malformed URLs——问题不在 URL 本身是否完美而在于决定链接分类的关键判断不应被 fragment 部分的合法性所干扰。背后的判定器isURI 的实现isURI定义在 src/Text/Pandoc/URI.hs其逻辑是isURI :: T.Text - Bool isURI t -- If its a base 64 data: URI, avoid the expensive call to parseURI: case A.parseOnly (pBase64DataURI * A.endOfInput) t of Right () - True Left _ - -- we URI-escape non-ASCII characters because otherwise parseURI will choke: maybe False hasKnownScheme . parseURI . escapeURIString isAscii . T.unpack $ t where hasKnownScheme (Set.member schemes) . T.toLower . T.filter (/ :) . T.pack . uriScheme从中可以提取两个事实两阶段检查先快速匹配 base64 形式的data:URI避免昂贵的通用解析否则调用Network.URI.parseURI做完整解析白名单校验解析成功后还要求 scheme 属于 schemes 集合——该集合来自 IANA 官方 URI scheme 注册表并额外补充了doi、javascript、isbn、pmid等非官方但常用的 scheme。此外该模块还提供escapeURI见 src/Text/Pandoc/URI.hs用于转义空格及|{}[]^ 等字符。这解释了链接分支的第一个 case当链接文本本身就是一个合法的、且与转义后的 URL 完全一致的 URI 时[Str s] | isURI src escapeURI s src写出器直接输出裸 URL不带括号和文本避免冗余。链接输出的完整分支一张决策表结合 MediaWiki.hs 的源码可以整理出 MediaWiki 写出器对链接的完整决策逻辑条件输出形式说明链接文本是单个Str且isURI src成立、文本等于escapeURI src裸 URLhttps://example.com/x文本与地址相同省略外链包装isURI (T.takeWhile (/ #) src)成立#11562 修复外链[src label]只要 schemehost/path 合法即视为外部链接src label且不满足外链条件内链[[src]]目标与显示文本相同省略管道其余情况内链带文本[[src\|label]]标准 wikilink目标以/开头剥离前导/后再按内链处理MediaWiki 中前导/表示指向帮助页面同时注意写出器还会调用removeLinks剥离链接文本内部的嵌套链接源码注释HTML 不允许a嵌套aMediaWiki 亦然。如何运行与验证该测试该测试属于 pandoc 的命令测试套件。仓库中test/command/下的每个.md文件会被 test/Tests/Command.hs 加载为一个用例并通过 test/test-pandoc.hs 汇总执行。其执行机制值得注意见 test/Tests/Command.hs测试运行时会调用test-pandoc --emulate来模拟真实的pandoc命令行而不是直接调用系统安装的 pandoc 二进制从而保证测试针对的是当前仓库源码编译出的版本。构建并运行整个测试套件的方式为make test也可以只构建测试二进制后单独执行cabal build test-pandoc cabal run test-pandoc -- -p command # 或直接运行全部命令测试若要手动复现测试行为使用仓库构建出的 pandoc 执行printf [example](https://example.com/#/#room)\n\n[example](https://example.com/##)\n \ | pandoc -t mediawiki输出应与 test/command/11562.md 中^D之后的两行完全一致。小结回归测试 11562 虽小却完整展示了 pandoc 的一条质量保障链路社区报告畸形 URL 导致的错误输出 → 写出器修复先截断 fragment 再做isURI判定→ 以命令测试固化行为 → 在变更日志中留档。对于使用 pandoc 做 Markdown 到 MediaWiki 转换的开发者理解[url label]与[[target|label]]的判定边界能够准确预判含 fragment、锚点等特殊字符的链接在转换后的呈现形态也便于在遇到链接分类异常时快速定位到 src/Text/Pandoc/Writers/MediaWiki.hs 中的对应分支。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进