""" DOCX parser for Microsoft Word documents. This parser handles .docx files, extracting OMML equations and converting them to MathML format using the existing DOCX processing pipeline. """ import os from typing import List from ..core.interfaces import Parser class DocxParser(Parser): """ Parser for Microsoft Word .docx files. This parser handles .docx files by extracting OMML equations and converting them to MathML using the existing DOCX processing pipeline. Examples: Parse DOCX file: >>> parser = DocxParser() >>> results = parser.parse("equations.docx") >>> isinstance(results, list) True """ def __init__(self): """Initialize the DOCX parser.""" pass def parse(self, source: str) -> List[str]: """ Parse DOCX file and return list of MathML equation strings. Args: source: Path to .docx file Returns: List of MathML equation strings (extracted from DOCX) Raises: FileNotFoundError: If DOCX file doesn't exist ValueError: If file is not a valid .docx file RuntimeError: If DOCX processing fails Examples: >>> parser = DocxParser() >>> # Assuming test.docx exists with equations >>> results = parser.parse("test.docx") >>> all(isinstance(eq, str) for eq in results) True """ # Validate file exists if not os.path.exists(source): raise FileNotFoundError(f"DOCX file not found: {source}") # Validate file extension if not source.lower().endswith('.docx'): raise ValueError(f"File must have .docx extension: {source}") # Import here to avoid circular dependencies from ..processing import OMMLExtractor from ..core import omml_to_mathml from ..utils.styling_postprocessor import apply_styling_to_mathml_from_omml # 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') try: # Extract OMML from DOCX using OMMLExtractor extractor = OMMLExtractor() equation_dicts = extractor.extract_from_docx(source) if not equation_dicts: # No equations found return [] # Convert each OMML to MathML results = [] for eq_dict in equation_dicts: try: # Extract the cleaned OMML string from the dictionary omml_str = eq_dict.get('cleaned_omml', eq_dict.get('omml', '')) if not omml_str: continue # Step 1: XSLT transformation mathml_str = omml_to_mathml(omml_str, xsl_path) # Step 2: Post-process to fix table structure and add styling # CRITICAL: This fixes the OMML2MML.xsl bug where should be mathml_str = apply_styling_to_mathml_from_omml(omml_str, mathml_str) 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 equation: {e}") continue return results except Exception as e: raise RuntimeError(f"DOCX processing failed: {e}") 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 is a .docx file, False otherwise Examples: >>> parser = DocxParser() >>> parser.can_handle("equations.docx") True >>> parser.can_handle("x") False """ # Check file extension if source.lower().endswith('.docx'): return True # Check if it's an existing file with .docx extension if os.path.exists(source) and source.lower().endswith('.docx'): return True return False @property def parser_type(self) -> str: """Get the parser type identifier.""" return "docx" def __repr__(self) -> str: """String representation for debugging.""" return "DocxParser()"