Add coverage to testing
- Added .coveragerc - Added tests/test_helpers.py - Added coverage to develop requirements - Code cleaning for xpath_helpers.py
This commit is contained in:
parent
12f94db836
commit
5d715f98fc
|
@ -0,0 +1,3 @@
|
||||||
|
[run]
|
||||||
|
branch = True
|
||||||
|
source = elementpath/
|
|
@ -7,6 +7,7 @@
|
||||||
.project
|
.project
|
||||||
.ipynb_checkpoints/
|
.ipynb_checkpoints/
|
||||||
.tox/
|
.tox/
|
||||||
|
.coverage
|
||||||
doc/_*
|
doc/_*
|
||||||
__pycache__/
|
__pycache__/
|
||||||
dist/
|
dist/
|
||||||
|
|
|
@ -33,22 +33,25 @@ XPATH_2_DEFAULT_NAMESPACES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# XML namespace attributes
|
# XML namespace attributes
|
||||||
XML_BASE_QNAME = '{%s}base' % XML_NAMESPACE
|
XML_BASE = '{%s}base' % XML_NAMESPACE
|
||||||
XML_LANG_QNAME = '{%s}lang' % XML_NAMESPACE
|
XML_LANG = '{%s}lang' % XML_NAMESPACE
|
||||||
XML_SPACE_QNAME = '{%s}space' % XML_NAMESPACE
|
XML_SPACE = '{%s}space' % XML_NAMESPACE
|
||||||
XML_ID_QNAME = '{%s}id' % XML_NAMESPACE
|
XML_ID = '{%s}id' % XML_NAMESPACE
|
||||||
|
|
||||||
# XML Schema Instance namespace attributes
|
# XML Schema Instance namespace attributes
|
||||||
XSI_TYPE_QNAME = '{%s}type' % XSI_NAMESPACE
|
XSI_TYPE = '{%s}type' % XSI_NAMESPACE
|
||||||
XSI_NIL_QNAME = '{%s}nil' % XSI_NAMESPACE
|
XSI_NIL = '{%s}nil' % XSI_NAMESPACE
|
||||||
XSI_SCHEMA_LOCATION_QNAME = '{%s}schemaLocation' % XSI_NAMESPACE
|
XSI_SCHEMA_LOCATION = '{%s}schemaLocation' % XSI_NAMESPACE
|
||||||
XSI_NONS_SCHEMA_LOCATION_QNAME = '{%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_NOTATION = '{%s}NOTATION' % XSD_NAMESPACE
|
||||||
XSD_ANY_ATOMIC_TYPE = '{%s}anyAtomicType' % XSD_NAMESPACE
|
XSD_ANY_ATOMIC_TYPE = '{%s}anyAtomicType' % XSD_NAMESPACE
|
||||||
XSD_UNTYPED = '{%s}untyped' % XSD_NAMESPACE
|
XSD_UNTYPED = '{%s}untyped' % XSD_NAMESPACE
|
||||||
XSD_UNTYPED_ATOMIC = '{%s}untypedAtomic' % 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):
|
def get_namespace(name):
|
||||||
|
|
|
@ -18,7 +18,7 @@ from .exceptions import ElementPathSyntaxError, ElementPathTypeError, ElementPat
|
||||||
from .datatypes import UntypedAtomic, DayTimeDuration, YearMonthDuration, XSD_BUILTIN_TYPES
|
from .datatypes import UntypedAtomic, DayTimeDuration, YearMonthDuration, XSD_BUILTIN_TYPES
|
||||||
from .xpath_context import XPathSchemaContext
|
from .xpath_context import XPathSchemaContext
|
||||||
from .tdop_parser import Parser, MultiLabel
|
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
|
XPATH_FUNCTIONS_NAMESPACE, XSD_NAMESPACE, qname_to_prefixed
|
||||||
from .xpath_token import XPathToken
|
from .xpath_token import XPathToken
|
||||||
from .xpath_helpers import AttributeNode, NamespaceNode, is_etree_element, is_xpath_node, is_element_node, \
|
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
|
item = context.item
|
||||||
if is_element_node(item):
|
if is_element_node(item):
|
||||||
for elem in item.iter():
|
for elem in item.iter():
|
||||||
if elem.get(XML_ID_QNAME) == value:
|
if elem.get(XML_ID) == value:
|
||||||
yield elem
|
yield elem
|
||||||
|
|
||||||
|
|
||||||
|
@ -1143,11 +1143,11 @@ def evaluate(self, context=None):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
lang = context.item.attrib[XML_LANG_QNAME].strip()
|
lang = context.item.attrib[XML_LANG].strip()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
for elem in context.iter_ancestor():
|
for elem in context.iter_ancestor():
|
||||||
if XML_LANG_QNAME in elem.attrib:
|
if XML_LANG in elem.attrib:
|
||||||
lang = elem.attrib[XML_LANG_QNAME]
|
lang = elem.attrib[XML_LANG]
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -13,11 +13,10 @@ Helper functions for XPath nodes and functions.
|
||||||
"""
|
"""
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from .compat import PY3
|
from .compat import PY3, urlparse
|
||||||
from .namespaces import XML_BASE_QNAME, XML_ID_QNAME, XSI_TYPE_QNAME, XSI_NIL_QNAME, \
|
from .namespaces import XML_BASE, XSI_NIL, XSD_UNTYPED, XSD_UNTYPED_ATOMIC
|
||||||
XSD_UNTYPED, XSD_UNTYPED_ATOMIC, prefixed_to_qname
|
from .exceptions import ElementPathValueError, xpath_error
|
||||||
from .exceptions import xpath_error
|
from .datatypes import UntypedAtomic, ncname_validator
|
||||||
from .datatypes import UntypedAtomic
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Node types
|
# 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.
|
# for documents. Generic tuples are used for representing attributes and named-tuples for namespaces.
|
||||||
###
|
###
|
||||||
def is_element_node(obj, tag=None):
|
def is_element_node(obj, tag=None):
|
||||||
if tag is None:
|
"""
|
||||||
return is_etree_element(obj) and not callable(obj.tag)
|
Returns `True` if the first argument is an element node matching the tag, `False` otherwise.
|
||||||
elif not is_etree_element(obj):
|
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
|
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] == '*':
|
elif tag[0] == '*':
|
||||||
if not obj.tag:
|
try:
|
||||||
return False
|
_, name = tag.split(':')
|
||||||
elif obj.tag[0] == '{':
|
except (ValueError, IndexError):
|
||||||
return obj.tag.split('}')[1] == tag.split(':')[1]
|
raise ElementPathValueError("unexpected format %r for argument 'tag'" % tag)
|
||||||
else:
|
else:
|
||||||
return obj.tag == tag.split(':')[1]
|
return obj.tag.split('}')[1] == name if obj.tag[0] == '{' else obj.tag == name
|
||||||
elif tag[-1] == '*':
|
elif tag[-1] == '*':
|
||||||
if not obj.tag:
|
if tag[0] != '{' or '}' not in tag:
|
||||||
return False
|
raise ElementPathValueError("unexpected format %r for argument 'tag'" % tag)
|
||||||
elif obj.tag[0] == '{':
|
return obj.tag.split('}')[0][1:] == tag.split('}')[0][1:] if obj.tag[0] == '{' else False
|
||||||
return obj.tag.split('}')[0][1:] == tag.split('}')[0][1:]
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
return obj.tag == tag
|
return obj.tag == tag
|
||||||
|
|
||||||
|
|
||||||
def is_attribute_node(obj, name=None):
|
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)
|
return isinstance(obj, AttributeNode)
|
||||||
elif not isinstance(obj, AttributeNode):
|
elif not isinstance(obj, AttributeNode):
|
||||||
return False
|
return False
|
||||||
elif name[0] == '*':
|
elif name[0] == '*':
|
||||||
if obj[0][0] == '{':
|
try:
|
||||||
return obj[0].split('}')[1] == name.split(':')[1]
|
_, _name = name.split(':')
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
raise ElementPathValueError("unexpected format %r for argument 'name'" % name)
|
||||||
else:
|
else:
|
||||||
return obj[0] == name.split(':')[1]
|
return obj[0].split('}')[1] == _name if obj[0][0] == '{' else obj[0] == _name
|
||||||
elif name[-1] == '*':
|
elif name[-1] == '*':
|
||||||
if obj[0][0] == '{':
|
if name[0] != '{' or '}' not in name:
|
||||||
return obj[0].split('}')[0][1:] == name.split('}')[0][1:]
|
raise ElementPathValueError("unexpected format %r for argument 'name'" % name)
|
||||||
else:
|
return obj[0].split('}')[0][1:] == name.split('}')[0][1:] if obj[0][0] == '{' else False
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
return obj[0] == name
|
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):
|
def node_attributes(obj):
|
||||||
if is_element_node(obj):
|
if is_element_node(obj):
|
||||||
return obj.attrib
|
return obj.attrib
|
||||||
|
@ -132,49 +150,49 @@ def node_attributes(obj):
|
||||||
def node_base_uri(obj):
|
def node_base_uri(obj):
|
||||||
try:
|
try:
|
||||||
if is_element_node(obj):
|
if is_element_node(obj):
|
||||||
return obj.attrib[XML_BASE_QNAME]
|
return obj.attrib[XML_BASE]
|
||||||
elif is_document_node(obj):
|
elif is_document_node(obj):
|
||||||
return obj.getroot().attrib[XML_BASE_QNAME]
|
return obj.getroot().attrib[XML_BASE]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def node_document_uri(obj):
|
def node_document_uri(obj):
|
||||||
# Try the xml:base of root node because an ElementTree doesn't save reference to source.
|
if is_document_node(obj):
|
||||||
for uri in node_base_uri(obj):
|
try:
|
||||||
return uri
|
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):
|
def node_children(obj):
|
||||||
if is_element_node(obj):
|
if is_element_node(obj):
|
||||||
for child in obj:
|
return (child for child in obj)
|
||||||
return child
|
|
||||||
elif is_document_node(obj):
|
elif is_document_node(obj):
|
||||||
return obj.getroot()
|
return (child for child in [obj.getroot()])
|
||||||
|
|
||||||
|
|
||||||
def node_is_id(obj):
|
def node_is_id(obj):
|
||||||
if is_element_node(obj):
|
if is_element_node(obj):
|
||||||
return XML_ID_QNAME in obj.attrib
|
return ncname_validator(obj.text)
|
||||||
elif is_attribute_node(obj):
|
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):
|
if is_element_node(obj):
|
||||||
try:
|
return obj.text is not None and all(ncname_validator(x) for x in obj.text.split())
|
||||||
node_type = obj.attrib[XSI_TYPE_QNAME]
|
elif is_attribute_node(obj):
|
||||||
except KeyError:
|
return all(ncname_validator(x) for x in obj[1].split())
|
||||||
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")
|
|
||||||
|
|
||||||
|
|
||||||
def node_nilled(obj):
|
def node_nilled(obj):
|
||||||
if is_element_node(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):
|
def node_kind(obj):
|
||||||
|
@ -197,9 +215,7 @@ def node_kind(obj):
|
||||||
def node_name(obj):
|
def node_name(obj):
|
||||||
if is_element_node(obj):
|
if is_element_node(obj):
|
||||||
return obj.tag
|
return obj.tag
|
||||||
elif is_attribute_node(obj):
|
elif is_attribute_node(obj) or is_namespace_node(obj):
|
||||||
return obj[0]
|
|
||||||
elif is_namespace_node(obj):
|
|
||||||
return obj[0]
|
return obj[0]
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,11 +238,19 @@ def node_string_value(obj):
|
||||||
|
|
||||||
def node_type_name(obj, schema=None):
|
def node_type_name(obj, schema=None):
|
||||||
if is_element_node(obj):
|
if is_element_node(obj):
|
||||||
if schema is None:
|
if schema is not None:
|
||||||
return XSD_UNTYPED
|
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):
|
elif is_attribute_node(obj):
|
||||||
if schema is None:
|
if schema is not None:
|
||||||
return XSD_UNTYPED_ATOMIC # TODO: from a PSVI ...
|
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):
|
elif is_text_node(obj):
|
||||||
return XSD_UNTYPED_ATOMIC
|
return XSD_UNTYPED_ATOMIC
|
||||||
|
|
||||||
|
@ -280,9 +304,7 @@ def data_value(obj):
|
||||||
elif not is_xpath_node(obj):
|
elif not is_xpath_node(obj):
|
||||||
return obj
|
return obj
|
||||||
else:
|
else:
|
||||||
value = node_string_value(obj)
|
return UntypedAtomic(node_string_value(obj))
|
||||||
if value is not None:
|
|
||||||
return UntypedAtomic(value)
|
|
||||||
|
|
||||||
|
|
||||||
def number_value(obj):
|
def number_value(obj):
|
||||||
|
|
|
@ -71,9 +71,7 @@ class Selector(object):
|
||||||
self.root_token = self.parser.parse(path)
|
self.root_token = self.parser.parse(path)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return u'%s(path=%r, namespaces=%r, parser=%s)' % (
|
return u'%s(path=%r, parser=%s)' % (self.__class__.__name__, self.path, self.parser.__class__.__name__)
|
||||||
self.__class__.__name__, self.path, self.namespaces, self.parser.__class__.__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def namespaces(self):
|
def namespaces(self):
|
||||||
|
@ -101,3 +99,5 @@ class Selector(object):
|
||||||
"""
|
"""
|
||||||
context = XPathContext(root)
|
context = XPathContext(root)
|
||||||
return self.root_token.select(context)
|
return self.root_token.select(context)
|
||||||
|
|
||||||
|
# 45-48, 74, 81
|
|
@ -1,6 +1,7 @@
|
||||||
# Requirements for setup a development environment
|
# Requirements for setup a development environment
|
||||||
setuptools
|
setuptools
|
||||||
tox
|
tox
|
||||||
|
coverage
|
||||||
lxml
|
lxml
|
||||||
xmlschema>=1.0.9
|
xmlschema>=1.0.9
|
||||||
Sphinx
|
Sphinx
|
||||||
|
|
|
@ -612,6 +612,15 @@ class TimezoneTypeTest(unittest.TestCase):
|
||||||
self.assertEqual(str(Timezone.fromstring('+05:00')), '+05:00')
|
self.assertEqual(str(Timezone.fromstring('+05:00')), '+05:00')
|
||||||
self.assertEqual(str(Timezone.fromstring('-13:15')), '-13:15')
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -24,6 +24,7 @@ import unittest
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
|
from tests.test_helpers import HelpersTest
|
||||||
from tests.test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest
|
from tests.test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest
|
||||||
from tests.test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest
|
from tests.test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest
|
||||||
from tests.test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest
|
from tests.test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest
|
||||||
|
@ -32,6 +33,7 @@ if __name__ == '__main__':
|
||||||
from tests.test_package import PackageTest
|
from tests.test_package import PackageTest
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Python 2 fallback
|
# Python 2 fallback
|
||||||
|
from test_helpers import HelpersTest
|
||||||
from test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest
|
from test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest
|
||||||
from test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest
|
from test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest
|
||||||
from test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest
|
from test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest
|
||||||
|
|
|
@ -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 <brunato@sissa.it>
|
||||||
|
#
|
||||||
|
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('<node a1="10"/>')
|
||||||
|
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('<A/>'))
|
||||||
|
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 = '<A xmlns:xml="http://www.w3.org/XML/1998/namespace" xml:base="/" />'
|
||||||
|
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 = '<A xmlns:xml="http://www.w3.org/XML/1998/namespace" xml:base="/root" />'
|
||||||
|
document = ElementTree.parse(io.StringIO(xml_test))
|
||||||
|
self.assertEqual(node_document_uri(document), '/root')
|
||||||
|
|
||||||
|
xml_test = '<A xmlns:xml="http://www.w3.org/XML/1998/namespace" xml:base="http://xpath.test" />'
|
||||||
|
document = ElementTree.parse(io.StringIO(xml_test))
|
||||||
|
self.assertEqual(node_document_uri(document), 'http://xpath.test')
|
||||||
|
|
||||||
|
xml_test = '<A xmlns:xml="http://www.w3.org/XML/1998/namespace" xml:base="dir1/dir2" />'
|
||||||
|
document = ElementTree.parse(io.StringIO(xml_test))
|
||||||
|
self.assertIsNone(node_document_uri(document))
|
||||||
|
|
||||||
|
xml_test = '<A xmlns:xml="http://www.w3.org/XML/1998/namespace" xml:base="http://[xpath.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("<A><B1/><B2/></A>")
|
||||||
|
self.assertListEqual(list(node_children(elem)), elem[:])
|
||||||
|
document = ElementTree.parse(io.StringIO("<A><B1/><B2/></A>"))
|
||||||
|
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('<A>xyz</A>')))
|
||||||
|
self.assertFalse(node_is_id(ElementTree.XML('<A>xyz abc</A>')))
|
||||||
|
self.assertFalse(node_is_id(ElementTree.XML('<A>12345</A>')))
|
||||||
|
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('<A>xyz</A>')))
|
||||||
|
self.assertTrue(node_is_idrefs(ElementTree.XML('<A>xyz abc</A>')))
|
||||||
|
self.assertFalse(node_is_idrefs(ElementTree.XML('<A>12345</A>')))
|
||||||
|
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 = '<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />'
|
||||||
|
self.assertTrue(node_nilled(ElementTree.XML(xml_test)))
|
||||||
|
xml_test = '<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="false" />'
|
||||||
|
self.assertFalse(node_nilled(ElementTree.XML(xml_test)))
|
||||||
|
self.assertFalse(node_nilled(ElementTree.XML('<A />')))
|
||||||
|
|
||||||
|
def test_node_kind_function(self):
|
||||||
|
document = ElementTree.parse(io.StringIO(u'<A/>'))
|
||||||
|
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'<A>123<B1>456</B1><B2>789</B2></A>'))
|
||||||
|
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("""<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<xs:attribute name="slot" type="xs:token" />
|
||||||
|
<xs:element name="frame" type="xs:decimal" />
|
||||||
|
</xs:schema>""")
|
||||||
|
)
|
||||||
|
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()
|
|
@ -13,7 +13,7 @@ import unittest
|
||||||
import lxml.etree
|
import lxml.etree
|
||||||
|
|
||||||
from elementpath import *
|
from elementpath import *
|
||||||
from elementpath.namespaces import XML_LANG_QNAME, XSD_NAMESPACE
|
from elementpath.namespaces import XML_LANG, XSD_NAMESPACE
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
|
@ -52,7 +52,7 @@ class XPath2ParserXMLSchemaTest(test_xpath2_parser.XPath2ParserTest):
|
||||||
self.check_value("schema-element(xs:schema)", context.item, context)
|
self.check_value("schema-element(xs:schema)", context.item, context)
|
||||||
self.check_tree("schema-element(xs:group)", '(schema-element (: (xs) (group)))')
|
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(nil)")
|
||||||
self.wrong_name("schema-attribute(xs:string)")
|
self.wrong_name("schema-attribute(xs:string)")
|
||||||
self.check_value("schema-attribute(xml:lang)", None)
|
self.check_value("schema-attribute(xml:lang)", None)
|
||||||
|
|
|
@ -16,6 +16,22 @@ from elementpath import *
|
||||||
|
|
||||||
|
|
||||||
class SelectorTest(unittest.TestCase):
|
class SelectorTest(unittest.TestCase):
|
||||||
|
root = ElementTree.XML('<author>Dickens</author>')
|
||||||
|
|
||||||
|
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):
|
def test_issue_001(self):
|
||||||
selector = Selector("//FullPath[ends-with(., 'Temp')]")
|
selector = Selector("//FullPath[ends-with(., 'Temp')]")
|
||||||
|
|
|
@ -1032,7 +1032,7 @@ class XPath1ParserTest(unittest.TestCase):
|
||||||
def test_default_namespace(self):
|
def test_default_namespace(self):
|
||||||
root = self.etree.XML('<foo>bar</foo>')
|
root = self.etree.XML('<foo>bar</foo>')
|
||||||
self.check_selector('/foo', root, [root])
|
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
|
# XPath 1.0 ignores the default namespace
|
||||||
self.check_selector('/foo', root, [root], namespaces={'': 'ns'}) # foo --> foo
|
self.check_selector('/foo', root, [root], namespaces={'': 'ns'}) # foo --> foo
|
||||||
else:
|
else:
|
||||||
|
@ -1046,6 +1046,15 @@ class XPath1ParserTest(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.check_selector('/foo', root, [root], namespaces={'': 'ns'})
|
self.check_selector('/foo', root, [root], namespaces={'': 'ns'})
|
||||||
|
|
||||||
|
root = self.etree.XML('<A xmlns="http://xpath.test/ns"><B1/></A>')
|
||||||
|
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):
|
class LxmlXPath1ParserTest(XPath1ParserTest):
|
||||||
etree = lxml.etree
|
etree = lxml.etree
|
||||||
|
|
15
tox.ini
15
tox.ini
|
@ -5,16 +5,27 @@
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py27, py34, py35, py36, py37
|
envlist = py27, py34, py35, py36, py37
|
||||||
|
skip_missing_interpreters = true
|
||||||
toxworkdir = {homedir}/.tox/elementpath
|
toxworkdir = {homedir}/.tox/elementpath
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
lxml
|
lxml
|
||||||
xmlschema>=1.0.9
|
xmlschema~=1.0.9
|
||||||
commands = python -m unittest
|
commands = python -m unittest
|
||||||
|
|
||||||
[testenv:py27]
|
[testenv:py27]
|
||||||
deps =
|
deps =
|
||||||
lxml
|
lxml
|
||||||
xmlschema>=1.0.9
|
xmlschema~=1.0.9
|
||||||
commands = python tests/test_elementpath.py
|
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
|
||||||
|
|
Loading…
Reference in New Issue