Coverage for gws-app/gws/lib/xmlx/element.py: 99%
147 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""XmlElement implementation."""
3from typing import Iterable, Optional, cast
4import xml.etree.ElementPath as ElementPath
6import gws
8from . import error, namespace, serializer
11class XmlElement(gws.XmlElement):
12 """XML element."""
14 def __init__(self, tag: str, attrib: Optional[dict] = None, **extra):
15 """Create an element.
17 Names are taken as they are, no namespace resolution is done.
19 Args:
20 tag: Tag name, local or Clark.
21 attrib: Attributes.
22 **extra: Additional attributes.
23 """
25 self.tag = tag
26 self.name = namespace.plain_name(tag)
27 self.text = ''
28 self.tail = ''
29 self.namespaces = []
30 self._children = []
32 self.attrib = {}
33 if attrib:
34 self.attrib.update(attrib)
35 if extra:
36 self.attrib.update(extra)
38 # ElementTree.Element implementations, copied from ElementTree.py
40 def __repr__(self):
41 return f'<{self.__class__.__name__} {self.tag!r} at {id(self):#x}>'
43 def makeelement(self, tag, attrib):
44 """Create a new element of the same class, as in ``ElementTree``.
46 The new element is not added to this element.
48 Args:
49 tag: Tag name, local or Clark.
50 attrib: Attributes.
52 Returns:
53 A new element.
54 """
56 return self.__class__(tag, attrib)
58 def __copy__(self):
59 """Shallow copy: the children are shared with the original."""
61 elem = self.__class__(self.tag, self.attrib)
62 elem.text = self.text
63 elem.tail = self.tail
64 elem.namespaces = list(self.namespaces)
65 elem._children = list(self._children)
66 return elem
68 def __len__(self):
69 return len(self._children)
71 def __getitem__(self, index):
72 return self._children[index]
74 def __setitem__(self, index, element):
75 """Replace the child at the given index."""
77 self._children[index] = element
79 def __delitem__(self, index):
80 """Remove the child at the given index."""
82 del self._children[index]
84 def append(self, subelement):
85 self._children.append(subelement)
87 def extend(self, elements):
88 for element in elements:
89 self._children.append(element)
91 def insert(self, index, subelement):
92 self._children.insert(index, subelement)
94 def remove(self, subelement):
95 self._children.remove(subelement)
97 def find(self, path):
98 return cast(Optional[gws.XmlElement], ElementPath.find(self, path))
100 def findtext(self, path, default=''):
101 return cast(str, ElementPath.findtext(self, path, default))
103 def findall(self, path):
104 return cast(list[gws.XmlElement], ElementPath.findall(self, path))
106 def iterfind(self, path):
107 return cast(Iterable[gws.XmlElement], ElementPath.iterfind(self, path))
109 def clear(self):
110 self.attrib = {}
111 self.namespaces = []
112 self._children = []
113 self.text = self.tail = ''
115 def get(self, key, default=''):
116 return self.attrib.get(key, default)
118 def set(self, key, value):
119 self.attrib[key] = value
121 def keys(self):
122 return self.attrib.keys()
124 def items(self):
125 return self.attrib.items()
127 def iter(self, tag=None):
128 if tag == '*':
129 tag = None
130 if tag is None or self.tag == tag:
131 yield self
132 for e in self._children:
133 yield from e.iter(tag)
135 def itertext(self):
136 t = self.text
137 if t:
138 yield t
139 for e in self:
140 yield from e.itertext()
141 t = e.tail
142 if t:
143 yield t
145 ## extensions
147 def __bool__(self):
148 """An element is always true, also when it has no children."""
150 return True
152 def __iter__(self):
153 return iter(self._children)
155 def require(self, path):
156 el = self.find(path)
157 if el is None:
158 raise error.Error(f'XmlElement: required element not found: {path!r}')
159 return el
161 def children(self):
162 return self._children
164 def hasattr(self, key):
165 return key in self.attrib
167 def isa(self, *names):
168 n = self.name.lower()
169 return any(n == s.lower() for s in names)
171 def to_dict(self):
172 return {
173 'tag': self.tag,
174 'attrib': dict(self.attrib),
175 'text': self.text,
176 'tail': self.tail,
177 'children': [c.to_dict() for c in self._children],
178 }
180 def to_string(self, opts=None):
181 ser = serializer.Serializer(self, opts=opts)
182 return ser.to_string()
184 ##
186 def add(self, tag, attrib=None, **extra):
187 el = self.__class__(tag, attrib or {}, **extra)
188 self.append(el)
189 return el
191 def declare(self, *namespaces):
192 for ns in namespaces:
193 if not any(n.uri == ns.uri and n.prefix == ns.prefix for n in self.namespaces):
194 self.namespaces.append(ns)
196 def findfirst(self, *paths):
197 if not paths:
198 return self._children[0] if len(self._children) > 0 else None
199 for path in paths:
200 el = self.find(path)
201 if el is not None:
202 return el
203 return None
205 def textof(self, *paths):
206 for path in paths:
207 el = self.find(path)
208 if el is not None and el.text:
209 return el.text
210 return ''
212 def textlist(self, *paths, deep=False):
213 ls = self._collect_tags_and_text(paths, deep)
214 return [text for _, text in ls]
216 def textdict(self, *paths, deep=False):
217 ls = self._collect_tags_and_text(paths, deep)
218 return dict(ls)
220 def _collect_tags_and_text(self, paths, deep):
221 """Collect ``(tag, stripped text)`` pairs for ``textlist`` and ``textdict``."""
223 def walk(el):
224 s = (el.text or '').strip()
225 if s:
226 ls.append((el.tag, s))
227 if deep:
228 for c in el:
229 walk(c)
231 ls = []
233 if not paths:
234 for el in self:
235 walk(el)
236 else:
237 for path in paths:
238 for el in self.findall(path):
239 walk(el)
241 return ls