diff --git a/elementpath/xpath1_parser.py b/elementpath/xpath1_parser.py index 6c58333..61da0d4 100644 --- a/elementpath/xpath1_parser.py +++ b/elementpath/xpath1_parser.py @@ -15,7 +15,7 @@ import decimal from .compat import PY3, string_base_type from .exceptions import ElementPathSyntaxError, ElementPathNameError, MissingContextError from .datatypes import UntypedAtomic, DayTimeDuration, YearMonthDuration, \ - NumericTypeProxy, ArithmeticTypeProxy, XSD_BUILTIN_TYPES + NumericTypeProxy, ArithmeticTypeProxy from .xpath_context import XPathSchemaContext from .tdop_parser import Parser, MultiLabel from .namespaces import XML_ID, XML_LANG, XPATH_1_DEFAULT_NAMESPACES, \ @@ -252,19 +252,9 @@ def select(self, context=None): if name[0] != '{' and self.parser.default_namespace: name = '{%s}%s' % (self.parser.default_namespace, name) - for item in context.iter_children_or_self(): - xsd_type = self.match_xsd_type(item, name) - if xsd_type is not None: - primitive_type = self.parser.schema.get_primitive_type(xsd_type) - try: - value = XSD_BUILTIN_TYPES[primitive_type.local_name or 'anyType'].value - except KeyError: - value = XSD_BUILTIN_TYPES['anyType'].value - - if isinstance(item, AttributeNode): - yield TypedAttribute(item, value) - else: - yield TypedElement(item, value) + for schema_item in context.iter_children_or_self(): + if self.match_xsd_type(schema_item, name) is not None: + yield self.get_typed_node(context, schema_item) return if name[0] != '{' and self.parser.default_namespace: @@ -288,53 +278,22 @@ def select(self, context=None): # Try to match the type using the path for item in context.iter_children_or_self(): - try: - if is_attribute_node(item, name): - path = context.get_path(item) - xsd_attribute = self.parser.schema.find(path, self.parser.namespaces) + if is_attribute_node(item, name) or is_element_node(item, tag): + path = context.get_path(item) + xsd_component = self.parser.schema.find(path, self.parser.namespaces) - if xsd_attribute is not None: - self.xsd_type = xsd_attribute.type - yield TypedAttribute(item, self.xsd_type.decode(item[1])) - else: - self.xsd_type = self.parser.schema - yield item - elif is_element_node(item, tag): - path = context.get_path(item) - xsd_element = self.parser.schema.find(path, self.parser.namespaces) - - if xsd_element is not None: - self.xsd_type = xsd_element.type - if isinstance(item, TypedElement): - yield item - elif self.xsd_type.is_simple() or self.xsd_type.has_simple_content(): - yield TypedElement(item, self.xsd_type.decode(item.text)) - else: - yield item - else: - self.xsd_type = self.parser.schema - yield item - - except (TypeError, ValueError): - msg = "Type {!r} does not match sequence type of {!r}" - self.wrong_sequence_type(msg.format(self.xsd_type, item)) + # print(path, xsd_component) + if xsd_component is not None: + self.xsd_type = xsd_component.type + else: + self.xsd_type = self.parser.schema + yield self.get_typed_node(context, item) else: # XSD typed selection for item in context.iter_children_or_self(): - try: - if is_attribute_node(item, name): - yield TypedAttribute(item, self.xsd_type.decode(item[1])) - elif is_element_node(item, tag): - if isinstance(item, TypedElement): - yield item - elif self.xsd_type.is_simple() or self.xsd_type.has_simple_content(): - yield TypedElement(item, self.xsd_type.decode(item.text)) - else: - yield item - except (TypeError, ValueError): - msg = "Type {!r} does not match sequence type of {!r}" - self.wrong_sequence_type(msg.format(self.xsd_type, item)) + if is_attribute_node(item, name) or is_element_node(item, tag): + yield self.get_typed_node(context, item) ### @@ -411,13 +370,37 @@ def select(self, context=None): else: value = '{%s}%s' % (namespace, self[1].value) - if context is not None: + if context is None: + return + elif isinstance(context, XPathSchemaContext): + for schema_item in context.iter_children_or_self(): + if self.match_xsd_type(schema_item, value) is not None: + yield self.get_typed_node(context, schema_item) + + elif self.xsd_type is self.parser.schema: for item in context.iter_children_or_self(): if is_attribute_node(item, value): yield item elif is_element_node(item, value): yield item + elif self.xsd_type is None or isinstance(self.xsd_type, AbstractSchemaProxy): + for item in context.iter_children_or_self(): + if is_attribute_node(item, value) or is_element_node(item, value): + path = context.get_path(item) + xsd_component = self.parser.schema.find(path, self.parser.namespaces) + if xsd_component is not None: + self.xsd_type = xsd_component.type + else: + self.xsd_type = self.parser.schema + yield self.get_typed_node(context, item) + + else: + # XSD typed selection + for item in context.iter_children_or_self(): + if is_attribute_node(item, value) or is_element_node(item, value): + yield self.get_typed_node(context, item) + ### # Namespace URI as in ElementPath @@ -725,7 +708,7 @@ def select(self, context=None): yield result else: items = [] - left_results = list(self[0].select(context)) + left_results = [x for x in self[0].select(context)] context.size = len(left_results) for context.position, context.item in enumerate(left_results): if not is_xpath_node(context.item): @@ -747,26 +730,6 @@ def select(self, context=None): yield result -@method('/') -def evaluate(self, context=None): - """ - General evaluation method for path operators, that may returns the a single value or None. - """ - if context is not None: - selector = iter(self.select(context)) - try: - value = next(selector) - except StopIteration: - return - else: - try: - next(selector) - except StopIteration: - return self.data_value(value) - else: - self.wrong_context_type("atomized operand is a sequence of length greater than one") - - @method('//') def select(self, context=None): if context is None: @@ -799,7 +762,7 @@ def led(self, left): def select(self, context=None): if context is not None: for position, item in enumerate(self[0].select(context), start=1): - predicate = list(self[1].select(context.copy())) + predicate = [x for x in self[1].select(context.copy())] if len(predicate) == 1 and isinstance(predicate[0], NumericTypeProxy): if position == predicate[0]: yield item @@ -968,7 +931,7 @@ def select(self, context=None): def select(self, context=None): if context is not None: item = context.item - for elem in reversed(list(context.iter_ancestors(axis=self.symbol))): + for elem in reversed([x for x in context.iter_ancestors(axis=self.symbol)]): context.item = elem yield elem yield item @@ -1042,7 +1005,7 @@ def evaluate(self, context=None): @method(function('count', nargs=1)) def evaluate(self, context=None): - return len(list(self[0].select(context))) + return len([x for x in self[0].select(context)]) @method(function('id', nargs=1)) diff --git a/elementpath/xpath2_functions.py b/elementpath/xpath2_functions.py index 6e1cbe8..c2fb115 100644 --- a/elementpath/xpath2_functions.py +++ b/elementpath/xpath2_functions.py @@ -324,7 +324,7 @@ def evaluate(self, context=None): # Aggregate functions @method(function('avg', nargs=1)) def evaluate(self, context=None): - result = list(self[0].select(context)) + result = [x for x in self[0].select(context)] if not result: return result elif isinstance(result[0], Duration): @@ -436,7 +436,7 @@ def select(self, context=None): @method(function('reverse', nargs=1)) def select(self, context=None): - for result in reversed(list(self[0].select(context))): + for result in reversed([x for x in self[0].select(context)]): yield result @@ -451,7 +451,7 @@ def select(self, context=None): @method(function('unordered', nargs=1)) def select(self, context=None): - for result in sorted(list(self[0].select(context)), key=lambda x: self.string_value(x)): + for result in sorted([x for x in self[0].select(context)], key=lambda x: self.string_value(x)): yield result diff --git a/elementpath/xpath2_parser.py b/elementpath/xpath2_parser.py index 048f5ad..8e3681a 100644 --- a/elementpath/xpath2_parser.py +++ b/elementpath/xpath2_parser.py @@ -425,7 +425,7 @@ def evaluate(self, context=None): @method('if') def select(self, context=None): - if self.boolean_value(list(self[0].select(context))): + if self.boolean_value([x for x in self[0].select(context)]): for result in self[1].select(context): yield result else: @@ -745,13 +745,13 @@ def evaluate(self, context=None): def evaluate(self, context=None): symbol = self.symbol - left = list(self[0].select(context)) + left = [x for x in self[0].select(context)] if not left: return elif len(left) > 1 or not is_xpath_node(left[0]): self[0].wrong_type("left operand of %r must be a single node" % symbol) - right = list(self[1].select(context)) + right = [x for x in self[1].select(context)] if not right: return elif len(right) > 1 or not is_xpath_node(right[0]): @@ -783,7 +783,7 @@ def evaluate(self, context=None): self.wrong_type(str(err)) return else: - return list(range(start, stop)) + return [x for x in range(start, stop)] @method('to') diff --git a/elementpath/xpath_context.py b/elementpath/xpath_context.py index 60bb649..6e3fffe 100644 --- a/elementpath/xpath_context.py +++ b/elementpath/xpath_context.py @@ -103,15 +103,13 @@ class XPathContext(object): except KeyError: return - @lru_cache(maxsize=1024) def get_path(self, item): """Cached path resolver for elements and attributes. Returns absolute paths.""" path = [] - if isinstance(item, (AttributeNode, TypedAttribute)): path.append('@%s' % item[0]) item = self._elem - elif isinstance(item, TypedElement): + if isinstance(item, TypedElement): item = item[0] while True: diff --git a/elementpath/xpath_token.py b/elementpath/xpath_token.py index a819491..c2b2e37 100644 --- a/elementpath/xpath_token.py +++ b/elementpath/xpath_token.py @@ -27,12 +27,14 @@ from decimal import Decimal from .compat import string_base_type, unicode_type from .exceptions import xpath_error from .namespaces import XQT_ERRORS_NAMESPACE -from .xpath_nodes import AttributeNode, NamespaceNode, TypedElement, is_etree_element, \ - is_attribute_node, elem_iter_strings, is_text_node, is_namespace_node, \ - is_comment_node, is_processing_instruction_node, is_element_node, \ - is_document_node, is_xpath_node, is_schema_node +from .xpath_nodes import AttributeNode, NamespaceNode, TypedAttribute, TypedElement, \ + is_etree_element, is_attribute_node, elem_iter_strings, is_text_node, \ + is_namespace_node, is_comment_node, is_processing_instruction_node, \ + is_element_node, is_document_node, is_xpath_node, is_schema_node from .datatypes import UntypedAtomic, Timezone, DayTimeDuration, XSD_BUILTIN_TYPES +from .schema_proxy import AbstractSchemaProxy from .tdop_parser import Token +from .xpath_context import XPathSchemaContext def ordinal(n): @@ -61,7 +63,7 @@ class XPathToken(Token): :param context: The XPath dynamic context. """ - return list(self.select(context)) + return [x for x in self.select(context)] def select(self, context=None): """ @@ -207,7 +209,7 @@ class XPathToken(Token): for item in self.select(context): value = self.data_value(item) if value is None: - raise self.error('FOTY0012', "argument node does not have a typed value: {}".format(item)) + raise self.error('FOTY0012', "argument node {!r} does not have a typed value".format(item)) else: yield value @@ -229,6 +231,8 @@ class XPathToken(Token): except StopIteration: if isinstance(value, UntypedAtomic): value = str(value) + if isinstance(context, XPathSchemaContext): + return value if self.xsd_type is not None and isinstance(value, string_base_type): try: value = self.xsd_type.decode(value) @@ -250,10 +254,11 @@ class XPathToken(Token): :returns: a list of data couples. """ if context is None: - operand1, operand2 = list(self[0].select()), list(self[1].select()) + operand1 = [x for x in self[0].select()] + operand2 = [x for x in self[1].select()] else: - operand1 = list(self[0].select(context.copy())) - operand2 = list(self[1].select(context.copy())) + operand1 = [x for x in self[0].select(context.copy())] + operand2 = [x for x in self[1].select(context.copy())] if self.parser.compatibility_mode: # Boolean comparison if one of the results is a single boolean value (1.) @@ -284,11 +289,9 @@ class XPathToken(Token): :param context: the XPath dynamic context. """ for result in self.select(context): - if not isinstance(result, tuple): - yield result # not a namedtuple-wrapped result - elif hasattr(result[0], 'type'): - yield result[0] # an XSD schema attribute/element - elif not isinstance(result, NamespaceNode): + if isinstance(result, TypedElement): + yield result[0] + elif isinstance(result, (AttributeNode, TypedAttribute)): yield result[1] else: yield result @@ -419,6 +422,39 @@ class XPathToken(Token): self.wrong_context_type("Multiple XSD type matching during static analysis") return xsd_type + def get_typed_node(self, context, item): + """ + Returns a typed node if the token is bound to an XSD type. + + :param context: the XPath dynamic context. + :param item: an untyped XPath attribute ot element. + """ + if isinstance(self.xsd_type, (type(None), AbstractSchemaProxy)): + return item + + if isinstance(context, XPathSchemaContext): + primitive_type = self.parser.schema.get_primitive_type(self.xsd_type) + try: + value = XSD_BUILTIN_TYPES[primitive_type.local_name or 'anyType'].value + except KeyError: + value = XSD_BUILTIN_TYPES['anyType'].value + + if isinstance(item, AttributeNode): + return TypedAttribute(item, value) + else: + return TypedElement(item, value) + else: + try: + if isinstance(item, AttributeNode): + return TypedAttribute(item, self.xsd_type.decode(item[1])) + elif self.xsd_type.is_simple() or self.xsd_type.has_simple_content(): + return TypedElement(item, self.xsd_type.decode(item.text)) + else: + return item + except (TypeError, ValueError): + msg = "Type {!r} does not match sequence type of {!r}" + self.wrong_sequence_type(msg.format(self.xsd_type, item)) + @contextlib.contextmanager def use_locale(self, collation): """A context manager for setting a specific collation for a code block.""" diff --git a/tests/test_elementpath.py b/tests/test_elementpath.py index c101dc8..eb48490 100644 --- a/tests/test_elementpath.py +++ b/tests/test_elementpath.py @@ -24,8 +24,11 @@ import unittest if __name__ == '__main__': try: - from tests.test_helpers import ExceptionHelpersTest, NamespaceHelpersTest, NodeHelpersTest + from tests.test_exceptions import ExceptionsTest + from tests.test_namespaces import NamespacesTest from tests.test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest + from tests.test_xpath_nodes import XPathNodesTest + from tests.test_xpath_token import XPathTokenTest from tests.test_xpath_context import XPathContextTest from tests.test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest from tests.test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest @@ -34,8 +37,11 @@ if __name__ == '__main__': from tests.test_package import PackageTest except ImportError: # Python 2 fallback - from test_helpers import ExceptionHelpersTest, NamespaceHelpersTest, NodeHelpersTest + from test_exceptions import ExceptionsTest + from test_namespaces import NamespacessTest from test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest + from test_xpath_nodes import XPathNodesTest + from test_xpath_token import XPathTokenTest from test_xpath_context import XPathContextTest from test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest from test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..21c731d --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c), 2018-2019, SISSA (International School for Advanced Studies). +# All rights reserved. +# This file is distributed under the terms of the MIT License. +# See the file 'LICENSE' in the root directory of the present +# distribution, or http://opensource.org/licenses/MIT. +# +# @author Davide Brunato +# +from __future__ import unicode_literals +import unittest + +from elementpath.exceptions import ElementPathError, xpath_error +from elementpath.namespaces import XSD_NAMESPACE +from elementpath.xpath1_parser import XPath1Parser + + +class ExceptionsTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.parser = XPath1Parser(namespaces={'xs': XSD_NAMESPACE, 'tst': "http://xpath.test/ns"}) + + def test_exception_repr(self): + err = ElementPathError("unknown error") + self.assertEqual(str(err), 'unknown error') + err = ElementPathError("unknown error", code='XPST0001') + self.assertEqual(str(err), '[XPST0001] unknown error.') + token = self.parser.symbol_table['true'](self.parser) + err = ElementPathError("unknown error", code='XPST0001', token=token) + self.assertEqual(str(err), "'true' function: [XPST0001] unknown error.") + + def test_xpath_error(self): + self.assertEqual(str(xpath_error('XPST0001')), '[err:XPST0001] Parser not bound to a schema.') + self.assertEqual(str(xpath_error('err:XPDY0002', "test message")), '[err:XPDY0002] test message.') + self.assertRaises(ValueError, xpath_error, '') + self.assertRaises(ValueError, xpath_error, 'error:XPDY0002') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_namespaces.py b/tests/test_namespaces.py new file mode 100644 index 0000000..05799fb --- /dev/null +++ b/tests/test_namespaces.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c), 2018-2019, SISSA (International School for Advanced Studies). +# All rights reserved. +# This file is distributed under the terms of the MIT License. +# See the file 'LICENSE' in the root directory of the present +# distribution, or http://opensource.org/licenses/MIT. +# +# @author Davide Brunato +# +from __future__ import unicode_literals +import unittest + +from elementpath.namespaces import XSD_NAMESPACE, get_namespace, qname_to_prefixed, \ + prefixed_to_qname + + +class NamespacesTest(unittest.TestCase): + namespaces = { + 'xs': XSD_NAMESPACE, + 'tst': "http://xpath.test/ns" + } + + # namespaces.py module + def test_get_namespace_function(self): + self.assertEqual(get_namespace('A'), '') + self.assertEqual(get_namespace('{ns}foo'), 'ns') + self.assertEqual(get_namespace('{}foo'), '') + self.assertEqual(get_namespace('{A}B{C}'), 'A') + + def test_qname_to_prefixed_function(self): + self.assertEqual(qname_to_prefixed('{ns}foo', {'bar': 'ns'}), 'bar:foo') + self.assertEqual(qname_to_prefixed('{ns}foo', {'': 'ns'}), 'foo') + self.assertEqual(qname_to_prefixed('foo', {'': 'ns'}), 'foo') + + def test_prefixed_to_qname_function(self): + self.assertEqual(prefixed_to_qname('{ns}foo', {'bar': 'ns'}), '{ns}foo') + self.assertEqual(prefixed_to_qname('bar:foo', {'bar': 'ns'}), '{ns}foo') + self.assertEqual(prefixed_to_qname('foo', {'': 'ns'}), '{ns}foo') + + with self.assertRaises(ValueError): + prefixed_to_qname('bar:foo', self.namespaces) + with self.assertRaises(ValueError): + prefixed_to_qname('bar:foo:bar', {'bar': 'ns'}) + with self.assertRaises(ValueError): + prefixed_to_qname(':foo', {'': 'ns'}) + with self.assertRaises(ValueError): + prefixed_to_qname('foo:', {'': 'ns'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_xpath1_parser.py b/tests/test_xpath1_parser.py index ed444db..39e6a97 100644 --- a/tests/test_xpath1_parser.py +++ b/tests/test_xpath1_parser.py @@ -211,52 +211,6 @@ class XPath1ParserTest(unittest.TestCase): else: self.assertTrue(expected(results)) - def test_boolean_value_function(self): - token = self.parser.parse('true()') - elem = ElementTree.Element('A') - with self.assertRaises(TypeError): - token.boolean_value(elem) - - self.assertFalse(token.boolean_value([])) - self.assertTrue(token.boolean_value([elem])) - self.assertFalse(token.boolean_value([0])) - self.assertTrue(token.boolean_value([1])) - with self.assertRaises(TypeError): - token.boolean_value([1, 1]) - with self.assertRaises(TypeError): - token.boolean_value(elem) - self.assertFalse(token.boolean_value(0)) - self.assertTrue(token.boolean_value(1)) - - def test_data_value_function(self): - token = self.parser.parse('true()') - self.assertIsNone(token.data_value(None)) - - def test_string_value_function(self): - token = self.parser.parse('true()') - - document = ElementTree.parse(io.StringIO(u'123456789')) - element = ElementTree.Element('schema') - attribute = AttributeNode('id', '0212349350') - namespace = NamespaceNode('xs', 'http://www.w3.org/2001/XMLSchema') - comment = ElementTree.Comment('nothing important') - pi = ElementTree.ProcessingInstruction('action', 'nothing to do') - text = u'betelgeuse' - self.assertEqual(token.string_value(document), '123456789') - self.assertEqual(token.string_value(element), '') - self.assertEqual(token.string_value(attribute), '0212349350') - self.assertEqual(token.string_value(namespace), 'http://www.w3.org/2001/XMLSchema') - self.assertEqual(token.string_value(comment), 'nothing important') - self.assertEqual(token.string_value(pi), 'action nothing to do') - self.assertEqual(token.string_value(text), 'betelgeuse') - self.assertEqual(token.string_value(None), '') - self.assertEqual(token.string_value(10), '10') - - def test_number_value_function(self): - token = self.parser.parse('true()') - self.assertEqual(token.number_value("19"), 19) - self.assertTrue(math.isnan(token.number_value("not a number"))) - # Wrong XPath expression checker shortcuts def wrong_syntax(self, path): self.assertRaises(SyntaxError, self.parser.parse, path) diff --git a/tests/test_helpers.py b/tests/test_xpath_nodes.py similarity index 70% rename from tests/test_helpers.py rename to tests/test_xpath_nodes.py index 15f0376..5e57697 100644 --- a/tests/test_helpers.py +++ b/tests/test_xpath_nodes.py @@ -14,74 +14,14 @@ import unittest import io import xml.etree.ElementTree as ElementTree -from elementpath.exceptions import ElementPathError, xpath_error -from elementpath.namespaces import XSD_NAMESPACE, get_namespace, qname_to_prefixed, \ - prefixed_to_qname from elementpath.xpath_nodes import AttributeNode, NamespaceNode, is_etree_element, \ is_element_node, is_attribute_node, is_comment_node, is_document_node, \ is_namespace_node, is_processing_instruction_node, is_text_node, node_attributes, \ node_base_uri, node_document_uri, node_children, node_is_id, node_is_idrefs, \ node_nilled, node_kind, node_name -from elementpath.xpath_token import ordinal -from elementpath.xpath1_parser import XPath1Parser -class ExceptionHelpersTest(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.parser = XPath1Parser(namespaces={'xs': XSD_NAMESPACE, 'tst': "http://xpath.test/ns"}) - - def test_exception_repr(self): - err = ElementPathError("unknown error") - self.assertEqual(str(err), 'unknown error') - err = ElementPathError("unknown error", code='XPST0001') - self.assertEqual(str(err), '[XPST0001] unknown error.') - token = self.parser.symbol_table['true'](self.parser) - err = ElementPathError("unknown error", code='XPST0001', token=token) - self.assertEqual(str(err), "'true' function: [XPST0001] unknown error.") - - def test_xpath_error(self): - self.assertEqual(str(xpath_error('XPST0001')), '[err:XPST0001] Parser not bound to a schema.') - self.assertEqual(str(xpath_error('err:XPDY0002', "test message")), '[err:XPDY0002] test message.') - self.assertRaises(ValueError, xpath_error, '') - self.assertRaises(ValueError, xpath_error, 'error:XPDY0002') - - -class NamespaceHelpersTest(unittest.TestCase): - namespaces = { - 'xs': XSD_NAMESPACE, - 'tst': "http://xpath.test/ns" - } - - # namespaces.py module - def test_get_namespace_function(self): - self.assertEqual(get_namespace('A'), '') - self.assertEqual(get_namespace('{ns}foo'), 'ns') - self.assertEqual(get_namespace('{}foo'), '') - self.assertEqual(get_namespace('{A}B{C}'), 'A') - - def test_qname_to_prefixed_function(self): - self.assertEqual(qname_to_prefixed('{ns}foo', {'bar': 'ns'}), 'bar:foo') - self.assertEqual(qname_to_prefixed('{ns}foo', {'': 'ns'}), 'foo') - self.assertEqual(qname_to_prefixed('foo', {'': 'ns'}), 'foo') - - def test_prefixed_to_qname_function(self): - self.assertEqual(prefixed_to_qname('{ns}foo', {'bar': 'ns'}), '{ns}foo') - self.assertEqual(prefixed_to_qname('bar:foo', {'bar': 'ns'}), '{ns}foo') - self.assertEqual(prefixed_to_qname('foo', {'': 'ns'}), '{ns}foo') - - with self.assertRaises(ValueError): - prefixed_to_qname('bar:foo', self.namespaces) - with self.assertRaises(ValueError): - prefixed_to_qname('bar:foo:bar', {'bar': 'ns'}) - with self.assertRaises(ValueError): - prefixed_to_qname(':foo', {'': 'ns'}) - with self.assertRaises(ValueError): - prefixed_to_qname('foo:', {'': 'ns'}) - - -class NodeHelpersTest(unittest.TestCase): +class XPathNodesTest(unittest.TestCase): elem = ElementTree.XML('') def test_is_etree_element_function(self): @@ -231,28 +171,5 @@ class NodeHelpersTest(unittest.TestCase): self.assertEqual(node_name(namespace), 'xs') -class XPathTokenHelpersTest(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.parser = XPath1Parser(namespaces={'xs': XSD_NAMESPACE, 'tst': "http://xpath.test/ns"}) - - def test_ordinal_function(self): - self.assertEqual(ordinal(1), '1st') - self.assertEqual(ordinal(2), '2nd') - self.assertEqual(ordinal(3), '3rd') - self.assertEqual(ordinal(4), '4th') - self.assertEqual(ordinal(11), '11th') - self.assertEqual(ordinal(23), '23rd') - self.assertEqual(ordinal(34), '34th') - - def test_get_argument_method(self): - token = self.parser.symbol_table['true'](self.parser) - - self.assertIsNone(token.get_argument(2)) - with self.assertRaises(TypeError): - token.get_argument(1, required=True) - - if __name__ == '__main__': unittest.main() diff --git a/tests/test_xpath_token.py b/tests/test_xpath_token.py new file mode 100644 index 0000000..99e414e --- /dev/null +++ b/tests/test_xpath_token.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c), 2018-2019, SISSA (International School for Advanced Studies). +# All rights reserved. +# This file is distributed under the terms of the MIT License. +# See the file 'LICENSE' in the root directory of the present +# distribution, or http://opensource.org/licenses/MIT. +# +# @author Davide Brunato +# +from __future__ import unicode_literals +import unittest +import io +import math +import xml.etree.ElementTree as ElementTree + +from elementpath.namespaces import XSD_NAMESPACE +from elementpath.xpath_nodes import AttributeNode, TypedAttribute, TypedElement, NamespaceNode +from elementpath.xpath_token import ordinal +from elementpath.xpath_context import XPathContext +from elementpath.xpath1_parser import XPath1Parser + + +class XPathTokenTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.parser = XPath1Parser(namespaces={'xs': XSD_NAMESPACE, 'tst': "http://xpath.test/ns"}) + + def test_ordinal_function(self): + self.assertEqual(ordinal(1), '1st') + self.assertEqual(ordinal(2), '2nd') + self.assertEqual(ordinal(3), '3rd') + self.assertEqual(ordinal(4), '4th') + self.assertEqual(ordinal(11), '11th') + self.assertEqual(ordinal(23), '23rd') + self.assertEqual(ordinal(34), '34th') + + def test_get_argument_method(self): + token = self.parser.symbol_table['true'](self.parser) + + self.assertIsNone(token.get_argument(2)) + with self.assertRaises(TypeError): + token.get_argument(1, required=True) + + def test_select_results(self): + token = self.parser.parse('.') + elem = ElementTree.Element('A', attrib={'max': '30'}) + elem.text = '10' + + context = XPathContext(elem) + self.assertListEqual(list(token.select_results(context)), [elem]) + + context = XPathContext(elem, item=TypedElement(elem, 10)) + self.assertListEqual(list(token.select_results(context)), [elem]) + + context = XPathContext(elem, item=AttributeNode('max', '30')) + self.assertListEqual(list(token.select_results(context)), ['30']) + + context = XPathContext(elem, item=TypedAttribute(AttributeNode('max', '30'), 30)) + self.assertListEqual(list(token.select_results(context)), [30]) + + context = XPathContext(elem, item=10) + self.assertListEqual(list(token.select_results(context)), [10]) + + context = XPathContext(elem, item='10') + self.assertListEqual(list(token.select_results(context)), ['10']) + + def test_boolean_value_function(self): + token = self.parser.parse('true()') + elem = ElementTree.Element('A') + with self.assertRaises(TypeError): + token.boolean_value(elem) + + self.assertFalse(token.boolean_value([])) + self.assertTrue(token.boolean_value([elem])) + self.assertFalse(token.boolean_value([0])) + self.assertTrue(token.boolean_value([1])) + with self.assertRaises(TypeError): + token.boolean_value([1, 1]) + with self.assertRaises(TypeError): + token.boolean_value(elem) + self.assertFalse(token.boolean_value(0)) + self.assertTrue(token.boolean_value(1)) + + def test_data_value_function(self): + token = self.parser.parse('true()') + self.assertIsNone(token.data_value(None)) + + def test_string_value_function(self): + token = self.parser.parse('true()') + + document = ElementTree.parse(io.StringIO(u'123456789')) + element = ElementTree.Element('schema') + attribute = AttributeNode('id', '0212349350') + namespace = NamespaceNode('xs', 'http://www.w3.org/2001/XMLSchema') + comment = ElementTree.Comment('nothing important') + pi = ElementTree.ProcessingInstruction('action', 'nothing to do') + text = u'betelgeuse' + self.assertEqual(token.string_value(document), '123456789') + self.assertEqual(token.string_value(element), '') + self.assertEqual(token.string_value(attribute), '0212349350') + self.assertEqual(token.string_value(namespace), 'http://www.w3.org/2001/XMLSchema') + self.assertEqual(token.string_value(comment), 'nothing important') + self.assertEqual(token.string_value(pi), 'action nothing to do') + self.assertEqual(token.string_value(text), 'betelgeuse') + self.assertEqual(token.string_value(None), '') + self.assertEqual(token.string_value(10), '10') + + def test_number_value_function(self): + token = self.parser.parse('true()') + self.assertEqual(token.number_value("19"), 19) + self.assertTrue(math.isnan(token.number_value("not a number"))) + + +if __name__ == '__main__': + unittest.main()