From 5d715f98fc10570bc4e1452477265e780cb8496c Mon Sep 17 00:00:00 2001 From: Davide Brunato Date: Thu, 16 May 2019 12:28:15 +0200 Subject: [PATCH] Add coverage to testing - Added .coveragerc - Added tests/test_helpers.py - Added coverage to develop requirements - Code cleaning for xpath_helpers.py --- .coveragerc | 3 + .gitignore | 1 + elementpath/namespaces.py | 21 +-- elementpath/xpath1_parser.py | 10 +- elementpath/xpath_helpers.py | 138 +++++++++-------- elementpath/xpath_selectors.py | 6 +- requirements-dev.txt | 1 + tests/test_datatypes.py | 9 ++ tests/test_elementpath.py | 2 + tests/test_helpers.py | 260 +++++++++++++++++++++++++++++++++ tests/test_schema_proxy.py | 4 +- tests/test_selectors.py | 16 ++ tests/test_xpath1_parser.py | 11 +- tox.ini | 15 +- 14 files changed, 417 insertions(+), 80 deletions(-) create mode 100644 .coveragerc create mode 100644 tests/test_helpers.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..d2184d1 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +branch = True +source = elementpath/ diff --git a/.gitignore b/.gitignore index 12ef57f..6d9458f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .project .ipynb_checkpoints/ .tox/ +.coverage doc/_* __pycache__/ dist/ diff --git a/elementpath/namespaces.py b/elementpath/namespaces.py index 834d35f..0233636 100644 --- a/elementpath/namespaces.py +++ b/elementpath/namespaces.py @@ -33,22 +33,25 @@ XPATH_2_DEFAULT_NAMESPACES = { } # XML namespace attributes -XML_BASE_QNAME = '{%s}base' % XML_NAMESPACE -XML_LANG_QNAME = '{%s}lang' % XML_NAMESPACE -XML_SPACE_QNAME = '{%s}space' % XML_NAMESPACE -XML_ID_QNAME = '{%s}id' % XML_NAMESPACE +XML_BASE = '{%s}base' % XML_NAMESPACE +XML_LANG = '{%s}lang' % XML_NAMESPACE +XML_SPACE = '{%s}space' % XML_NAMESPACE +XML_ID = '{%s}id' % XML_NAMESPACE # XML Schema Instance namespace attributes -XSI_TYPE_QNAME = '{%s}type' % XSI_NAMESPACE -XSI_NIL_QNAME = '{%s}nil' % XSI_NAMESPACE -XSI_SCHEMA_LOCATION_QNAME = '{%s}schemaLocation' % XSI_NAMESPACE -XSI_NONS_SCHEMA_LOCATION_QNAME = '{%s}schemaLocation' % XSI_NAMESPACE +XSI_TYPE = '{%s}type' % XSI_NAMESPACE +XSI_NIL = '{%s}nil' % XSI_NAMESPACE +XSI_SCHEMA_LOCATION = '{%s}schemaLocation' % XSI_NAMESPACE +XSI_NONS_SCHEMA_LOCATION = '{%s}schemaLocation' % XSI_NAMESPACE -# XSD tags and attributes +# XML Schema types XSD_NOTATION = '{%s}NOTATION' % XSD_NAMESPACE XSD_ANY_ATOMIC_TYPE = '{%s}anyAtomicType' % XSD_NAMESPACE XSD_UNTYPED = '{%s}untyped' % XSD_NAMESPACE XSD_UNTYPED_ATOMIC = '{%s}untypedAtomic' % XSD_NAMESPACE +XSD_ID = '{%s}ID' % XSD_NAMESPACE +XSD_IDREF = '{%s}IDREF' % XSD_NAMESPACE +XSD_IDREFS = '{%s}IDREFS' % XSD_NAMESPACE def get_namespace(name): diff --git a/elementpath/xpath1_parser.py b/elementpath/xpath1_parser.py index ee18cf7..dfecfdd 100644 --- a/elementpath/xpath1_parser.py +++ b/elementpath/xpath1_parser.py @@ -18,7 +18,7 @@ from .exceptions import ElementPathSyntaxError, ElementPathTypeError, ElementPat from .datatypes import UntypedAtomic, DayTimeDuration, YearMonthDuration, XSD_BUILTIN_TYPES from .xpath_context import XPathSchemaContext from .tdop_parser import Parser, MultiLabel -from .namespaces import XML_ID_QNAME, XML_LANG_QNAME, XPATH_1_DEFAULT_NAMESPACES, \ +from .namespaces import XML_ID, XML_LANG, XPATH_1_DEFAULT_NAMESPACES, \ XPATH_FUNCTIONS_NAMESPACE, XSD_NAMESPACE, qname_to_prefixed from .xpath_token import XPathToken from .xpath_helpers import AttributeNode, NamespaceNode, is_etree_element, is_xpath_node, is_element_node, \ @@ -972,7 +972,7 @@ def select(self, context=None): item = context.item if is_element_node(item): for elem in item.iter(): - if elem.get(XML_ID_QNAME) == value: + if elem.get(XML_ID) == value: yield elem @@ -1143,11 +1143,11 @@ def evaluate(self, context=None): return False else: try: - lang = context.item.attrib[XML_LANG_QNAME].strip() + lang = context.item.attrib[XML_LANG].strip() except KeyError: for elem in context.iter_ancestor(): - if XML_LANG_QNAME in elem.attrib: - lang = elem.attrib[XML_LANG_QNAME] + if XML_LANG in elem.attrib: + lang = elem.attrib[XML_LANG] break else: return False diff --git a/elementpath/xpath_helpers.py b/elementpath/xpath_helpers.py index d384f4f..97735d2 100644 --- a/elementpath/xpath_helpers.py +++ b/elementpath/xpath_helpers.py @@ -13,11 +13,10 @@ Helper functions for XPath nodes and functions. """ from collections import namedtuple -from .compat import PY3 -from .namespaces import XML_BASE_QNAME, XML_ID_QNAME, XSI_TYPE_QNAME, XSI_NIL_QNAME, \ - XSD_UNTYPED, XSD_UNTYPED_ATOMIC, prefixed_to_qname -from .exceptions import xpath_error -from .datatypes import UntypedAtomic +from .compat import PY3, urlparse +from .namespaces import XML_BASE, XSI_NIL, XSD_UNTYPED, XSD_UNTYPED_ATOMIC +from .exceptions import ElementPathValueError, xpath_error +from .datatypes import UntypedAtomic, ncname_validator ### # Node types @@ -53,43 +52,61 @@ def elem_iter_strings(elem): # for documents. Generic tuples are used for representing attributes and named-tuples for namespaces. ### def is_element_node(obj, tag=None): - if tag is None: - return is_etree_element(obj) and not callable(obj.tag) - elif not is_etree_element(obj): + """ + Returns `True` if the first argument is an element node matching the tag, `False` otherwise. + Raises a ValueError if the argument tag has to be used but it's in a wrong format. + + :param obj: the node to be tested. + :param tag: a fully qualified name, a local name or a wildcard. The accepted wildcard formats \ + are '*', '*:*', '*:local-name' and '{namespace}*'. + """ + if not is_etree_element(obj) or callable(obj.tag): return False + elif tag is None: + return True + elif not obj.tag: + return obj.tag == tag + elif tag == '*' or tag == '*:*': + return obj.tag != '' elif tag[0] == '*': - if not obj.tag: - return False - elif obj.tag[0] == '{': - return obj.tag.split('}')[1] == tag.split(':')[1] + try: + _, name = tag.split(':') + except (ValueError, IndexError): + raise ElementPathValueError("unexpected format %r for argument 'tag'" % tag) else: - return obj.tag == tag.split(':')[1] + return obj.tag.split('}')[1] == name if obj.tag[0] == '{' else obj.tag == name elif tag[-1] == '*': - if not obj.tag: - return False - elif obj.tag[0] == '{': - return obj.tag.split('}')[0][1:] == tag.split('}')[0][1:] - else: - return True + if tag[0] != '{' or '}' not in tag: + raise ElementPathValueError("unexpected format %r for argument 'tag'" % tag) + return obj.tag.split('}')[0][1:] == tag.split('}')[0][1:] if obj.tag[0] == '{' else False else: return obj.tag == tag def is_attribute_node(obj, name=None): - if name is None: + """ + Returns `True` if the first argument is an attribute node matching the name, `False` otherwise. + Raises a ValueError if the argument name has to be used but it's in a wrong format. + + :param obj: the node to be tested. + :param name: a fully qualified name, a local name or a wildcard. The accepted wildcard formats \ + are '*', '*:*', '*:local-name' and '{namespace}*'. + """ + if name is None or name == '*' or name == '*:*': return isinstance(obj, AttributeNode) elif not isinstance(obj, AttributeNode): return False elif name[0] == '*': - if obj[0][0] == '{': - return obj[0].split('}')[1] == name.split(':')[1] + try: + _, _name = name.split(':') + except (ValueError, IndexError): + raise ElementPathValueError("unexpected format %r for argument 'name'" % name) else: - return obj[0] == name.split(':')[1] + return obj[0].split('}')[1] == _name if obj[0][0] == '{' else obj[0] == _name elif name[-1] == '*': - if obj[0][0] == '{': - return obj[0].split('}')[0][1:] == name.split('}')[0][1:] - else: - return True + if name[0] != '{' or '}' not in name: + raise ElementPathValueError("unexpected format %r for argument 'name'" % name) + return obj[0].split('}')[0][1:] == name.split('}')[0][1:] if obj[0][0] == '{' else False else: return obj[0] == name @@ -123,7 +140,8 @@ def is_xpath_node(obj): ### -# Node accessors +# Node accessors: in this implementation node accessors return None instead of empty sequence. +# Ref: https://www.w3.org/TR/xpath-datamodel-31/#dm-document-uri def node_attributes(obj): if is_element_node(obj): return obj.attrib @@ -132,49 +150,49 @@ def node_attributes(obj): def node_base_uri(obj): try: if is_element_node(obj): - return obj.attrib[XML_BASE_QNAME] + return obj.attrib[XML_BASE] elif is_document_node(obj): - return obj.getroot().attrib[XML_BASE_QNAME] + return obj.getroot().attrib[XML_BASE] except KeyError: pass def node_document_uri(obj): - # Try the xml:base of root node because an ElementTree doesn't save reference to source. - for uri in node_base_uri(obj): - return uri + if is_document_node(obj): + try: + uri = obj.getroot().attrib[XML_BASE] + parts = urlparse(uri) + except (KeyError, ValueError): + pass + else: + if parts.scheme and parts.netloc or parts.path.startswith('/'): + return uri def node_children(obj): if is_element_node(obj): - for child in obj: - return child + return (child for child in obj) elif is_document_node(obj): - return obj.getroot() + return (child for child in [obj.getroot()]) def node_is_id(obj): if is_element_node(obj): - return XML_ID_QNAME in obj.attrib + return ncname_validator(obj.text) elif is_attribute_node(obj): - return obj[0] == XML_ID_QNAME + return ncname_validator(obj[1]) -def node_is_idrefs(obj, namespaces): +def node_is_idrefs(obj): if is_element_node(obj): - try: - node_type = obj.attrib[XSI_TYPE_QNAME] - except KeyError: - pass - else: - return prefixed_to_qname(node_type, namespaces) in ("IDREF", "IDREFS") - elif is_attribute_node(obj) and obj[0] == XSI_TYPE_QNAME: - return prefixed_to_qname(obj[1], namespaces) in ("IDREF", "IDREFS") + return obj.text is not None and all(ncname_validator(x) for x in obj.text.split()) + elif is_attribute_node(obj): + return all(ncname_validator(x) for x in obj[1].split()) def node_nilled(obj): if is_element_node(obj): - return obj.get(XSI_NIL_QNAME) == 'true' + return obj.get(XSI_NIL) in ('true', '1') def node_kind(obj): @@ -197,9 +215,7 @@ def node_kind(obj): def node_name(obj): if is_element_node(obj): return obj.tag - elif is_attribute_node(obj): - return obj[0] - elif is_namespace_node(obj): + elif is_attribute_node(obj) or is_namespace_node(obj): return obj[0] @@ -222,11 +238,19 @@ def node_string_value(obj): def node_type_name(obj, schema=None): if is_element_node(obj): - if schema is None: - return XSD_UNTYPED + if schema is not None: + xsd_element = schema.get_element(obj.tag) + if xsd_element is not None: + return xsd_element.type.name + return XSD_UNTYPED + elif is_attribute_node(obj): - if schema is None: - return XSD_UNTYPED_ATOMIC # TODO: from a PSVI ... + if schema is not None: + xsd_attribute = schema.get_attribute(obj[0]) + if xsd_attribute is not None: + return xsd_attribute.type.name + return XSD_UNTYPED_ATOMIC + elif is_text_node(obj): return XSD_UNTYPED_ATOMIC @@ -280,9 +304,7 @@ def data_value(obj): elif not is_xpath_node(obj): return obj else: - value = node_string_value(obj) - if value is not None: - return UntypedAtomic(value) + return UntypedAtomic(node_string_value(obj)) def number_value(obj): diff --git a/elementpath/xpath_selectors.py b/elementpath/xpath_selectors.py index 2b09a51..22d4360 100644 --- a/elementpath/xpath_selectors.py +++ b/elementpath/xpath_selectors.py @@ -71,9 +71,7 @@ class Selector(object): self.root_token = self.parser.parse(path) def __repr__(self): - return u'%s(path=%r, namespaces=%r, parser=%s)' % ( - self.__class__.__name__, self.path, self.namespaces, self.parser.__class__.__name__ - ) + return u'%s(path=%r, parser=%s)' % (self.__class__.__name__, self.path, self.parser.__class__.__name__) @property def namespaces(self): @@ -101,3 +99,5 @@ class Selector(object): """ context = XPathContext(root) return self.root_token.select(context) + +# 45-48, 74, 81 \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 3568818..80abb07 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,7 @@ # Requirements for setup a development environment setuptools tox +coverage lxml xmlschema>=1.0.9 Sphinx diff --git a/tests/test_datatypes.py b/tests/test_datatypes.py index 77adc94..c5ff57a 100644 --- a/tests/test_datatypes.py +++ b/tests/test_datatypes.py @@ -612,6 +612,15 @@ class TimezoneTypeTest(unittest.TestCase): self.assertEqual(str(Timezone.fromstring('+05:00')), '+05:00') self.assertEqual(str(Timezone.fromstring('-13:15')), '-13:15') + def test_eq_operator(self): + self.assertEqual(Timezone.fromstring('+05:00'), Timezone.fromstring('+05:00')) + + def test_ne_operator(self): + self.assertNotEqual(Timezone.fromstring('+05:00'), Timezone.fromstring('+06:00')) + + def test_hashing(self): + self.assertEqual(hash(Timezone.fromstring('+05:00')), 1289844826723787395) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_elementpath.py b/tests/test_elementpath.py index 93ef0ec..65092f9 100644 --- a/tests/test_elementpath.py +++ b/tests/test_elementpath.py @@ -24,6 +24,7 @@ import unittest if __name__ == '__main__': try: + from tests.test_helpers import HelpersTest from tests.test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest from tests.test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest from tests.test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest @@ -32,6 +33,7 @@ if __name__ == '__main__': from tests.test_package import PackageTest except ImportError: # Python 2 fallback + from test_helpers import HelpersTest from test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest from test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest from test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 0000000..457c78c --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,260 @@ +#!/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 xmlschema import XMLSchema +from elementpath.schema_proxy import XMLSchemaProxy +from elementpath.namespaces import XSD_NAMESPACE, get_namespace, qname_to_prefixed, prefixed_to_qname +from elementpath.xpath_helpers 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, node_string_value, node_type_name, boolean_value, data_value, number_value + + +class HelpersTest(unittest.TestCase): + elem = ElementTree.XML('') + 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'}) + + def test_is_etree_element_function(self): + self.assertTrue(is_etree_element(self.elem)) + self.assertFalse(is_etree_element('text')) + self.assertFalse(is_etree_element(None)) + + def test_is_element_node_function(self): + elem = ElementTree.Element('alpha') + empty_tag_elem = ElementTree.Element('') + self.assertTrue(is_element_node(elem, '*')) + self.assertFalse(is_element_node(empty_tag_elem, '*')) + with self.assertRaises(ValueError): + is_element_node(elem, '**') + with self.assertRaises(ValueError): + is_element_node(elem, '*:*:*') + with self.assertRaises(ValueError): + is_element_node(elem, 'foo:*') + self.assertFalse(is_element_node(empty_tag_elem, 'foo:*')) + self.assertFalse(is_element_node(elem, '{foo}*')) + + def test_is_attribute_node_function(self): + attr = AttributeNode('a1', '10') + self.assertTrue(is_attribute_node(attr, '*')) + with self.assertRaises(ValueError): + is_attribute_node(attr, '**') + with self.assertRaises(ValueError): + is_attribute_node(attr, '*:*:*') + with self.assertRaises(ValueError): + is_attribute_node(attr, 'foo:*') + self.assertTrue(is_attribute_node(attr, '*:a1')) + self.assertFalse(is_attribute_node(attr, '{foo}*')) + self.assertTrue(is_attribute_node(AttributeNode('{foo}a1', '10'), '{foo}*')) + + def test_is_comment_node_function(self): + comment = ElementTree.Comment('nothing important') + self.assertTrue(is_comment_node(comment)) + self.assertFalse(is_comment_node(self.elem)) + + def test_is_document_node_function(self): + document = ElementTree.parse(io.StringIO('')) + self.assertTrue(is_document_node(document)) + self.assertFalse(is_document_node(self.elem)) + + def test_is_namespace_node_function(self): + namespace = NamespaceNode('xs', 'http://www.w3.org/2001/XMLSchema') + self.assertTrue(is_namespace_node(namespace)) + self.assertFalse(is_namespace_node(self.elem)) + + def test_is_processing_instruction_node_function(self): + pi = ElementTree.ProcessingInstruction('action', 'nothing to do') + self.assertTrue(is_processing_instruction_node(pi)) + self.assertFalse(is_processing_instruction_node(self.elem)) + + def test_is_text_node_function(self): + self.assertTrue(is_text_node('alpha')) + self.assertFalse(is_text_node(self.elem)) + + def test_node_attributes_function(self): + self.assertEqual(node_attributes(self.elem), self.elem.attrib) + self.assertIsNone(node_attributes('a text node')) + + def test_node_base_uri_function(self): + xml_test = '' + self.assertEqual(node_base_uri(ElementTree.XML(xml_test)), '/') + document = ElementTree.parse(io.StringIO(xml_test)) + self.assertEqual(node_base_uri(document), '/') + self.assertIsNone(node_base_uri(self.elem)) + self.assertIsNone(node_base_uri('a text node')) + + def test_node_document_uri_function(self): + self.assertIsNone(node_document_uri(self.elem)) + + xml_test = '' + document = ElementTree.parse(io.StringIO(xml_test)) + self.assertEqual(node_document_uri(document), '/root') + + xml_test = '' + document = ElementTree.parse(io.StringIO(xml_test)) + self.assertEqual(node_document_uri(document), 'http://xpath.test') + + xml_test = '' + document = ElementTree.parse(io.StringIO(xml_test)) + self.assertIsNone(node_document_uri(document)) + + xml_test = '' + document = ElementTree.parse(io.StringIO(xml_test)) + self.assertIsNone(node_document_uri(document)) + + def test_node_children_function(self): + self.assertListEqual(list(node_children(self.elem)), []) + elem = ElementTree.XML("") + self.assertListEqual(list(node_children(elem)), elem[:]) + document = ElementTree.parse(io.StringIO("")) + self.assertListEqual(list(node_children(document)), [document.getroot()]) + self.assertIsNone(node_children('a text node')) + + def test_node_is_id_function(self): + self.assertTrue(node_is_id(ElementTree.XML('xyz'))) + self.assertFalse(node_is_id(ElementTree.XML('xyz abc'))) + self.assertFalse(node_is_id(ElementTree.XML('12345'))) + self.assertTrue(node_is_id(AttributeNode('id', 'alpha'))) + self.assertFalse(node_is_id(AttributeNode('id', 'alpha beta'))) + self.assertFalse(node_is_id(AttributeNode('id', '12345'))) + self.assertIsNone(node_is_id('a text node')) + + def test_node_is_idref_function(self): + self.assertTrue(node_is_idrefs(ElementTree.XML('xyz'))) + self.assertTrue(node_is_idrefs(ElementTree.XML('xyz abc'))) + self.assertFalse(node_is_idrefs(ElementTree.XML('12345'))) + self.assertTrue(node_is_idrefs(AttributeNode('id', 'alpha'))) + self.assertTrue(node_is_idrefs(AttributeNode('id', 'alpha beta'))) + self.assertFalse(node_is_idrefs(AttributeNode('id', '12345'))) + self.assertIsNone(node_is_idrefs('a text node')) + + def test_node_nilled_function(self): + xml_test = '' + self.assertTrue(node_nilled(ElementTree.XML(xml_test))) + xml_test = '' + self.assertFalse(node_nilled(ElementTree.XML(xml_test))) + self.assertFalse(node_nilled(ElementTree.XML(''))) + + def test_node_kind_function(self): + document = ElementTree.parse(io.StringIO(u'')) + 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(node_kind(document), 'document') + self.assertEqual(node_kind(element), 'element') + self.assertEqual(node_kind(attribute), 'attribute') + self.assertEqual(node_kind(namespace), 'namespace') + self.assertEqual(node_kind(comment), 'comment') + self.assertEqual(node_kind(pi), 'processing-instruction') + self.assertEqual(node_kind(text), 'text') + self.assertIsNone(node_kind(None)) + self.assertIsNone(node_kind(10)) + + def test_node_name_function(self): + elem = ElementTree.Element('root') + attr = AttributeNode('a1', '20') + namespace = NamespaceNode('xs', 'http://www.w3.org/2001/XMLSchema') + self.assertEqual(node_name(elem), 'root') + self.assertEqual(node_name(attr), 'a1') + self.assertEqual(node_name(namespace), 'xs') + + def test_node_string_value_function(self): + 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(node_string_value(document), '123456789') + self.assertEqual(node_string_value(element), '') + self.assertEqual(node_string_value(attribute), '0212349350') + self.assertEqual(node_string_value(namespace), 'http://www.w3.org/2001/XMLSchema') + self.assertEqual(node_string_value(comment), 'nothing important') + self.assertEqual(node_string_value(pi), 'action nothing to do') + self.assertEqual(node_string_value(text), 'betelgeuse') + self.assertIsNone(node_string_value(None)) + self.assertIsNone(node_string_value(10)) + + def test_node_type_name_function(self): + schema = XMLSchemaProxy( + XMLSchema(""" + + + """) + ) + elem = ElementTree.Element('frame') + self.assertEqual(node_type_name(elem, schema), '{http://www.w3.org/2001/XMLSchema}decimal') + self.assertEqual(node_type_name(elem), '{http://www.w3.org/2001/XMLSchema}untyped') + elem = ElementTree.Element('alpha') + self.assertEqual(node_type_name(elem, schema), '{http://www.w3.org/2001/XMLSchema}untyped') + + attr = AttributeNode('slot', 'x1') + self.assertEqual(node_type_name(attr, schema), '{http://www.w3.org/2001/XMLSchema}token') + self.assertEqual(node_type_name(attr), '{http://www.w3.org/2001/XMLSchema}untypedAtomic') + attr = AttributeNode('alpha', 'x1') + self.assertEqual(node_type_name(attr, schema), '{http://www.w3.org/2001/XMLSchema}untypedAtomic') + + self.assertEqual(node_type_name('slot'), '{http://www.w3.org/2001/XMLSchema}untypedAtomic') + self.assertIsNone(node_type_name(10)) + + def test_boolean_value_function(self): + elem = ElementTree.Element('A') + with self.assertRaises(TypeError): + boolean_value(elem) + + def test_data_value_function(self): + self.assertIsNone(data_value(None)) + + def test_number_value_function(self): + self.assertEqual(number_value("19"), 19) + self.assertTrue(math.isnan(number_value("not a number"))) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_schema_proxy.py b/tests/test_schema_proxy.py index 76866ba..3509a8a 100644 --- a/tests/test_schema_proxy.py +++ b/tests/test_schema_proxy.py @@ -13,7 +13,7 @@ import unittest import lxml.etree from elementpath import * -from elementpath.namespaces import XML_LANG_QNAME, XSD_NAMESPACE +from elementpath.namespaces import XML_LANG, XSD_NAMESPACE try: # noinspection PyPackageRequirements @@ -52,7 +52,7 @@ class XPath2ParserXMLSchemaTest(test_xpath2_parser.XPath2ParserTest): self.check_value("schema-element(xs:schema)", context.item, context) self.check_tree("schema-element(xs:group)", '(schema-element (: (xs) (group)))') - context.item = AttributeNode(XML_LANG_QNAME, 'en') + context.item = AttributeNode(XML_LANG, 'en') self.wrong_name("schema-attribute(nil)") self.wrong_name("schema-attribute(xs:string)") self.check_value("schema-attribute(xml:lang)", None) diff --git a/tests/test_selectors.py b/tests/test_selectors.py index d57bd03..f1df915 100644 --- a/tests/test_selectors.py +++ b/tests/test_selectors.py @@ -16,6 +16,22 @@ from elementpath import * class SelectorTest(unittest.TestCase): + root = ElementTree.XML('Dickens') + + def test_select_function(self): + self.assertListEqual(select(self.root, 'text()'), ['Dickens']) + + def test_iter_select_function(self): + self.assertListEqual(list(iter_select(self.root, 'text()')), ['Dickens']) + + def test_selector_class(self): + selector = Selector('/A') + self.assertEqual(repr(selector), "Selector(path='/A', parser=XPath2Parser)") + self.assertEqual(selector.namespaces, XPath2Parser.DEFAULT_NAMESPACES) + + selector = Selector('text()') + self.assertListEqual(selector.select(self.root), ['Dickens']) + self.assertListEqual(list(selector.iter_select(self.root)), ['Dickens']) def test_issue_001(self): selector = Selector("//FullPath[ends-with(., 'Temp')]") diff --git a/tests/test_xpath1_parser.py b/tests/test_xpath1_parser.py index 61360cd..46e9fd3 100644 --- a/tests/test_xpath1_parser.py +++ b/tests/test_xpath1_parser.py @@ -1032,7 +1032,7 @@ class XPath1ParserTest(unittest.TestCase): def test_default_namespace(self): root = self.etree.XML('bar') self.check_selector('/foo', root, [root]) - if type(self.parser) is XPath1Parser: + if self.parser.version == '1.0': # XPath 1.0 ignores the default namespace self.check_selector('/foo', root, [root], namespaces={'': 'ns'}) # foo --> foo else: @@ -1046,6 +1046,15 @@ class XPath1ParserTest(unittest.TestCase): else: self.check_selector('/foo', root, [root], namespaces={'': 'ns'}) + root = self.etree.XML('') + if self.parser.version > '1.0' or not hasattr(root, 'nsmap'): + self.check_selector("name(tst:B1)", root, 'tst:B1', namespaces={'tst': "http://xpath.test/ns"}) + if self.parser.version > '1.0': + self.check_selector("name(B1)", root, 'B1', namespaces={'': "http://xpath.test/ns"}) + else: + # XPath 1.0 ignores the default namespace declarations + self.check_selector("name(B1)", root, '', namespaces={'': "http://xpath.test/ns"}) + class LxmlXPath1ParserTest(XPath1ParserTest): etree = lxml.etree diff --git a/tox.ini b/tox.ini index 24f03d9..d6df838 100644 --- a/tox.ini +++ b/tox.ini @@ -5,16 +5,27 @@ [tox] envlist = py27, py34, py35, py36, py37 +skip_missing_interpreters = true toxworkdir = {homedir}/.tox/elementpath [testenv] deps = lxml - xmlschema>=1.0.9 + xmlschema~=1.0.9 commands = python -m unittest [testenv:py27] deps = lxml - xmlschema>=1.0.9 + xmlschema~=1.0.9 commands = python tests/test_elementpath.py + +[testenv:py37] +commands = + coverage run -p setup.py test -q + coverage combine + coverage report -m +deps = + lxml + xmlschema~=1.0.9 + coverage