-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathexemel.py
149 lines (103 loc) · 4.17 KB
/
exemel.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""Converts a dictionary into an XML document"""
import collections
try:
import collections.abc as abc
except ImportError:
# python2.7 does not have the abc module
abc = collections
from future.utils import iteritems
from lxml import etree
class Error(Exception):
"""Base exception class for exemel errors"""
def build(dictionary, root='root', encoding=None):
"""Builds an XML element from a dictionary-like object
Args:
dictionary (collections.abc.Mapping): The structure to be converted
Keyword Args:
root (string): The tag of the root element. Defaults to 'root'.
encoding (string): The encoding of the resulting string. Defaults to
a byte string.
Returns:
string: An XML element built from the given data structure
"""
element = _build_element_from_dict(root, dictionary)
return etree.tostring(element, encoding=encoding)
def build_element(dictionary, root='root'):
"""Builds an XML element from a dictionary-like object
Args:
dictionary (collections.abc.Mapping): The structure to be converted
Keyword Args:
root (string): The tag of the root element. Defaults to 'root'.
Returns:
lxml.etree._Element: An XML element built from the given data structure
"""
return _build_element_from_dict(root, dictionary)
def _build_element_from_dict(name, dictionary, parent_namespace=None):
try:
namespace = dictionary['#ns']
except KeyError:
namespace = parent_namespace
tag = _make_tag(name, namespace)
element = etree.Element(tag)
for key, value in _iter_items_except_namespace(dictionary):
if key.startswith('@'):
_set_attribute(element, key[1:], value)
elif key == '#text':
_set_text(element, value)
else:
_add_sub_elements(element, key, value, namespace)
return element
def _make_tag(name, namespace):
return name if namespace is None else etree.QName(namespace, name)
def _iter_items_except_namespace(dictionary):
for key, value in iteritems(dictionary):
if key != '#ns':
yield key, value
def _set_attribute(element, name, value):
if value is not None:
element.set(name, _convert_to_text(value))
def _set_text(element, value):
if value is not None:
element.text = _convert_to_text(value)
def _add_sub_elements(element, name, value, namespace):
if etree.iselement(value):
_validate_element_name(value, name)
element.append(value)
elif isinstance(value, abc.Mapping):
element.append(_build_element_from_dict(name, value, namespace))
elif (isinstance(value, abc.Iterable) and
not isinstance(value, str)):
for sub_elem in _build_elements_from_iterable(name, value, namespace):
element.append(sub_elem)
else:
element.append(_build_element_from_value(name, value, namespace))
def _build_elements_from_iterable(name, iterable, parent_namespace):
for item in iterable:
if etree.iselement(item):
_validate_element_name(item, name)
element = item
elif isinstance(item, abc.Mapping):
element = _build_element_from_dict(name, item, parent_namespace)
else:
element = _build_element_from_value(name, item, parent_namespace)
yield element
def _build_element_from_value(name, value, parent_namespace):
tag = _make_tag(name, parent_namespace)
element = etree.Element(tag)
_set_text(element, value)
return element
def _convert_to_text(value):
if isinstance(value, bool):
text = 'true' if value else 'false'
else:
text = str(value)
return text
def _validate_element_name(element, expected_name):
actual_name = etree.QName(element.tag).localname
if actual_name != expected_name:
raise MismatchedElementNameError(expected_name, actual_name)
class MismatchedElementNameError(Error):
def __init__(self, expected_name, actual_name):
super(MismatchedElementNameError, self).__init__(
"Element with name '{}' was added where name '{}' was "
"expected".format(actual_name, expected_name))