diff --git a/elementpath/xpath1_parser.py b/elementpath/xpath1_parser.py index e8eb712..62dea67 100644 --- a/elementpath/xpath1_parser.py +++ b/elementpath/xpath1_parser.py @@ -652,24 +652,24 @@ def select(self, context=None): ### # Path expressions -@method('//', bp=80) -@method('/', bp=80) +@method('//', bp=75) +@method('/', bp=75) def nud(self): next_token = self.parser.next_token if next_token.symbol == '(end)' and self.symbol == '/': return self elif not self.parser.next_is_path_step_token(): next_token.wrong_syntax() - self[:] = self.parser.expression(80), + self[:] = self.parser.expression(75), return self -@method('//', bp=80) -@method('/', bp=80) +@method('//') +@method('/') def led(self, left): if not self.parser.next_is_path_step_token(): self.parser.next_token.wrong_syntax() - self[:] = left, self.parser.expression(80) + self[:] = left, self.parser.expression(75) return self @@ -743,7 +743,7 @@ def select(self, context=None): ### # Predicate filters -@method('[', bp=75) +@method('[', bp=80) def led(self, left): self.parser.next_token.unexpected(']') self[:] = left, self.parser.expression() @@ -995,13 +995,7 @@ def evaluate(self, context=None): @method(function('count', nargs=1)) def evaluate(self, context=None): - results = self[0].evaluate(context) - if isinstance(results, list): - return len(results) - elif results is not None: - return 1 - else: - return 0 + return len(list(self[0].select(context))) @method(function('id', nargs=1)) diff --git a/elementpath/xpath_token.py b/elementpath/xpath_token.py index 9ecb5a7..4b90443 100644 --- a/elementpath/xpath_token.py +++ b/elementpath/xpath_token.py @@ -19,6 +19,7 @@ In XPath there are 7 kinds of nodes: Element-like objects are used for representing elements and comments, ElementTree-like objects for documents. Generic tuples are used for representing attributes and named-tuples for namespaces. """ +from __future__ import unicode_literals import locale import contextlib from decimal import Decimal @@ -95,23 +96,25 @@ class XPathToken(Token): def source(self): symbol, label = self.symbol, self.label if label == 'axis': - return u'%s::%s' % (self.symbol, self[0].source) + return '%s::%s' % (self.symbol, self[0].source) elif label in ('function', 'constructor'): - return u'%s(%s)' % (self.symbol, ', '.join(item.source for item in self)) + return '%s(%s)' % (self.symbol, ', '.join(item.source for item in self)) elif symbol == ':': - return u'%s:%s' % (self[0].source, self[1].source) + return '%s:%s' % (self[0].source, self[1].source) elif symbol == '(': - return '()' if not self else u'(%s)' % self[0].source + return '()' if not self else '(%s)' % self[0].source + elif symbol == '[': + return '%s[%s]' % (self[0].source, self[1].source) elif symbol == ',': - return u'%s, %s' % (self[0].source, self[1].source) + return '%s, %s' % (self[0].source, self[1].source) elif symbol == '$': - return u'$%s' % self[0].source + return '$%s' % self[0].source elif symbol == '{': - return u'{%s}%s' % (self[0].value, self[1].value) + return '{%s}%s' % (self[0].value, self[1].value) elif symbol == 'instance': - return u'%s instance of %s' % (self[0].source, ''.join(t.source for t in self[1:])) + return '%s instance of %s' % (self[0].source, ''.join(t.source for t in self[1:])) elif symbol == 'treat': - return u'%s treat as %s' % (self[0].source, ''.join(t.source for t in self[1:])) + return '%s treat as %s' % (self[0].source, ''.join(t.source for t in self[1:])) return super(XPathToken, self).source @property @@ -463,13 +466,13 @@ class XPathToken(Token): if obj is None: return '' elif is_element_node(obj): - return u''.join(elem_iter_strings(obj)) + return ''.join(elem_iter_strings(obj)) elif is_attribute_node(obj): return obj[1] elif is_text_node(obj): return obj elif is_document_node(obj): - return u''.join(e.text for e in obj.getroot().iter() if e.text is not None) + return ''.join(e.text for e in obj.getroot().iter() if e.text is not None) elif is_namespace_node(obj): return obj[1] elif is_comment_node(obj): diff --git a/tests/test_xpath1_parser.py b/tests/test_xpath1_parser.py index f51cbe2..78402b3 100644 --- a/tests/test_xpath1_parser.py +++ b/tests/test_xpath1_parser.py @@ -362,7 +362,7 @@ class XPath1ParserTest(unittest.TestCase): self.check_tree('(1 + 2) * 3', '(* (+ (1) (2)) (3))') self.check_tree("false() and true()", '(and (false) (true))') self.check_tree("false() or true()", '(or (false) (true))') - self.check_tree("./A/B[C][D]/E", '(/ ([ ([ (/ (/ (.) (A)) (B)) (C)) (D)) (E))') + self.check_tree("./A/B[C][D]/E", '(/ (/ (/ (.) (A)) ([ ([ (B) (C)) (D))) (E))') self.check_tree("string(xml:lang)", '(string (: (xml) (lang)))') def test_token_source(self): @@ -906,6 +906,11 @@ class XPath1ParserTest(unittest.TestCase): self.check_selector("number(/values/d)", root, 44.0) self.check_selector("number(/values/a)", root, TypeError) + def test_count_function(self): + root = self.etree.XML('') + self.check_selector("count(B)", root, 3) + self.check_selector("count(.//C)", root, 5) + def test_sum_function(self): root = self.etree.XML(XML_DATA_TEST) self.check_value("sum($values)", 35) @@ -1121,6 +1126,17 @@ class XPath1ParserTest(unittest.TestCase): self.check_selector("/A/*[' ']", root, root[:]) self.check_selector("/A/*['']", root, []) + root = self.etree.XML("") + + self.check_tree("child::a[b][c]", '([ ([ (child (a)) (b)) (c))') + 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) + def test_union(self): root = self.etree.XML('') self.check_selector('/A/B2 | /A/B1', root, root[:2]) diff --git a/tests/test_xpath2_parser.py b/tests/test_xpath2_parser.py index 1f632a4..5bede88 100644 --- a/tests/test_xpath2_parser.py +++ b/tests/test_xpath2_parser.py @@ -187,22 +187,22 @@ 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[. = $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]], variables={'a': 'Stevens'}) - self.check_selector("book/author[. = 'Stevens'][2]", root, [root[1][1]]) + 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'}) + self.check_selector("book/author[. = 'Stevens'][2]", root, []) self.check_selector("for $a in fn:distinct-values(book/author) return $a", root, ['Stevens', 'Abiteboul', 'Buneman', 'Suciu']) - self.check_selector("for $a in fn:distinct-values(book/author) " - "return book/author[. = $a]", root, [root[0][1], root[1][1]] + root[2][1:4]) + self.check_selector("for $a in fn:distinct-values(book/author) return book/author[. = $a]", + root, [root[0][1], root[1][1]] + root[2][1:4]) - self.check_selector("for $a in fn:distinct-values(book/author) " - "return book/author[. = $a][1]", root, [root[0][1]] + root[2][1:4]) + self.check_selector("for $a in fn:distinct-values(book/author) return book/author[. = $a][1]", + root, [root[0][1], root[1][1]] + root[2][1:4]) self.check_selector( - "for $a in fn:distinct-values(book/author) " - "return (book/author[. = $a][1], book[author = $a]/title)", root, - [root[0][1], root[0][0], root[1][0], root[2][1], root[2][0], root[2][2], root[2][0], + "for $a in fn:distinct-values(book/author) return (book/author[. = $a][1], book[author = $a]/title)", + root, + [root[0][1], root[1][1], root[0][0], root[1][0], root[2][1], root[2][0], root[2][2], root[2][0], root[2][3], root[2][0]] ) @@ -1112,7 +1112,8 @@ class XPath2ParserTest(test_xpath1_parser.XPath1ParserTest): self.check_select("node()", [], context) def test_count_function(self): - root = self.etree.XML('') + super(XPath2ParserTest, self).test_count_function() + root = self.etree.XML('') self.check_selector("count(5)", root, 1) self.check_value("count((0, 1, 2 + 1, 3 - 1))", 4)