Source code for rads.xml.etree
"""XML tools using :mod:`xml.etree.ElementTree`."""
import xml.etree.ElementTree as etree
from typing import Iterator, Mapping, Optional
from xml.etree.ElementTree import (
ParseError,
XMLParser,
fromstring,
fromstringlist,
parse,
)
from ..xml import base
__all__ = [
"ParseError",
"Element",
"XMLParser",
"parse",
"fromstring",
"fromstringlist",
"error_with_file",
]
[docs]class Element(base.Element):
"""XML element that encapsulates an element from the ElementTree module.
Does not support line number examination.
.. note::
It is recommended to use :class:`rads.xml.lxml.Element` if libxml is
available on your system as the etree version does not support line
numbers which can make debugging XML files for syntax errors more
difficult.
"""
def __init__(
self,
element: etree.Element,
*,
index: Optional[int] = None,
parent: Optional["Element"] = None,
file: Optional[str] = None,
):
"""
:param element:
XML element from the standard :mod:`xml.etree.ElementTree`
package.
:param index:
Index of element at current level, among it's siblings. Not
required if this element does not have any siblings.
:param parent:
The parent of this element.
:param file:
Filename of the XML document.
"""
assert parent is None or isinstance(parent, Element)
self._element = element
self._index = index
self._parent = parent
self._file = file
def __len__(self) -> int:
return len(self._element)
def __iter__(self) -> Iterator["Element"]:
for i, e in enumerate(self._element):
yield Element(e, index=i, parent=self, file=self._file)
[docs] def next(self) -> "Element": # noqa: D102
if self._parent is None or self._index is None:
raise StopIteration()
siblings = list(self._parent._element)
new_index = self._index + 1
if new_index >= len(siblings):
raise StopIteration()
return Element(
siblings[new_index], index=new_index, parent=self._parent, file=self._file
)
[docs] def prev(self) -> "Element": # noqa: D102
if self._parent is None or self._index is None:
raise StopIteration()
siblings = list(self._parent._element)
new_index = self._index - 1
if new_index < 0:
raise StopIteration()
return Element(
siblings[new_index], index=new_index, parent=self._parent, file=self._file
)
[docs] def up(self) -> "Element": # noqa: D102
if self._parent is None:
raise StopIteration()
return self._parent
[docs] def down(self) -> "Element": # noqa: D102
try:
element = list(self._element)[0]
return Element(element, index=0, parent=self, file=self.file)
except IndexError:
raise StopIteration()
@property
def file(self) -> Optional[str]:
return self._file
@property
def tag(self) -> str:
return self._element.tag
@property
def text(self) -> Optional[str]:
return self._element.text
@property
def attributes(self) -> Mapping[str, str]:
return self._element.attrib
[docs]def error_with_file(error: ParseError, file: str) -> ParseError:
"""Add filename to an XML parse error.
:param error:
Original XML parse error.
:param file:
Filename to add.
:return:
A new parse error (of the same type as `error`) with the `filename`
added.
"""
error.filename = file
new_error = type(error)(
error.msg, (file, error.position[0], error.position[1], error.text)
)
new_error.code = error.code
new_error.position = error.position
return new_error