r""" Visitor for (action/interactive) MathML elements. The element binds actions (like tooltips, highlighting, or toggling) to mathematical expressions. It typically has an actiontype attribute that specifies the kind of action. In LaTeX conversion, maction is treated as a transparent wrapper since LaTeX is a static typesetting system without interactive capabilities. Only the selected child (typically the first) is converted. Common action types: - toggle: Toggle between alternative expressions - statusline: Display expression in status line - tooltip: Show tooltip on hover - highlight: Highlight on mouseover """ from lxml import etree as ET from ..core.interfaces import Visitor, Strategy from ..utils import local_name as _local class MactionVisitor(Visitor): r""" Handles (action/interactive) MathML elements. Treats maction as a transparent wrapper, converting only the selected child (typically the first one) since LaTeX doesn't support interactivity. Examples: Toggle action (shows first alternative): x y → x Tooltip action (shows content, ignores tooltip): a variable a → a Highlight action (shows content without highlight): ab → \frac{a}{b} Selection attribute (selects specific child): x y z → y (second child, 1-indexed) Note: LaTeX has no interactive capabilities, so: - Only one child is rendered (the selected one) - Action types are ignored - Default: first child is selected """ def __init__(self, strategy: Strategy): """Initialize the Maction visitor.""" super().__init__(strategy) def visit(self, node: ET.Element) -> str: r""" Convert element to LaTeX (renders selected child only). Args: node: The element to convert Returns: LaTeX string with selected child converted Raises: ValueError: If node is not a element """ if not self.can_handle(node): raise ValueError(f"MactionVisitor cannot handle element '{node.tag}'") # Get selection attribute (1-indexed, defaults to 1) selection = node.get("selection", "1") try: selection_idx = int(selection) - 1 # Convert to 0-indexed except ValueError: selection_idx = 0 # Default to first child if invalid # Get children children = list(node) if not children: return "" # Ensure selection is within bounds if selection_idx < 0 or selection_idx >= len(children): selection_idx = 0 # Convert only the selected child return self._convert_child(children[selection_idx]) 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 maction, using text fallback", file=sys.stderr) return _text_content(child) def can_handle(self, node: ET.Element) -> bool: """Check if this visitor can handle the given node.""" return _local(node.tag) == 'maction' @property def element_name(self) -> str: """Get the element name this visitor handles.""" return 'maction' def __repr__(self) -> str: """String representation for debugging.""" return f"MactionVisitor(strategy={self.strategy.name})"