Skip to content

Commit

Permalink
Extend tests for XPath 1.0 parser
Browse files Browse the repository at this point in the history
  • Loading branch information
brunato committed Jan 6, 2021
1 parent 797f84f commit 3ed90cf
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 10 deletions.
26 changes: 18 additions & 8 deletions elementpath/xpath1/xpath1_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
xsd10_atomic_types, xsd11_atomic_types, ATOMIC_VALUES
from ..xpath_context import XPathSchemaContext
from ..tdop import Parser
from ..namespaces import XSD_ANY_SIMPLE_TYPE, XML_NAMESPACE, XSD_NAMESPACE, \
XSD_UNTYPED_ATOMIC, get_namespace, get_expanded_name, split_expanded_name
from ..namespaces import XSD_ANY_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE, XML_NAMESPACE, \
XSD_NAMESPACE, XSD_UNTYPED_ATOMIC, get_namespace, get_expanded_name, split_expanded_name
from ..schema_proxy import AbstractSchemaProxy
from ..xpath_token import XPathToken
from ..xpath_nodes import XPathNode, TypedElement, AttributeNode, TypedAttribute, \
Expand Down Expand Up @@ -175,7 +175,7 @@ def nud_(self):
self.parser.advance('(')
if nargs is None:
del self[:]
if self.parser.next_token.symbol == ')':
if self.parser.next_token.symbol in (')', '(end)'):
raise self.error(code, 'at least an argument is required')
while True:
self.append(self.parser.expression(5))
Expand All @@ -186,7 +186,9 @@ def nud_(self):
return self
elif nargs == 0:
if self.parser.next_token.symbol != ')':
raise self.error(code, '%s has no arguments' % str(self))
if self.parser.next_token.symbol != '(end)':
raise self.error(code, '%s has no arguments' % str(self))
raise self.parser.next_token.wrong_syntax()
self.parser.advance()
return self
elif isinstance(nargs, (tuple, list)):
Expand All @@ -196,7 +198,7 @@ def nud_(self):

k = 0
while k < min_args:
if self.parser.next_token.symbol == ')':
if self.parser.next_token.symbol in (')', '(end)'):
msg = 'Too few arguments: expected at least %s arguments' % min_args
raise self.wrong_nargs(msg if min_args > 1 else msg[:-1])

Expand All @@ -215,7 +217,7 @@ def nud_(self):
elif k == 0 and self.parser.next_token.symbol != ')':
self[k:] = self.parser.expression(5),
else:
break
break # pragma: no cover
k += 1

if self.parser.next_token.symbol == ',':
Expand Down Expand Up @@ -260,8 +262,12 @@ def is_instance(self, obj, type_qname):
if get_namespace(type_qname) == XSD_NAMESPACE:
if type_qname == XSD_UNTYPED_ATOMIC:
return isinstance(obj, UntypedAtomic)
elif type_qname == XSD_ANY_ATOMIC_TYPE:
return isinstance(obj, AnyAtomicType)
elif type_qname == XSD_ANY_SIMPLE_TYPE:
return isinstance(obj, (AnyAtomicType, list))
return isinstance(obj, AnyAtomicType) or \
isinstance(obj, list) and \
all(isinstance(x, AnyAtomicType) for x in obj)

try:
if self.xsd_version == '1.1':
Expand All @@ -271,7 +277,11 @@ def is_instance(self, obj, type_qname):
pass

if self.schema is not None:
return self.schema.is_instance(obj, type_qname)
try:
return self.schema.is_instance(obj, type_qname)
except KeyError:
pass

raise ElementPathKeyError("unknown type %r" % type_qname)

def is_sequence_type(self, value):
Expand Down
70 changes: 69 additions & 1 deletion tests/test_xpath1_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@
except ImportError:
lxml_etree = None

try:
import xmlschema
except ImportError:
xmlschema = None

from elementpath import *
from elementpath.namespaces import XSD_NAMESPACE, XPATH_FUNCTIONS_NAMESPACE
from elementpath.namespaces import XSD_NAMESPACE, XPATH_FUNCTIONS_NAMESPACE, \
XSD_ANY_ATOMIC_TYPE, XSD_ANY_SIMPLE_TYPE, XSD_UNTYPED_ATOMIC

try:
from tests import xpath_test_class
Expand Down Expand Up @@ -242,6 +248,65 @@ def test_wrong_nargs(self):
self.wrong_type("contains('XPath', 'XP', 20)")
self.wrong_type("boolean(1, 5)")

def test_xsd_qname_method(self):
qname = self.parser.xsd_qname('string')
self.assertEqual(qname, 'xs:string')

parser = self.parser.__class__(namespaces={'xs': XSD_NAMESPACE})
parser.namespaces['xsd'] = parser.namespaces.pop('xs')
self.assertEqual(parser.xsd_qname('string'), 'xsd:string')

parser.namespaces.pop('xsd')
with self.assertRaises(NameError) as ctx:
parser.xsd_qname('string')
self.assertIn('XPST0081', str(ctx.exception))

def test_is_instance_method(self):
self.assertTrue(self.parser.is_instance(datatypes.UntypedAtomic(1),
XSD_UNTYPED_ATOMIC))
self.assertFalse(self.parser.is_instance(1, XSD_UNTYPED_ATOMIC))
self.assertTrue(self.parser.is_instance(1, XSD_ANY_ATOMIC_TYPE))
self.assertFalse(self.parser.is_instance([1], XSD_ANY_ATOMIC_TYPE))
self.assertTrue(self.parser.is_instance(1, XSD_ANY_SIMPLE_TYPE))
self.assertTrue(self.parser.is_instance([1], XSD_ANY_SIMPLE_TYPE))

self.assertTrue(self.parser.is_instance('foo', '{%s}string' % XSD_NAMESPACE))
self.assertFalse(self.parser.is_instance(1, '{%s}string' % XSD_NAMESPACE))
self.assertTrue(self.parser.is_instance(1.0, '{%s}double' % XSD_NAMESPACE))
self.assertFalse(self.parser.is_instance(1.0, '{%s}float' % XSD_NAMESPACE))

self.parser._xsd_version = '1.1'
try:
self.assertTrue(self.parser.is_instance(1.0, '{%s}double' % XSD_NAMESPACE))
self.assertFalse(self.parser.is_instance(1.0, '{%s}float' % XSD_NAMESPACE))
finally:
self.parser._xsd_version = '1.0'

with self.assertRaises(KeyError):
self.parser.is_instance('foo', '{%s}unknown' % XSD_NAMESPACE)

if xmlschema is not None and self.parser.version > '1.0':
schema = xmlschema.XMLSchema("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="myInt">
<xs:restriction base="xs:int"/>
</xs:simpleType>
</xs:schema>""")

self.parser.schema = xmlschema.xpath.XMLSchemaProxy(schema)
try:
self.assertFalse(self.parser.is_instance(1.0, 'myInt'))
self.assertTrue(self.parser.is_instance(1, 'myInt'))
with self.assertRaises(KeyError):
self.parser.is_instance(1.0, 'dType')
finally:
self.parser.schema = None

def test_check_variables_method(self):
self.assertIsNone(self.parser.check_variables(
{'values': [1, 2, -1], 'myaddress': 'info@example.com', 'word': ''}
))

# XPath expression tests
def test_node_selection(self):
root = self.etree.XML('<A><B1/><B2/><B3/><B2/></A>')
Expand Down Expand Up @@ -736,6 +801,9 @@ def test_boolean_functions(self):
self.check_value("boolean(' ')", True)
self.check_value("boolean('')", False)

self.wrong_type("true(1)", 'XPST0017', "'true' function has no arguments")
self.wrong_syntax("true(", 'unexpected end of source')

if self.parser.version == '1.0':
self.wrong_syntax("boolean(())")
else:
Expand Down
9 changes: 8 additions & 1 deletion tests/test_xpath2_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def test_is_sequence_type_method(self):
self.assertTrue(self.parser.is_sequence_type('item()?'))
self.assertTrue(self.parser.is_sequence_type('xs:untypedAtomic+'))
self.assertFalse(self.parser.is_sequence_type(10))
self.assertFalse(self.parser.is_sequence_type(''))
self.assertFalse(self.parser.is_sequence_type('empty-sequence()*'))
self.assertFalse(self.parser.is_sequence_type('unknown'))
self.assertFalse(self.parser.is_sequence_type('unknown?'))
Expand Down Expand Up @@ -151,7 +152,7 @@ def test_check_variables_method(self):
{'values': 'xs:decimal+', 'myaddress': 'xs:string', 'word': 'xs:string'})

self.assertIsNone(self.parser.check_variables(
{'values': 1, 'myaddress': 'info@example.com', 'word': ''}
{'values': [1, 2, -1], 'myaddress': 'info@example.com', 'word': ''}
))

with self.assertRaises(NameError) as ctx:
Expand Down Expand Up @@ -946,6 +947,8 @@ def test_get_atomic_value(self):

token = self.parser.parse('true()')

self.assertEqual(self.parser.get_atomic_value('xs:int'), 1)
self.assertEqual(self.parser.get_atomic_value('xs:unknown'), UntypedAtomic('1'))
self.assertEqual(self.parser.get_atomic_value(schema.elements['d'].type),
UntypedAtomic('1'))

Expand All @@ -960,6 +963,10 @@ def test_get_atomic_value(self):
with self.assertRaises(AttributeError) as err:
self.parser.get_atomic_value(schema)

value = self.parser.get_atomic_value('unknown')
self.assertIsInstance(value, UntypedAtomic)
self.assertEqual(value, UntypedAtomic(value='1'))

value = self.parser.get_atomic_value(schema.elements['a'].type)
self.assertIsInstance(value, UntypedAtomic)
self.assertEqual(value, UntypedAtomic(value='1'))
Expand Down

0 comments on commit 3ed90cf

Please sign in to comment.