ARTICLE DETAIL

资讯详情

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

Pandoc 原始 LaTeX 解析边界探秘:`\start` 命令的 raw_tex 处理与 Markdown 读取器解析逻辑

Pandoc 原始 LaTeX 解析边界探秘:`\start` 命令的 raw_tex 处理与 Markdown 读取器解析逻辑 Pandoc 原始 LaTeX 解析边界探秘\start命令的 raw_tex 处理与 Markdown 读取器解析逻辑【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc导读在 Pandoc 的 Markdown 读取器中\start开头的原始 TeX 命令长期存在一个微妙的解析边界问题它们既可能是普通的 LaTeX/TeX 命令也可能是 ConTeXt 环境的起始标记如\starttext后者必须与\stop成对出现。位于test/command/3558.md的回归测试记录了 Pandoc 修复此问题的完整过程——从错误地将\startmulti当作 ConTeXt 环境吞掉到修复后正确产出RawBlock/RawInline节点。阅读本文后你将理解raw_tex扩展在 Markdown 输入中的工作原理、rawConTeXtEnvironment的匹配策略以及如何用pandoc -t native验证原始 TeX 内容的解析结果。测试文件解读一个最小化的解析回归用例测试内容与预期输出test/command/3558.md是 Pandoc 的命令式测试command test文件其格式约定为以包裹 shell 会话% pandoc -t native后的内容是命令行^D标志输入结束其后的行是期望的输出native 表示 Pandoc 内部 AST 的 Haskell 表示% pandoc -t native \multi hello \endmulti ^D [ RawBlock (Format tex) \\multi , Para [ Str hello ] , RawBlock (Format tex) \\endmulti ]测试内容本身非常简单输入三段内容——\multi、空行、hello、空行、\endmulti期望的输出是三个 AST 节点两个RawBlock (Format tex)包裹的原始 LaTeX 块以及中间的普通段落Para [Str hello]。即\multi和\endmulti被当作独立的原始 TeX 块原样保留中间的hello按普通 Markdown 段落解析。注意\multi是一个并不真实存在的 LaTeX 宏——这正是测试的关键所在解析器必须能处理任意\开头的控制序列而不要求其是已知命令。同时\multi与 ConTeXt 环境起始标记\start...的命名约定不同这里恰好用了一个边界附近的命令名。测试背后的历史issue #3558通过 changelog 可以还原此测试的来龙去脉。changelog.md 中 2.0 版本的 Markdown reader 条目记录道Allow raw latex commands starting with\start(#3558). Previously these werent allowed because they were interpreted as starting ConTeXt environments, even without a corresponding\stop...也就是说修复前的问题是任何以\start开头的命令都会被预先拦截并解释为 ConTeXt 环境的开始即使后面根本没有配对的\stop。例如\startmulti单独出现时解析器会试图寻找\stopmulti将中间内容整体吞入导致命令无法作为普通 raw TeX 被保留。这正是本测试用\multi不触碰\start前缀来验证修复效果的原因——它证明了解析器已经不会再对start前缀做一刀切的假设。raw_tex扩展Markdown 中内嵌原始 TeX 的入口扩展的语义在 Pandoc 的 Markdown 语法中原始 LaTeX/TeX/ConTeXt 的透传由raw_tex扩展控制。其默认状态与markdown变体的差异详见 MANUAL.txt 的扩展表raw_tex允许在 Markdown 源文档中直接内嵌原始 LaTeX、TeX 与 ConTeXt 代码。MANUAL 对该扩展的解释Extension:raw_tex要点如下行内 TeX 命令会被原样保留并在输出到 LaTeX、ConTeXt 等目标格式时透传例如This result was proved in \cite{jones.1967}.对于\begin{...}与\end{...}包裹的 LaTeX 环境环境内部的内容整体按原始 LaTeX 解释不再当作 Markdown 解析。行内 LaTeX 在输出到非 Markdown、LaTeX、Emacs Org mode、ConTeXt 的格式时会被忽略。更显式、更灵活的替代方案是raw_attribute扩展可用 {latex} 围栏代码块或{latex}行内属性显式标记原始内容Extension:raw_attribute。raw_tex扩展关闭时例如使用-raw_tex禁用文档中的 TeX 命令不再被识别为 raw可能退化为普通文本因此在跨格式转换时如转 docx需要注意此扩展对输出内容的影响。块级与行内级两条解析路径raw_tex在 Markdown 读取器中对应两个入口函数均位于 src/Text/Pandoc/Readers/Markdown.hs块级路径rawTeXBlockMarkdown.hs#L1161-L1172先guardEnabled Ext_raw_tex检查扩展是否启用然后尝试用rawConTeXtEnvironment或rawLaTeXBlock匹配一行或多行原始 TeX最终以B.rawBlock tex生成块节点。若匹配结果全是空白字符则返回空块不产生无意义的空 RawBlock。行内路径rawLaTeXInlineMarkdown.hs#L2139-L2144同样先检查Ext_raw_tex随后调用rawLaTeXInline匹配单个行内命令结果以B.rawInline tex生成行内节点。两个函数都统一使用tex作为格式名——源码注释明确说明这是因为匹配到的内容“might be context”可能是 ConTeXt 而非纯 LaTeX所以Format tex是一个涵盖 LaTeX、TeX 与 ConTeXt 的通用格式标签。这与测试期望输出中的RawBlock (Format tex)完全吻合。修复的核心rawConTeXtEnvironment的精确匹配修复前的缺陷从提交12ae1df5bAllow raw latex commands starting with\startin Markdown2017-04-06的 diff 可以看到修复前的rawLaTeXInline使用了如下过于宽泛的拦截逻辑rawLaTeXInline try $ do guardEnabled Ext_raw_tex lookAhead $ char \\ notFollowedBy (string start) -- context env RawInline _ s - rawLaTeXInline ...即只要看到反斜杠后紧跟start四个字母就直接判定为 ConTeXt 环境并拒绝按普通行内 raw 处理——即使后续内容并非合法的\startXXX环境形式。修复后的代码改为rawLaTeXInline try $ do guardEnabled Ext_raw_tex lookAhead (char \\) notFollowedBy rawConTeXtEnvironment RawInline _ s - rawLaTeXInline ...差别在于不再用字符串前缀做粗粒度判断而是先lookAhead确认以反斜杠开头再通过notFollowedBy rawConTeXtEnvironment做结构化的负向前瞻——只有当后续输入确实能被rawConTeXtEnvironment完整匹配时才认为这是 ConTeXt 环境并拒绝行内 raw 解析。rawConTeXtEnvironment的匹配规则rawConTeXtEnvironment定义于 Markdown.hs#L2146-L2153rawConTeXtEnvironment :: PandocMonad m ParsecT Sources st Text m Text rawConTeXtEnvironment try $ do string \\start completion - inBrackets (letter | digit | spaceChar) | takeWhile1P isLetter !contents - manyTill (rawConTeXtEnvironment | countChar 1 anyChar) (try $ string \\stop textStr completion) return $! \\start completion T.concat contents \\stop completion其匹配策略是成对匹配要求字面量\start环境名completion可以是方括号包裹的任意字母/数字/空格序列或连续的字母串之后必须出现\stop加上相同的环境名才闭合且允许嵌套环境递归匹配整个结构必须完整闭合rawConTeXtEnvironment才匹配成功。因此在修复后的逻辑中\starttext ... \stoptext能被rawConTeXtEnvironment完整匹配走 ConTeXt 环境路径\startmulti后没有\stopmultirawConTeXtEnvironment匹配失败notFollowedBy通过于是\startmulti落回rawLaTeXInline按普通 raw 命令处理。这也是 3558 测试选择\multi的原因——它验证了修复不再对start前缀“一刀切”同时\multi本身又刻意不用\start前缀确保测试结果不受 ConTeXt 环境匹配器的干扰聚焦于“未知命令可被原样保留”这一基本能力。与 LaTeX 读取器的协作rawLaTeXBlock与rawLaTeXInlineMarkdown 读取器中的 raw 解析并非全部自己实现而是大量复用了 LaTeX 读取器src/Text/Pandoc/Readers/LaTeX.hs中的底层解析器在文件头部即可看到导入语句import Text.Pandoc.Readers.LaTeX (applyMacros, rawLaTeXBlock, rawLaTeXInline)这两个函数的定义在 LaTeX.hs#L164-L219rawLaTeXBlock先lookAhead确认以\加字母开头然后对输入做分词getInputTokens优先尝试识别\include、\input、\subfile、\usepackage等文件级命令及宏定义这些被消费但不产出内容否则尝试匹配environment或blockCommand再将后续内容交给块解析器继续消费。可见它对“已知命令列表”与“环境”有明确区分。rawLaTeXInline类似地处理行内命令额外会补全命令后跟随的空花括号{}源码注释提到#5439相关的边界情况。值得注意的是 LaTeX.hs#L1050-L1055 中rawMaybeBlock的一段注释明确回应了 #3558 的修复...But we stop if we hit a\startXXX, since this might start a raw ConTeXt environment (this is important because this parser is used by the Markdown reader).即在“块级命令连续出现”的启发式扫描中一旦遇到\startXXX就停止继续按块命令收集因为其后可能开启一个原始 ConTeXt 环境。对应实现为startCommand守卫guard $ startT.isPrefixOfn。这说明修复后的策略在 LaTeX 读取器中同样生效\start前缀的命令不再被无条件当作普通块命令吞并而是给 ConTeXt 环境路径留出判断空间。在真实环境中验证命令运行与结果对照复现测试预期在安装了 pandoc 的环境中可直接复现测试文件中的命令。使用与测试完全一致的输入printf \\multi\n\nhello\n\n\\endmulti\n | pandoc -t native期望输出与test/command/3558.md中记录的完全一致[ RawBlock (Format tex) \\multi , Para [ Str hello ] , RawBlock (Format tex) \\endmulti ]反例验证真正的 ConTeXt 环境为了对照再看一个能被rawConTeXtEnvironment完整匹配的输入printf \\starttext\nhello\n\\stoptext\n | pandoc -t native此时\starttext ... \stoptext作为闭合的 ConTeXt 环境被整体保留输出中会呈现一个包含完整环境内容的RawBlock (Format tex)。两相对照即可直观看到#3558 修复的边界在于“无\stop配对的\start前缀命令应回退为普通 raw 命令”而有完整配对的 ConTeXt 环境依旧走环境路径。输出到 LaTeX 时的透传效果RawBlock (Format tex)节点在-t latex输出时会被原样透传。因此上述 Markdown 文档转换为 LaTeX 后\multi与\endmulti会逐字出现在输出中这保证了混合 Markdown 原始 TeX 文档在面向 LaTeX/PDF 工作流中的无损往返而输出到其他格式如 HTML时这些 raw tex 块会被丢弃体现 MANUAL.txt 中“行内 LaTeX 在非 TeX 类格式中被忽略”的规则。测试框架视角command test 的编写约定test/command/3558.md采用 Pandoc 的 command test 格式这类测试由 test/test-pandoc.hs 驱动执行。其约定为代码块内首行% pandoc ...是待执行的命令行以^D标志标准输入结束^D之后至代码块结束的内容是期望输出逐字符比较diff判定通过与否。这种格式特别适合回归测试它把“命令 输入 期望输出”封装成一个自包含的用例任何解析器行为变化都能被立即捕获。3558.md正是借此把“\start前缀命令的 raw 处理”固化为永久回归防线——即便未来重构 Markdown 或 LaTeX 读取器此用例也会持续验证该行为不被破坏。同目录下的 test/command/ 中还有大量同类编号用例如3558.md附近的3558前后编号文件共同构成 Pandoc 行为回归测试的庞大语料库。小结本次修复带来的行为边界综合测试、源码与 changelog 三条证据链可以得到关于\start前缀命令的清晰结论有完整\stop配对的结构如\starttext ... \stoptext仍被识别为 ConTeXt 环境整体作为原始内容保留格式标签为tex无配对的\start前缀命令如\startmulti在 #3558 修复后不再被误判为环境而是回退为普通 raw TeX 命令产出RawBlock/RawInline (Format tex)节点判断依据从“字符串前缀start”升级为“结构化匹配rawConTeXtEnvironment”该逻辑同时作用于 Markdown 读取器的块级rawTeXBlock与行内rawLaTeXInline路径并在 LaTeX 读取器的rawMaybeBlock启发式中同步生效。这一案例同时展示了 Pandoc 的一个工程习惯解析边界问题用最小化回归测试固化。test/command/3558.md虽然只有 8 行却精准覆盖了 raw TeX 解析中最容易出错的 ConTeXt 环境判定逻辑是理解raw_tex扩展行为的最佳起点。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表