""" Visitor for (number) MathML elements. The element represents numeric literals in MathML, including: - Integers (1, 2, 3, ...) - Decimals (3.14, 2.718, ...) - Scientific notation (1.5×10⁻³) - Fractions in decimal form (0.5) """ from lxml import etree as ET from ..core.interfaces import Visitor, Strategy from ..utils import text_content as _text_content, convert_spaces_to_latex, escape_latex_special_chars from ..data import CHEMICAL_ELEMENTS, COMMON_UNITS from ..converters.post_processor import PostProcessor class MnVisitor(Visitor): """ Handles (number) MathML elements. This visitor converts numbers to LaTeX format. Most numbers are returned as-is, but the strategy can customize formatting for specific subjects (e.g., chemistry, physics). Examples: Standard numbers: 123 → 123 3.14 → 3.14 0.5 → 0.5 Scientific notation (handled at operator level): 1.5×10-3 → 1.5 \\times 10^{-3} """ def __init__(self, strategy: Strategy): """ Initialize the Mn visitor. Args: strategy: The formatting strategy to use """ super().__init__(strategy) self._converter_context = None def set_converter_context(self, converter): """Set converter context for color suppression checking.""" self._converter_context = converter def visit(self, node: ET.Element) -> str: """ Convert element to LaTeX. Args: node: The element to convert Returns: LaTeX string representation Raises: ValueError: If node is not an element """ if not self.can_handle(node): raise ValueError(f"MnVisitor cannot handle element '{node.tag}'") # Get text content content = _text_content(node) # Handle empty content if not content: return "" # Escape LaTeX special characters before any other processing content = escape_latex_special_chars(content) # Handle spaces (NBSP → ~, space → \ ) if '\xa0' in content or ' ' in content: converted = convert_spaces_to_latex(content) # If content was ONLY spaces, return directly if not content.strip(): return self._wrap_with_color(converted, node) # Update content with converted spaces content = converted # Delegate to strategy for subject-specific number formatting result = self.strategy.format_number(content, node) return self._wrap_with_color(result, node) def _wrap_with_color(self, latex: str, node: ET.Element) -> str: """ Wrap LaTeX with color command if node has color attribute. Args: latex: The LaTeX string to wrap node: The element node Returns: LaTeX string, optionally wrapped with color command """ # Use the centralized color wrapping utility with suppression support from ..utils.color_helpers import wrap_element_with_color # Get converter context from multiple sources (for backward compatibility): # 1. Direct visitor context (set by legacy_adapter) # 2. Strategy context (shared across all visitors, including child visitors) context = getattr(self, '_converter_context', None) if context is None and hasattr(self.strategy, '_converter_context'): context = self.strategy._converter_context return wrap_element_with_color(latex, node, context) def can_handle(self, node: ET.Element) -> bool: """ Check if this visitor can handle the given node. Args: node: The MathML element to check Returns: True if node is an element, False otherwise """ from ..utils import local_name as _local return _local(node.tag) == 'mn' @property def element_name(self) -> str: """Get the element name this visitor handles.""" return 'mn' def __repr__(self) -> str: """String representation for debugging.""" return f"MnVisitor(strategy={self.strategy.name})"