# -*- coding: utf-8 -*- """ Various XML utilities """ from typing import Dict from xml.etree import ElementTree def _conv_name(x: str) -> str: """ If this XML tree has an xmlns attribute, then etree will add it to the beginning of the tag, like: "{http://path}tag". """ if "}" in x: comps = x.split("}") name = comps[1] return name return x def _to_dict(xmltree: ElementTree) -> Dict: """ Converts an XML ElementTree to a dictionary that only contains items. This is the default behavior in version 2017.7. This will default to prevent unexpected parsing issues on modules dependent on this. """ # If this object has no children, the for..loop below will return nothing # for it, so just return a single dict representing it. if not xmltree: name = _conv_name(xmltree.tag) return {name: xmltree.text} xmldict = {} for item in xmltree: name = _conv_name(item.tag) if name not in xmldict: if item: xmldict[name] = _to_dict(item) else: xmldict[name] = item.text else: # If a tag appears more than once in the same place, convert it to # a list. This may require that the caller watch for such a thing # to happen, and behave accordingly. if not isinstance(xmldict[name], list): xmldict[name] = [xmldict[name]] xmldict[name].append(_to_dict(item)) return xmldict def _to_full_dict(xmltree: ElementTree): """ Returns the full XML dictionary including attributes. """ xmldict = {} for attrName, attrValue in xmltree.attrib.items(): xmldict[attrName] = attrValue if not xmltree: if not xmldict: # If we don't have attributes, we should return the value as a string # ex: test return xmltree.text elif xmltree.text: # XML allows for empty sets with attributes, so we need to make sure that capture this. # ex: xmldict[_conv_name(xmltree.tag)] = xmltree.text for item in xmltree: name = _conv_name(item.tag) if name not in xmldict: xmldict[name] = _to_full_dict(item) else: # If a tag appears more than once in the same place, convert it to # a list. This may require that the caller watch for such a thing # to happen, and behave accordingly. if not isinstance(xmldict[name], list): xmldict[name] = [xmldict[name]] xmldict[name].append(_to_full_dict(item)) return xmldict def to_dict(xmltree: ElementTree, attr: bool = False): """ Convert an XML tree into a dict. The tree that is passed in must be an ElementTree object. Args: xmltree: An ElementTree object. attr: If true, attributes will be parsed. If false, they will be ignored. """ if attr: return _to_full_dict(xmltree) else: return _to_dict(xmltree)