forked from adsr303/etobj
-
Notifications
You must be signed in to change notification settings - Fork 0
/
etobj.py
197 lines (150 loc) · 4.96 KB
/
etobj.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
"""Convert an ElementTree to an objectified API."""
import collections
import re
import itertools
class Element(collections.Sequence):
def __init__(self, elem, parent=None, attr_error_class=None):
# Use __dict__ directly to avoid calls to __setattr__.
self.__dict__['_elem'] = elem
self.__dict__['_parent'] = parent
if attr_error_class is not None:
self.__dict__['_attr_error_class'] = attr_error_class
elif parent is not None:
self.__dict__['_attr_error_class'] = parent._attr_error_class
else:
self.__dict__['_attr_error_class'] = AttributeError
def __getitem__(self, key):
elems = _siblings(self)
if isinstance(key, slice):
return [Element(e, self.parent) for e in elems[key]]
return Element(elems[key], self.parent)
def __len__(self):
return len(_siblings(self))
def __getattr__(self, name):
return Element(_find(self, name), self)
def __setattr__(self, name, value):
# Consider an attribute listed by dir() as 'well-known'. Only setting
# an 'unknown' attribute should result in creating a subelement.
if name in dir(self):
super(Element, self).__setattr__(name, value)
else:
if isinstance(value, type(self.elem)):
new = value
elif isinstance(value, Element):
new = value.elem
else:
new = _newelem(self, name, text=value)
name = _prepend_elem_namespace(self.elem, name)
if new.tag != name:
new.tag = name
try:
current = _find(self, name)
except self._attr_error_class as e:
self.elem.append(new)
else:
idx = list(self.elem).index(current)
self.elem[idx] = new
def __delattr__(self, name):
if name in dir(self):
super(Element, self).__delattr__(name)
else:
elem = _find(self, name)
self.elem.remove(elem)
def __str__(self):
return self.text or ''
def __eq__(self, other):
if other is self:
return True
if isinstance(other, Element):
return _equal(self.elem, other.elem)
if isinstance(other, basestring):
return str(self) == other
return NotImplemented
def __ne__(self, other):
eq = (self == other)
if eq is NotImplemented:
return NotImplemented
return not eq
@property
def tag(self):
return self.elem.tag
@tag.setter
def tag(self, value):
self.elem.tag = value
@property
def text(self):
return self.elem.text
@text.setter
def text(self, value):
self.elem.text = value
@property
def tail(self):
return self.elem.tail
@tail.setter
def tail(self, value):
self.elem.tail = value
def get(self, key, default=None):
return self.elem.get(key, default)
def set(self, key, value):
self.elem.set(key, value)
def keys(self):
return self.elem.keys()
def items(self):
return self.elem.items()
@property
def attrib(self):
return self.elem.attrib
@property
def elem(self):
return self._elem
@property
def parent(self):
return self._parent
def root(obj):
while obj.parent is not None:
obj = obj.parent
return obj
def iterancestors(obj):
anc = obj.parent
while anc is not None:
yield anc
anc = anc.parent
def shallow_signature(obj):
return _shallow_signature(obj.elem)
def deep_signature(obj):
return _deep_signature(obj.elem)
# Alias to expose it as function
objectify = Element
NS_RE = re.compile(r'\{.+\}')
def _shallow_signature(elem):
return (elem.tag, elem.attrib, elem.text, [], elem.tail)
def _deep_signature(elem):
children = [_deep_signature(c) for c in list(elem)]
return (elem.tag, elem.attrib, elem.text, children, elem.tail)
def _equal(elem1, elem2):
if _shallow_signature(elem1) != _shallow_signature(elem2):
return False
if len(elem1) != len(elem2):
return False
return all(
_equal(x, y) for x, y in itertools.izip(list(elem1), list(elem2)))
def _get_namespace(elem):
m = NS_RE.match(elem.tag)
return m.group(0) if m else None
def _prepend_namespace(ns, name):
return '{}{}'.format(ns, name) if ns else name
def _prepend_elem_namespace(elem, name):
return _prepend_namespace(_get_namespace(elem), name)
def _newelem(obj, tag, text):
new = obj.elem.makeelement(tag, {})
new.text = text
return new
def _find(obj, name):
elem = obj.elem.find(_prepend_elem_namespace(obj.elem, name))
if elem is None:
raise obj._attr_error_class('no such child: {}'.format(name))
return elem
def _siblings(obj):
if obj.parent is None:
return [obj.elem]
return obj.parent.elem.findall(obj.tag)