From 31b5ab4961303c519adf1db3fa2a43e99494aa9a Mon Sep 17 00:00:00 2001 From: Davide Brunato Date: Wed, 27 Jan 2021 12:23:36 +0100 Subject: [PATCH] Fix XPath node check for intersect/except operator --- elementpath/xpath2/xpath2_parser.py | 12 ++++---- tests/test_xpath2_parser.py | 43 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/elementpath/xpath2/xpath2_parser.py b/elementpath/xpath2/xpath2_parser.py index db6e96d0..16955969 100644 --- a/elementpath/xpath2/xpath2_parser.py +++ b/elementpath/xpath2/xpath2_parser.py @@ -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': @@ -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)) @@ -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)) diff --git a/tests/test_xpath2_parser.py b/tests/test_xpath2_parser.py index bb6f6934..a12fab37 100644 --- a/tests/test_xpath2_parser.py +++ b/tests/test_xpath2_parser.py @@ -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('') + + 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() @@ -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(''), + 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('')) @@ -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(''), + 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) @@ -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('''