Pandoc LaTeX 阅读器对 siunitx 宏的完整支持:从 golden test 6658 看 \num、\si、\SIrange、\ang 的解析与转换原理

发布时间:2026/9/20 13:54:17
Pandoc LaTeX 阅读器对 siunitx 宏的完整支持:从 golden test 6658 看 \num、\si、\SIrange、\ang 的解析与转换原理 Pandoc LaTeX 阅读器对 siunitx 宏的完整支持从 golden test 6658 看 \num、\si、\SIrange、\ang 的解析与转换原理【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc本文以仓库中的命令测试用例 test/command/6658.md 为骨架系统讲解 Pandoc 的 LaTeX 阅读器如何将siunitx宏包中的\num、\si、\SI、\SIlist、\SIrange、\ang等命令转换为语义化的 HTML/结构化文档内联元素。读者将掌握这些宏的完整转换规则、per-mode等选项的实际效果、数值解析逗号、科学计数法、不确定度、角度度分秒格式等细节并能通过源码 src/Text/Pandoc/Readers/LaTeX/SIunitx.hs 理解每一步转换的底层实现。背景为什么 Pandoc 要专门支持 siunitxsiunitx是 LaTeX 生态中最流行的科学计量排版宏包之一用于排版数值\num、带单位的量\SI、单位列表\SIlist、数值范围\SIrange和角度\ang。当用户使用pandoc -f latex -t html或 docx、epub 等其他格式把包含科学计量内容的 LaTeX 文档转换为其他格式时这些宏必须被解析成有意义的文本内容而不是被当作未知命令丢弃。Pandoc 对该功能经历了从部分支持到完整支持的演进changelog.md 中记录了这一过程最早引入partial siunitx supportissue #3588此后逐步补充单位命令#4296、#4773、修复\micro等命令#5921、支持squared/cubed/tothe#6657、补齐缺失命令#6658即本文主题在 2.13 版本中Improved siunitx support#6658、#6620并将 siunitx 相关代码重构为独立的内部模块后续版本实现了 siunitx v3 命令#7614将\unit、\qty、\qtyrange、\qtylist分别作为\si、\SI、\SIrange、\SIlist的同义词。测试文件 test/command/6658.md 正是 issue #6658 对应的回归测试golden test它以一组 LaTeX 输入和预期的 HTML 输出作为基准确保上述命令的转换行为不随代码演进而回退。测试用例全貌输入与期望输出test/command/6658.md 的完整内容是% pandoc -f latex -t html \num{12345,67890} \num{1-2i} \num{.3e45} \num{1.654 x 2.34 x 3.430} \si{kg.m.s^{-1}} \si{\kilogram\metre\per\second} \si[per-modesymbol]{\kilogram\metre\per\second} \si[per-modesymbol]{\kilogram\metre\per\ampere\per\second} \numlist{10;20;30} \SIlist{0.13;0.67;0.80}{\milli\metre} \numrange{10}{20} \SIrange{0.13}{0.67}{\milli\metre} \ang{10} \ang{1;2;3} \ang{;;1} \ang{10;;} \ang{-0;1;} \si{kg.m/s^2} \si{g_{polymer}~mol_{cat}.s^{-1}} \si{\kilo\gram\metre\per\square\second} \si{\gram\per\cubic\centi\metre} \si{\square\volt\cubic\lumen\per\farad} \si{\metre\squared\per\gray\cubic\lux} \si{\henry\second}对应的期望输出为p12345.67890/p p1 ± 2i/p p0.3 × 10sup45/sup/p p1.654 × 2.34 × 3.430/p pkg m ssup−1/sup/p pkg m ssup−1/sup/p pkg m/s/p pkg m/A/s/p p10, 20, amp; 30/p p0.13 mm, 0.67 mm, amp; 0.80 mm/p p10–20/p p0.13 mm–0.67 mm/p p10°/p p1°2′3″/p p1″/p p10°/p p-0°1′/p pkg m/ssup2/sup/p pgsubpolymer/sub molsubcat/sub ssup−1/sup/p pkg m ssup−2/sup/p pg cmsup−3/sup/p pVsup2/sup lmsup3/sup Fsup−1/sup/p pmsup2/sup Gysup−1/sup lxsup3/sup/p pH s/p观察这些输出可以发现三个贯穿始终的设计原则保留语义排版指数使用sup、下标使用sub、±、×、−U2212 减号、°′″等符号用 Unicode 字符呈现去 LaTeX 化\.、~、x、-等 LaTeX 分隔符/记号被转换为自然语言中的空格、乘号与正负号选项感知per-modesymbol会改变\per的呈现方式负指数 → 斜杠。命令注册siunitx 模块如何接入 LaTeX 阅读器siunitx 相关的解析器全部集中在独立模块 src/Text/Pandoc/Readers/LaTeX/SIunitx.hs 中changelog 记录其从主解析器中Factored out而来。该模块导出唯一的siunitxCommands函数返回一张命令名 → 解析器的映射表SIunitx.hs#L57-L72命令含 v3 别名处理函数功能\si/\unitdosi纯单位排版\SI/\qtydoSI数值 单位\SIrange/\qtyrangedoSIrange True数值范围 单位\SIlist/\qtylistdoSIlist数值列表 单位\numrangedoSIrange False纯数值范围\numlistdoSInumlist纯数值列表\numdoSInum纯数值\angdoSIang角度在 LaTeX 阅读器主模块中该表通过M.unions与重音、引用、缩写、verbatim 等命令表合并注册为全局内联命令之一LaTeX.hs#L370-L384 中的inlineCommands其中 LaTeX.hs#L375 为siunitxCommands tok。这意味着这些命令在任何普通内联上下文中都可被识别无需加载宏包声明。数值解析 \num从字符串到结构化数字\num由doSInum实现SIunitx.hs#L92-L93先skipopts跳过可选参数再用braced取出花括号内容并交给tonum。tonumL95-L99将字符串交给parseNum解析若解析失败则原样输出文本保证健壮性。parseNumL125-L180由多个parseNumPart子解析器按顺序组合|表示按优先级尝试覆盖以下记号记号转换结果测试用例对应小数/整数parseDecimalNum原样保留前导.自动补0\num{.3e45}→0.3 × 10⁴⁵括号不确定度(...)按小数点位置对齐补零用±连接源码 L156-L170逗号,转为小数点.\num{12345,67890}→12345.67890-或\pm转为±前后为不换行空格\xa0\num{1-2i}→1 ± 2ii原样输出虚数单位1 ± 2ix转为×乘号\num{1.654 x 2.34 x 3.430}e指数转为× 10supn/sup\num{.3e45}→0.3 × 10sup45/sup空白忽略—需要注意parseDecimalNum会将字符串中的-替换为 Unicode 负号−hyphenToMinusL131-L133因此科学计数法中的负指数在 HTML 中呈现为10sup−45/sup而非-45。changelog 中提到的Fix negative numbers in siunitx commands2.11 版本修复即与此逻辑相关。单位解析 \si单位表、前缀表与组合规则\si由dosiL74-L77实现先解析可选参数keyvals用于读取per-mode等键值选项再解析花括号内的单位表达式。核心是siUnitL222-L289它通过many1 siUnitPart解析一或多个单位部件部件之间用不换行空格\xa0连接。siUnitPart的处理顺序由try组合保证回溯分隔符跳过.、~和普通空白——这解释了\si{kg.m.s^{-1}}中的点号被输出为空格kg m s⁻¹以及\si{g_{polymer}~mol_{cat}.s^{-1}}中~被吞掉幂前缀 基础单位siPrefix * siBase支持\square上标 2、\cubic上标 3、\raisetothe{n}任意指数见 L243-L254基础单位 后缀修饰siSuffix支持\squared、\cubed、\tothe{n}、^{n}上标和_{n}下标见 L255-L270——这正是测试中\square\volt→V²、\metre\squared→m²的来源中缀运算符\per默认把后一个单位的指数取负如\per\second→s⁻¹若per-modesymbol则输出/或显式/保留斜杠如\si{kg.m/s^2}→kg m/s²见 L232-L242。siBaseL277-L289负责把 LaTeX 命令名或普通单词映射为显示文本优先查siUnitMap与siUnitModifierMap若名字不在表中则回退为普通单词解析。单位映射表 siUnitMapsiUnitMapL316-L471收录了 SI 基础单位、导出单位、常数与文本形式的单位名称例如基础单位\metre→m、\kilogram→kg、\second→s、\ampere→A、\kelvin→K、\mole→mol、\candela→cd导出单位\newton→N、\pascal→Pa、\joule→J、\watt→W、\volt→V、\farad→F、\ohm→Ω、\henry→H、\gray→Gy、\lumen→lm、\lux→lx、\tesla→T、\weber→Wb、\becquerel→Bq、\sievert→Sv、\katal→kat其他常用量\celsius→°C、\degree→°、\percent→%、\litre→l、\hour→h、\minute→min、\angstrom→Å、\dalton→Da、\bar→bar、\arcminute→′、\arcsecond→″物理常数\clight→斜体c₀、\electronmass→斜体mₑ、\elementarycharge→斜体e、\bohr→斜体a₀、\planckbar→ℏ、\hartree→斜体Eₕ。这些映射解释了\si{\henry\second}→H s与\si{\gram\per\cubic\centi\metre}→g cm⁻³。词头映射表 siUnitModifierMapsiUnitModifierMapL291-L314覆盖全部 SI 词头\kilo→k、\mega→M、\giga→G、\tera→T、\peta→P、\exa→E、\zetta→Z、\yotta→Y、\deci→d、\centi→c、\milli→m、\micro→μ、\nano→n、\pico→p、\femto→f、\atto→a、\zepto→z、\yocto→y以及\deca/\deka→da。siBase中的词头与单位组合方式是先解析词头命令得到前缀字符串再递归解析后续单位(il ) $ siBase从而支持\kilo\gram→kg、\milli\metre→mm、\centi\metre→cm等复合形式。测试中\SIlist{0.13;0.67;0.80}{\milli\metre}→0.13 mm, 0.67 mm, 0.80 mm正是词头单位组合的结果。\per 的两种模式负指数 vs 斜杠\per的行为由siInfixL232-L242控制当解析器在options中查不到per-modesymbol时采用负指数风格——调用negateExponentL271-L276把后续单位的指数取负若没有指数则补⁻¹当per-modesymbol被显式给出时直接输出/。测试中的三组对照清晰展示了这一点\si{\kilogram\metre\per\second}→kg m s⁻¹默认负指数\si[per-modesymbol]{\kilogram\metre\per\second}→kg m/s斜杠\si[per-modesymbol]{\kilogram\metre\per\ampere\per\second}→kg m/A/s连续斜杠。注意在默认负指数模式下\per后面若有多个单位如\per\ampere\per\second每个单位都会被取负指数从而得到kg m A⁻¹ s⁻¹的形式。数值 单位\SI 与 \SIlist、\SIrange\SI由doSIL80-L90实现其语法为\SI{数值}[前缀]{单位}解析数值后可选的方括号内容作为数值前缀如货币符号\$再接单位。返回值用不换行空格emptyOr160L219-L220连接各部分保证数值与单位不会在换行处断开。\SIlist由doSIlistL112-L123实现用T.splitOn ;以分号切分数值列表每个数值经tonum解析后拼接同一单位最后按a, b, c的英文列表惯例用逗号与连接。\numlistdoSInumlistL101-L110逻辑相同但不带单位。测试验证\numlist{10;20;30}→10, 20, 30\SIlist{0.13;0.67;0.80}{\milli\metre}→0.13 mm, 0.67 mm, 0.80 mm。\SIrange由doSIrangeL197-L217实现解析起点、终点均支持可选前缀与单位后用 en-dashU2011即–连接当includeUnits为真\SIrange时每个端点都带单位为假\numrange时不带。测试验证\numrange{10}{20}→10–20\SIrange{0.13}{0.67}{\milli\metre}→0.13 mm–0.67 mm。角度 \ang度分秒的容错解析\ang由doSIangL182-L194实现。输入以分号分隔的 13 段分别对应度°U00B0、分′U2032、秒″U2033然后用ps repeat 保证缺省字段被当作空字符串跳过用dropPlus丢弃段首的号负号-保留空段不输出任何符号。测试中的五个用例覆盖了各种边界\ang{10}→10°\ang{1;2;3}→1°2′3″\ang{;;1}→1″前两段为空\ang{10;;}→10°被丢弃\ang{-0;1;}→-0°1′负号保留末段为空。siunitx v3 命令别名自 Pandoc 2.17 起changelog 记录于 #7614LaTeX 阅读器将 siunitx v3 的新命令映射为 v2 命令的同义词\unit≡\si\qty≡\SI\qtyrange≡\SIrange\qtylist≡\SIlist这保证了使用新版 siunitx 语法如\qty{5}{\metre}的文档也能被正确转换映射关系见 SIunitx.hs#L57-L72 的注释与命令表。实现边界与限制从源码可以确认以下边界changelog 亦有所提及上下文限制早期版本曾修复siunitx 单位命令只在 siunitx 上下文中识别的问题#4842——例如\l升的缩写不能与 LaTeX 的\lł 字符冲突因此模块只注册siunitxCommands表中列出的命令避免污染其他 LaTeX 语义非 siunitx 用法\num等命令的花括号内容若无法被parseNum识别如包含复杂宏会走tonum的Left _ - text value分支原样输出不会报错单位表范围siUnitMap与siUnitModifierMap是静态表各约 150 与 21 项未收录的单位命令会回退为普通单词解析而不是报错。如何复现与验证在仓库根目录执行需已安装 pandoc 可执行文件pandoc -f latex -t html然后粘贴测试用例中的 LaTeX 输入并以 Ctrl-DEOF结束即可得到与 test/command/6658.md 期望输出一致的 HTML。该文件本身采用 pandoc 命令测试框架的格式首行% pandoc -f latex -t html声明命令行随后是输入^D之后为期望输出可在make test测试套件中作为回归用例持续运行防止 siunitx 支持在未来版本中退化。小结通过 test/command/6658.md 这一个用例可以完整观察到 Pandoc LaTeX 阅读器对 siunitx 宏的工程化支持数值解析器parseNum将逗号、-、x、e指数等 LaTeX 记法映射为 Unicode 排版单位解析器siUnit通过静态单位表与词头表组合出任意 SI 单位并依据per-mode选项切换\per的呈现列表、范围与角度命令则分别实现了, 连接、en-dash 与度分秒的容错处理。这套实现既保证了科学计量内容在跨格式转换中的语义完整也为\qty、\unit等 v3 语法提供了向后兼容的入口是 LaTeX 阅读器中颇具代表性的宏命令 → 结构化文档转换范例。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考