""" Visitor registry/factory for MathML element visitors. This module provides a centralized registry for mapping MathML element names to their corresponding visitor classes. """ from typing import Dict, Type, Optional, List from ..core.interfaces import Visitor, Strategy, VisitorRegistry as IVisitorRegistry from ..core.context import ConversionContext class VisitorRegistry(IVisitorRegistry): """ Registry for MathML element visitors. This class maintains a mapping of element names (e.g., 'mi', 'mo', 'mn') to their corresponding visitor classes. It provides visitor instances on demand with the appropriate strategy and context injected. Example: >>> registry = VisitorRegistry() >>> registry.register('mi', MiVisitor) >>> registry.register('mo', MoVisitor) >>> strategy = MathStrategy() >>> context = ConversionContext(input_type='mathml') >>> mi_visitor = registry.get_visitor('mi', strategy, context) """ def __init__(self, context: Optional[ConversionContext] = None): """ Initialize the registry with empty mappings. Args: context: Optional shared conversion context for all visitors """ self._visitors: Dict[str, Type[Visitor]] = {} self._default_visitor: Type[Visitor] | None = None self.context = context def register(self, element_name: str, visitor_class: type) -> None: """ Register a visitor class for a specific element type. Args: element_name: MathML element name (e.g., 'mi', 'mo', 'mn') visitor_class: The visitor class to handle this element Raises: TypeError: If visitor_class is not a subclass of Visitor """ if not issubclass(visitor_class, Visitor): raise TypeError( f"visitor_class must be a subclass of Visitor, " f"got {type(visitor_class)}" ) self._visitors[element_name] = visitor_class def get_visitor(self, element_name: str, strategy: Strategy, context: Optional[ConversionContext] = None) -> Visitor: """ Get a visitor instance for the given element type. Args: element_name: MathML element name (e.g., 'mi', 'mo') strategy: The conversion strategy to inject into the visitor context: Optional conversion context (uses registry's context if not provided) Returns: Visitor instance configured with the strategy and context Raises: ValueError: If no visitor registered for element_name and no default set """ visitor_class = self._visitors.get(element_name) if visitor_class is None: if self._default_visitor is not None: visitor_class = self._default_visitor else: raise ValueError( f"No visitor registered for element '{element_name}' " f"and no default visitor configured" ) # Use provided context or fall back to registry's context ctx = context if context is not None else self.context # Try to instantiate with context, fall back to strategy-only if needed try: return visitor_class(strategy, ctx) except TypeError: # Visitor doesn't accept context parameter yet return visitor_class(strategy) def has_visitor(self, element_name: str) -> bool: """ Check if a visitor is registered for the element type. Args: element_name: MathML element name Returns: True if visitor is registered or default exists, False otherwise """ return element_name in self._visitors or self._default_visitor is not None def set_default_visitor(self, visitor_class: Type[Visitor]) -> None: """ Set a default visitor for unregistered element types. Args: visitor_class: The visitor class to use as default Raises: TypeError: If visitor_class is not a subclass of Visitor """ if not issubclass(visitor_class, Visitor): raise TypeError( f"visitor_class must be a subclass of Visitor, " f"got {type(visitor_class)}" ) self._default_visitor = visitor_class def get_registered_elements(self) -> List[str]: """ Get list of all registered element names. Returns: List of element names with registered visitors """ return list(self._visitors.keys()) def clear(self) -> None: """Clear all registered visitors.""" self._visitors.clear() self._default_visitor = None def __repr__(self) -> str: """String representation for debugging.""" elements = ', '.join(self._visitors.keys()) if self._visitors else 'none' default = f", default={self._default_visitor.__name__}" if self._default_visitor else "" return f"VisitorRegistry(registered=[{elements}]{default})" def __len__(self) -> int: """Get number of registered visitors.""" return len(self._visitors)