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

from pyxsd.tree import iter_tree


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] 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