-
Notifications
You must be signed in to change notification settings - Fork 17
/
xmldict.py
142 lines (128 loc) · 4 KB
/
xmldict.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
"""
xmldict
~~~~~~~~~~~~~~~~~~~~~~~~~
Convert xml to python dictionaries.
"""
import datetime
def xml_to_dict(root_or_str, strict=True):
"""
Converts `root_or_str` which can be parsed xml or a xml string to dict.
"""
root = root_or_str
if isinstance(root, str):
import xml.etree.cElementTree as ElementTree
root = ElementTree.XML(root_or_str)
return {root.tag: _from_xml(root, strict)}
def dict_to_xml(dict_xml):
"""
Converts `dict_xml` which is a python dict to corresponding xml.
"""
return _to_xml(dict_xml)
# Functions below this line are implementation details.
# Unless you are changing code, don't bother reading.
# The functions above constitute the user interface.
def _to_xml(el):
"""
Converts `el` to its xml representation.
"""
val = None
if isinstance(el, dict):
val = _dict_to_xml(el)
elif isinstance(el, bool):
val = str(el).lower()
else:
val = el
if val is None: val = 'null'
return val
def _extract_attrs(els):
"""
Extracts attributes from dictionary `els`. Attributes are keys which start
with '@'
"""
if not isinstance(els, dict):
return ''
return ''.join(' %s="%s"' % (key[1:], value) for key, value in els.iteritems()
if key.startswith('@'))
def _dict_to_xml(els):
"""
Converts `els` which is a python dict to corresponding xml.
"""
def process_content(tag, content):
attrs = _extract_attrs(content)
text = isinstance(content, dict) and content.get('#text', '') or ''
return '<%s%s>%s%s</%s>' % (tag, attrs, _to_xml(content), text, tag)
tags = []
for tag, content in els.iteritems():
# Text and attributes
if tag.startswith('@') or tag == '#text' or tag == '#value':
continue
elif isinstance(content, list):
for el in content:
tags.append(process_content(tag, el))
elif isinstance(content, dict):
tags.append(process_content(tag, content))
else:
tags.append('<%s>%s</%s>' % (tag, _to_xml(content), tag))
return ''.join(tags)
def _str_to_datetime(date_str):
try:
val = datetime.datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%SZ")
except ValueError:
val = date_str
return val
def _str_to_boolean(bool_str):
if bool_str.lower() != 'false' and bool(bool_str):
return True
return False
def _from_xml(el, strict):
"""
Extracts value of xml element element `el`.
"""
val = None
# Parent node.
if len(el):
val = {}
for e in el:
tag = e.tag
v = _from_xml(e, strict)
if tag in val:
# Multiple elements share this tag, make them a list
if not isinstance(val[tag], list):
val[tag] = [val[tag]]
val[tag].append(v)
else:
# First element with this tag
val[tag] = v
# Simple node.
else:
attribs = el.items()
# An element with attributes.
if attribs and strict:
val = dict(('@%s' % k, v) for k, v in dict(attribs).iteritems())
if el.text:
converted = _val_and_maybe_convert(el)
val['#text'] = el.text
if converted != el.text:
val['#value'] = converted
elif el.text:
# An element with no subelements but text.
val = _val_and_maybe_convert(el)
elif attribs:
val = dict(attribs)
return val
def _val_and_maybe_convert(el):
"""
Converts `el.text` if `el` has attribute `type` with valid value.
"""
text = el.text.strip()
data_type = el.get('type')
convertor = _val_and_maybe_convert.convertors.get(data_type)
if convertor:
return convertor(text)
else:
return text
_val_and_maybe_convert.convertors = {
'boolean': _str_to_boolean,
'datetime': _str_to_datetime,
'integer': int
}