#!/usr/bin/env python # encoding: utf-8 # # Copyright SAS Institute # # Licensed under the Apache License, Version 2.0 (the License); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ''' XML Utilities ''' from __future__ import print_function, division, absolute_import, unicode_literals import collections import re import six import warnings import xml.etree.ElementTree as ET from .keyword import keywordify from ..base import ESPObject def _cast_attrs(attrs): out = {} for name, value in (attrs or {}).items(): if value is None: continue elif value is True: value = 'true' elif value is False: value = 'false' else: value = '%s' % value out[name.replace('_', '-')] = value return out def new_elem(elem_name, attrib=None, text_content=None, **kwargs): ''' Create a new element Parameters ---------- elem_name : string The tag name of the element attrib : dict, optional The attributes to set text_content : string, optional The text content of the new element **kwargs : keyword arguments, optional Additional attributes as keyword arguments Returns ------- :class:`ElementTree.Element` ''' attrib = _cast_attrs(attrib) kwargs = _cast_attrs(kwargs) out = ET.Element(elem_name, attrib=attrib, **kwargs) if text_content is not None: out.text = '%s' % text_content return out def add_elem(parent_elem, child_elem, attrib=None, text_content=None, **kwargs): ''' Add a new element to the specified parent element Parameters ---------- parent_elem : ElementTree.Element The parent element child_elem : string The name of an element or an XML fragment attrib : dict, optional The attributes to set text_content : string, optional The text content of the new element **kwargs : keyword arguments, optional Additional attributes as keyword arguments Returns ------- :class:`ElementTree.Element` ''' attrib = _cast_attrs(attrib) kwargs = _cast_attrs(kwargs) # child_elem is an Element if isinstance(child_elem, ET.Element): out = child_elem if attrib: for key, value in attrib.items(): out.set(key, value) for key, value in kwargs.items(): out.set(key, value) parent_elem.append(out) # child_elem is an XML fragment elif re.match(r'^\s*<', child_elem): out = ET.fromstring(child_elem) if attrib: for key, value in attrib.items(): out.set(key, value) for key, value in kwargs.items(): out.set(key, value) parent_elem.append(out) # child_elem is an element name else: out = ET.SubElement(parent_elem, child_elem, attrib=attrib, **kwargs) if text_content is not None: out.text = '%s' % text_content return out def add_properties(elem, *args, **kwargs): ''' Add a ``properties`` node to the given element Parameters ---------- elem : ElementTree.Element The element to add properties to verbatim : boolean, optional Should property names be used verbatim? *args : two-element-tuples, optional Passed to dict constructor as properties **kwargs : keyword arguments, optional Passed to dict constructor as properties Returns ------- :class:`ElementTree.Element` ''' verbatim = kwargs.pop('verbatim', False) bool_as_int = kwargs.pop('bool_as_int', False) props = add_elem(elem, 'properties') for key, value in sorted(dict(*args, **kwargs).items()): if value is None: continue if isinstance(value, (list, tuple, set)): value = ','.join(value) elif value is True: if bool_as_int: value = '1' else: value = 'true' elif value is False: if bool_as_int: value = '0' else: value = 'false' add_elem(props, 'property', dict(name=keywordify(key)), text_content=value) return props def xml_indent(elem, level=0): ''' Add whitespace to XML for pretty-printing Parameters ---------- elem : ElementTree.Element The element to modify with whitespace level : int, optional The level of indent Returns ------- ``None`` ''' i = '\n' + (level * ' ') if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + ' ' if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: xml_indent(elem, level + 1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i def from_xml(data): ''' Convert XML to ElementTree.Element Parameters ---------- data : string The XML to parse Returns ------- :class:`ElementTree.Element` ''' try: return ET.fromstring(data) except: for i, line in enumerate(data.split('\n')): print(i+1, line) raise def to_xml(elem, encoding=None, pretty=False): ''' Export element to XML Parameters ---------- elem : ElementTree.Element or xml-string The element to export encoding : string, optional The output encoding Returns ------- string ''' if isinstance(elem, six.string_types): elem = ET.fromstring(elem) # In-place editing!! if pretty: xml_indent(elem) if encoding is None: return ET.tostring(elem, encoding='utf-8').decode() return ET.tostring(elem, encoding=encoding) def get_attrs(obj, extra=[], exclude=[]): ''' Retrieve XML attributes from object If ``obj`` has an ``xml_map`` dictionary attribute, it indicates the object attr to xml attr mapping. class MyObject(object): xml_map = dict(object_attr='xml_attr', same_name_object_attr='same_name_object_attr') Parameters ---------- obj : object The object to get attributes from Returns ------- dict ''' if isinstance(exclude, six.string_types): exclude = [exclude] out = obj._get_attributes() if isinstance(extra, six.string_types): extra = [extra] if extra: for item in extra: out[item] = getattr(obj, item) if exclude: for item in exclude: out.pop(item, None) return {k: '%s' % v for k, v in out.items() if v is not None} def ensure_element(data): ''' Ensure the given object is an ElementTree.Element Parameters ---------- data : string or Element Returns ------- :class:`ElementTree.Element` ''' if isinstance(data, six.string_types): return from_xml(data) return data