Fix count() function and '//', '/' binding power
This commit is contained in:
parent
18871936cb
commit
307d638f96
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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('<A><B><C/><C/></B><B/><B><C/><C/><C/></B></A>')
|
||||
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("<root><a><b/></a><a><b/><c/></a><a><c/></a></root>")
|
||||
|
||||
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("<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)
|
||||
|
||||
def test_union(self):
|
||||
root = self.etree.XML('<A><B1><C1/><C2/><C3/></B1><B2><C1/><C2/><C3/><C4/></B2><B3/></A>')
|
||||
self.check_selector('/A/B2 | /A/B1', root, root[:2])
|
||||
|
|
|
@ -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('<A><B1><C1/><C2/></B1><B2/><B3><C3/><C4/><C5/></B3></A>')
|
||||
super(XPath2ParserTest, self).test_count_function()
|
||||
root = self.etree.XML('<root/>')
|
||||
self.check_selector("count(5)", root, 1)
|
||||
self.check_value("count((0, 1, 2 + 1, 3 - 1))", 4)
|
||||
|
||||
|
|
Loading…
Reference in New Issue