Source code for icalendar.parser.xcal.adapter

from __future__ import annotations

import re
from collections import defaultdict
from xml.etree.ElementTree import Element

from icalendar.error import XCalParsingError
from icalendar.parser.xcal.string import string_to_xsd_float

REGEX_WHITESPACE = re.compile(r"\s+", re.MULTILINE)


[docs] class ElementAdapter: """An adapter class with convenience methods for XML elements. This is a read-only access adapter. All operations on ElementAdapter are reproducible and do not change the underlying element. """
[docs] @classmethod def with_element(cls, element: Element | ElementAdapter) -> ElementAdapter: """Create a new element adapter or return the present one.""" if isinstance(element, ElementAdapter): return element return cls(element)
def __init__(self, element: Element) -> None: self._element = element self._tag = self._element.tag.split("}")[-1].lower() @property def element(self) -> Element: """The underlying element.""" return self._element @property def tag(self) -> str: """A sanitized element tag.""" return self._tag @property def is_extension(self) -> bool: """Whether the element is in a namespace other than the iCalendar namespace. Elements without a namespace belong to iCalendar. Extension elements directly in ``properties`` are XML properties. See also: :rfc:`6321#section-4.1`. Returns: ``True`` for an element outside the iCalendar namespace, or ``False`` for an element either in the iCalendar namespace or without a namespace. """ tag = self._element.tag return tag.startswith("{") and not tag.startswith( "{urn:ietf:params:xml:ns:icalendar-2.0}" ) @property def children(self) -> list[ChildElementAdapter]: """A list of child elements.""" tags = defaultdict(int) children = [] for child_element in self._element: tag = child_element.tag tags[tag] += 1 children.append(ChildElementAdapter(child_element, self, tags[tag])) return children
[docs] def get_child_with_tag(self, tag: str) -> ChildElementAdapter | None: """Get the first child element with the given tag.""" tag = tag.lower() for child in self.children: if child.tag == tag: return child return None
[docs] def parse_tag(self, tag: str): """Get the first child element with the given tag. Returns: The first child element with the given tag. Raises: XCalParsingError: If the tag is not found. """ child = self.get_child_with_tag(tag) if child is None: raise XCalParsingError(f"Tag {tag} not found", None, self) return child
[docs] def get_xsd_string(self) -> str: """Return the element's text as ``xsd:string``. This is the only datatype that leaves all the whitespace. .. seealso:: `xmlschemata.org <https://books.xmlschemata.org/relaxng/ch19-77303.html>`__ """ return self._element.text or ""
[docs] def get_xsd_token(self) -> str: """Return the element's text as ``xsd:token``. ``xsd:token`` is the most appropriate datatype to use for strings that don't care about whitespace. .. seealso:: `xmlschemata.org <https://books.xmlschemata.org/relaxng/ch19-77319.html>`__ """ return REGEX_WHITESPACE.sub(" ", self.get_xsd_string()).strip()
[docs] def get_text_without_whitespace(self) -> str: """Return the element's text with all whitespace removed.""" return REGEX_WHITESPACE.sub("", self.get_xsd_string())
[docs] def get_xpath(self) -> str: """Return the path in the XML file where this element occurs.""" return f"/{self.tag}"
[docs] def get_xsd_float(self) -> float: """Return the element's text parsed as ``xsd:float``. Returns: The parsed float value. Raises: XCalParsingError: If the value is not a float. """ try: return string_to_xsd_float(self.get_xsd_token()) except (TypeError, ValueError) as e: raise XCalParsingError( "Expected xsd:float", self.get_xsd_token(), self, ) from e
def __repr__(self) -> str: """Return the text representation of this element.""" return f"Element@{self.get_xpath()}"
[docs] def make_parent(self, tag: str) -> ElementAdapter: """Return an element adapter for a parent.""" parent = Element(tag) parent.append(self._element) return ElementAdapter(parent)
[docs] def get_inner_text(self) -> str: """Return the inner text. This is useful when there is a broken property. """ return self.get_xsd_string()
[docs] class ChildElementAdapter(ElementAdapter): """An adapter class with convenience methods for child elements. Parameters: child: The child element. index: The index for building the XPath in the parent. parent: The parent element adapter. """ def __init__(self, child: Element, parent: ElementAdapter, index: int = 1) -> None: super().__init__(child) self._parent = parent self._index = index @property def parent(self) -> ElementAdapter: """The parent element.""" return self._parent
[docs] def get_xpath(self) -> str: """Return the path in the XML file where this element occurs.""" return f"{self.parent.get_xpath()}/{self.tag}[{self._index}]"