ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Pandoc LaTeX 脚注分离式写法支持:\footnotemark 与 \footnotetext 的解析原理与实战验证

Pandoc LaTeX 脚注分离式写法支持:\footnotemark 与 \footnotetext 的解析原理与实战验证 Pandoc LaTeX 脚注分离式写法支持\footnotemark 与 \footnotetext 的解析原理与实战验证【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc导读在 LaTeX 中脚注既可以写成\footnote{内容}这种内联形式也可以拆成文中标记\footnotemark与文末/表末文本\footnotetext两部分——后者常出现在表格、\caption等不便直接内嵌脚注内容的场景。Pandoc 的 LaTeX 阅读器从 3.x 起针对 issue #11450完整支持了这种分离式写法在解析阶段先把\footnotemark记录为占位标记、把\footnotetext的内容按编号存入映射最后统一合并为标准的Note内联元素。本文以仓库中的命令行回归测试 test/command/11450.md 为骨架结合阅读器/写入器源码讲解这套机制的行为规则、编号匹配逻辑以及与普通\footnote混用时的顺序处理并给出可复现的命令验证。一、问题背景LaTeX 的两种脚注写法LaTeX 原生提供三种脚注相关命令命令作用特点\footnote{text}在当前处生成带编号的脚注标记与文本一体编号自动\footnotemark[n]仅在文中输出脚注标记编号可指定编号也可省略编号自动递增\footnotetext[n]{text}仅在当前位置输出脚注文本编号默认沿用当前计数器也可显式指定\footnotemark与\footnotetext分离式写法在 LaTeX 生态中相当常见典型场景包括表格单元格内的脚注tabular环境对\footnote支持不佳、\caption标题中的脚注、以及需要把脚注文本集中放到页面底部某处的文档排版。在 Pandoc 引入该支持前这类 LaTeX 文档转换时要么丢失脚注内容、要么生成错误的原生表示。二、三个核心测试场景行为规范即文档test/command/11450.md 是针对该特性的命令行回归测试其标题直白地说明了用途Test for \footnotemark and \footnotetext (issue #11450)。测试全部通过pandoc -f latex -t native把 LaTeX 输入转为 Pandoc 原生 AST 表示从而精确断言解析结果。下面逐一拆解。场景 1基础用法——分离式脚注合并为 Note输入Text\footnotemark{}. \footnotetext{The footnote content.}期望输出[ Para [ Str Text , Note [ Para [ Str The , Space , Str footnote , Space , Str content. ] ] , Str . ] ]这条用例确立了最核心的行为\footnotemark在文中出现的位置会被替换成一条完整的Note其内容来自后续或文末对应的\footnotetext。文本Text之后紧跟Note随后才是句号.说明脚注标记被就地解析成脚注节点而\footnotetext命令本身在 AST 中不产生任何可见输出只贡献内容。场景 2显式编号——按编号精确配对输入First\footnotemark[1] and second\footnotemark[2]. \footnotetext[1]{First note.} \footnotetext[2]{Second note.}期望输出[ Para [ Str First , Note [ Para [ Str First , Space , Str note. ] ] , Space , Str and , Space , Str second , Note [ Para [ Str Second , Space , Str note. ] ] , Str . ] ]这条用例验证了显式编号参数[n]的配对规则\footnotemark[1]与\footnotetext[1]{First note.}配对\footnotemark[2]与\footnotetext[2]{Second note.}配对。注意即便\footnotetext的书写顺序与\footnotemark出现顺序一致合并依据依然是编号而非位置。这也解释了为什么两个\footnotemark可以连用只要编号不同它们会分别解析为两条独立的Note且各自携带正确的内容。场景 3与普通脚注混用——编号空间统一输入Text\footnotemark[1] and more\footnote{Regular footnote.} \footnotetext[1]{Marked footnote.}期望输出[ Para [ Str Text , Note [ Para [ Str Marked , Space , Str footnote. ] ] , Space , Str and , Space , Str more , Note [ Para [ Str Regular , Space , Str footnote. ] ] ] ]这条用例最有实战价值\footnotemark[1]\footnotetext[1]{Marked footnote.}组合与普通\footnote{Regular footnote.}共享同一个脚注编号空间。输出中Text后紧跟编号为 1 的Marked footnote.more后紧跟Regular footnote.编号为 2顺序与文中出现顺序一致。也就是说显式编号的\footnotemark不会影响后续普通\footnote的自动编号——普通脚注的编号从当前计数器继续累加。三、源码原理从 Span 占位到 Note 解析的两阶段机制要理解上述行为为何成立需要阅读 LaTeX 阅读器核心实现 src/Text/Pandoc/Readers/LaTeX.hs。整个机制分两个阶段解析阶段记录与收尾阶段合并。1. 解析阶段mark 转占位 Spantext 入映射两个命令在命令表中注册src/Text/Pandoc/Readers/LaTeX.hs#L441-L442(footnotemark, footnotemark) (footnotetext, footnotetext)\footnotemark的解析函数src/Text/Pandoc/Readers/LaTeX.hs#L531-L541核心逻辑为footnotemark :: PandocMonad m LP m Inlines footnotemark do mbNum - optionalFootnoteNum noteNum - case mbNum of Just n - return n Nothing - do updateState $ \st - st{ sLastNoteNum sLastNoteNum st 1 } sLastNoteNum $ getState return $ B.spanWith (, [footnote-mark], [(note-num, tshow noteNum)]) mempty要点通过optionalFootnoteNumsrc/Text/Pandoc/Readers/LaTeX.hs#L556-L562读取可选的[n]参数省略编号时自动递增内部计数器sLastNoteNum编号从 1 开始关键技巧\footnotemark不直接生成Note而是生成一个带特殊标记的空Span——类名footnote-mark、键值属性note-num记录编号。这是因为此时对应的脚注文本可能尚未出现\footnotetext往往写在文档后面无法立刻构造完整的Note。\footnotetext的解析函数src/Text/Pandoc/Readers/LaTeX.hs#L543-L554与之对称footnotetext :: PandocMonad m LP m Inlines footnotetext do mbNum - optionalFootnoteNum noteNum - case mbNum of Just n - return n Nothing - sLastNoteNum $ getState contents - grouped block walkM resolveNoteLabel updateState $ \st - st{ sFootnoteTexts M.insert noteNum contents (sFootnoteTexts st) } return mempty要点同样支持显式编号省略编号时沿用当前计数器值sLastNoteNum正文内容按块解析后存入状态中的sFootnoteTexts :: Map Int Blocks编号 → 内容块函数返回mempty即该命令在行内流中不产生任何内容——这正是场景 1 中 AST 里只有Note、没有多余占位节点的原因。2. 收尾阶段walk 合并缺失编号安全降级整个文档解析完成后阅读器调用resolveFootnoteMarks对全文做一次遍历src/Text/Pandoc/Readers/LaTeX.hs#L124walk (resolveFootnoteMarks (sFootnoteTexts st)) $ walk (resolveRefs (sLabels st)) doc合并函数src/Text/Pandoc/Readers/LaTeX.hs#L139-L149resolveFootnoteMarks :: M.Map Int Blocks - Inline - Inline resolveFootnoteMarks fnTexts (Span (_, classes, kvs) _) | footnote-mark elem classes , Just numText - lookup note-num kvs , [(n, )] - reads (T.unpack numText) case M.lookup n fnTexts of Just contents - Note (toList contents) Nothing - Str -- No matching footnotetext found resolveFootnoteMarks _ x x逻辑清晰只匹配带footnote-mark类名的Span读出note-num编号在sFootnoteTexts映射中查找编号命中则原地替换为Note——这就是场景 2 中两个Note能按编号各归其位的机制若找不到对应的\footnotetext编号缺失安全降级为空字符串Str 避免崩溃或产生伪脚注。这种找不到就放弃的宽容策略对残缺文档很友好。这套两阶段设计解决了 LaTeX 分离式脚注的固有难题标记与文本在源码中位置分离、书写顺序自由只有等整个文档解析完毕才能完成配对因此必须借助解析器状态暂存、最终统一 walk 合并。四、反向视角LaTeX 写入器的配套输出值得注意的是LaTeX 写入器writer在--reference-links/external-notes场景下做了相反的拆分当脚注需要外部化如放在 description 列表项、表格单元格内部等 LaTeX 语法受限的位置时写入器把Note拆成文中\footnotemark{}与文末\footnotetext{...}。在 src/Text/Pandoc/Writers/LaTeX.hs#L1231-L1255 的inlineToLaTeX (Note contents)分支中if externalNotes then do modify $ \st - st{ stNotes noteContents : stNotes st } return \\footnotemark{} else return $ \\footnote beamerMark braces noteContents即开启外部脚注时正文处只输出\footnotemark{}内容压入stNotes栈收尾时由 src/Text/Pandoc/Writers/LaTeX/Notes.hs 的notesToLaTeX统一输出\footnotetext{...}多个脚注时用\addtocounter{footnote}{1}/\addtocounter{footnote}{1-n}修正计数器。这与阅读器形成了完美的双向闭环读方向合并、写方向拆分两侧共享同一套\footnotemark/\footnotetext协议。五、其他回归用例该特性在真实文档中的落地\footnotemark/\footnotetext的输出路径在仓库中还有其他回归测试可以印证test/command/8240.md 验证脚注出现在 description 列表项标题\item[Header\footnotemark{}:]时写入器用\footnotemark{}占位、在列表结束后补\footnotetext{A footnote.}保证列表结构不被脚注破坏test/command/1023.md 验证表格单元格内的脚注如Name^[In English.]被拆为\footnotemark{}置于单元格内、多个\footnotetext{...}置于tabular之后并用\addtocounter恢复编号\addtocounter{footnote}{-1}→ 第一个\footnotetext→\addtocounter{footnote}{1}→ 第二个\footnotetexttest/command/5476.md 同样覆盖了相关脚注输出行为。这些用例共同说明分离式脚注不仅是阅读器的解析能力也是写入器在受限环境中保持脚注完整性的标准输出策略。六、实战验证如何在本地复现在仓库根目录或任意装有当前版本 pandoc 的环境下直接运行回归测试对应的命令即可验证# 场景 1基础分离式脚注 printf Text\\footnotemark{}.\n\\footnotetext{The footnote content.}\n \ | pandoc -f latex -t native # 场景 2显式编号配对 printf First\\footnotemark[1] and second\\footnotemark[2].\n\\footnotetext[1]{First note.}\n\\footnotetext[2]{Second note.}\n \ | pandoc -f latex -t native # 场景 3与普通脚注混用 printf Text\\footnotemark[1] and more\\footnote{Regular footnote.}\n\\footnotetext[1]{Marked footnote.}\n \ | pandoc -f latex -t native输出应与上文列出的三个 AST 完全一致。也可以直接运行整条命令测试套件例如通过make test见 Makefile执行包含 test/command/11450.md 在内的全部命令测试该文件由 test/command/ 目录下的命令行测试框架自动收集运行。七、行为边界与使用建议综合测试与源码可以总结出这套机制的若干边界行为编号优先于顺序\footnotemark与\footnotetext的配对只看显式编号不看书写先后。若编号冲突两个\footnotetext使用同一编号后解析者会覆盖映射中的前值M.insert语义。省略编号即自动编号不带[n]的\footnotemark依次递增1、2、3…不带[n]的\footnotetext沿用当前计数器——因此常规先 mark 后 text、不写编号的文档也能正确配对。缺失文本安全降级\footnotemark找不到对应\footnotetext时解析为空字符串文档不会报错但该脚注会静默丢失——转换前建议用pandoc --verbose或检查原生输出确认配对完整。编号空间与普通\footnote统一显式编号的 mark 会占用编号空间后续普通\footnote从当前计数器继续累加两者混用时顺序按文中出现位置保持见场景 3。对需要批量转换含表格/标题脚注的 LaTeX 文档的开发者而言这四点是决定转换结果是否完整的关键。掌握这套机制后你可以放心地把依赖\footnotemark/\footnotetext的文档交给 pandoc并通过-t native先行检查脚注合并结果。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表