"""Best-effort OMML → readable LaTeX-like string for $$ / $ fences.""" from __future__ import annotations _OMML_NS = "http://schemas.openxmlformats.org/officeDocument/2006/math" def _local(tag: str) -> str: if "}" in tag: return tag.rsplit("}", 1)[-1] return tag def omml_to_latex(element) -> str: """Flatten OMML tree to a readable formula string (not perfect LaTeX).""" if element is None: return "" parts: list[str] = [] _walk(element, parts) text = "".join(parts) text = " ".join(text.split()) return text.strip() def _walk(node, parts: list[str]) -> None: tag = _local(node.tag) if tag == "t": parts.append(node.text or "") return if tag == "f": # fraction num = _child(node, "num") den = _child(node, "den") parts.append(r"\frac{") if num is not None: _walk(num, parts) parts.append("}{") if den is not None: _walk(den, parts) parts.append("}") return if tag == "sSup": base = _child(node, "e") sup = _child(node, "sup") if base is not None: _walk(base, parts) parts.append("^{") if sup is not None: _walk(sup, parts) parts.append("}") return if tag == "sSub": base = _child(node, "e") sub = _child(node, "sub") if base is not None: _walk(base, parts) parts.append("_{") if sub is not None: _walk(sub, parts) parts.append("}") return if tag == "sSubSup": base = _child(node, "e") sub = _child(node, "sub") sup = _child(node, "sup") if base is not None: _walk(base, parts) parts.append("_{") if sub is not None: _walk(sub, parts) parts.append("}^{") if sup is not None: _walk(sup, parts) parts.append("}") return if tag == "rad": deg = _child(node, "deg") e = _child(node, "e") if deg is not None and "".join(deg.itertext()).strip(): parts.append(r"\sqrt[") _walk(deg, parts) parts.append("]{") else: parts.append(r"\sqrt{") if e is not None: _walk(e, parts) parts.append("}") return if tag == "nary": for child in node: _walk(child, parts) return if tag in ("oMath", "oMathPara", "e", "num", "den", "sup", "sub", "deg", "fPr", "ctrlPr", "rPr", "sSupPr", "sSubPr", "sSubSupPr", "radPr", "naryPr", "dPr", "boxPr", "argSz"): for child in node: _walk(child, parts) return if tag == "r": for child in node: _walk(child, parts) return if tag == "d": # delimiter parts.append("(") for child in node: if _local(child.tag) != "dPr": _walk(child, parts) parts.append(")") return if tag in ("pPr", "rFonts", "sz", "szCs", "color", "jc"): return for child in node: _walk(child, parts) if node.tail: parts.append(node.tail) def _child(node, local_name: str): for child in node: if _local(child.tag) == local_name: return child return None def find_omml(paragraph_element) -> list: """Return all m:oMath / m:oMathPara elements under a paragraph.""" if paragraph_element is None: return [] found = [] for el in paragraph_element.iter(): tag = _local(el.tag) if tag in ("oMath", "oMathPara"): found.append(el) return found def paragraph_has_omml(paragraph_element) -> bool: return bool(find_omml(paragraph_element))