diff --git a/elementpath/xpath1_parser.py b/elementpath/xpath1_parser.py
index 62dea67..3f05cf8 100644
--- a/elementpath/xpath1_parser.py
+++ b/elementpath/xpath1_parser.py
@@ -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
###
diff --git a/elementpath/xpath_context.py b/elementpath/xpath_context.py
index 37d4874..44f47a2 100644
--- a/elementpath/xpath_context.py
+++ b/elementpath/xpath_context.py
@@ -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
diff --git a/tests/test_xpath1_parser.py b/tests/test_xpath1_parser.py
index 78402b3..422f560 100644
--- a/tests/test_xpath1_parser.py
+++ b/tests/test_xpath1_parser.py
@@ -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('')
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("")
+
+ 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('')
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("")
- 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('')
diff --git a/tests/test_xpath2_parser.py b/tests/test_xpath2_parser.py
index 5bede88..41bed69 100644
--- a/tests/test_xpath2_parser.py
+++ b/tests/test_xpath2_parser.py
@@ -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('1103050')
+ 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)