r""" Visitor for (nth root) MathML elements. The element represents nth roots with explicit index. The LaTeX conversion uses \sqrt[n]{...} notation. Note: When index is 2, optimizes to \sqrt{...} (standard square root). """ from lxml import etree as ET from ..core.interfaces import Visitor, Strategy from ..utils import text_content as _text_content, local_name as _local class MrootVisitor(Visitor): r""" Handles (nth root) MathML elements. Converts nth root expressions to LaTeX \sqrt[n]{...} notation. Optimizes square roots (index=2) to \sqrt{...}. Examples: Cube root: x3 → \sqrt[3]{x} Fourth root: 164 → \sqrt[4]{16} Square root (optimized): x2 → \sqrt{x} Variable index: xn → \sqrt[n]{x} Complex base: x+1 3 → \sqrt[3]{x + 1} """ def __init__(self, strategy: Strategy): """Initialize the Mroot visitor.""" super().__init__(strategy) def visit(self, node: ET.Element) -> str: r""" Convert element to LaTeX. Args: node: The element to convert Returns: LaTeX nth root string (\sqrt[n]{content} or \sqrt{content}) Raises: ValueError: If node is not an element ValueError: If mroot has fewer than 2 children """ if not self.can_handle(node): raise ValueError(f"MrootVisitor cannot handle element '{node.tag}'") children = list(node) # mroot must have exactly 2 children: base and index # Handle malformed input gracefully for MathJax compatibility if len(children) < 2: if len(children) == 0: return "\\sqrt{}" elif len(children) == 1: # Only base provided, assume square root base = self._convert_child(children[0]) return f"\\sqrt{self._brace(base)}" # Convert base (radicand) and index (root degree) base = self._convert_child(children[0]) index = self._convert_child(children[1]) # Optimize: omit [2] for square roots (standard mathematical notation) # Also omit empty brackets [] when index is empty index_stripped = index.strip() if index_stripped == "2" or not index_stripped: return f"\\sqrt{self._brace(base)}" else: return f"\\sqrt[{index}]{self._brace(base)}" def _convert_child(self, child: ET.Element) -> str: """Convert a child element to LaTeX.""" tag = _local(child.tag) # Import visitors as needed to avoid circular imports if tag == 'mi': from .mi_visitor import MiVisitor return MiVisitor(self.strategy).visit(child) elif tag == 'mo': from .mo_visitor import MoVisitor return MoVisitor(self.strategy).visit(child) elif tag == 'mn': from .mn_visitor import MnVisitor return MnVisitor(self.strategy).visit(child) elif tag == 'mfrac': from .mfrac_visitor import MfracVisitor return MfracVisitor(self.strategy).visit(child) elif tag == 'msup': from .msup_visitor import MsupVisitor return MsupVisitor(self.strategy).visit(child) elif tag == 'msub': from .msub_visitor import MsubVisitor return MsubVisitor(self.strategy).visit(child) elif tag == 'mrow': from .mrow_visitor import MrowVisitor return MrowVisitor(self.strategy).visit(child) elif tag == 'msqrt': from .msqrt_visitor import MsqrtVisitor return MsqrtVisitor(self.strategy).visit(child) elif tag == 'mroot': from .mroot_visitor import MrootVisitor return MrootVisitor(self.strategy).visit(child) elif tag == 'msubsup': from .msubsup_visitor import MsubsupVisitor return MsubsupVisitor(self.strategy).visit(child) elif tag == 'munder': from .munder_visitor import MunderVisitor return MunderVisitor(self.strategy).visit(child) elif tag == 'mover': from .mover_visitor import MoverVisitor return MoverVisitor(self.strategy).visit(child) elif tag == 'munderover': from .munderover_visitor import MunderoverVisitor return MunderoverVisitor(self.strategy).visit(child) elif tag == 'mfenced': from .mfenced_visitor import MfencedVisitor return MfencedVisitor(self.strategy).visit(child) elif tag == 'mtable': from .mtable_visitor import MtableVisitor return MtableVisitor(self.strategy).visit(child) elif tag == 'mtext': from .mtext_visitor import MtextVisitor return MtextVisitor(self.strategy).visit(child) elif tag == 'mstyle': from .mstyle_visitor import MstyleVisitor return MstyleVisitor(self.strategy).visit(child) elif tag == 'mspace': from .mspace_visitor import MspaceVisitor return MspaceVisitor(self.strategy).visit(child) elif tag == 'menclose': from .menclose_visitor import MencloseVisitor return MencloseVisitor(self.strategy).visit(child) elif tag == 'mmultiscripts': from .mmultiscripts_visitor import MmultiscriptsVisitor return MmultiscriptsVisitor(self.strategy).visit(child) elif tag == 'maction': from .maction_visitor import MactionVisitor return MactionVisitor(self.strategy).visit(child) elif tag == 'merror': from .merror_visitor import MerrorVisitor return MerrorVisitor(self.strategy).visit(child) elif tag == 'mpadded': from .mpadded_visitor import MpaddedVisitor return MpaddedVisitor(self.strategy).visit(child) elif tag == 'mphantom': from .mphantom_visitor import MphantomVisitor return MphantomVisitor(self.strategy).visit(child) elif tag == 'ms': from .ms_visitor import MsVisitor return MsVisitor(self.strategy).visit(child) elif tag == 'mglyph': from .mglyph_visitor import MglyphVisitor return MglyphVisitor(self.strategy).visit(child) elif tag == 'maligngroup': from .maligngroup_visitor import MaligngroupVisitor return MaligngroupVisitor(self.strategy).visit(child) elif tag == 'malignmark': from .malignmark_visitor import MalignmarkVisitor return MalignmarkVisitor(self.strategy).visit(child) elif tag == 'mlabeledtr': from .mlabeledtr_visitor import MlabeledtrVisitor return MlabeledtrVisitor(self.strategy).visit(child) else: # Fallback: return text content for unknown elements import sys print(f"[WARNING] Unknown element <{tag}> in mroot, using text fallback", file=sys.stderr) return _text_content(child) def _brace(self, content: str) -> str: """Wrap content in braces.""" return f"{{{content}}}" def can_handle(self, node: ET.Element) -> bool: """Check if this visitor can handle the given node.""" return _local(node.tag) == 'mroot' @property def element_name(self) -> str: """Get the element name this visitor handles.""" return 'mroot' def __repr__(self) -> str: """String representation for debugging.""" return f"MrootVisitor(strategy={self.strategy.name})"