ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

pandoc 的 opendocument 交叉引用扩展(xrefs_name / xrefs_number)源码级解析

pandoc 的 opendocument 交叉引用扩展(xrefs_name / xrefs_number)源码级解析 pandoc 的 opendocument 交叉引用扩展xrefs_name / xrefs_number源码级解析【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc本篇文章基于 pandoc 官方命令行测试 test/command/6774.md围绕opendocument及odt写入器的两个扩展xrefs_name与xrefs_number展开。你将理解pandoc 如何把文档内部链接转换为真正的 OpenDocument 交叉引用字段、名称型与编号型引用各自的 XML 输出形态、二者组合时的优先级规则以及为什么生成的文档必须刷新后引用内容才会更新。适合需要定制 ODT/ODS 导出、或想深入理解 pandoc 扩展机制与写入器内部结构的开发者。一、背景从静态链接到动态交叉引用在 ODFOpenDocument文档里普通的文档内部链接例如指向某个章节标题的超链接是静态的即使标题文字或编号发生变化链接文本也不会自动跟随。而 ODF 规范提供了两类动态引用元素text:bookmark-ref引用文档中的书签bookmark通常用于引用章节标题text:sequence-ref引用编号序列sequence通常用于引用带编号的表格table与图片figure。pandoc 在 src/Text/Pandoc/Writers/OpenDocument.hs 的mkLink函数中实现了这两类元素的生成而是否启用、以何种格式输出则由两个扩展开关控制扩展声明位置行为xrefs_nameExtensions.hsExt_xrefs_name内部链接替换为交叉引用引用目标的名称或标题captionxrefs_numberExtensions.hsExt_xrefs_number内部链接替换为交叉引用引用目标的编号原链接文本被丢弃两个扩展的官方行为说明见 MANUAL.txt它们可启用/禁用的输出格式均为odt、opendocument并且可以相互组合。二、测试用例全景四种场景的输入与输出测试文件 test/command/6774.md 一共包含 4 个命令行测试输入均为native格式的 pandoc AST输出均为opendocument格式 XML。下面逐一复现并解读。场景一默认行为不启用任何扩展第一条命令没有任何扩展输出中内部链接仍是普通超链接% pandoc -f native -t opendocument --quiet [Header 1 (chapter1,[],[]) [Str The,Space,Str Chapter] ,Para [Str Chapter,Space,Str 1,Space,Str references,Space,Link (,[],[]) [Str The,Space,Str Chapter] (#chapter1,)]] ^D text:h text:style-nameHeading_20_1 text:outline-level1text:bookmark-start text:namechapter1 /The Chaptertext:bookmark-end text:namechapter1 //text:h text:p text:style-nameFirst_20_paragraphChapter 1 references text:a xlink:typesimple xlink:href#chapter1 office:nametext:span text:style-nameDefinitionThe Chapter/text:span/text:a/text:p要点输入 AST 里章节标题Header 1带有标识符chapter1正文段落的链接目标为#chapter1标题被输出为带text:bookmark-start/text:bookmark-end书签的text:h段落这是后续所有场景中交叉引用的锚点而正文中的链接被输出为text:a xlink:href#chapter1即一个静态超链接链接文字就是原始链接文本 The Chapter外面套了text:span样式Definition。场景二启用xrefs_name—— 名称型交叉引用% pandoc -f native -t opendocumentxrefs_name --quiet [Header 1 (chapter1,[],[]) [Str The,Space,Str Chapter] ,Para [Str Chapter,Space,Str 1,Space,Str references,Space,Link (,[],[]) [Str The,Space,Str Chapter] (#chapter1,)] ,Figure (lalune,[],[]) (Caption Nothing [Para [Str Voyage dans la Lune]]) [Plain [Image (,[],[]) [Str lalune] (lalune.jpg,)]] ,Para [Str Image,Space,Str 1,Space,Str references,Space,Link (,[],[]) [Str La,Space,Str Lune] (#lalune,)]] ^D text:h text:style-nameHeading_20_1 text:outline-level1text:bookmark-start text:namechapter1 /The Chaptertext:bookmark-end text:namechapter1 //text:h text:p text:style-nameFirst_20_paragraphChapter 1 references text:bookmark-ref text:reference-formattext text:ref-namechapter1The Chapter/text:bookmark-ref/text:p text:p text:style-nameFigureWithCaptiondraw:frame draw:nameimg1 text:anchor-typeas-chardraw:image xlink:hreflalune.jpg xlink:typesimple xlink:showembed xlink:actuateonLoad //draw:frame/text:p text:p text:style-nameFigureCaptionVoyage dans la Lune/text:p text:p text:style-nameText_20_bodyImage 1 references text:sequence-ref text:reference-formatcaption text:ref-namelaluneLa Lune/text:sequence-ref/text:p本次输入新增了一个带标题的Figure块图片资源为 test/lalune.jpg。输出中发生两处关键变化指向标题chapter1的链接变成了text:bookmark-ref text:reference-formattext text:ref-namechapter1引用格式为text即名称/标题文字指向图片lalune的链接变成了text:sequence-ref text:reference-formatcaption text:ref-namelalune引用格式为caption即图注文字。text:ref-name记录的是被引用对象的标识符text:reference-format决定刷新文档后显示什么内容。MANUAL 明确指出交叉引用中的文字只有在文档刷新后才会与被引用项保持一致见 MANUAL.txt——也就是说写入 XML 时保留原始链接文本作为占位真正由 LibreOffice / OpenOffice 打开文档并更新字段后显示内容才会替换为目标的标题或编号。场景三启用xrefs_number—— 编号型交叉引用% pandoc -f native -t opendocumentxrefs_number --quiet [Header 1 (chapter1,[],[]) [Str The,Space,Str Chapter] ,Para [Str Chapter,Space,Str 1,Space,Str references,Space,Link (,[],[]) [Str The,Space,Str Chapter] (#chapter1,)] ,Figure (lalune,[],[]) (Caption Nothing [Para [Str lalune]]) [Plain [Image (,[],[]) [Str lalune] (lalune.jpg,Voyage dans la Lune)]] ,Para [Str Image,Space,Str 1,Space,Str references,Space,Link (,[],[]) [Str La,Space,Str Lune] (#lalune,)]] ^D text:h text:style-nameHeading_20_1 text:outline-level1text:bookmark-start text:namechapter1 /The Chaptertext:bookmark-end text:namechapter1 //text:h text:p text:style-nameFirst_20_paragraphChapter 1 references text:bookmark-ref text:reference-formatnumber text:ref-namechapter1/text:bookmark-ref/text:p text:p text:style-nameFigureWithCaptiondraw:frame draw:nameimg1 text:anchor-typeas-chardraw:image xlink:hreflalune.jpg xlink:typesimple xlink:showembed xlink:actuateonLoad //draw:frame/text:p text:p text:style-nameFigureCaptionlalune/text:p text:p text:style-nameText_20_bodyImage 1 references text:sequence-ref text:reference-formatvalue text:ref-namelalune/text:sequence-ref/text:p对比场景二本场景两个引用元素的格式分别变为标题引用text:bookmark-ref text:reference-formatnumber且元素内容为空/text:bookmark-ref直接闭合——这正是 MANUAL 所说原始链接文本被丢弃MANUAL.txt图片引用text:sequence-ref text:reference-formatvalue同样为空内容刷新后显示图片的编号值。场景四同时启用xrefs_number与xrefs_name—— 编号 名称% pandoc -f native -t opendocumentxrefs_numberxrefs_name --quiet [Header 1 (chapter1,[],[]) [Str The,Space,Str Chapter] ,Para [Str Chapter,Space,Str 1,Space,Str references,Space,Link (,[],[]) [Str The,Space,Str Chapter] (#chapter1,)] ,Figure (lalune,[],[]) (Caption Nothing [Para [Str Voyage dans la Lune]]) [Plain [Image (,[],[]) [Str lalune] (lalune.jpg,)]] ,Para [Str Image,Space,Str 1,Space,Str references,Space,Link (,[],[]) [Str La,Space,Str Lune] (#lalune,)]] ^D text:h text:style-nameHeading_20_1 text:outline-level1text:bookmark-start text:namechapter1 /The Chaptertext:bookmark-end text:namechapter1 //text:h text:p text:style-nameFirst_20_paragraphChapter 1 references text:bookmark-ref text:reference-formatnumber text:ref-namechapter1/text:bookmark-reftext:s /text:bookmark-ref text:reference-formattext text:ref-namechapter1The Chapter/text:bookmark-ref/text:p text:p text:style-nameFigureWithCaptiondraw:frame draw:nameimg1 text:anchor-typeas-chardraw:image xlink:hreflalune.jpg xlink:typesimple xlink:showembed xlink:actuateonLoad //draw:frame/text:p text:p text:style-nameFigureCaptionVoyage dans la Lune/text:p text:p text:style-nameText_20_bodyImage 1 references text:sequence-ref text:reference-formatvalue text:ref-namelalune/text:sequence-reftext:s /text:sequence-ref text:reference-formatcaption text:ref-namelaluneLa Lune/text:sequence-ref/text:p两个扩展同时启用时每个引用位置会输出两个相邻的引用元素中间用text:s /一个空格分隔标题引用先输出reference-formatnumber的空bookmark-ref再输出reference-formattext且带名称占位的bookmark-ref刷新后呈现编号 空格 名称图片引用同理先reference-formatvalue再reference-formatcaption。这与 MANUAL 中number 将出现在 name 之前MANUAL.txt以及name/caption numbers 将出现在 number 之后MANUAL.txt的说明完全吻合。三、源码级实现剖析理解了输出形态后来看 src/Text/Pandoc/Writers/OpenDocument.hs 中的实现链路。3.1 引用目标的收集stIdentTypes与ReferenceType写入器维护一个状态字段stIdentTypes :: [(Text, ReferenceType)]L107记录文档中哪些标识符对应哪类引用目标。ReferenceType是仅有三个构造子的枚举L83-L86data ReferenceType HeaderRef | TableRef | FigureRef这个表在遍历块时通过collectBlockIdent收集L312-L316let collectBlockIdent (Header _ (ident,_,_) _) [(ident,HeaderRef)] collectBlockIdent (Figure (ident,_,_) _ _ ) [(ident,FigureRef)] collectBlockIdent (Table (ident,_,_) _ _ _ _ _) [(ident,TableRef)]也就是说带ident属性的Header被登记为HeaderRefFigure被登记为FigureRefTable被登记为TableRef——这与测试中标题chapter1、图片lalune的行为一一对应。3.2 核心分发逻辑mkLinkmkLinkL848-L885是交叉引用生成的核心函数它的工作分三步第一步判断链接是否指向文档内已知目标L850-L852let maybeIdentAndType case T.uncons s of Just (#, ident) - find ((ident ) . fst) identTypes _ - Nothing只有链接目标以#开头、且该标识符出现在stIdentTypes中时maybeIdentAndType才返回Just (ident, type)才会走交叉引用分支。第二步根据引用类型与扩展开关选择元素L862-L873bookmarkRef | isEnabled Ext_xrefs_number o isEnabled Ext_xrefs_name o bookmarkRefNameNumber | isEnabled Ext_xrefs_name o bookmarkRefName | otherwise bookmarkRefNumber其中bookmarkRefNumber ident bookmarkRef number ident mempty bookmarkRefName ident bookmarkRef text ident d bookmarkRefNameNumber ident bookmarkRefNumber ident inlineSpace bookmarkRefName ident可以看到组合模式场景四就是在源码层面把两个元素用text:s拼接而成。sequenceRefL866-L873的逻辑与bookmarkRef完全对称只是格式串分别是value/caption。第三步按引用类型分发到元素L878-L885linkOrReference case maybeIdentAndType of Just (ident, HeaderRef) - bookmarkRef ident Just (ident, TableRef) - sequenceRef ident Just (ident, FigureRef) - sequenceRef ident _ - link in if isEnabled Ext_xrefs_name o || isEnabled Ext_xrefs_number o then linkOrReference else linkHeaderRef→text:bookmark-refTableRef/FigureRef→text:sequence-ref未知目标Nothing或未启用任何扩展 → 维持原来的text:a静态链接。同时注意mkLink中d inSpanTags Definition dL853当链接没有变成引用时链接文本外面仍会套上名为Definition的text:span样式见场景一输出。3.3 两类引用元素的分工ODF 元素生成函数适用引用类型名称型格式编号型格式text:bookmark-refbookmarkRefHeaderReftext标题文字number章节编号text:sequence-refsequenceRefTableRef/FigureRefcaption题注文字value序列编号值之所以标题用bookmark-ref、图表用sequence-ref是因为 ODF 中标题引用依赖书签而表格/图片的编号来自自动编号序列sequence。源码中text:reference-format的取值text、number、caption、value直接对应测试输出中的四个reference-format属性值。四、使用建议与注意事项综合 MANUAL 与测试可以总结出几条实战要点编号型引用依赖标题编号与题注MANUAL 明确警告——xrefs_number要真正有用生成的文档中必须启用标题编号且表格、图片题注必须可用例如配合native_numbering扩展见 MANUAL.txt。native_numbering本身的说明见 MANUAL.txt。引用内容需刷新文档所有交叉引用字段的文字与编号只有打开文档并刷新字段或重新保存后才会更新。写入时保留的只是原始链接文本占位xrefs_name或空内容xrefs_number。适用范围xrefs_name与xrefs_number只对odt/opendocument输出格式生效MANUAL.txt、MANUAL.txt对其他格式无效。组合顺序固定同时启用时编号在前、名称在后、中间一个空格此顺序由源码中bookmarkRefNameNumber/sequenceRefNameNumber的拼接方式决定不可通过命令行调整。仅影响内部链接只有指向#标识符且该标识符确实对应文档内标题/图表时才会转换指向不存在目标或外部 URL 的链接仍按普通超链接输出mkLink中maybeIdentAndType Nothing的分支。五、扩展的来历这两个扩展在 pandoc 2.11 版本引入对应 GitHub issue #6774由 Nils Carlson 贡献changelog 记录见 changelog.mdAdd two extensions,xrefs_nameandxrefs_number. Links to headings, figures and tables inside the document are substituted with cross-references that will use the name or caption of the referenced item forxrefs_nameor the number forxrefs_number. 而 test/command/6774.md 正是为验证该功能而添加的回归测试——它以最小化的 AST 输入锁定了四种扩展组合下的 XML 输出防止后续重构破坏交叉引用行为。六、小结通过 test/command/6774.md 这组测试用例我们完整看到了 pandoc opendocument 写入器交叉引用的四种输出形态静态链接、名称型、编号型、编号名称组合型。它们的生成逻辑集中在 OpenDocument.hs 的mkLink函数中引用目标由stIdentTypes预先收集HeaderRef/TableRef/FigureRef三类目标分别落到bookmark-ref与sequence-ref两种 ODF 元素上。如果你正在为 ODT 导出定制交叉引用样式或需要排查为什么我的链接没有变成引用可以从这三个位置测试、mkLink、Extensions.hs入手排查。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表