"""
:mod:`zsl.utils.xml_helper` -- xml helpers
==========================================
Helper functions for working with XML and ElementTree.
"""
from functools import reduce
import xml.etree.ElementTree as ET
import requests
[docs]
class NotValidXmlException(Exception):
"""Exception raised on invalid xml"""
# TODO try to use some build in exception #13299
pass
[docs]
def required_attributes(element, *attributes):
"""Check element for required attributes. Raise ``NotValidXmlException`` on error.
:param element: ElementTree element
:param attributes: list of attributes names to check
:raises NotValidXmlException: if some argument is missing
"""
if not reduce(lambda still_valid, param: still_valid and param in element.attrib, attributes, True):
raise NotValidXmlException(msg_err_missing_attributes(element.tag, *attributes))
[docs]
def required_elements(element, *children):
"""Check element (``xml.etree.ElementTree.Element``) for required children, defined as XPath. Raise
``NotValidXmlException`` on error.
:param element: ElementTree element
:param children: list of XPaths to check
:raises NotValidXmlException: if some child is missing
"""
for child in children:
if element.find(child) is None:
raise NotValidXmlException(msg_err_missing_children(element.tag, *children))
[docs]
def required_items(element, children, attributes):
"""Check an xml element to include given attributes and children.
:param element: ElementTree element
:param children: list of XPaths to check
:param attributes: list of attributes names to check
:raises NotValidXmlException: if some argument is missing
:raises NotValidXmlException: if some child is missing
"""
required_elements(element, *children)
required_attributes(element, *attributes)
[docs]
def msg_err_missing_attributes(tag, *attributes):
"""Format message for missing attributes.
:param tag: tag name
:param attributes: list of attributes
:return: message
:rtype: str
"""
return "Missing one or more required attributes (%s) in xml tag %s" % ('|'.join(attributes), tag)
[docs]
def msg_err_missing_children(tag, *children):
"""Format message for missing children.
:param tag: tag name
:param children: list of children
:return: message
:rtype: str
"""
return "Missing one or more required children (%s) in xml tag %s" % ('|'.join(children), tag)
[docs]
def attrib_to_dict(element, *args, **kwargs):
"""For an ElementTree ``element`` extract specified attributes. If an attribute does not exists, its value will be
``None``.
attrib_to_dict(element, 'attr_a', 'attr_b') -> {'attr_a': 'value', 'attr_a': 'value'}
Mapping between xml attributes and dictionary keys is done with kwargs.
attrib_to_dict(element, my_new_name = 'xml_atribute_name', ..)
"""
if len(args) > 0:
return {key: element.get(key) for key in args}
if len(kwargs) > 0:
return {new_key: element.get(old_key) for new_key, old_key in kwargs.items()}
return element.attrib
[docs]
def get_xml_root(xml_path):
"""Load and parse an xml by given xml_path and return its root.
:param xml_path: URL to a xml file
:type xml_path: str
:return: xml root
"""
r = requests.get(xml_path)
root = ET.fromstring(r.content)
return root
[docs]
def element_to_int(element, attribute=None):
"""Convert ``element`` object to int. If attribute is not given, convert ``element.text``.
:param element: ElementTree element
:param attribute: attribute name
:type attribute: str
:returns: integer
:rtype: int
"""
if attribute is not None:
return int(element.get(attribute))
else:
return int(element.text)
[docs]
def create_el(name, text=None, attrib=None):
"""Create element with given attributes and set element.text property to given
text value (if text is not None)
:param name: element name
:type name: str
:param text: text node value
:type text: str
:param attrib: attributes
:type attrib: dict
:returns: xml element
:rtype: Element
"""
if attrib is None:
attrib = {}
el = ET.Element(name, attrib)
if text is not None:
el.text = text
return el