Skip to content

Commit f408550

Browse files
fix ../ xpath queries
This commit implements robust support for the .. (parent) XPath selector. Tree Structure Changes: It adds a _parent attribute to the Element class. The init method and all list-modification methods are updated to automatically maintain this parent pointer when children are modified. Path Resolution: It updates ElementPath.py to use these _parent pointers when resolving. If a parent pointer is missing (e.g., for objects not attached to the current context), it falls back to building a temporary parent map by traversing from the root.
1 parent 190cc4e commit f408550

File tree

3 files changed

+75
-10
lines changed

3 files changed

+75
-10
lines changed

src/emeraldtree/ElementPath.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,22 @@ def select(context, result):
102102

103103
def prepare_dot_dot(next, token):
104104
def select(context, result):
105-
parent_map = context.parent_map
106-
if parent_map is None:
107-
context.parent_map = parent_map = {}
108-
for p in context.root.iter():
109-
for e in p:
110-
parent_map[e] = p
111105
for elem in result:
112-
if elem in parent_map:
113-
yield parent_map[elem]
106+
parent = getattr(elem, '_parent', None)
107+
if parent is not None:
108+
yield parent
109+
else:
110+
if context.parent_map is None:
111+
context.parent_map = {}
112+
for p in context.root.iter():
113+
try:
114+
iter(p)
115+
except TypeError:
116+
continue
117+
for e in p:
118+
context.parent_map[e] = p
119+
if elem in context.parent_map:
120+
yield context.parent_map[elem]
114121
return select
115122

116123
def prepare_predicate(next, token):

src/emeraldtree/tests/test_tree.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ def test_Element_findall_bracketed_tag():
170170
assert result[0] is b1 # b1 has 'c' childs
171171

172172
def test_Element_findall_dotdot():
173-
pytest.skip('broken')
174173
c1 = Element('c')
175174
c2 = Element('c')
176175
text = "text"

src/emeraldtree/tree.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ class Element(Node):
121121

122122
attrib = None
123123

124+
# Parent pointer (internal). None for root or detached elements.
125+
_parent = None
126+
124127
##
125128
# (Attribute) Text before first subelement. This is either a
126129
# string or the value None, if there was no text.
@@ -151,6 +154,10 @@ def __init__(self, tag, attrib=None, children=(), **extra):
151154
self.tag = tag
152155
self.attrib = attrib
153156
self._children = list(children)
157+
# set parent pointers for element children
158+
for ch in self._children:
159+
if isinstance(ch, Element):
160+
ch._parent = self
154161

155162
def __repr__(self):
156163
return "<Element {} at {:x}>".format(repr(self.tag), id(self))
@@ -186,7 +193,34 @@ def __getitem__(self, index):
186193
# @exception AssertionError If element is not a valid object.
187194

188195
def __setitem__(self, index, element):
189-
self._children.__setitem__(index, element)
196+
# clear parent of replaced children and set parent of new ones
197+
if isinstance(index, slice):
198+
# clear parents for removed elements
199+
old_items = self._children[index]
200+
for old in old_items:
201+
if isinstance(old, Element):
202+
old._parent = None
203+
# assign
204+
self._children[index] = element
205+
# set parents for new elements
206+
try:
207+
iterator = iter(element)
208+
except TypeError:
209+
iterator = None
210+
if iterator is not None:
211+
for new in element:
212+
if isinstance(new, Element):
213+
new._parent = self
214+
else:
215+
try:
216+
old = self._children[index]
217+
except Exception:
218+
old = None
219+
if isinstance(old, Element):
220+
old._parent = None
221+
self._children[index] = element
222+
if isinstance(element, Element):
223+
element._parent = self
190224

191225
##
192226
# Deletes the given subelement.
@@ -195,6 +229,19 @@ def __setitem__(self, index, element):
195229
# @exception IndexError If the given element does not exist.
196230

197231
def __delitem__(self, index):
232+
# clear parent pointer for removed element(s)
233+
if isinstance(index, slice):
234+
old_items = self._children[index]
235+
for old in old_items:
236+
if isinstance(old, Element):
237+
old._parent = None
238+
else:
239+
try:
240+
old = self._children[index]
241+
except Exception:
242+
old = None
243+
if isinstance(old, Element):
244+
old._parent = None
198245
self._children.__delitem__(index)
199246

200247
##
@@ -205,6 +252,8 @@ def __delitem__(self, index):
205252

206253
def append(self, element):
207254
self._children.append(element)
255+
if isinstance(element, Element):
256+
element._parent = self
208257

209258
##
210259
# Appends subelements from a sequence.
@@ -215,6 +264,9 @@ def append(self, element):
215264

216265
def extend(self, elements):
217266
self._children.extend(elements)
267+
for e in elements:
268+
if isinstance(e, Element):
269+
e._parent = self
218270

219271
##
220272
# Inserts a subelement at the given position in this element.
@@ -224,6 +276,8 @@ def extend(self, elements):
224276

225277
def insert(self, index, element):
226278
self._children.insert(index, element)
279+
if isinstance(element, Element):
280+
element._parent = self
227281

228282
##
229283
# Removes a matching subelement. Unlike the <b>find</b> methods,
@@ -236,11 +290,16 @@ def insert(self, index, element):
236290

237291
def remove(self, element):
238292
self._children.remove(element)
293+
if isinstance(element, Element):
294+
element._parent = None
239295

240296
##
241297
# Removes all subelements.
242298

243299
def remove_all(self):
300+
for ch in self._children:
301+
if isinstance(ch, Element):
302+
ch._parent = None
244303
self._children = []
245304

246305
##

0 commit comments

Comments
 (0)