r""" Visitor for (spacing) MathML elements. The element represents horizontal spacing in mathematical expressions. It uses the width attribute to specify the amount of space. Common spacing types: - Named spaces: thinmathspace, mediummathspace, thickmathspace - Negative spaces: negativethinmathspace, etc. - Explicit sizes: 0.1em, 1em, 2em This visitor maps MathML spacing to LaTeX spacing commands. """ from lxml import etree as ET from ..core.interfaces import Visitor, Strategy from ..utils import local_name as _local class MspaceVisitor(Visitor): r""" Handles (spacing) MathML elements. Converts spacing specifications to LaTeX spacing commands. Examples: Thin space: → \, Medium space: → \: Thick space: → \; Quad space: → \quad Qquad space: → \qquad Negative space: → \! Default (no width): → \, (thin space) """ # Mapping from MathML spacing names to LaTeX commands SPACING_MAP = { # Named mathematical spaces "thinmathspace": "\\,", "mediummathspace": "\\:", "thickmathspace": "\\;", "verythinmathspace": "\\!", "veryverythinmathspace": "\\!", # Negative spaces (for fine-tuning) "negativethinmathspace": "\\!", "negativemediummathspace": "\\!", "negativethickmathspace": "\\!", # Explicit em-based sizes "0em": "", # No space "0.1em": "\\,", "0.2em": "\\:", "0.3em": "\\;", "0.5em": "\\;", "1em": "\\quad", "2em": "\\qquad", # Alternative common values "thin": "\\,", "medium": "\\:", "thick": "\\;", } def __init__(self, strategy: Strategy): """Initialize the Mspace visitor.""" super().__init__(strategy) def visit(self, node: ET.Element) -> str: r""" Convert element to LaTeX spacing. Args: node: The element to convert Returns: LaTeX spacing command (e.g., \,, \:, \;, \quad, \qquad) Raises: ValueError: If node is not an element """ if not self.can_handle(node): raise ValueError(f"MspaceVisitor cannot handle element '{node.tag}'") # Get width attribute width = node.get("width", "") # Handle empty width (default to thin space) if not width: return "\\," # Check if width is in our mapping if width in self.SPACING_MAP: return self.SPACING_MAP[width] # Try to parse numeric width (e.g., "0.5em", "1.5em") spacing_cmd = self._parse_numeric_width(width) if spacing_cmd: return spacing_cmd # Default: thin space return "\\," def _parse_numeric_width(self, width: str) -> str: """ Parse numeric width values and convert to LaTeX spacing. Args: width: Width string (e.g., "0.5em", "1.5em") Returns: LaTeX spacing command, or empty string if cannot parse """ # Remove whitespace width = width.strip() # Try to extract numeric value from em-based widths if width.endswith("em"): try: value = float(width[:-2]) # Map numeric values to spacing commands if value <= 0: return "" elif value < 0.15: return "\\," # thin elif value < 0.25: return "\\:" # medium elif value < 0.6: return "\\;" # thick elif value < 1.5: return "\\quad" else: return "\\qquad" except ValueError: pass # Try to extract numeric value from pt-based widths if width.endswith("pt"): try: value = float(width[:-2]) # Map pt values to spacing (rough approximation) # 1em ≈ 10pt in typical math fonts if value <= 0: return "" elif value < 2: return "\\," elif value < 4: return "\\:" elif value < 6: return "\\;" elif value < 12: return "\\quad" else: return "\\qquad" except ValueError: pass # Could not parse return "" def can_handle(self, node: ET.Element) -> bool: """Check if this visitor can handle the given node.""" return _local(node.tag) == 'mspace' @property def element_name(self) -> str: """Get the element name this visitor handles.""" return 'mspace' def __repr__(self) -> str: """String representation for debugging.""" return f"MspaceVisitor(strategy={self.strategy.name})"