""" MathML parser for direct MathML input. This parser handles MathML XML input, performing validation and extraction of individual math equations. """ import sys from typing import List from pathlib import Path from lxml import etree as ET from ..core.interfaces import Parser # Import utilities from omml_to_tex package for HTML entity decoding and equation extraction sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from ..utils import decode_html_entities, extract_math_equations class MathMLParser(Parser): """ Parser for MathML XML input. This parser handles direct MathML XML input, performing basic validation and extracting individual elements. Examples: Single equation: >>> parser = MathMLParser() >>> mathml = "x" >>> results = parser.parse(mathml) >>> len(results) 1 Multiple equations: >>> parser = MathMLParser() >>> mathml = "xy" >>> results = parser.parse(mathml) >>> len(results) 2 """ def __init__(self): """Initialize the MathML parser.""" pass def parse(self, source: str) -> List[str]: """ Parse MathML input and return list of MathML equation strings. Args: source: MathML XML string or file path Returns: List of MathML equation strings (one per element) Raises: ValueError: If input is not valid MathML XML RuntimeError: If parsing fails Examples: >>> parser = MathMLParser() >>> mathml = "x+y" >>> results = parser.parse(mathml) >>> len(results) 1 >>> 'x' in results[0] 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 MathML file: {e}") # STRATEGY 1: Try regex-based extraction first (handles multi-equation files + HTML entities) # This is the same approach as the original omml_to_tex.py try: equations = extract_math_equations(source) if equations: return equations except Exception: # If regex extraction fails, fall through to XML parsing pass # STRATEGY 2: Try XML parsing as fallback (single well-formed document) try: root = ET.fromstring(source.encode('utf-8')) except ET.XMLSyntaxError as e: raise ValueError(f"Invalid XML syntax: {e}") # Find all elements math_elements = [] # Check if root itself is a element if self._is_math_element(root): math_elements.append(root) else: # Search for elements (with or without namespace) math_elements.extend(root.findall('.//{http://www.w3.org/1998/Math/MathML}math')) math_elements.extend(root.findall('.//math')) if not math_elements: # No elements found - treat entire document as one equation if it looks like MathML if self._looks_like_mathml(root): math_elements.append(root) else: raise ValueError("No elements found in MathML input") # Convert each element to string results = [] for math_elem in math_elements: mathml_str = ET.tostring(math_elem, encoding='unicode') results.append(mathml_str) 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 MathML, False otherwise Examples: >>> parser = MathMLParser() >>> parser.can_handle("x") True >>> parser.can_handle("...") False """ # Check for MathML markers content_start = source.strip()[:200] return ' str: """Get the parser type identifier.""" return "mathml" def _is_math_element(self, element: ET.Element) -> bool: """Check if element is a element.""" local_name = element.tag.split('}')[-1] if '}' in element.tag else element.tag return local_name == 'math' def _looks_like_mathml(self, element: ET.Element) -> bool: """Check if element tree looks like MathML content.""" # Check for common MathML element tags mathml_tags = {'mi', 'mo', 'mn', 'mrow', 'mfrac', 'msup', 'msub', 'mtext'} # Get all element tags in tree all_tags = set() for elem in element.iter(): local_name = elem.tag.split('}')[-1] if '}' in elem.tag else elem.tag all_tags.add(local_name) # If at least 2 MathML tags present, consider it MathML return len(all_tags & mathml_tags) >= 2 def __repr__(self) -> str: """String representation for debugging.""" return "MathMLParser()"