Skip to content

Commit

Permalink
Fix XPath node check for intersect/except operator
Browse files Browse the repository at this point in the history
  • Loading branch information
brunato committed Jan 27, 2021
1 parent 5fb6cdf commit 31b5ab4
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 7 deletions.
12 changes: 5 additions & 7 deletions elementpath/xpath2/xpath2_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ def select(self, context=None):
raise self.missing_context()

s1, s2 = set(self[0].select(copy(context))), set(self[1].select(copy(context)))
if any(not is_xpath_node(x) for x in s1) or any(not is_xpath_node(x) for x in s1):
if any(not is_xpath_node(x) for x in s1) or any(not is_xpath_node(x) for x in s2):
raise self.error('XPTY0004', 'only XPath nodes are allowed')

if self.symbol == 'except':
Expand Down Expand Up @@ -568,10 +568,9 @@ def nud(self):
if tk[0].value == variable[0].value:
raise tk.error('XPST0008', 'loop variable in its range expression')

if self.parser.next_token.symbol == ',':
self.parser.advance()
else:
if self.parser.next_token.symbol != ',':
break
self.parser.advance()

self.parser.advance('satisfies')
self.append(self.parser.expression(5))
Expand Down Expand Up @@ -616,10 +615,9 @@ def nud(self):
if tk[0].value == variable[0].value:
raise tk.error('XPST0008', 'loop variable in its range expression')

if self.parser.next_token.symbol == ',':
self.parser.advance()
else:
if self.parser.next_token.symbol != ',':
break
self.parser.advance()

self.parser.advance('return')
self.append(self.parser.expression(5))
Expand Down
43 changes: 43 additions & 0 deletions tests/test_xpath2_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,23 @@ def test_match_sequence_type_method(self):
self.assertFalse(self.parser.match_sequence_type('1', 'xs:unknown'))
self.assertFalse(self.parser.match_sequence_type('1', 'tns0:string'))

def test_variable_reference(self):
root = self.etree.XML('<a><b1/><b2/></a>')

context = XPathContext(root=root, variables={'var1': root[0]})
self.check_value('$var1', root[0], context=context)

context = XPathContext(root=root, variables={'tns:var1': root[0]})
self.check_raise('$tns:var1', NameError, 'XPST0081', context=context)

# Test dynamic evaluation error
parser = XPath2Parser(namespaces={'tns': 'http://xpath.test/ns'})
token = parser.parse('$tns:var1')
parser.namespaces.pop('tns')
with self.assertRaises(NameError) as ctx:
token.evaluate(context)
self.assertIn('XPST0081', str(ctx.exception))

def test_check_variables_method(self):
self.parser.variable_types.update(
(k, get_sequence_type(v)) for k, v in self.variables.items()
Expand Down Expand Up @@ -292,6 +309,14 @@ def test_quantifier_expressions(self):
self.check_value('some $x in (1, 2, "cat") satisfies $x * 2 = 4', True, context)
self.check_value('every $x in (1, 2, "cat") satisfies $x * 2 = 4', False, context)

# From W3C XQuery/XPath tests
context = XPathContext(root=self.etree.XML('<dummy/>'),
variables={'result': [43, 44, 45]})

self.check_value('some $i in $result satisfies $i = 44', True, context)
self.check_value('every $i in $result satisfies $i = 44', False, context)
self.check_raise('some $foo in (1, $foo) satisfies 1', NameError, 'XPST0008')

def test_for_expressions(self):
# Cases from XPath 2.0 examples
context = XPathContext(root=self.etree.XML('<dummy/>'))
Expand Down Expand Up @@ -346,6 +371,13 @@ def test_for_expressions(self):
root[2][0], root[2][2], root[2][0], root[2][3], root[2][0]]
)

# From W3C XQuery/XPath tests
context = XPathContext(root=self.etree.XML('<dummy/>'),
variables={'result': [43, 44, 45]})

self.check_value('for $i in $result return $i + 10', [53, 54, 55], context)
self.check_raise('for $foo in (1, $foo) return 1', NameError, 'XPST0008')

def test_idiv_operator(self):
self.check_value("5 idiv 2", 2)
self.check_value("-3.5 idiv -2", 1)
Expand Down Expand Up @@ -744,6 +776,17 @@ def test_union_intersect_except_operators(self):
self.check_select('$seq1 except $seq2', [], context=context)
self.check_select('$seq2 except $seq3', root[:1], context=context)

self.wrong_type('1 intersect 1', 'XPTY0004',
'only XPath nodes are allowed', context=context)
self.wrong_type('1 except $seq1', 'XPTY0004',
'only XPath nodes are allowed', context=context)
self.wrong_type('1 union $seq1', 'XPTY0004',
'only XPath nodes are allowed', context=context)
self.wrong_type('$seq1 intersect 1', 'XPTY0004',
'only XPath nodes are allowed', context=context)
self.wrong_type('$seq1 union 1', 'XPTY0004',
'only XPath nodes are allowed', context=context)

def test_node_comparison_operators(self):
# Test cases from https://www.w3.org/TR/xpath20/#id-node-comparisons
root = self.etree.XML('''
Expand Down

0 comments on commit 31b5ab4

Please sign in to comment.