diff --git a/elementpath/xpath1/xpath1_parser.py b/elementpath/xpath1/xpath1_parser.py index eb1159c7..98a05480 100644 --- a/elementpath/xpath1/xpath1_parser.py +++ b/elementpath/xpath1/xpath1_parser.py @@ -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, \ @@ -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)) @@ -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)): @@ -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]) @@ -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 == ',': @@ -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': @@ -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): diff --git a/tests/test_xpath1_parser.py b/tests/test_xpath1_parser.py index 6498f003..aaf958c4 100644 --- a/tests/test_xpath1_parser.py +++ b/tests/test_xpath1_parser.py @@ -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 @@ -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(""" + + + + + """) + + 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('') @@ -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: diff --git a/tests/test_xpath2_parser.py b/tests/test_xpath2_parser.py index e14c579e..9c6a7581 100644 --- a/tests/test_xpath2_parser.py +++ b/tests/test_xpath2_parser.py @@ -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?')) @@ -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: @@ -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')) @@ -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'))