Skip to content

DOCX math: a delimiter with several arguments keeps only the last one #2573

Description

@Zhuoxi2000

When a Word equation contains a delimiter (<m:d>) with more than one argument, MarkItDown keeps only the last argument and drops the rest without any warning. The separator character (<m:sepChr>) is never read either.

Word writes this structure for the bracket templates "with separators" (Insert > Equation > Bracket), for example (a|b), {x|x>0} and bra-ket notation.

Repro

from xml.etree import ElementTree as ET
from markitdown.converter_utils.docx.math.omml import OMML_NS, oMath2Latex

M = f'xmlns:m="{OMML_NS[1:-1]}"'

def latex(xml):
    return oMath2Latex(ET.fromstring(f"<m:oMath {M}>{xml}</m:oMath>")).latex

r = lambda t: f"<m:r><m:t>{t}</m:t></m:r>"

# Explicit separator
print(latex(r("f") + '<m:d><m:dPr><m:sepChr m:val=","/></m:dPr>'
            f"<m:e>{r('x')}</m:e><m:e>{r('y')}</m:e></m:d>"))
# -> f\left(y\right)

# Set-builder braces, default separator (no sepChr)
print(latex('<m:d><m:dPr><m:begChr m:val="{"/><m:endChr m:val="}"/></m:dPr>'
            f"<m:e>{r('x')}</m:e><m:e>{r('x>0')}</m:e></m:d>"))
# -> \left\{x>0\right\}

End to end, adding the first equation to a copy of tests/test_files/equations.docx makes MarkItDown().convert() emit $$f\left(y\right)$$.

Expected vs actual

Input Expected Actual
f + <m:d> with sepChr=",", args x, y f\left(x,y\right) f\left(y\right)
<m:d> braces, no sepChr, args x, x>0 \left\{x|x>0\right\} \left\{x>0\right\}
<m:d> with sepChr=";", args a, b, c \left(a;b;c\right) \left(c\right)

In the OMML schema (ECMA-376 / ISO/IEC 29500-1, CT_D), <m:e> has maxOccurs="unbounded". The sepChr element "specifies the character that separates base arguments e in the delimiter object d". When the element is omitted, the separator is a vertical bar (U+2502). When it is present without m:val, there is no separator. See the Open XML SDK SeparatorChar reference, which reproduces the spec text. Dropping arguments silently loses document content.

Cause

In packages/markitdown/src/markitdown/converter_utils/docx/math/omml.py, oMath2Latex.do_d reads its children with process_children_dict(). That function returns a dict keyed by tag name, so each <m:e> overwrites the previous one and c_dict["e"] holds only the last argument. Separately, Pr.__val_tags (and Pr.tag2meth) do not include sepChr, so pr.sepChr is always None.

Suggested fix

In do_d, collect every <m:e> in order with process_children_list(elm, include=("e",)). Have Pr read sepChr, with an empty value when m:val is missing. Then join the arguments with the separator, falling back to | when sepChr is absent. A single-argument delimiter gives the same output as before.

Environment

  • markitdown main at 1f9530a (0.1.8)
  • Python 3.13, macOS

AI assistance: drafted with an AI coding assistant (Claude). The reproduction above was run locally against the current default branch.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions