Source code for pyxsd.transforms.transform

"""Transform framework.

See the transform authoring guide in the documentation for tutorials
on writing transformers, an overview of this class, basic use
instructions, and documentation on the included transform libraries.
"""

import abc
from collections.abc import Callable, Iterator
from typing import Any


def _nameMatches(nodeName: str, wanted: str) -> bool:
    """Whether a node name matches a requested tag name.

    In namespaced mode a node's ``_name_`` is the Clark form
    (``{uri}local``) while transforms normally address elements by their
    local name. An exact match always counts; otherwise the local part
    of the node name is compared to the requested name, so
    ``getElementsByName(root, "trkpt")`` keeps working in either mode.
    """
    if nodeName == wanted:
        return True
    return nodeName.startswith("{") and nodeName.split("}")[-1] == wanted


[docs] def iter_tree(instance: Any) -> Iterator[Any]: """Yield every tree node at or below ``instance``, depth-first. Lists (and tuples) are descended into item by item and dictionaries by value; anything without both ``_children_`` and ``_attribs_`` is skipped. Each yielded node is visited before its children (pre-order). This generator powers :meth:`~pyxsd.transforms.transform.Transform.walk` and is the supported way to iterate a tree directly:: for node in iter_tree(root): ... - ``instance``: a tree node, or a list/dict of them. """ if isinstance(instance, (list, tuple)): for item in instance: yield from iter_tree(item) elif isinstance(instance, dict): for item in instance.values(): yield from iter_tree(item) elif hasattr(instance, "_children_") and hasattr(instance, "_attribs_"): yield instance for child in instance._children_: yield from iter_tree(child)
[docs] class Transform(abc.ABC): """The base abstract class for all transforms. All methods should mix into the usable transform classes. Contains methods to retrieve elements from the tree. Subclasses must accept the tree root in their ``__init__``; that makes the class abstract until it does, so framework-only subclasses (like :class:`~pyxsd.transforms.displayer.Displayer`) cannot be instantiated by accident. Subclasses should call ``super().__init__(root)``, which stores the root as ``self.root``. """ @abc.abstractmethod def __init__(self, root: Any) -> None: """Store the tree root as ``self.root``. Kept abstract so the plain ``Transform`` class cannot be instantiated; concrete subclasses call it through ``super()``. """ self.root = root
[docs] def makeElemObj(self, name: str) -> Any: """Creates a new element that contains the proper tree structure. """ class ElemObjClass: def __init__(self, name: str): self._children_: list[Any] = [] self._attribs_: dict[str, str] = {} self._name_ = name self._value_ = None return ElemObjClass(name)
[docs] def makeCommentElem(self, comment: str) -> Any: """Makes a comment element.""" obj = self.makeElemObj("_comment_") obj._value_ = comment return obj
[docs] def iter_tree(self, instance: Any) -> Iterator[Any]: """Yield every tree node at or below ``instance``, depth-first. Method form of the module-level :func:`iter_tree`; see there for semantics. - ``instance``: a tree node, or a list/dict of them. """ return iter_tree(instance)
[docs] def walk(self, instance: Any, visitor: Callable[..., Any], *args: Any, **kwargs: Any) -> None: """Walks through the tree structure and runs a provided visitor function on all elements. The visitor is called as ``visitor(node, attrNames, elemNames, *args, **kwargs)`` where ``node`` is the tree node being visited, ``attrNames`` is the list of its attribute names, and ``elemNames`` is the list of its children's names. Traversal is driven by :meth:`iter_tree`. """ for node in self.iter_tree(instance): elemNames = [c._name_ for c in node._children_] attrNames = list(node._attribs_.keys()) visitor(node, attrNames, elemNames, *args, **kwargs)
[docs] def classCollector( self, instance: Any, attrNames: list[str], elemNames: list[str], collectorDict: dict[str, list[Any]], ) -> None: """Visitor function to make a dictionary that associates a class with its instances. The class name is the key, and the value is the list of associated instances. See ``getInstancesByClassName``. """ className = instance.__class__.__name__ collection: list[Any] | None = collectorDict.get(className) if collection is None: collection = [] collectorDict[className] = collection collection.append(instance)
[docs] def tagCollector( self, instance: Any, attrNames: list[str], elemNames: list[str], collectorDict: dict[str, list[Any]], ) -> None: """A visitor function that is used to make a dictionary that associates a tag name with its children. See ``getAllSubElements``. """ for i, tagName in enumerate(elemNames): obj = instance._children_[i] if obj is None: continue collection = collectorDict.get(tagName) if collection is None: collection = [] collectorDict[tagName] = collection collection.append(obj)
[docs] def tagFinder( self, instance: Any, attrNames: list[str], elemNames: list[str], collection: list[Any], name: str, ) -> None: """A visitor function to collect all tags with a particular name and put them into a list. See ``getElementsByName``. """ for i, tagName in enumerate(elemNames): if _nameMatches(tagName, name): obj = instance._children_[i] if obj is not None: collection.append(obj)
[docs] def getInstancesByClassName(self, root: Any) -> dict[str, list[Any]]: """Uses the ``walk`` function with the ``classCollector`` visitor function to associate a class name with the class's instances. """ collectorDict: dict[str, list[Any]] = {} self.walk(root, self.classCollector, collectorDict) return collectorDict
[docs] def getAllSubElements(self, root: Any) -> dict[str, list[Any]]: """Uses the ``walk`` function with the ``tagCollector`` visitor function to make a dictionary that associates all elements with their sub-elements. """ collectorDict: dict[str, list[Any]] = {} self.walk(root, self.tagCollector, collectorDict) return collectorDict
[docs] def getElementsByName(self, root: Any, name: str) -> list[Any]: """Uses the ``walk`` function with the ``tagFinder`` visitor function to make a list containing all elements with a particular name. """ collection: list[Any] = [] self.walk(root, self.tagFinder, collection, name) return collection
[docs] def find(self, tagName: str, baseElem: Any) -> Any | None: """Finds an element from a given tagName. Returns the first one found, or returns None. This function is an alternative to the walk/visitor functions. See ``getElementsByName``. """ if _nameMatches(baseElem._name_, tagName): return baseElem for child in baseElem._children_: returnedElement = self.find(tagName, child) if returnedElement is not None: return returnedElement return None
[docs] def findAll(self, tagName: str, baseElem: Any) -> list[Any] | None: """Finds all elements with a given tagName. Returns a list of elements or None. This function is an alternative to the walk/visitor functions. See ``getElementsByName``. """ found = [] if _nameMatches(baseElem._name_, tagName): found.append(baseElem) for child in baseElem._children_: returnedElement = self.findAll(tagName, child) if returnedElement is not None: found.extend(returnedElement) if found: return found return None