Pandoc `--embed-resources` 内联 SVG 与 class 属性合并机制解析:从命令测试 9652 看源码实现
Pandoc--embed-resources内联 SVG 与 class 属性合并机制解析从命令测试 9652 看源码实现【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc导读本文以 pandoc 仓库中编号 9652 的命令测试test/command/9652.md为切入点深入剖析--embed-resources选项在处理inline-svg类图片时的行为当img标签与所引用 SVG 文件同时携带class属性时pandoc 会如何合并二者。读完本文你将掌握--embed-resources的完整语义、inline-svg与use去重机制、class 属性合并规则以及对应的源码实现路径可直接复用于自己的 HTML/SVG 自包含构建与排障。一、背景--embed-resources与自包含 HTML1.1 选项语义--embed-resources[true|false]是 pandoc 用于生成“自包含”self-containedHTML 的开关。按 MANUAL.txt 的定义它产生一个无外部依赖的独立 HTML 文件通过data:URI 将链接的脚本、样式表、图片和视频内容内嵌进文档生成的文件无需外部文件、也无需联网即可在浏览器中正常显示仅对 HTML 输出格式生效包括html4、html5、htmllhs、html5lhs、s5、slidy、slideous、dzslides、revealjs绝对 URL 指向的脚本/图片/样式表会被下载相对 URL 资源则相对于工作目录首个源文件为本地时或相对于 base URL首个源文件为远程时查找带有data-external1属性的元素会被原样保留其链接内容不会被内嵌限制通过 JavaScript动态加载的资源无法内嵌因此--math-methodmathjax时字体可能缺失离线自包含的 reveal.js 中缩放、演讲者备注等高级特性可能失效。--self-contained是--embed-resources --standalone的已废弃同义词MANUAL.txt。1.2 SVG 的两种内嵌策略MANUAL 特别说明了对 SVG 图片的差异化处理MANUAL.txt普通 SVG生成data:URI 形式的img srcdata:...标签带inline-svg类的 SVG直接插入内联svg元素。当同一 SVG 在文档中出现多次时推荐使用这种策略因为 pandoc 会利用use元素引用来减少重复。命令测试 9652 正是围绕第二种策略构造的回归用例。二、命令测试 9652一个最小复现测试文件 test/command/9652.md 完整内容如下% pandoc -f markdown -t html --embed-resources {html} img classsomething inline-svg srccommand/9652.svg / ^D svg idsvg_b627f92299158b36552b roleimg width504.00pt height360.00pt viewBox0 0 504.00 360.00 classsomething inline-svg please-do-not-delete-me /svg这个命令测试文件是 pandoc 命令测试套件的标准格式第一行% pandoc -f markdown -t html --embed-resources声明待执行的命令行输入部分用 Markdown 的 fenced raw HTML 块{html}注入一个真实的img标签^D结束输入之后是期望输出pandoc 测试框架会将其与实际输出逐字节比对。该用例对应的 SVG 源文件为 test/command/9652.svg内容是一个空svg?xml version1.0 encodingUTF-8 ? svg xmlnshttp://www.w3.org/2000/svg xmlns:xlinkhttp://www.w3.org/1999/xlink classplease-do-not-delete-me width504.00pt height360.00pt viewBox0 0 504.00 360.00 /svg测试意图img的class是something inline-svgSVG 文件的class是please-do-not-delete-me。期望输出中内联后的svg同时携带两组 classsomething inline-svg please-do-not-delete-me——这正是 “Merge class attribute when both img and svg specify it”当 img 与 svg 都指定 class 属性时合并二者这一变更的回归验证见 changelog.md。三、源码实现class 合并与use去重3.1 整体管线--embed-resources的核心实现在Text.Pandoc.SelfContained模块src/Text/Pandoc/SelfContained.hs。入口函数makeSelfContained第 467-473 行将输入 HTML 用 TagSoup 解析为标签流初始化一个携带svgMap哈希 → (id, SVG 属性)与fetchCache资源抓取缓存的状态然后逐标签递归转换并重新渲染makeSelfContained :: PandocMonad m T.Text - m T.Text makeSelfContained inp do let tags parseTags inp let convertState ConvertState { svgMap mempty, fetchCache mempty } out - evalStateT (convertTags tags) convertState return $ renderTags out3.2 判定inline-svg在 convertTags 处理带源属性的标签时首先判定是否为内联 SVG 场景convertTags (t(TagOpen tagname as):ts) | any (isSourceAttribute tagname) as do let inlineSvgs tagname img case T.words $ lookup class as of Nothing - False Just cs - inline-svg elem cs即仅当标签是img且其 class 属性中按空白切分后含有inline-svg时才启用内联策略。isSourceAttribute覆盖了src、data-src、link标签的href、poster、data-background-image等源属性。3.3 抓取与哈希对每个源属性调用processAttribute第 210-224 行。当抓取结果 MIME 为image/svgxml且当前是内联场景时返回左值(hash, svgTags)Fetched (image/svgxml, bs) | inlineSvgs - do let hash T.pack $ take 20 $ show $ hashWith SHA1 $ B.filter (/\r) bs return $ Left (hash, getSvgTags (toText bs))注意两处细节哈希用SHA1对 SVG 原始字节计算并取前 20 个字符计算哈希前会过滤\r以保证 Windows 与非 Windows 平台上的测试结果一致。getSvgTags第 244-249 行会丢弃?xml声明、注释以及/svg之后的内容只保留svg.../svg片段。3.4 首次出现内联完整 SVG 并登记svgMap首次遇到某哈希时svgMap中无对应条目第 183-208 行将完整 SVG 内联到输出中并计算一个稳定的idSVG 自带 id 则沿用否则用svg_ hash随后把(id, 合并后属性)存入svgMapNothing - case dropWhile (not . isTagOpenName svg) tags of TagOpen svg svgattrs : tags - do let attrs combineSvgAttrs svgattrs svgImgAttrs let svgid case lookup id attrs of Just id - id Nothing - svg_ hash ... modify $ \st - st{ svgMap M.insert hash (svgid, attrs) (svgMap st) }这正是测试 9652 输出中svg idsvg_b627f92299158b36552b ...的由来该空 SVG 本身没有 id于是 pandoc 依据内容哈希生成svg_前缀的 id。此外为了规避同一文档中多个不同 SVG 内部锚点如fill:url(#gradient)的 id 冲突代码会给内部所有id、xlink:href、href以及url(#...)引用加上svgid_前缀addIdPrefix/fixUrl第 193-206 行。3.5 重复出现用use引用去重当同一哈希再次出现svgMap命中第 170-181 行不再重复内联而是输出一个引用首次定义 id 的空壳svgJust (svgid, svgattrs) - do let attrs [(k,v) | (k,v) - combineSvgAttrs svgattrs svgImgAttrs , k / id] return $ TagOpen svg attrs : TagOpen use [(href, # svgid), (width, 100%), (height, 100%)] : TagClose use : TagClose svg : rest这就是 MANUAL 中所说“use元素用来减少重复”的实现多份相同 SVG 在最终 HTML 中只有一份完整定义其余通过use href#svg_...引用。3.6 class 属性合并测试 9652 的核心断言无论首次还是重复出现最终属性都由combineSvgAttrs第 251-288 行计算。其合并逻辑为combinedAttrs [(k, v) | (k, v) - imgAttrs , k / class] [(k, v) | (k, v) - svgAttrs , isNothing (lookup k imgAttrs) , k notElem [xmlns, xmlns:xlink, version, class]] mergedClasses mergedClasses case (lookup class imgAttrs, lookup class svgAttrs) of (Just c1, Just c2) - [(class, c1 c2)] _ - []规则可归纳为img 属性优先img上的非class属性全部保留svg上已有的属性若img也指定则被img覆盖清理命名空间svg的xmlns、xmlns:xlink、version、class不直接透传class 合并本次变更核心当img与svg都带 class 时二者以空格连接合并为一个 class即c1 c2。套用到测试 9652img class something inline-svgsvg class please-do-not-delete-me合并结果 something inline-svg please-do-not-delete-me与期望输出完全一致。svg idsvg_b627f92299158b36552b roleimg width504.00pt height360.00pt viewBox0 0 504.00 360.00 classsomething inline-svg please-do-not-delete-me这里的roleimg与aria-label由addRole/addAriaLabel第 228-240 行补加——内联 SVG 会丢失img的 alt 文本因此用roleimg加aria-label取自 alt来维持可访问性。此外combineSvgAttrs还处理viewBox/width/height的推算第 253-261 行若 svg 有 viewBox 而无 width/height则从 viewBox 推导若 img 提供了 width/height 而 svg 无 viewBox则生成0 0 w h的 viewBox并去掉数值末尾的.0。四、围绕该测试的配套资源4.1 SVG 源文件测试引用的 test/command/9652.svg 是一个最小空 SVG只携带 class、width、height、viewBox 四个属性。它以classplease-do-not-delete-me命名本身就是为了验证即使 SVG 内部声明了 class内联后该 class 也不会被丢弃而是与 img 的 class 合并保留。4.2 变更记录佐证changelog.md 在Text.Pandoc.SelfContained条目下明确记录了这次行为变更Merge class attribute when both img and svg specify it (#9652, Carlos Scheidegger).同时在 changelog 的构建/测试条目中还有 “Fix command test for #9652”changelog.md说明该命令测试作为回归用例被纳入测试套件。4.3 测试套件运行方式pandoc 的命令测试由 test/test-pandoc.hs 驱动。test/command/目录下的*.md文件即为用例格式为“命令行 输入 ^D 期望输出”框架执行命令后将实际输出与期望输出比对。9652 用例的输入通过 Markdown raw HTML 块{html}注入原生img标签因为目标场景是“HTML 中已有 img 标签”这一自包含处理阶段而不是 Markdown 图片语法解析。五、实战验证与注意事项5.1 本地复现在仓库根目录执行与测试等价的命令即可本地复现需先构建 pandocpandoc -f markdown -t html --embed-resources EOF {html} img classsomething inline-svg srctest/command/9652.svg /EOF预期输出即为内联后的 svg其 class 为 something inline-svg please-do-not-delete-me。注意 src 需指向实际存在的 SVG 文件路径相对路径按工作目录解析。 ### 5.2 实操要点 1. **启用内联 SVG 必须加 inline-svg 类**普通 img 引用的 SVG 只会被转成 data: URI不会内联展开 2. **多实例去重**同一 SVG 多次出现时第二次起输出 use href#svg_hash节省文件体积——这是 MANUAL 推荐使用 inline-svg 的典型场景 3. **class 会叠加**img 与 svg 的 class 以空格拼接可能超出预期若不想保留 SVG 内部的 class需在源 SVG 中去掉 class 属性 4. **属性优先级**img 上的属性如 width/height优先于 SVG 内部属性可用于按引用位置微调尺寸 5. **可访问性**内联后 alt 文本转为 roleimg 与 aria-label请确保为内联 SVG 的 img 提供有意义的 alt 6. **数据 URI 兜底**非内联场景无 inline-svg 类下SVG 以 data:image/svgxml;base64,... 形式嵌入见 makeDataURI[src/Text/Pandoc/SelfContained.hs#L47-L58](https://link.gitcode.com/i/83ce8c49b035981e76ebf476eba22666#L47-L58)文本类 MIMEtext/*走 URI 转义而非 base64xml 后缀还会剔除 \r。 --- ## 六、小结 命令测试 [test/command/9652.md](https://link.gitcode.com/i/1dbed53bdbbb9184baf8b7b74b28b085) 虽然只有短短九行却精准锁定了 --embed-resources 处理 inline-svg 时的一个关键行为**img 与 svg 同时携带 class 属性时二者合并保留**。其背后是 [src/Text/Pandoc/SelfContained.hs](https://link.gitcode.com/i/83ce8c49b035981e76ebf476eba22666) 中 combineSvgAttrs 的 mergedClasses 逻辑以及与 SHA1 内容哈希、svgMap 状态、use 去重、role/aria-label 补全共同构成的一整套自包含 SVG 处理管线。理解这条测试也就理解了 pandoc 在离线 HTML 场景下对 SVG 资源的完整处理策略可作为排查“内联后 class 丢失 / 出现多余 class / 重复 SVG 体积膨胀”等问题的直接依据。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考