Source code for rads.xml.base

"""Generic XML tools, not relating to a specific backend."""

from abc import ABC, abstractmethod
from collections.abc import Sized
from itertools import chain
from typing import Iterable, Iterator, Mapping, Optional, Union

__all__ = ["Element"]


[docs]class Element(Iterable["Element"], Sized, ABC): """A generic XML element. Base class of XML elements. """ def __repr__(self) -> str: """Get text representation of the element. :return: Opening tag of the element. """ attributes = " ".join( '{:s}="{}"'.format(k, v) for k, v in self.attributes.items() ) if attributes: attributes = " " + attributes return "<{:s}{:s}>".format(self.tag, attributes) @abstractmethod def __iter__(self) -> Iterator["Element"]: """Get the children of this element. :return: An iterable to the children of this element in the same order as they are in the XML file. """ @abstractmethod def __len__(self) -> int: """Get number of children. :return: Number of children. """
[docs] def dumps( self, *, indent: Optional[Union[int, str]] = None, _current_indent: str = "" ) -> str: """Get string representation of this element and all child elements. :param indent: Amount to indent each level. Can be given as an int or a string. Defaults to 4 spaces. :return: String representation of this and all child elements. """ attributes = "" text = "" children = "" closing_indent = "" multiline = False # compute next indent if isinstance(indent, int): next_indent = _current_indent + " " * indent elif isinstance(indent, str): next_indent = _current_indent + indent else: next_indent = _current_indent + " " if self.attributes: attributes = " " + " ".join( '{:s}="{}"'.format(k, v) for k, v in self.attributes.items() ) if self: # has children children_ = ( c.dumps(indent=indent, _current_indent=next_indent) for c in self ) children = "\n".join(chain([""], children_, [""])) multiline = True if self.text and self.text.strip(): text = self.text.rstrip() if "\n" in text: multiline = True if multiline: text = text + "\n" if multiline: closing_indent = _current_indent format_str = ( "{_current_indent:s}<{tag:s}{attributes:s}>" "{text:s}{children:s}{closing_indent:s}</{tag:s}>" ) text = format_str.format( _current_indent=_current_indent, tag=self.tag, attributes=attributes, text=text, children=children, closing_indent=closing_indent, ) return text
[docs] @abstractmethod def next(self) -> "Element": """Get the next sibling element. :return: Next XML sibling element. :raises StopIteration: If there is no next sibling element. """
[docs] @abstractmethod def prev(self) -> "Element": """Get the previous sibling element. :return: Previous XML sibling element. :raises StopIteration: If there is no previous sibling element. """
[docs] @abstractmethod def up(self) -> "Element": """Get the parent of this element. :return: Parent XML element. :raises StopIteration: If there is no parent element. """
[docs] @abstractmethod def down(self) -> "Element": """Get the first child of this element. :return: First child XML element. :raises StopIteration: If this element does not have any children. """
@property def file(self) -> Optional[str]: """Get the name of the XML file containing this element. :return: Name of the file containing this element, or None. """ return None @property def opening_line(self) -> Optional[int]: """Get the opening line of the XML element. :return: Opening line number, or None. """ return None @property def num_lines(self) -> Optional[int]: """Get the number of lines making up the XML element. :return: Number of lines in XML element, or None. """ return None @property def closing_line(self) -> Optional[int]: """Get the closing line of the XML element. :return: Closing line number, or None. """ return None @property @abstractmethod def tag(self) -> str: """Tag name of the element.""" @property @abstractmethod def text(self) -> Optional[str]: """Internal text of the element.""" @property @abstractmethod def attributes(self) -> Mapping[str, str]: """The attributes of the element, as a dictionary."""