Add iter_preceding() to XPathContext
This commit is contained in:
parent
307d638f96
commit
8cc023d077
|
@ -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
|
||||
|
||||
|
||||
###
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>')
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue