Add iter_preceding() to XPathContext

This commit is contained in:
Davide Brunato 2019-09-24 16:12:12 +02:00
parent 307d638f96
commit 8cc023d077
4 changed files with 63 additions and 21 deletions

View File

@ -754,13 +754,10 @@ def led(self, left):
@method('[')
def select(self, context=None):
if context is not None:
left_results = list(self[0].select(context))
context.size = len(left_results)
for context.position, context.item in enumerate(left_results):
for position, _ in enumerate(self[0].select(context), start=1):
predicate = list(self[1].select(context.copy()))
if len(predicate) == 1 and not isinstance(predicate[0], bool) and \
isinstance(predicate[0], (int, float)):
if context.position == predicate[0] - 1:
if len(predicate) == 1 and isinstance(predicate[0], NumericTypeProxy):
if position == predicate[0]:
yield context.item
elif self.boolean_value(predicate):
yield context.item
@ -939,14 +936,9 @@ def select(self, context=None):
@method(axis('preceding'))
def select(self, context=None):
if context is not None and is_element_node(context.item):
elem = context.item
ancestors = set(context.iter_ancestors(axis=self.symbol))
for e in context.root.iter():
if e is elem:
break
if e not in ancestors:
context.item = e
yield e
for _ in context.iter_preceding():
for result in self[0].select(context):
yield result
###

View File

@ -82,6 +82,7 @@ class XPathContext(object):
@property
def parent_map(self):
# TODO: try to implement a dynamic parent map to save memory ...
if self._parent_map is None:
self._parent_map = {child: elem for elem in self._root.iter() for child in elem}
return self._parent_map
@ -138,6 +139,34 @@ class XPathContext(object):
self.item, self.size, self.position, self.axis = status
def iter_preceding(self):
status = self.item, self.size, self.position, self.axis
elem = self.item
if not is_etree_element(elem):
return
self.axis = 'preceding'
ancestors = []
while True:
try:
parent = self.parent_map[elem]
except KeyError:
break
else:
ancestors.append(parent)
elem = parent
elem = self.item
for e in self.root.iter():
if e is elem:
break
if e not in ancestors:
self.item = e
yield e
self.item, self.size, self.position, self.axis = status
def iter_parent(self, axis=None):
status = self.item, self.size, self.position, self.axis
self.axis = axis

View File

@ -1079,12 +1079,19 @@ class XPath1ParserTest(unittest.TestCase):
root, root[0], root[0][0], root[1], root[1][0], root[2], root[2][0]
])
def test_preceding_axes(self):
def test_preceding_axis(self):
root = self.etree.XML('<A><B1><C1/><C2/><C3/></B1><B2><C1/><C2/><C3/><C4/></B2></A>')
self.check_selector('/A/B1/C2/preceding::*', root, [root[0][0]])
self.check_selector('/A/B2/C4/preceding::*', root, [
root[0], root[0][0], root[0][1], root[0][2], root[1][0], root[1][1], root[1][2]
])
root = self.etree.XML("<root><e><a><b/></a><a><b/></a></e><e><a/></e></root>")
self.check_tree("/root/e/preceding::b", '(/ (/ (/ (root)) (e)) (preceding (b)))')
self.check_selector('/root/e[2]/preceding::b', root, [root[0][0][0], root[0][1][0]])
def test_preceding_sibling_axis(self):
root = self.etree.XML('<A><B1><C1/><C2/><C3/></B1><B2><C1/><C2/><C3/><C4/></B2></A>')
self.check_selector('/A/B1/C2/preceding-sibling::*', root, [root[0][0]])
self.check_selector('/A/B2/C4/preceding-sibling::*', root, [root[1][0], root[1][1], root[1][2]])
self.check_selector('/A/B1/C2/preceding-sibling::C3', root, [])
@ -1132,10 +1139,15 @@ class XPath1ParserTest(unittest.TestCase):
self.check_selector("child::a[b][c]", root, [root[1]])
root = self.etree.XML("<root><e><a><b/></a><a><b/></a></e><e><a/></e></root>")
self.check_selector("e/a[not(b)]", root, [root[1][0]])
self.check_selector("preceding::e/a[not(b)]", root, [])
self.check_selector("e/a[preceding::e/a[not(b)]]", root, [])
self.check_selector("not(e/a[preceding::e/a[not(b)]])", root, True)
self.check_tree("a[not(b)]", '([ (a) (not (b)))')
self.check_value("a[not(b)]", [], context=XPathContext(root, item=root[0]))
self.check_value("a[not(b)]", [root[1][0]], context=XPathContext(root, item=root[1]))
self.check_tree("preceding::a[not(b)]", '([ (preceding (a)) (not (b)))')
self.check_value("a[preceding::a[not(b)]]", [], context=XPathContext(root, item=root[0]))
self.check_value("a[preceding::a[not(b)]]", [], context=XPathContext(root, item=root[1]))
def test_union(self):
root = self.etree.XML('<A><B1><C1/><C2/><C3/></B1><B2><C1/><C2/><C3/><C4/></B2><B3/></A>')

View File

@ -185,7 +185,7 @@ class XPath2ParserTest(test_xpath1_parser.XPath1ParserTest):
""")
# Test step-by-step, testing also other basic features.
self.check_selector("author[1]", root[0], [root[0][1]])
self.check_selector("book/author[1]", root, [root[0][1], root[1][1], root[2][1]])
self.check_selector("book/author[. = $a]", root, [root[0][1], root[1][1]], variables={'a': 'Stevens'})
self.check_tree("book/author[. = $a][1]", '(/ (book) ([ ([ (author) (= (.) ($ (a)))) (1)))')
self.check_selector("book/author[. = $a][1]", root, [root[0][1], root[1][1]], variables={'a': 'Stevens'})
@ -219,7 +219,8 @@ class XPath2ParserTest(test_xpath1_parser.XPath1ParserTest):
self.wrong_value("-3.5 idiv 0")
self.wrong_value("xs:float('INF') idiv 2")
def test_comparison_operators2(self):
def test_comparison_operators(self):
super(XPath2ParserTest, self).test_comparison_operators()
self.check_value("0.05 eq 0.05", True)
self.check_value("19.03 ne 19.02999", True)
self.check_value("-1.0 eq 1.0", False)
@ -264,6 +265,14 @@ class XPath2ParserTest(test_xpath1_parser.XPath1ParserTest):
self.check_value('@min le @max', False, context=XPathContext(root=root))
self.check_value('@min le @maximum', None, context=XPathContext(root=root))
root = self.etree.XML('<root><a>1</a><a>10</a><a>30</a><a>50</a></root>')
self.check_selector("a = (1 to 30)", root, True)
self.check_selector("a = (2)", root, False)
self.check_selector("a[1] = (1 to 10, 30)", root, True)
self.check_selector("a[2] = (1 to 10, 30)", root, True)
self.check_selector("a[3] = (1 to 10, 30)", root, True)
self.check_selector("a[4] = (1 to 10, 30)", root, False)
def test_number_functions2(self):
# Test cases taken from https://www.w3.org/TR/xquery-operators/#numeric-value-functions
self.check_value("abs(10.5)", 10.5)