Word 文档公式转换详解:以 Docling 的 equations.docx.md 基准文件剖析 OMML 到 LaTeX 的实现
【免费下载链接】doclingGet your documents ready for gen AI项目地址: https://gitcode.com/GitHub_Trending/do/docling
Docling 将 Word(.docx)文档转换为结构化 DoclingDocument 与 Markdown 时,数学公式的处理是一条独立的实现链路:Word 内部用 OMML(Office Math Markup Language)存储公式,Docling 将其逐元素转换为 LaTeX,再按“行内公式 / 独立公式”两种形态嵌入正文。本文以转换基准文件tests/data/docx/groundtruth/equations.docx.md为主体,完整解析它所覆盖的公式形态、Docling 的转换输出结构(Markdown、DoclingDocument JSON、层级树),并深入源码说明 OMML→LaTeX 的实现与测试验证方式。读完后你将能够:读懂该基准文件的每一段内容对应哪种公式场景、定位公式转换在 docling/backend/msword_backend.py 与 docling/backend/docx/latex/omml.py 中的实现位置、并知道如何运行相关测试验证转换结果。
基准文件是什么:一次 Word 公式文档转换的期望输出
equations.docx.md 是 Docling 测试数据目录中的一份 groundtruth(基准输出)。它与以下三个文件构成同一文档的四种视图:
| 文件 | 作用 |
|---|---|
| equations.docx | 转换输入源(Word 文档,公式以 OMML 形式存储) |
| equations.docx.md | 期望的 Markdown 输出(本文主体) |
| equations.docx.json | 期望的 DoclingDocument JSON 结构 |
| equations.docx.itxt | 期望的文档层级树(item 列表)表示 |
端对端转换测试会将源文档实际转换出的 Markdown 与该基准文件逐字比对,因此这份文件本身就是“公式转换能力”的可执行验收标准。下面先完整给出基准文件内容,再逐类解析其覆盖的公式场景。
This is a word document and this is an inline equation: $A= \pi r^{2}$ . - First item with inline equation: $A= \pi r^{2}$ is the area formula. - Second item with equations: $E=mc^{2}$ and $F=ma$ are physics formulas. - The formula $a^{2}+b^{2}=c^{2}$ is the Pythagorean theorem. If instead, I want an equation by line, I can do this: $$a^{2}+b^{2}=c^{2} \times 23$$ And that is an equation by itself. Cheers! This is another equation: $$f\left(x\right)=a_{0}+\sum_{n=1}^{ \infty }\left(a_{n}\cos(\frac{n \pi x}{L})+b_{n}\sin(\frac{n \pi x}{L})\right)$$ This is text. This is text. ...(连续重复的填充段落) This is a word document and this is an inline equation: $A= \pi r^{2}$ . If instead, I want an equation by line, I can do this: $$\left(x+a\right)^{n}=\sum_{k=0}^{n}\left(\genfrac{}{}{0pt}{}{n}{k}\right)x^{k}a^{n-k}$$ And that is an equation by itself. Cheers! This is another equation: $$\left(1+x\right)^{n}=1+\frac{nx}{1!}+\frac{n\left(n-1\right)x^{2}}{2!}+ \text{ \textellipsis }$$ This is text. ...(连续重复的填充段落) This is a word document and these are inline equations: $N_{s}^{H}$ / $N_{s}^{P}$ . If instead, I want an equation by line, I can do this: $$e^{x}=1+\frac{x}{1!}+\frac{x^{2}}{2!}+\frac{x^{3}}{3!}+ \text{ \textellipsis } , - \infty < x < \infty$$ And that is an equation by itself. Cheers! Large operators and integrals are represented with n-ary objects in OMML XML: $$\sum_{0}^{2}x$$ $$\bigcup_{n=1}^{m}\left(X_{n} \cap Y_{n}\right)$$ $$\prod_{k=1}^{n}A_{k}$$ $$\bigwedge_{}^{}x$$ $$\int_{}^{}(2x+1)dx$$ $$\iint_{0}^{1}xdx$$ $$\iiint_{}^{}ydy$$ $$\oint_{}^{}\frac{dy}{dx}$$ $$\oiint_{0}^{2 \pi }idt$$ $$\oiiint_{C}^{}\frac{1}{z}dz$$ Operators used with limits: $$\operatorname{argmax}_{ \epsilon}f(x), \lim_{n}{\left(1+\frac{1}{n}\right)}^{n} , \max_{0 \leq x \leq 1}xe^{-x^{2}}, unsupported_{n}{\left(1+\frac{1}{n}\right)}^{n}$$ Equations with the OMML group character object: $$P_{ x}=\underbrace{S \cdot T \cdot G \cdot (x+y+z)}_{group\ with\ underbraces}+e^{x}$$ $$Q_{ y}=\overset{group\ with\ overbraces}{\overbrace{G \cdot T \cdot S \cdot (x+y+z)}}+e^{y}$$ $$s\left\{max\right\}= A \times B$$逐类解析:这份基准文件覆盖了哪些公式场景
基准文件并非随意堆砌公式,而是按 OMML 的构造类型逐段组织,每一段对应转换链路中的一个关键能力点。
行内公式(inline equation)
第一段正文与随后三个列表项展示的是“文字与公式混排”场景:
- 普通段落中的行内公式:
This is a word document and this is an inline equation: $A= \pi r^{2}$ . - 列表项中的行内公式:三条
-列表项分别覆盖“单公式”“同一列表项内两个公式($E=mc^{2}$与$F=ma$)“公式夹在文字中间”三种情况。 - 同一列表项内公式与文本交替出现:如第二项中
Second item with equations: $E=mc^{2}$ and $F=ma$ are physics formulas.,公式之间还夹着普通文本and。
值得注意的是第三处混排段落:$N_{s}^{H}$ / $N_{s}^{P}$中两个行内公式之间用斜杠分隔,且第二个公式后保留了一个零宽字符(基准文件第 31 行),这是对“公式紧贴标点/不可见字符”这类边角输入的输出保真验证——转换结果不能吞掉也不能错误改写这些字符。
独立公式(display equation)
独立成行的公式在 Markdown 输出中统一使用$$...$$包裹。基准文件包含多组:
- 带乘积尾项的简单式:
$$a^{2}+b^{2}=c^{2} \times 23$$ - 傅里叶级数(求和、上下限、无穷大、分数嵌套):
$$f\left(x\right)=a_{0}+\sum_{n=1}^{ \infty }\left(a_{n}\cos(\frac{n \pi x}{L})+b_{n}\sin(\frac{n \pi x}{L})\right)$$ - 二项式定理,其中组合数被转换为
\genfrac{}{}{0pt}{}{n}{k}形式,说明 Docling 对 OMML 中的 binomial 构造有专门映射 - 展开式中的
\text{ \textellipsis },即省略号经 OMML 的 text 对象转出为 LaTeX 的\textellipsis
每个$$公式段落前后都有空的 text 段落占位(见基准文件第 7、10 行的空段),这一点在 equations.docx.itxt 的层级树中可以一一对应(后文展开)。
n-ary 大算子与积分
基准文件中“Large operators and integrals are represented with n-ary objects in OMML XML”一节,集中列出了 10 个 n-ary 构造:
$$\sum_{0}^{2}x$$ $$\bigcup_{n=1}^{m}\left(X_{n} \cap Y_{n}\right)$$ $$\prod_{k=1}^{n}A_{k}$$ $$\bigwedge_{}^{}x$$ $$\int_{}^{}(2x+1)dx$$ $$\iint_{0}^{1}xdx$$ $$\iiint_{}^{}ydy$$ $$\oint_{}^{}\frac{dy}{dx}$$ $$\oiint_{0}^{2 \pi }idt$$ $$\oiiint_{C}^{}\frac{1}{z}dz$$覆盖了求和、并集、连乘、逻辑与、单/双/三重积分、闭环积分、双/三重闭环积分,并且包含上、下界留空(\int_{}^{})的情况,验证转换器对空 limits 的降级输出不会报错。
带极限的算子与“unsupported”回退
Operators used with limits一节的单行公式同时包含四种算子:
$$\operatorname{argmax}_{ \epsilon}f(x), \lim_{n}{\left(1+\frac{1}{n}\right)}^{n} , \max_{0 \leq x \leq 1}xe^{-x^{2}}, unsupported_{n}{\left(1+\frac{1}{n}\right)}^{n}$$前三个(argmax、lim、max)是算子与下标极限组合的标准写法;第四个unsupported_{n}{...}是故意使用的未知算子名——从源码结构看,docling/backend/docx/latex/latex_dict.py 中定义了 LIM_FUNC(极限类函数)、FUNC(函数类)等算子映射表,已知算子会被赋予极限排版,而unsupported这类不在映射表中的名字走原样输出的回退路径。把这两种行为放进同一个公式,可以在一条断言里同时验证“正确映射”与“未知算子不崩溃”。
OMML group 字符对象与特殊字符
结尾两节覆盖更细的构造:
$$P_{ x}=\underbrace{S \cdot T \cdot G \cdot (x+y+z)}_{group\ with\ underbraces}+e^{x}$$ $$Q_{ y}=\overset{group\ with\ overbraces}{\overbrace{G \cdot T \cdot S \cdot (x+y+z)}}+e^{y}$$ $$s\left\{max\right\}= A \times B$$前两个分别是\underbrace与\overset{...}{\overbrace{...}}的花括号分组构造,第三个\left\{max\right\}验证反斜杠转义字符在花括号中的输出。
源码链路:OMML 是如何变成这些 LaTeX 的
基准文件里每一行$$与$都来自同一条转换链路,核心代码集中在两个文件。
OMML→LaTeX 转换器:docling/backend/docx/latex/omml.py
docling/backend/docx/latex/omml.py(约 864 行)的模块 docstring 说明了它的职责:将 Word 文档中的 OMML 元素转换为 LaTeX 格式,处理分数、上下标、矩阵、极限与特殊字符;文件头部还注明其实现改编自开源的 dwml 项目(2025-01-23 引入)。几个可直接观察到的实现事实:
- 模块以
OMML_NS = "{http://schemas.openxmlformats.org/officeDocument/2006/math}"作为 OMML 命名空间常量,所有 lxml 元素查找都基于该命名空间; - 大量转换规则以字典常量形式放在 docling/backend/docx/latex/latex_dict.py(
LIM_FUNC、FUNC、GROUPING_FUNCS、RAD、POS、MATH_CHARS等),omml.py 顶部一次性导入——基准文件中lim/max/argmax与unsupported的不同输出,正是查这张表的结果; - 模块对
pylatexenc.latexencode.UnicodeToLatexEncoder做了带保护的可选导入(try: import ... except ImportError: pass,并注释说明该依赖由MsWordDocumentBackend.__init__统一保证),用于把公式中的 Unicode 数学字符编码为 LaTeX。
段落内公式混排:msword_backend.py 的 _handle_equations_in_text
docling/backend/msword_backend.py 是 .docx 后端主体(约 3794 行),公式相关的关键方法:
_handle_equations_in_text(约 L1925):遍历段落元素,把普通文本片段与公式分别收集,公式以带标记的形式(<eq>...</eq>)进入only_equations列表并插入原文本的对应位置,返回(output_text, only_equations)。方法内有一道防护:若解析得到的公式/文本子串与原段文本无法对齐(数量或内容不一致),会记录日志并放弃公式解析、原样返回原段落文本——宁可输出纯文本也不输出错位的公式。这正是基准文件中混排段落能逐字比对的前提。_add_inline_equations_to_parent(约 L2410):把“文本 + 行内公式”作为子项挂到父元素下,注释明确说明它同时服务于普通段落与列表项两条路径;_add_list_item_with_equations(约 L2639):专门处理含行内公式的列表项,与_handle_text_elements中“若列表项检测到len(equations) > 0则走特殊分支”的调用点对应——这就是基准文件三条-列表项在输出中仍保留列表语义、且公式以$...$嵌在项内文字中间的原因。- 表格单元格内同样会调用
_handle_equations_in_text(约 L2793-L2797),表格公式的覆盖由table_with_equations.docx姊妹样本单独验证(见文末扩展阅读)。
输出结构印证:JSON 与层级树里的 FormulaItem
基准 Markdown 只是同一转换结果的投影,结构化的真相在 equations.docx.json 与 equations.docx.itxt 中。
DoclingDocument JSON
该 JSON 顶部声明:
{ "schema_name": "DoclingDocument", "version": "1.10.0", "name": "equations", "origin": { "mimetype": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "binary_hash": 14529667209329784536, "filename": "equations.docx" }, ... }origin记录了输入文件类型与内容哈希;body.children通过$ref(如#/groups/0、#/texts/17)引用正文元素。含行内公式的段落在结构中呈现为 group(inline 容器)挂多个子项(text 与 formula 交替),独立公式段落则直接作为顶层 formula 元素出现在 body 子项序列中。
层级树 .itxt 的逐项对应
equations.docx.itxt 用缩进树呈现了同一结构,例如基准文件开头的混排段落在树中是:
item-0 at level 0: unspecified: group _root_ item-1 at level 1: inline: group group item-2 at level 2: text: This is a word document and this is an inline equation: item-3 at level 2: formula: A= \pi r^{2} item-4 at level 2: text: . item-5 at level 1: list: group list item-6 at level 2: list_item: item-7 at level 3: inline: group group item-8 at level 4: text: First item with inline equation: item-9 at level 4: formula: A= \pi r^{2} item-10 at level 4: text: is the area formula.可以清楚看到:含公式的段落被包装为inline: group,其下text与formula子项交替排列;列表项为list -> list_item -> inline: group的三层嵌套。独立公式则直接挂在一级,如item-25 at level 1: formula: a^{2}+b^{2}=c^{2} \times 23。另外,.itxt 对超长公式行会做省略显示(如f\left(x\right)=a_{0}+\sum_{n=1} ... })+...一行),这是树视图的展示截断,完整公式仍以 JSON 与 Markdown 输出为准。
验证与复现:如何确认这份基准仍然成立
端到端测试比对
tests/test_backend_msword.py 中的test_e2e_docx_conversions(L100) 是核心验收用例:它对tests/data/docx/sources/下的 docx 样本执行转换,并将生成的 Markdown 与groundtruth/目录中同名.md基准逐一比对——equations.docx在其中。公式输出的任何一个字符变化(例如\bigwedge写成\bigwedge_{}^{}x的变体)都会使该测试失败。
公式混排逻辑的单元测试
同一测试文件中还有针对混排解析的定向用例:
test_handle_equations_in_text_returns_original_text_on_mismatch(L1022):当解析子串与原段文本不匹配时,方法必须返回原始文本、equations为空;test_handle_equations_in_text_skips_empty_substrings(L1039):空子串应被跳过而公式仍被收录;test_handle_text_elements_inline_equations_stop_when_text_is_consumed(L1110):文本被公式标记消费完毕后,循环应正确终止。
在仓库根目录下安装好开发依赖后运行:
pytest tests/test_backend_msword.py -k equations即可只跑与公式相关的用例;跑全文件则覆盖列表、表格、代码块、页眉页脚等其他 docx 场景(同一文件约 1642 行、50+ 个测试函数)。
手动复现转换结果
不想跑测试时,可以直接对源文档做转换并查看输出:
docling convert tests/data/docx/sources/equations.docx或使用 Python API(参考 docs/examples/minimal.py 的最简用法):
from docling.document_converter import DocumentConverter converter = DocumentConverter() result = converter.convert("tests/data/docx/sources/equations.docx") print(result.document.export_to_markdown())把打印出的 Markdown 与 equations.docx.md 对照,即可手工验证本文解析的全部公式形态;调用result.document的 JSON 导出则可对照 equations.docx.json 检查 FormulaItem 的层级。
相关公式样本:这套机制的更宽覆盖
tests/data/docx/下还有一组聚焦 OMML 细节的姊妹样本,与equations.docx共同构成公式转换的完整测试面:
| 样本(sources 目录下) | 针对的 OMML 场景 |
|---|---|
| omml_frac_superscript.docx | 分数与上下标组合构造 |
| omml_func_log.docx | 函数类算子(log 等)映射 |
| omml_multi_equation_paragraph.docx | 同一自然段内多段公式 |
| omml_text_escapes_in_math.docx | 公式内 text 对象的转义字符 |
| table_with_equations.docx | 表格单元格内的公式(对应 msword_backend.py 的单元格分支) |
每个样本同样配有.md/.json/.itxt三种 groundtruth,验证方式与本文完全一致。
小结
equations.docx.md这份看似简单的基准文件,实际是 Docling “Word 公式 → LaTeX/Markdown” 转换能力的浓缩验收清单:行内与独立公式、n-ary 大算子与积分、极限算子及未知算子回退、花括号分组与特殊字符,全部以可逐字断言的形式固定下来。其背后的实现分两层——omml.py 负责 OMML 元素到 LaTeX 字符串的元素级翻译(映射表在 latex_dict.py),msword_backend.py 负责把翻译结果按段落、列表项、表格单元格的上下文安全地装配回文档结构,并在文本对齐失败时降级为纯文本输出。理解了这一文件与这条链路,你就掌握了在 Docling 中排查任何 docx 公式转换问题的完整路径:先看基准文件确定期望行为,再沿_handle_equations_in_text与 omml 转换器定位偏差。
【免费下载链接】doclingGet your documents ready for gen AI项目地址: https://gitcode.com/GitHub_Trending/do/docling
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考