""" OMML parser for Office Math Markup Language input. This parser handles OMML XML input from Microsoft Office, converting it to MathML format using the existing OMML processing pipeline. """ from typing import List from lxml import etree as ET from ..core.interfaces import Parser class OmmlParser(Parser): """ Parser for OMML (Office Math Markup Language) XML input. This parser handles OMML XML from Microsoft Office documents, converting it to MathML using the existing OMML transformation pipeline. Examples: Single OMML equation: >>> parser = OmmlParser() >>> omml = 'x' >>> results = parser.parse(omml) >>> len(results) >= 1 True """ def __init__(self): """Initialize the OMML parser.""" pass def parse(self, source: str) -> List[str]: """ Parse OMML input and return list of MathML equation strings. Args: source: OMML XML string or file path Returns: List of MathML equation strings (converted from OMML) Raises: ValueError: If input is not valid OMML XML RuntimeError: If OMML to MathML conversion fails Examples: >>> parser = OmmlParser() >>> omml = '...' >>> results = parser.parse(omml) >>> isinstance(results, list) True """ import os # Check if source is a file path if os.path.exists(source): try: with open(source, 'r', encoding='utf-8') as f: source = f.read() except (IOError, UnicodeDecodeError) as e: raise ValueError(f"Failed to read OMML file: {e}") # Parse XML try: root = ET.fromstring(source.encode('utf-8')) except ET.XMLSyntaxError as e: raise ValueError(f"Invalid XML syntax: {e}") # Find all OMML math elements # OMML uses or elements omml_ns = {'m': 'http://schemas.openxmlformats.org/officeDocument/2006/math'} # Find oMath elements (with or without namespace) omath_elements = [] omath_elements.extend(root.findall('.//m:oMath', omml_ns)) omath_elements.extend(root.findall('.//m:oMathPara', omml_ns)) # Try without namespace (in case it's not declared) if not omath_elements: for elem in root.iter(): local_name = elem.tag.split('}')[-1] if '}' in elem.tag else elem.tag if local_name in ('oMath', 'oMathPara'): omath_elements.append(elem) if not omath_elements: # Check if root itself is an OMML element root_local = root.tag.split('}')[-1] if '}' in root.tag else root.tag if root_local in ('oMath', 'oMathPara'): omath_elements.append(root) else: raise ValueError("No OMML math elements ( or ) found in input") # Convert each OMML element to MathML results = [] for omml_elem in omath_elements: try: mathml_str = self._omml_to_mathml(omml_elem) if mathml_str and mathml_str.strip(): results.append(mathml_str) except Exception as e: # Log error but continue with other equations print(f"Warning: Failed to convert OMML element: {e}") continue if not results: raise RuntimeError("Failed to convert any OMML elements to MathML") return results def can_handle(self, source: str) -> bool: """ Check if this parser can handle the given input source. Args: source: Input source to check Returns: True if source appears to be OMML, False otherwise Examples: >>> parser = OmmlParser() >>> parser.can_handle("...") True >>> parser.can_handle("x") False """ # Check for OMML markers content_start = source.strip()[:200] return ' str: """Get the parser type identifier.""" return "omml" def _omml_to_mathml(self, omml_element: ET.Element) -> str: """ Convert OMML element to MathML string with styling preservation. This wraps the existing OMML to MathML conversion from the core module, and adds post-processing to preserve styling (bold, fonts, colors) that the XSLT stylesheet doesn't preserve. Args: omml_element: OMML XML element Returns: MathML string with styling attributes Raises: RuntimeError: If conversion fails """ # Import here to avoid circular dependencies import os from ..core import omml_to_mathml from ..utils.styling_postprocessor import apply_styling_to_mathml_from_omml try: # Convert OMML element to string omml_str = ET.tostring(omml_element, encoding='unicode') # Get XSL path (in core module) current_dir = os.path.dirname(os.path.abspath(__file__)) core_dir = os.path.join(os.path.dirname(current_dir), 'core') xsl_path = os.path.join(core_dir, 'OMML2MML.xsl') # Step 1: Use XSLT to convert OMML to MathML mathml_str = omml_to_mathml(omml_str, xsl_path) # Step 2: Post-process to add styling (bold, fonts, colors) # The XSLT doesn't preserve m:sty, m:scr, w:color, etc. # This step extracts styling from OMML and applies to MathML mathml_str = apply_styling_to_mathml_from_omml(omml_str, mathml_str) return mathml_str except Exception as e: raise RuntimeError(f"OMML to MathML conversion failed: {e}") def __repr__(self) -> str: """String representation for debugging.""" return "OmmlParser()"