r""" Visitor for (square root) MathML elements. The element represents square roots (√). The LaTeX conversion uses \sqrt{...} notation. """ 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 MsqrtVisitor(Visitor): r""" Handles (square root) MathML elements. Converts square root expressions to LaTeX \sqrt notation. Examples: Simple number: 2 → \sqrt{2} Variable: x → \sqrt{x} Complex expression: x+1 → \sqrt{x + 1} Nested root: x → \sqrt{\sqrt{x}} Fraction under root: 12 → \sqrt{\dfrac{1}{2}} """ def __init__(self, strategy: Strategy): """Initialize the Msqrt visitor.""" super().__init__(strategy) def visit(self, node: ET.Element) -> str: r""" Convert element to LaTeX. Args: node: The element to convert Returns: LaTeX square root string (\sqrt{content}) Raises: ValueError: If node is not an element """ if not self.can_handle(node): raise ValueError(f"MsqrtVisitor cannot handle element '{node.tag}'") children = list(node) # Empty msqrt (edge case) if not children: return "\\sqrt{}" # Convert all children and join with spaces parts = [] for child in children: part = self._convert_child(child) if part: # Only add non-empty parts parts.append(part) radicand = ' '.join(parts).strip() # Return \sqrt{radicand} return f"\\sqrt{self._brace(radicand)}" def _convert_child(self, child: ET.Element) -> str: """ Convert a child element to LaTeX. This method instantiates the appropriate visitor for each child type. """ 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': # Recursive: msqrt can contain other msqrt elements return self.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 msqrt, 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) == 'msqrt' @property def element_name(self) -> str: """Get the element name this visitor handles.""" return 'msqrt' def __repr__(self) -> str: """String representation for debugging.""" return f"MsqrtVisitor(strategy={self.strategy.name})"