+149
@@ -0,0 +1,149 @@
|
||||
"""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))
|
||||
Reference in New Issue
Block a user