Skip to content

Commit c8f2308

Browse files
committed
fix: handle math run with no text child in OMML->LaTeX conversion
do_r() called elm.findtext("./m:t") and iterated over the result directly. When a math run (<m:r>) has no <m:t> text child (e.g. a run that only carries formatting properties, produced by some Word equation editors), findtext() returns None and iterating over it raises TypeError: 'NoneType' object is not iterable. Because equation pre-processing is applied at the whole-document.xml level with a blanket try/except, this single malformed run aborts LaTeX conversion for every equation in the document, silently dropping all native Word equations from the output.
1 parent e144e0a commit c8f2308

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

packages/markitdown/src/markitdown/converter_utils/docx/math/omml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def do_r(self, elm):
373373
@todo \text (latex pure text support)
374374
"""
375375
_str = []
376-
for s in elm.findtext("./{0}t".format(OMML_NS)):
376+
for s in elm.findtext("./{0}t".format(OMML_NS)) or "":
377377
# s = s if isinstance(s,unicode) else unicode(s,'utf-8')
378378
_str.append(self._t_dict.get(s, s))
379379
return escape_latex(BLANK.join(_str))
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Regression test for a crash in the OMML -> LaTeX converter when a math run
4+
(<m:r>) has no <m:t> text child (e.g. a run that only carries formatting
5+
properties). Previously `do_r()` called `elm.findtext(...)` directly and
6+
iterated over the result, which raised `TypeError: 'NoneType' object is not
7+
iterable` when the run had no text, aborting equation conversion for the
8+
entire document.
9+
"""
10+
11+
from xml.etree import ElementTree as ET
12+
13+
from markitdown.converter_utils.docx.math.omml import OMML_NS, oMath2Latex
14+
15+
MATH_NS_DECL = f'xmlns:m="{OMML_NS[1:-1]}"'
16+
17+
18+
def _parse_omath(xml_fragment: str):
19+
wrapped = f"<m:oMath {MATH_NS_DECL}>{xml_fragment}</m:oMath>"
20+
return ET.fromstring(wrapped)
21+
22+
23+
def test_run_without_text_child_does_not_crash():
24+
# <m:r> with only <m:rPr>, no <m:t> child.
25+
element = _parse_omath("<m:r><m:rPr/></m:r>")
26+
# Should not raise TypeError: 'NoneType' object is not iterable
27+
result = oMath2Latex(element)
28+
assert result.latex == ""
29+
30+
31+
def test_run_with_text_still_converts():
32+
element = _parse_omath("<m:r><m:t>x</m:t></m:r>")
33+
result = oMath2Latex(element)
34+
assert result.latex == "x"
35+
36+
37+
def test_subscript_with_missing_text_run_does_not_crash():
38+
# Mirrors a real-world document: a subscript expression where one of the
39+
# runs involved has no text (e.g. produced by some Word equation editors).
40+
element = _parse_omath(
41+
"<m:sSub>"
42+
"<m:e><m:r><m:t>l</m:t></m:r></m:e>"
43+
"<m:sub><m:r><m:rPr/></m:r><m:r><m:t>1</m:t></m:r></m:sub>"
44+
"</m:sSub>"
45+
)
46+
result = oMath2Latex(element)
47+
assert "l" in result.latex
48+
assert "1" in result.latex

0 commit comments

Comments
 (0)