ARTICLE DETAIL

资讯详情

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

Quarkdown 行内数学公式(Math Span)语法全解析:从 Lexer 正则到 KaTeX 渲染的完整链路

Quarkdown 行内数学公式(Math Span)语法全解析:从 Lexer 正则到 KaTeX 渲染的完整链路 Quarkdown 行内数学公式Math Span语法全解析从 Lexer 正则到 KaTeX 渲染的完整链路【免费下载链接】quarkdown Markdown with superpowers: from ideas to papers, presentations, websites, books, and knowledge bases.项目地址: https://gitcode.com/GitHub_Trending/qu/quarkdownQuarkdown 原生支持 TeX 数学公式其中行内公式inline math使用$ 表达式 $语法要求定界符两侧必须各留一个空格。本文以官方解析测试用例 mathspan.md 为骨架逐行拆解合法与非法位置并深入 Lexer 正则、AST 节点、HTML 渲染 与 KaTeX 前端渲染器读完你将掌握行内公式的完整解析规则与底层实现原理并能在实际文档中正确规避边界陷阱。一、行内数学公式语法速览Quarkdown 的行内公式采用$作为定界符格式为$ 表达式 $规则要点起始$之后必须紧跟一个空格或制表符结束$之前必须恰有一个空格或制表符且其前不能再有空白$的前后即整个公式的外侧必须位于行首、空白或非单词字符之后/之前表达式内部不允许出现换行表达式内容会被自动trim()因此多余的边缘空格不影响结果。该语法的权威用户文档见 tex-formulae.qd其中还覆盖了单行块公式$ ... $独占一段、多行块公式$$$ ... $$$围栏与.math函数 等进阶用法。本文聚焦于行内Math Span形态。二、逐行解读官方解析测试用例 mathspan.md测试资源文件 mathspan.md 一共 19 行覆盖了行内公式的合法位置与边界行为。逐行归类如下。2.1 合法用例7 种可被正确识别的位置源文本说明$ Math expression $独立成行text $ Math expression $ text夹在句子中间$ Math expression $ text行首、后接文本text $ Math expression $行尾Text,$ Math expression $ text前接逗号非单词字符$ Math expression $, text后接逗号Text,$ Math expression $前接逗号、位于行尾以上 7 种情况下解析结果均为同一个MathSpan其表达式内容为Math expression——这正是 InlineParserTest.mathSpan() 中repeat(7)断言Math expression所验证的内容。2.2 非法与边界用例解析器如何拒绝错误语法源文本解析结果原因Text$ Not math expression $ text不识别为公式起始$紧跟在单词字符t之后不满足行首/空白/非单词字符的前置边界条件整个片段按普通文本处理$ Math $expression $ text表达式为Math $expression内部的$紧邻非空白字符e按正则规则被吸收进表达式内容不属于定界符$ Math $ abc $ expression $两个独立公式Math与expression中间abc为普通文本第一个$ Math $自洽闭合后后续内容重新开始匹配这些边界行为的判定完全由 Lexer 层正则驱动下一节展开说明。三、底层实现ONELINE_MATH 正则如何界定行内公式行内数学 token 的识别定义在 QuarkdownInlineTokenRegexPatterns.inlineMathTokenRegexPattern( name InlineMath, wrap ::InlineMathToken, regex RegexBuilder((?^|\\s|\\W)math(?$|\\s|\\W)) .withReference(math, PatternHelpers.ONELINE_MATH) .build(), )其中外层(?^|\s|\W)与(?$|\s|\W)是两个零宽边界断言前置断言$之前必须是行首、空白或非单词字符非[A-Za-z0-9_]这正是Text$场景被拒的原因——t是单词字符后置断言结束$之后必须是行末、空白或非单词字符因此$ Math expression $, text中的逗号可以被接受。核心的 ONELINE_MATH 模式定义在PatternHelpers中\$[ \t] // 起始定界符$ 一个空格/制表符 ((?:[^\$\n]|[^\s]\$|\$[^\s])?) // 非贪婪内容禁换行允许紧邻非空白的内层 $ (?![ \t])[ \t]\$ // 结束定界符恰一个空格/制表符 $其前不可再有空白逐段含义\$[ \t]起始$后必须紧跟且仅跟一个空格或制表符内容部分为非贪婪匹配?逐字符允许三类情况[^$\n]除$和换行外的任意字符[^\s]\$非空白字符后跟$如x$\$[^\s]$后跟非空白字符如$x。后两类合起来保证了内部$只要两侧至少有一侧是非空白就会被当作内容而非定界符——这正是$ Math $expression $输出Math $expression的原因(?![ \t])[ \t]\$结束定界符必须由一个空格/制表符 $构成且该空格之前不能是空白保证定界符间距恰为一格。注意由于该模式在同一行内识别若$ 表达式 $独占一行且不与其他内容混排则会被归类为块级 OnelineMathToken渲染为居中公式只有混排在其他文本中才属于行内 InlineMathToken。官方文档 tex-formulae.qd 特别提醒单行块公式语法不会中断段落若不将公式单独成段会因 Markdown 的 lazy 续行规则被降级识别为行内公式。四、从 Token 到 ASTMathSpan 节点的诞生Lexer 产出InlineMathToken后由 InlineTokenParser.visit(token: InlineMathToken) 转换为 AST 节点override fun visit(token: InlineMathToken): Node { val groups token.data.groups.iterator(consumeAmount 2) return MathSpan(expression groups.next().trim()) }这里groups.next()取出正则捕获组的内容trim()去除首尾空白——这与定界符间距恰为一格的规则配合最终存入节点的expression就是干净的 TeX 表达式。AST 节点 MathSpan 定义如下class MathSpan( val expression: String, override val style: NodeStyle NodeStyle.DEFAULT, ) : Node, StylableNode, PrimitiveFunctionBackedNode { override val isBackingCallBlock: Boolean get() false override val backingFunctionName: String get() math override fun toFunctionCallArguments() functionCallArguments { arg(content, evaluable(expression)) arg(block, boolean(false)) } }关键信息expressionTeX 表达式内容style节点支持样式属性与容器相同的 元素样式属性例如fontsize、foreground等backingFunctionName math说明$ ... $语法背后由.math函数 提供能力支撑且block参数固定为false行内。这也意味着通过.extend {math}扩展.math会同时影响所有$ ... $行内公式节点本身是PrimitiveFunctionBackedNode因此可与函数调用体系互转。五、测试如何验证解析结果核心单元测试位于 InlineParserTest.mathSpan()Test fun mathSpan() { val nodes inlineIteratorMathSpan(readSource(/parsing/inline/mathspan.md), assertType false) repeat(7) { assertEquals(Math expression, nodes.next().expression) } assertEquals($$Math $expression, nodes.next().expression) assertEquals(Math, nodes.next().expression) assertEquals(expression, nodes.next().expression) assertFalse(nodes.hasNext()) }测试断言与源码逐条对应前 7 个节点对应表格中 7 种合法位置表达式均为Math expression第 8 个节点表达式为Math $expression内层$被吸收最后两个节点分别为Math与expression$ Math $ abc $ expression $拆分出的两个独立公式assertFalse(nodes.hasNext())确认整份输入被完整消费Text$ Not math expression $ text确实未被解析为任何公式。同一份资源文件既驱动文档示例又驱动单元测试保证了语法文档与实现始终同步。六、渲染链路formula标签与 KaTeX 前端渲染6.1 HTML 端行内与块级公式的标记差异在 HTML 渲染器中行内 MathSpan 与块级 Math 均输出formula标签差异在于data-block属性!-- 行内公式MathSpan无>formulas.forEach((formula) { const content formula.textContent; const isBlock formula.dataset.block ; if (!content) return; formula.innerHTML katex.renderToString(content, { throwOnError: false, displayMode: isBlock, macros: texMacros || {}, }); });displayMode由data-block决定行内公式为false块级公式为true居中显示throwOnError: false即使 TeX 语法有误也输出错误占位而非中断渲染macros读取window.texMacros该对象由文档级 TeX 宏.texmacro 提供宏同样适用于$ ... $行内公式。七、实战建议与易错点严格遵守单空格定界$ expr $中表达式两侧必须有且仅有一个空格/制表符写成$expr$或$ expr $均不会被识别为公式。注意前置边界Text$ ... $不会触发公式解析因为$前是单词字符需要紧跟文本时请改用.math {expr}函数调用形式见 tex-formulae.qd 的.math小节。行内公式禁换行内容中出现换行会终止匹配多行公式请使用$$$ ... $$$围栏块。内层$的处理内容中的$只要两侧至少一侧紧邻非空白字符就会被保留在表达式中如$ Math $expression $若希望表达式中出现独立的$定界符语义请改用.math函数或 TeX 宏。样式与扩展行内公式支持与容器一致的样式属性且可通过.extend {math}统一影响所有$ ... $与.math公式自定义命令通过.texmacro定义参数化宏#1、#2行内公式中同样可用。八、总结行内数学公式看似只是两个$夹一段表达式实则包含严谨的边界判定Lexer 层通过 ONELINE_MATH 正则与零宽断言精确界定 token 范围Parser 层将其收敛为 MathSpan 节点HTML 层输出formula标签最终由按需加载的 KaTeX 完成排版。整个链路由 mathspan.md 与 InlineParserTest.mathSpan() 双向锁定文档、实现与测试三者互相印证任何一侧的修改都会在测试中暴露问题。掌握这些规则你就能在 Quarkdown 中写出稳定、可预期的数学公式排版。【免费下载链接】quarkdown Markdown with superpowers: from ideas to papers, presentations, websites, books, and knowledge bases.项目地址: https://gitcode.com/GitHub_Trending/qu/quarkdown创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表