Merge branch 'develop' for upgrade to v1.0.15

This commit is contained in:
Davide Brunato 2019-10-13 22:11:58 +02:00
commit 298ceb117a
49 changed files with 2472 additions and 1128 deletions

View File

@ -2,6 +2,14 @@
CHANGELOG
*********
`v1.0.15`_ (2019-10-13)
=======================
* Improved XPath 2.0 bindings
* Added logging for schema initialization and building (handled with argument *loglevel*)
* Update encoding of collapsed contents with a new model based reordering method
* Removed XLink namespace from meta-schema (loaded from a fallback location like XHTML)
* Fixed half of failed W3C instance tests (remain 255 over 15344 tests)
`v1.0.14`_ (2019-08-27)
=======================
* Added XSD 1.1 validator with class *XMLSchema11*
@ -256,3 +264,4 @@ v0.9.6 (2017-05-05)
.. _v1.0.11: https://github.com/brunato/xmlschema/compare/v1.0.10...v1.0.11
.. _v1.0.13: https://github.com/brunato/xmlschema/compare/v1.0.11...v1.0.13
.. _v1.0.14: https://github.com/brunato/xmlschema/compare/v1.0.13...v1.0.14
.. _v1.0.15: https://github.com/brunato/xmlschema/compare/v1.0.14...v1.0.15

View File

@ -164,8 +164,6 @@ Resource access API
.. autofunction:: xmlschema.normalize_url
XSD components API
------------------

View File

@ -62,7 +62,7 @@ author = 'Davide Brunato'
# The short X.Y version.
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '1.0.14'
release = '1.0.15'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -103,21 +103,21 @@ The global maps can be accessed through :attr:`XMLSchema.maps` attribute:
>>> from pprint import pprint
>>> pprint(sorted(schema.maps.types.keys())[:5])
['{http://example.com/vehicles}vehicleType',
'{http://www.w3.org/1999/xlink}actuateType',
'{http://www.w3.org/1999/xlink}arcType',
'{http://www.w3.org/1999/xlink}arcroleType',
'{http://www.w3.org/1999/xlink}extended']
'{http://www.w3.org/2001/XMLSchema}ENTITIES',
'{http://www.w3.org/2001/XMLSchema}ENTITY',
'{http://www.w3.org/2001/XMLSchema}ID',
'{http://www.w3.org/2001/XMLSchema}IDREF']
>>> pprint(sorted(schema.maps.elements.keys())[:10])
['{http://example.com/vehicles}bikes',
'{http://example.com/vehicles}cars',
'{http://example.com/vehicles}vehicles',
'{http://www.w3.org/1999/xlink}arc',
'{http://www.w3.org/1999/xlink}locator',
'{http://www.w3.org/1999/xlink}resource',
'{http://www.w3.org/1999/xlink}title',
'{http://www.w3.org/2001/XMLSchema}all',
'{http://www.w3.org/2001/XMLSchema}annotation',
'{http://www.w3.org/2001/XMLSchema}any']
'{http://www.w3.org/2001/XMLSchema}any',
'{http://www.w3.org/2001/XMLSchema}anyAttribute',
'{http://www.w3.org/2001/XMLSchema}appinfo',
'{http://www.w3.org/2001/XMLSchema}attribute',
'{http://www.w3.org/2001/XMLSchema}attributeGroup']
Schema objects include methods for finding XSD elements and attributes in the schema.
Those are methods ot the ElementTree's API, so you can use an XPath expression for

View File

@ -6,8 +6,8 @@ publiccodeYmlVersion: '0.2'
name: xmlschema
url: 'https://github.com/sissaschool/xmlschema'
landingURL: 'https://github.com/sissaschool/xmlschema'
releaseDate: '2019-08-27'
softwareVersion: v1.0.14
releaseDate: '2019-10-13'
softwareVersion: v1.0.15
developmentStatus: stable
platforms:
- linux

View File

@ -2,7 +2,7 @@
setuptools
tox
coverage
elementpath~=1.2.0
elementpath~=1.3.0
lxml
memory_profiler
pathlib2 # For Py27 tests on resources

View File

@ -38,8 +38,8 @@ class InstallCommand(install):
setup(
name='xmlschema',
version='1.0.14',
install_requires=['elementpath~=1.2.0'],
version='1.0.15',
install_requires=['elementpath~=1.3.0'],
packages=['xmlschema'],
include_package_data=True,
cmdclass={

View File

@ -11,7 +11,7 @@ toxworkdir = {homedir}/.tox/xmlschema
[testenv]
deps =
lxml
elementpath~=1.2.0
elementpath~=1.3.0
py27: pathlib2
memory: memory_profiler
docs: Sphinx
@ -22,6 +22,11 @@ deps =
commands = python xmlschema/tests/test_all.py {posargs}
whitelist_externals = make
[testenv:py38]
deps =
lxml==4.3.5
elementpath~=1.3.0
[testenv:package]
commands = python xmlschema/tests/test_package.py

View File

@ -10,6 +10,7 @@
#
from .exceptions import XMLSchemaException, XMLSchemaRegexError, XMLSchemaURLError, \
XMLSchemaNamespaceError
from .etree import etree_tostring
from .resources import (
normalize_url, fetch_resource, load_xml_resource, fetch_namespaces,
fetch_schema_locations, fetch_schema, XMLResource
@ -29,7 +30,7 @@ from .validators import (
XsdGlobals, XMLSchemaBase, XMLSchema, XMLSchema10, XMLSchema11
)
__version__ = '1.0.14'
__version__ = '1.0.15'
__author__ = "Davide Brunato"
__contact__ = "brunato@sissa.it"
__copyright__ = "Copyright 2016-2019, SISSA"

View File

@ -194,7 +194,7 @@ def iterparse_character_group(s, expand_ranges=False):
raise XMLSchemaRegexError("bad character %r at position %d" % (s[k], k))
escaped = on_range = False
char = s[k]
if k >= length - 1 or s[k + 1] != '-':
if k >= length - 2 or s[k + 1] != '-':
yield ord(char)
elif s[k] == '\\':
if escaped:
@ -209,7 +209,7 @@ def iterparse_character_group(s, expand_ranges=False):
yield ord('\\')
on_range = False
char = s[k]
if k >= length - 1 or s[k + 1] != '-':
if k >= length - 2 or s[k + 1] != '-':
yield ord(char)
if escaped:
yield ord('\\')

View File

@ -18,9 +18,9 @@ import warnings
from .compat import ordered_dict_class, unicode_type
from .exceptions import XMLSchemaValueError
from .etree import etree_element, lxml_etree_element, etree_register_namespace, lxml_etree_register_namespace
from .namespaces import XSI_NAMESPACE
from .helpers import local_name
from .qnames import local_name
from .etree import etree_element, lxml_etree_element, etree_register_namespace, lxml_etree_register_namespace
from xmlschema.namespaces import NamespaceMapper
ElementData = namedtuple('ElementData', ['tag', 'text', 'content', 'attributes'])
@ -36,6 +36,7 @@ attributes.
def raw_xml_encode(value):
"""Encodes a simple value to XML."""
if isinstance(value, bool):
return 'true' if value else 'false'
elif isinstance(value, (list, tuple)):
@ -260,7 +261,7 @@ class XMLSchemaConverter(NamespaceMapper):
:return: a data structure containing the decoded data.
"""
result_dict = self.dict()
if level == 0 and xsd_element.is_global and not self.strip_namespaces and self:
if level == 0 and xsd_element.is_global() and not self.strip_namespaces and self:
schema_namespaces = set(xsd_element.namespaces.values())
result_dict.update(
('%s:%s' % (self.ns_prefix, k) if k else self.ns_prefix, v) for k, v in self.items()
@ -359,7 +360,7 @@ class XMLSchemaConverter(NamespaceMapper):
else:
ns_name = self.unmap_qname(name)
for xsd_child in xsd_element.type.content_type.iter_elements():
matched_element = xsd_child.matched_element(ns_name)
matched_element = xsd_child.match(ns_name, resolve=True)
if matched_element is not None:
if matched_element.type.is_list():
content.append((ns_name, value))
@ -456,7 +457,7 @@ class UnorderedConverter(XMLSchemaConverter):
# `value` is a list but not a list of lists or list of dicts.
ns_name = self.unmap_qname(name)
for xsd_child in xsd_element.type.content_type.iter_elements():
matched_element = xsd_child.matched_element(ns_name)
matched_element = xsd_child.match(ns_name, resolve=True)
if matched_element is not None:
if matched_element.type.is_list():
content_lu[self.unmap_qname(name)] = [value]
@ -576,7 +577,7 @@ class ParkerConverter(XMLSchemaConverter):
content.append((ns_name, item))
else:
for xsd_child in xsd_element.type.content_type.iter_elements():
matched_element = xsd_child.matched_element(ns_name)
matched_element = xsd_child.match(ns_name, resolve=True)
if matched_element is not None:
if matched_element.type.is_list():
content.append((ns_name, value))
@ -721,7 +722,7 @@ class BadgerFishConverter(XMLSchemaConverter):
else:
ns_name = unmap_qname(name)
for xsd_child in xsd_element.type.content_type.iter_elements():
matched_element = xsd_child.matched_element(ns_name)
matched_element = xsd_child.match(ns_name, resolve=True)
if matched_element is not None:
if matched_element.type.is_list():
content.append((ns_name, value))
@ -841,7 +842,7 @@ class AbderaConverter(XMLSchemaConverter):
else:
ns_name = unmap_qname(name)
for xsd_child in xsd_element.type.content_type.iter_elements():
matched_element = xsd_child.matched_element(ns_name)
matched_element = xsd_child.match(ns_name, resolve=True)
if matched_element is not None:
if matched_element.type.is_list():
content.append((ns_name, value))
@ -898,7 +899,7 @@ class JsonMLConverter(XMLSchemaConverter):
for name, value, _ in self.map_content(data.content)
])
if level == 0 and xsd_element.is_global and not self.strip_namespaces and self:
if level == 0 and xsd_element.is_global() and not self.strip_namespaces and self:
attributes.update([('xmlns:%s' % k if k else 'xmlns', v) for k, v in self.items()])
if attributes:
result_list.insert(1, attributes)

View File

@ -25,12 +25,16 @@ def get_context(source, schema=None, cls=None, locations=None, base_url=None,
if cls is None:
cls = XMLSchema
if schema is None:
try:
schema, locations = fetch_schema_locations(source, locations, base_url=base_url)
except ValueError:
if schema is None:
raise
elif not isinstance(schema, XMLSchemaBase):
schema = cls(schema, validation='strict', locations=locations, base_url=base_url,
defuse=defuse, timeout=timeout)
else:
schema = cls(schema, validation='strict', locations=locations, defuse=defuse, timeout=timeout)
elif not isinstance(schema, XMLSchemaBase):
schema = cls(schema, validation='strict', locations=locations, base_url=base_url,
defuse=defuse, timeout=timeout)
if not isinstance(source, XMLResource):
source = XMLResource(source, defuse=defuse, timeout=timeout, lazy=lazy)

View File

@ -13,8 +13,8 @@ This module contains ElementTree setup and helpers for xmlschema package.
"""
from __future__ import unicode_literals
import sys
import re
import importlib
import re
from collections import Counter
try:
@ -23,10 +23,9 @@ except ImportError:
lxml_etree = None
from .compat import PY3
from .exceptions import XMLSchemaValueError, XMLSchemaTypeError
from .namespaces import XSLT_NAMESPACE, HFP_NAMESPACE, VC_NAMESPACE
from .helpers import get_namespace, get_qname, qname_to_prefixed
from .xpath import ElementPathMixin
from .exceptions import XMLSchemaTypeError
from .namespaces import XSLT_NAMESPACE, HFP_NAMESPACE, VC_NAMESPACE, get_namespace
from .qnames import get_qname, qname_to_prefixed
###
# Programmatic import of xml.etree.ElementTree
@ -130,11 +129,6 @@ class SafeXMLParser(PyElementTree.XMLParser):
)
def is_etree_element(elem):
"""More safer test for matching ElementTree elements."""
return hasattr(elem, 'tag') and hasattr(elem, 'attrib') and not isinstance(elem, ElementPathMixin)
def etree_tostring(elem, namespaces=None, indent='', max_lines=None, spaces_for_tab=4, xml_declaration=False):
"""
Serialize an Element tree to a string. Tab characters are replaced by whitespaces.
@ -159,19 +153,21 @@ def etree_tostring(elem, namespaces=None, indent='', max_lines=None, spaces_for_
if isinstance(elem, etree_element):
if namespaces:
for prefix, uri in namespaces.items():
etree_register_namespace(prefix, uri)
if not re.match(r'ns\d+$', prefix):
etree_register_namespace(prefix, uri)
tostring = ElementTree.tostring
elif isinstance(elem, py_etree_element):
if namespaces:
for prefix, uri in namespaces.items():
PyElementTree.register_namespace(prefix, uri)
if not re.match(r'ns\d+$', prefix):
PyElementTree.register_namespace(prefix, uri)
tostring = PyElementTree.tostring
elif lxml_etree is not None:
if namespaces:
for prefix, uri in namespaces.items():
if prefix:
if prefix and not re.match(r'ns\d+$', prefix):
lxml_etree_register_namespace(prefix, uri)
tostring = lxml_etree.tostring
else:
@ -267,21 +263,6 @@ def etree_getpath(elem, root, namespaces=None, relative=True, add_position=False
return path
def etree_last_child(elem):
"""Returns the last child of the element, ignoring children that are lxml comments."""
for child in reversed(elem):
if not callable(child.tag):
return child
def etree_child_index(elem, child):
"""Return the index or raise ValueError if it is not a *child* of *elem*."""
for index in range(len(elem)):
if elem[index] is child:
return index
raise XMLSchemaValueError("%r is not a child of %r" % (child, elem))
def etree_elements_assert_equal(elem, other, strict=True, skip_comments=True):
"""
Tests the equality of two XML Element trees.

View File

@ -11,83 +11,19 @@
"""
This module contains various helper functions and classes.
"""
import re
from decimal import Decimal
from .exceptions import XMLSchemaValueError, XMLSchemaTypeError
from .compat import string_base_type
from .exceptions import XMLSchemaValueError
from .qnames import XSD_ANNOTATION
from .xpath import ElementPathMixin
XSD_FINAL_ATTRIBUTE_VALUES = {'restriction', 'extension', 'list', 'union'}
NAMESPACE_PATTERN = re.compile(r'{([^}]*)}')
def get_namespace(name):
try:
return NAMESPACE_PATTERN.match(name).group(1)
except (AttributeError, TypeError):
return ''
def get_qname(uri, name):
"""
Returns an expanded QName from URI and local part. If any argument has boolean value
`False` or if the name is already an expanded QName, returns the *name* argument.
:param uri: namespace URI
:param name: local or qualified name
:return: string or the name argument
"""
if not uri or not name or name[0] in ('{', '.', '/', '['):
return name
else:
return '{%s}%s' % (uri, name)
def local_name(qname):
"""
Return the local part of an expanded QName or a prefixed name. If the name
is `None` or empty returns the *name* argument.
:param qname: an expanded QName or a prefixed name or a local name.
"""
try:
if qname[0] == '{':
_, qname = qname.split('}')
elif ':' in qname:
_, qname = qname.split(':')
except IndexError:
return ''
except ValueError:
raise XMLSchemaValueError("the argument 'qname' has a wrong format: %r" % qname)
except TypeError:
if qname is None:
return qname
raise XMLSchemaTypeError("the argument 'qname' must be a string-like object or None")
else:
return qname
def qname_to_prefixed(qname, namespaces):
"""
Transforms a fully qualified name into a prefixed name using a namespace map. Returns the
*qname* argument if it's not a fully qualified name or if it has boolean value `False`.
:param qname: a fully qualified name or a local name.
:param namespaces: a map from prefixes to namespace URIs.
:return: string with a prefixed or local reference.
"""
if not qname:
return qname
namespace = get_namespace(qname)
for prefix, uri in sorted(filter(lambda x: x[1] == namespace, namespaces.items()), reverse=True):
if not uri:
return '%s:%s' % (prefix, qname) if prefix else qname
elif prefix:
return qname.replace('{%s}' % uri, '%s:' % prefix)
else:
return qname.replace('{%s}' % uri, '')
else:
return qname
def is_etree_element(elem):
"""More safer test for matching ElementTree elements."""
return hasattr(elem, 'tag') and hasattr(elem, 'attrib') and not isinstance(elem, ElementPathMixin)
def get_xsd_annotation(elem):
@ -147,6 +83,44 @@ def get_xsd_form_attribute(elem, attribute):
return value
def count_digits(number):
"""
Counts the digits of a number.
:param number: an int or a float or a Decimal or a string representing a number.
:return: a couple with the number of digits of the integer part and \
the number of digits of the decimal part.
"""
if isinstance(number, string_base_type):
number = str(Decimal(number)).lstrip('-+')
else:
number = str(number).lstrip('-+')
if 'E' in number:
significand, _, exponent = number.partition('E')
elif 'e' in number:
significand, _, exponent = number.partition('e')
elif '.' not in number:
return len(number.lstrip('0')), 0
else:
integer_part, _, decimal_part = number.partition('.')
return len(integer_part.lstrip('0')), len(decimal_part.rstrip('0'))
significand = significand.strip('0')
exponent = int(exponent)
num_digits = len(significand) - 1 if '.' in significand else len(significand)
if exponent > 0:
return num_digits + exponent, 0
else:
return 0, num_digits - exponent - 1
def strictly_equal(obj1, obj2):
"""Checks if the objects are equal and are of the same type."""
return obj1 == obj2 and type(obj1) is type(obj2)
class ParticleCounter(object):
"""
An helper class for counting total min/max occurrences of XSD particles.

View File

@ -12,9 +12,9 @@
This module contains namespace definitions for W3C core standards and namespace related classes.
"""
from __future__ import unicode_literals
import re
from .compat import MutableMapping, Mapping
from .helpers import get_namespace
XSD_NAMESPACE = 'http://www.w3.org/2001/XMLSchema'
"URI of the XML Schema Definition namespace (xs|xsd)"
@ -42,6 +42,16 @@ VC_NAMESPACE = 'http://www.w3.org/2007/XMLSchema-versioning'
"URI of the XML Schema Versioning namespace (vc)"
NAMESPACE_PATTERN = re.compile(r'{([^}]*)}')
def get_namespace(name):
try:
return NAMESPACE_PATTERN.match(name).group(1)
except (AttributeError, TypeError):
return ''
class NamespaceResourcesMap(MutableMapping):
"""
Dictionary for storing information about namespace resources. The values are
@ -82,7 +92,7 @@ class NamespaceResourcesMap(MutableMapping):
class NamespaceMapper(MutableMapping):
"""
A class to map/unmap namespace prefixes to URIs.
A class to map/unmap namespace prefixes to URIs. The
:param namespaces: Initial data with namespace prefixes and URIs.
"""

View File

@ -9,9 +9,11 @@
# @author Davide Brunato <brunato@sissa.it>
#
"""
This module contains qualified names constants.
This module contains qualified names constants and helpers.
"""
from __future__ import unicode_literals
from .exceptions import XMLSchemaTypeError, XMLSchemaValueError
from .namespaces import get_namespace
VC_TEMPLATE = '{http://www.w3.org/2007/XMLSchema-versioning}%s'
XML_TEMPLATE = '{http://www.w3.org/XML/1998/namespace}%s'
@ -181,3 +183,97 @@ XSD_DATE_TIME_STAMP = XSD_TEMPLATE % 'dateTimeStamp'
XSD_DAY_TIME_DURATION = XSD_TEMPLATE % 'dayTimeDuration'
XSD_YEAR_MONTH_DURATION = XSD_TEMPLATE % 'yearMonthDuration'
XSD_ERROR = XSD_TEMPLATE % 'error'
def get_qname(uri, name):
"""
Returns an expanded QName from URI and local part. If any argument has boolean value
`False` or if the name is already an expanded QName, returns the *name* argument.
:param uri: namespace URI
:param name: local or qualified name
:return: string or the name argument
"""
if not uri or not name or name[0] in ('{', '.', '/', '['):
return name
else:
return '{%s}%s' % (uri, name)
def local_name(qname):
"""
Return the local part of an expanded QName or a prefixed name. If the name
is `None` or empty returns the *name* argument.
:param qname: an expanded QName or a prefixed name or a local name.
"""
try:
if qname[0] == '{':
_, qname = qname.split('}')
elif ':' in qname:
_, qname = qname.split(':')
except IndexError:
return ''
except ValueError:
raise XMLSchemaValueError("the argument 'qname' has a wrong format: %r" % qname)
except TypeError:
if qname is None:
return qname
raise XMLSchemaTypeError("the argument 'qname' must be a string-like object or None")
else:
return qname
def qname_to_prefixed(qname, namespaces):
"""
Transforms a fully qualified name into a prefixed name using a namespace map.
Returns the *qname* argument if it's not a fully qualified name or if it has
boolean value `False`.
:param qname: an extended QName or a local name.
:param namespaces: a map from prefixes to namespace URIs.
:return: a QName in prefixed format or a local name.
"""
if not qname:
return qname
namespace = get_namespace(qname)
for prefix, uri in sorted(filter(lambda x: x[1] == namespace, namespaces.items()), reverse=True):
if not uri:
return '%s:%s' % (prefix, qname) if prefix else qname
elif prefix:
return qname.replace('{%s}' % uri, '%s:' % prefix)
else:
return qname.replace('{%s}' % uri, '')
else:
return qname
def qname_to_extended(qname, namespaces):
"""
Converts a QName in prefixed format or a local name to the extended QName format.
:param qname: a QName in prefixed format or a local name.
:param namespaces: a map from prefixes to namespace URIs.
:return: a QName in extended format or a local name.
"""
try:
if qname[0] == '{' or not namespaces:
return qname
except IndexError:
return qname
try:
prefix, name = qname.split(':', 1)
except ValueError:
if not namespaces.get(''):
return qname
else:
return '{%s}%s' % (namespaces[''], qname)
else:
try:
uri = namespaces[prefix]
except KeyError:
return qname
else:
return u'{%s}%s' % (uri, name) if uri else name

View File

@ -18,9 +18,9 @@ from .compat import (
pathname2url, URLError, uses_relative
)
from .exceptions import XMLSchemaTypeError, XMLSchemaValueError, XMLSchemaURLError, XMLSchemaOSError
from .namespaces import get_namespace
from .qnames import XSI_SCHEMA_LOCATION, XSI_NONS_SCHEMA_LOCATION
from .helpers import get_namespace
from .etree import ElementTree, PyElementTree, SafeXMLParser, is_etree_element, etree_tostring
from .etree import ElementTree, PyElementTree, SafeXMLParser, etree_tostring
DEFUSE_MODES = ('always', 'remote', 'never')
@ -285,7 +285,7 @@ class XMLResource(object):
def _fromsource(self, source):
url, lazy = None, self._lazy
if is_etree_element(source):
if hasattr(source, 'tag'):
self._lazy = False
return source, None, None, None # Source is already an Element --> nothing to load
elif isinstance(source, string_base_type):
@ -344,7 +344,7 @@ class XMLResource(object):
except (AttributeError, TypeError):
pass
else:
if is_etree_element(root):
if hasattr(root, 'tag'):
self._lazy = False
return root, source, None, None

View File

@ -20,13 +20,11 @@ import xmlschema
from xmlschema import XMLSchema
from xmlschema.compat import urlopen, URLError, unicode_type
from xmlschema.exceptions import XMLSchemaValueError
from xmlschema.etree import (
is_etree_element, etree_element, etree_register_namespace, etree_elements_assert_equal
)
from xmlschema.resources import fetch_namespaces
from xmlschema.qnames import XSD_SCHEMA
from xmlschema.helpers import get_namespace
from xmlschema.namespaces import XSD_NAMESPACE
from xmlschema.namespaces import XSD_NAMESPACE, get_namespace
from xmlschema.etree import etree_element, etree_register_namespace, etree_elements_assert_equal
from xmlschema.resources import fetch_namespaces
from xmlschema.helpers import is_etree_element
def has_network_access(*locations):

View File

@ -14,6 +14,7 @@ import pdb
import os
import pickle
import time
import logging
import warnings
from xmlschema import XMLSchemaBase
@ -46,6 +47,7 @@ def make_schema_test_class(test_file, test_args, test_num, schema_class, check_w
locations = test_args.locations
defuse = test_args.defuse
debug_mode = test_args.debug
loglevel = logging.DEBUG if debug_mode else None
class TestSchema(XsdValidatorTestCase):
@ -61,9 +63,10 @@ def make_schema_test_class(test_file, test_args, test_num, schema_class, check_w
def check_xsd_file(self):
if expected_errors > 0:
xs = schema_class(xsd_file, validation='lax', locations=locations, defuse=defuse)
xs = schema_class(xsd_file, validation='lax', locations=locations,
defuse=defuse, loglevel=loglevel)
else:
xs = schema_class(xsd_file, locations=locations, defuse=defuse)
xs = schema_class(xsd_file, locations=locations, defuse=defuse, loglevel=loglevel)
self.errors.extend(xs.maps.all_errors)
if inspect:

View File

@ -98,7 +98,11 @@ def make_validator_test_class(test_file, test_args, test_num, schema_class, chec
for _ in iter_nested_items(data1, dict_class=ordered_dict_class):
pass
elem1 = self.schema.encode(data1, path=root.tag, converter=converter, **kwargs)
try:
elem1 = self.schema.encode(data1, path=root.tag, converter=converter, **kwargs)
except XMLSchemaValidationError as err:
raise AssertionError(str(err) + msg_tmpl % "error during re-encoding")
if isinstance(elem1, tuple):
# When validation='lax'
if converter is not ParkerConverter:

View File

@ -15,14 +15,15 @@ This module runs tests on various internal helper functions.
from __future__ import unicode_literals
import unittest
import decimal
import xml.etree.ElementTree as ElementTree
from xmlschema import XMLSchema, XMLSchemaParseError
from xmlschema.etree import etree_element, prune_etree
from xmlschema.namespaces import XSD_NAMESPACE, XSI_NAMESPACE
from xmlschema.helpers import get_xsd_annotation, get_namespace, get_qname, local_name, \
qname_to_prefixed, get_xsd_derivation_attribute
from xmlschema.namespaces import XSD_NAMESPACE, XSI_NAMESPACE, get_namespace
from xmlschema.qnames import XSI_TYPE, XSD_SCHEMA, XSD_ELEMENT, XSD_SIMPLE_TYPE, XSD_ANNOTATION
from xmlschema.qnames import get_qname, local_name, qname_to_prefixed
from xmlschema.helpers import get_xsd_annotation, get_xsd_derivation_attribute, count_digits
class TestHelpers(unittest.TestCase):
@ -139,6 +140,34 @@ class TestHelpers(unittest.TestCase):
elem.append(etree_element(XSD_SIMPLE_TYPE))
self.assertEqual(component._parse_child_component(elem), elem[2])
def test_count_digits_function(self):
self.assertEqual(count_digits(10), (2, 0))
self.assertEqual(count_digits(-10), (2, 0))
self.assertEqual(count_digits(081.2), (2, 1))
self.assertEqual(count_digits(-081.200), (2, 1))
self.assertEqual(count_digits(0.51), (0, 2))
self.assertEqual(count_digits(-0.510), (0, 2))
self.assertEqual(count_digits(-0.510), (0, 2))
self.assertEqual(count_digits(decimal.Decimal('100.0')), (3, 0))
self.assertEqual(count_digits(decimal.Decimal('100.01')), (3, 2))
self.assertEqual(count_digits('100.01'), (3, 2))
self.assertEqual(count_digits(decimal.Decimal('100.0E+4')), (7, 0))
self.assertEqual(count_digits(decimal.Decimal('100.00001E+4')), (7, 1))
self.assertEqual(count_digits(decimal.Decimal('0100.00E4')), (7, 0))
self.assertEqual(count_digits(decimal.Decimal('0100.00E12')), (15, 0))
self.assertEqual(count_digits(decimal.Decimal('0100.00E19')), (22, 0))
self.assertEqual(count_digits(decimal.Decimal('100.0E-4')), (0, 2))
self.assertEqual(count_digits(decimal.Decimal('0100.00E-4')), (0, 2))
self.assertEqual(count_digits(decimal.Decimal('0100.00E-8')), (0, 6))
self.assertEqual(count_digits(decimal.Decimal('0100.00E-9')), (0, 7))
self.assertEqual(count_digits(decimal.Decimal('0100.00E-12')), (0, 10))
self.assertEqual(count_digits(decimal.Decimal('100.10E-4')), (0, 5))
self.assertEqual(count_digits(decimal.Decimal('0100.10E-12')), (0, 13))
class TestElementTreeHelpers(unittest.TestCase):

View File

@ -281,33 +281,33 @@ class TestGlobalMaps(unittest.TestCase):
def test_xsd_10_globals(self):
self.assertEqual(len(XMLSchema10.meta_schema.maps.notations), 2)
self.assertEqual(len(XMLSchema10.meta_schema.maps.types), 108)
self.assertEqual(len(XMLSchema10.meta_schema.maps.attributes), 18)
self.assertEqual(len(XMLSchema10.meta_schema.maps.attribute_groups), 9)
self.assertEqual(len(XMLSchema10.meta_schema.maps.groups), 18)
self.assertEqual(len(XMLSchema10.meta_schema.maps.elements), 45)
self.assertEqual(len([e.is_global for e in XMLSchema10.meta_schema.maps.iter_globals()]), 200)
self.assertEqual(len(XMLSchema10.meta_schema.maps.types), 92)
self.assertEqual(len(XMLSchema10.meta_schema.maps.attributes), 8)
self.assertEqual(len(XMLSchema10.meta_schema.maps.attribute_groups), 3)
self.assertEqual(len(XMLSchema10.meta_schema.maps.groups), 12)
self.assertEqual(len(XMLSchema10.meta_schema.maps.elements), 41)
self.assertEqual(len([e.is_global() for e in XMLSchema10.meta_schema.maps.iter_globals()]), 158)
self.assertEqual(len(XMLSchema10.meta_schema.maps.substitution_groups), 0)
def test_xsd_11_globals(self):
self.assertEqual(len(XMLSchema11.meta_schema.maps.notations), 2)
self.assertEqual(len(XMLSchema11.meta_schema.maps.types), 119)
self.assertEqual(len(XMLSchema11.meta_schema.maps.attributes), 24)
self.assertEqual(len(XMLSchema11.meta_schema.maps.attribute_groups), 10)
self.assertEqual(len(XMLSchema11.meta_schema.maps.groups), 19)
self.assertEqual(len(XMLSchema11.meta_schema.maps.elements), 51)
self.assertEqual(len([e.is_global for e in XMLSchema11.meta_schema.maps.iter_globals()]), 225)
self.assertEqual(len(XMLSchema11.meta_schema.maps.types), 103)
self.assertEqual(len(XMLSchema11.meta_schema.maps.attributes), 14)
self.assertEqual(len(XMLSchema11.meta_schema.maps.attribute_groups), 4)
self.assertEqual(len(XMLSchema11.meta_schema.maps.groups), 13)
self.assertEqual(len(XMLSchema11.meta_schema.maps.elements), 47)
self.assertEqual(len([e.is_global() for e in XMLSchema11.meta_schema.maps.iter_globals()]), 183)
self.assertEqual(len(XMLSchema11.meta_schema.maps.substitution_groups), 1)
def test_xsd_10_build(self):
self.assertEqual(len([e for e in XMLSchema10.meta_schema.maps.iter_globals()]), 200)
self.assertEqual(len([e for e in XMLSchema10.meta_schema.maps.iter_globals()]), 158)
self.assertTrue(XMLSchema10.meta_schema.maps.built)
XMLSchema10.meta_schema.maps.clear()
XMLSchema10.meta_schema.maps.build()
self.assertTrue(XMLSchema10.meta_schema.maps.built)
def test_xsd_11_build(self):
self.assertEqual(len([e for e in XMLSchema11.meta_schema.maps.iter_globals()]), 225)
self.assertEqual(len([e for e in XMLSchema11.meta_schema.maps.iter_globals()]), 183)
self.assertTrue(XMLSchema11.meta_schema.maps.built)
XMLSchema11.meta_schema.maps.clear()
XMLSchema11.meta_schema.maps.build()
@ -319,10 +319,10 @@ class TestGlobalMaps(unittest.TestCase):
for g in XMLSchema10.meta_schema.maps.iter_globals():
for c in g.iter_components():
total_counter += 1
if c.is_global:
if c.is_global():
global_counter += 1
self.assertEqual(global_counter, 200)
self.assertEqual(total_counter, 901)
self.assertEqual(global_counter, 158)
self.assertEqual(total_counter, 782)
def test_xsd_11_components(self):
total_counter = 0
@ -330,10 +330,10 @@ class TestGlobalMaps(unittest.TestCase):
for g in XMLSchema11.meta_schema.maps.iter_globals():
for c in g.iter_components():
total_counter += 1
if c.is_global:
if c.is_global():
global_counter += 1
self.assertEqual(global_counter, 225)
self.assertEqual(total_counter, 1051)
self.assertEqual(global_counter, 183)
self.assertEqual(total_counter, 932)
def test_xsd_11_restrictions(self):
all_model_type = XMLSchema11.meta_schema.types['all']

View File

@ -14,6 +14,7 @@ This module runs tests concerning model groups validation.
"""
import unittest
from xmlschema import XMLSchema10, XMLSchema11
from xmlschema.validators import ModelVisitor
from xmlschema.compat import ordered_dict_class
from xmlschema.tests import casepath, XsdValidatorTestCase
@ -150,9 +151,9 @@ class TestModelValidation(XsdValidatorTestCase):
self.check_stop(model) # <qualification> is optional
self.assertIsNone(model.element)
# --- XSD 1.0 schema ---
# --- XSD 1.0/1.1 meta-schema models ---
def test_simple_derivation_model(self):
def test_meta_simple_derivation_model(self):
"""
<xs:group name="simpleDerivation">
<xs:choice>
@ -162,7 +163,7 @@ class TestModelValidation(XsdValidatorTestCase):
</xs:choice>
</xs:group>
"""
group = self.schema_class.meta_schema.groups['simpleDerivation']
group = XMLSchema10.meta_schema.groups['simpleDerivation']
model = ModelVisitor(group)
self.check_advance_true(model) # <restriction> match
@ -185,8 +186,9 @@ class TestModelValidation(XsdValidatorTestCase):
self.check_advance_false(model, [(group, 0, group[:])]) # <other> not match with <union>
self.assertIsNone(model.element)
def test_simple_restriction_model(self):
def test_meta_simple_restriction_model(self):
"""
<!-- XSD 1.0 -->
<xs:group name="facets">
<xs:choice>
<xs:element ref="xs:minExclusive"/>
@ -210,25 +212,38 @@ class TestModelValidation(XsdValidatorTestCase):
<xs:group ref="xs:facets" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:group>
<!-- XSD 1.1 -->
<xs:group name="simpleRestrictionModel">
<xs:sequence>
<xs:element name="simpleType" type="xs:localSimpleType" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="xs:facet"/> <!-- Use a substitution group -->
<xs:any processContents="lax" namespace="##other"/>
</xs:choice>
</xs:sequence>
</xs:group>
"""
# Sequence with an optional single element and an optional unlimited choice.
group = self.schema_class.meta_schema.groups['simpleRestrictionModel']
model = ModelVisitor(group)
self.assertEqual(model.element, group[0])
self.check_advance_true(model) # <simpleType> match
self.assertEqual(model.element, group[1][0][0])
self.check_advance_false(model) # <maxExclusive> do not match
self.assertEqual(model.element, group[1][0][1])
self.check_advance_false(model) # <maxExclusive> do not match
self.assertEqual(model.element, group[1][0][2])
self.check_advance_true(model) # <maxExclusive> match
self.assertEqual(model.element, group[1][0][0])
for _ in range(12):
self.check_advance_false(model) # no match for all the inner choice group "xs:facets"
self.assertIsNone(model.element)
def test_schema_model(self):
if self.schema_class.XSD_VERSION == '1.0':
self.assertEqual(model.element, group[0])
self.check_advance_true(model) # <simpleType> match
self.assertEqual(model.element, group[1][0][0])
self.check_advance_false(model) # <maxExclusive> do not match
self.assertEqual(model.element, group[1][0][1])
self.check_advance_false(model) # <maxExclusive> do not match
self.assertEqual(model.element, group[1][0][2])
self.check_advance_true(model) # <maxExclusive> match
self.assertEqual(model.element, group[1][0][0])
for _ in range(12):
self.check_advance_false(model) # no match for all the inner choice group "xs:facets"
self.assertIsNone(model.element)
def test_meta_schema_top_model(self):
"""
<xs:group name="schemaTop">
<xs:choice>
@ -288,7 +303,7 @@ class TestModelValidation(XsdValidatorTestCase):
self.check_advance_true(model) # <attribute> match
self.assertIsNone(model.element)
def test_attr_declaration(self):
def test_meta_attr_declarations_group(self):
"""
<xs:group name="attrDecls">
<xs:sequence>
@ -322,7 +337,7 @@ class TestModelValidation(XsdValidatorTestCase):
self.check_advance(model, match)
self.assertEqual(model.element, group[1])
def test_complex_type_model(self):
def test_meta_complex_type_model(self):
"""
<xs:group name="complexTypeModel">
<xs:choice>
@ -343,6 +358,20 @@ class TestModelValidation(XsdValidatorTestCase):
<xs:element ref="xs:sequence"/>
</xs:choice>
</xs:group>
<xs:group name="complexTypeModel">
<xs:choice>
<xs:element ref="xs:simpleContent"/>
<xs:element ref="xs:complexContent"/>
<xs:sequence>
<xs:element ref="xs:openContent" minOccurs="0"/>
<xs:group ref="xs:typeDefParticle" minOccurs="0"/>
<xs:group ref="xs:attrDecls"/>
<xs:group ref="xs:assertions"/>
</xs:sequence>
</xs:choice>
</xs:group>
"""
group = self.schema_class.meta_schema.groups['complexTypeModel']
@ -357,27 +386,31 @@ class TestModelValidation(XsdValidatorTestCase):
self.check_advance_true(model) # <complexContent> match
self.assertIsNone(model.element)
model.restart()
self.assertEqual(model.element, group[0])
for match in [False, False, False, False, True]:
self.check_advance(model, match) # <all> match
self.check_stop(model)
self.assertIsNone(model.element)
if self.schema_class.XSD_VERSION == '1.0':
model.restart()
self.assertEqual(model.element, group[0])
for match in [False, False, False, False, True]:
self.check_advance(model, match) # <all> match
self.check_stop(model)
self.assertIsNone(model.element)
model.restart()
self.assertEqual(model.element, group[0])
for match in [False, False, False, False, True, False, True, False, False, False]:
self.check_advance(model, match) # <all> match, <attributeGroup> match
self.assertIsNone(model.element)
model.restart()
self.assertEqual(model.element, group[0])
for match in [False, False, False, False, True, False, True, False, False, False]:
self.check_advance(model, match) # <all> match, <attributeGroup> match
self.assertIsNone(model.element)
def test_schema_document_model(self):
def test_meta_schema_document_model(self):
group = self.schema_class.meta_schema.elements['schema'].type.content_type
# A schema model with a wrong tag
model = ModelVisitor(group)
self.assertEqual(model.element, group[0][0])
self.check_advance_false(model) # eg. anyAttribute
self.check_stop(model)
if self.schema_class.XSD_VERSION == '1.0':
self.assertEqual(model.element, group[0][0])
self.check_advance_false(model) # eg. anyAttribute
self.check_stop(model)
else:
self.assertEqual(model.element, group[0][0][0])
#
# Tests on schema test_cases/features/models/models.xsd
@ -540,9 +573,14 @@ class TestModelValidation(XsdValidatorTestCase):
self.check_stop(model)
class TestModelValidation11(TestModelValidation):
schema_class = XMLSchema11
class TestModelBasedSorting(XsdValidatorTestCase):
def test_sort_content(self):
# test of ModelVisitor's sort_content/iter_unordered_content
schema = self.get_schema("""
<xs:element name="A" type="A_type" />
<xs:complexType name="A_type">
@ -604,6 +642,161 @@ class TestModelBasedSorting(XsdValidatorTestCase):
model.sort_content([('B3', True), ('B2', 10)]), [('B2', 10), ('B3', True)]
)
def test_iter_collapsed_content_with_optional_elements(self):
schema = self.get_schema("""
<xs:element name="A" type="A_type" />
<xs:complexType name="A_type">
<xs:sequence>
<xs:element name="B1" minOccurs="0" />
<xs:element name="B2" minOccurs="0" />
<xs:element name="B3" />
<xs:element name="B4" />
<xs:element name="B5" />
<xs:element name="B6" minOccurs="0" />
<xs:element name="B7" />
</xs:sequence>
</xs:complexType>
""")
model = ModelVisitor(schema.types['A_type'].content_type)
content = [('B3', 10), ('B4', None), ('B5', True), ('B6', 'alpha'), ('B7', 20)]
model.restart()
self.assertListEqual(
list(model.iter_collapsed_content(content)), content
)
content = [('B3', 10), ('B5', True), ('B6', 'alpha'), ('B7', 20)] # Missing B4
model.restart()
self.assertListEqual(
list(model.iter_collapsed_content(content)), content
)
def test_iter_collapsed_content_with_repeated_elements(self):
schema = self.get_schema("""
<xs:element name="A" type="A_type" />
<xs:complexType name="A_type">
<xs:sequence>
<xs:element name="B1" minOccurs="0" />
<xs:element name="B2" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="B3" maxOccurs="unbounded" />
<xs:element name="B4" />
<xs:element name="B5" maxOccurs="unbounded" />
<xs:element name="B6" minOccurs="0" />
<xs:element name="B7" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
""")
model = ModelVisitor(schema.types['A_type'].content_type)
content = [
('B3', 10), ('B4', None), ('B5', True), ('B5', False), ('B6', 'alpha'), ('B7', 20)
]
self.assertListEqual(
list(model.iter_collapsed_content(content)), content
)
content = [('B3', 10), ('B3', 11), ('B3', 12), ('B4', None), ('B5', True),
('B5', False), ('B6', 'alpha'), ('B7', 20), ('B7', 30)]
model.restart()
self.assertListEqual(
list(model.iter_collapsed_content(content)), content
)
content = [('B3', 10), ('B3', 11), ('B3', 12), ('B4', None), ('B5', True), ('B5', False)]
model.restart()
self.assertListEqual(
list(model.iter_collapsed_content(content)), content
)
def test_iter_collapsed_content_with_repeated_groups(self):
schema = self.get_schema("""
<xs:element name="A" type="A_type" />
<xs:complexType name="A_type">
<xs:sequence minOccurs="1" maxOccurs="2">
<xs:element name="B1" minOccurs="0" />
<xs:element name="B2" minOccurs="0" />
</xs:sequence>
</xs:complexType>
""")
model = ModelVisitor(schema.types['A_type'].content_type)
content = [('B1', 1), ('B1', 2), ('B2', 3), ('B2', 4)]
self.assertListEqual(
list(model.iter_collapsed_content(content)),
[('B1', 1), ('B2', 3), ('B1', 2), ('B2', 4)]
)
# Model broken by unknown element at start
content = [('X', None), ('B1', 1), ('B1', 2), ('B2', 3), ('B2', 4)]
model.restart()
self.assertListEqual(list(model.iter_collapsed_content(content)), content)
content = [('B1', 1), ('X', None), ('B1', 2), ('B2', 3), ('B2', 4)]
model.restart()
self.assertListEqual(list(model.iter_collapsed_content(content)), content)
content = [('B1', 1), ('B1', 2), ('X', None), ('B2', 3), ('B2', 4)]
model.restart()
self.assertListEqual(list(model.iter_collapsed_content(content)), content)
content = [('B1', 1), ('B1', 2), ('B2', 3), ('X', None), ('B2', 4)]
model.restart()
self.assertListEqual(
list(model.iter_collapsed_content(content)),
[('B1', 1), ('B2', 3), ('B1', 2), ('X', None), ('B2', 4)]
)
content = [('B1', 1), ('B1', 2), ('B2', 3), ('B2', 4), ('X', None)]
model.restart()
self.assertListEqual(
list(model.iter_collapsed_content(content)),
[('B1', 1), ('B2', 3), ('B1', 2), ('B2', 4), ('X', None)]
)
def test_iter_collapsed_content_with_single_elements(self):
schema = self.get_schema("""
<xs:element name="A" type="A_type" />
<xs:complexType name="A_type">
<xs:sequence>
<xs:element name="B1" />
<xs:element name="B2" />
<xs:element name="B3" />
</xs:sequence>
</xs:complexType>
""")
model = ModelVisitor(schema.types['A_type'].content_type)
content = [('B1', 'abc'), ('B2', 10), ('B3', False)]
model.restart()
self.assertListEqual(list(model.iter_collapsed_content(content)), content)
content = [('B3', False), ('B1', 'abc'), ('B2', 10)]
model.restart()
self.assertListEqual(list(model.iter_collapsed_content(content)), content)
content = [('B1', 'abc'), ('B3', False), ('B2', 10)]
model.restart()
self.assertListEqual(list(model.iter_collapsed_content(content)), content)
content = [('B1', 'abc'), ('B1', 'def'), ('B2', 10), ('B3', False)]
model.restart()
self.assertListEqual(
list(model.iter_collapsed_content(content)),
[('B1', 'abc'), ('B2', 10), ('B3', False), ('B1', 'def')]
)
content = [('B1', 'abc'), ('B2', 10), ('X', None)]
model.restart()
self.assertListEqual(list(model.iter_collapsed_content(content)), content)
content = [('X', None), ('B1', 'abc'), ('B2', 10), ('B3', False)]
model.restart()
self.assertListEqual(list(model.iter_collapsed_content(content)), content)
if __name__ == '__main__':
from xmlschema.tests import print_test_header

View File

@ -390,6 +390,10 @@ class TestPatterns(unittest.TestCase):
self.assertEqual(regex, r'^([^\w\W])$')
self.assertRaises(XMLSchemaRegexError, get_python_regex, '[]')
def test_character_class_range(self):
regex = get_python_regex('[bc-]')
self.assertEqual(regex, r'^([\-bc])$')
if __name__ == '__main__':
from xmlschema.tests import print_test_header

View File

@ -26,8 +26,9 @@ from xmlschema import (
)
from xmlschema.tests import casepath
from xmlschema.compat import urlopen, urlsplit, uses_relative, StringIO
from xmlschema.etree import ElementTree, PyElementTree, lxml_etree, is_etree_element, \
from xmlschema.etree import ElementTree, PyElementTree, lxml_etree, \
etree_element, py_etree_element
from xmlschema.helpers import is_etree_element
def is_windows_path(path):

View File

@ -98,6 +98,18 @@ SKIPPED_TESTS = {
# Invalid XML tests
'../msData/additional/test93490_4.xml', # 4795: https://www.w3.org/Bugs/Public/show_bug.cgi?id=4078
'../msData/additional/test93490_8.xml', # 4799: Idem
# Skip for missing XML version 1.1 implementation
'../saxonData/XmlVersions/xv001.v01.xml', # 14850
'../saxonData/XmlVersions/xv003.v01.xml', # 14852
'../saxonData/XmlVersions/xv005.v01.xml', # 14854
'../saxonData/XmlVersions/xv006.v01.xml', # 14855: invalid character &#x07 (valid in XML 1.1)
'../saxonData/XmlVersions/xv006.n02.xml', # 14855: invalid character &#x10000 (valid in XML 1.1)
'../saxonData/XmlVersions/xv008.v01.xml', # 14857
'../saxonData/XmlVersions/xv008.n01.xml', # 14857
# Skip for TODO
'../sunData/combined/005/test.1.v.xml', # 3959: is valid but needs equality operators (#cos-ct-derived-ok)
}
XSD11_SKIPPED_TESTS = {
@ -185,7 +197,10 @@ def create_w3c_test_group_case(filename, group_elem, group_num, xsd_version='1.0
return
if source_href in SKIPPED_TESTS:
if args.numbers:
print("Skip test number %d ..." % testgroup_num)
if source_href.endswith('.xsd'):
print("Skip test number %d ..." % testgroup_num)
else:
print("Skip file %r for test number %d ..." % (source_href, testgroup_num))
return
# Normalize and check file path
@ -197,7 +212,9 @@ def create_w3c_test_group_case(filename, group_elem, group_num, xsd_version='1.0
test_conf = {}
for version in xsd_version.split():
if version not in args.version:
if 'version' in elem.attrib and version not in elem.attrib['version']:
continue
elif version not in args.version:
continue
elif version == '1.1' and source_href in XSD11_SKIPPED_TESTS:
continue
@ -227,9 +244,18 @@ def create_w3c_test_group_case(filename, group_elem, group_num, xsd_version='1.0
if test_conf:
test_conf['source'] = source_path
if schema_test and not source_path.endswith('.xml'):
test_conf['sources'] = [
os.path.normpath(
os.path.join(os.path.dirname(filename), schema_href.get('{%s}href' % XLINK_NAMESPACE))
)
for schema_href in elem.findall(tag)
]
return test_conf
if args.numbers and testgroup_num not in args.numbers:
if group_num == 1:
return # Skip introspection tests that have several failures due to schema mismatch.
elif args.numbers and group_num not in args.numbers:
return
name = group_elem.attrib['name']
@ -264,25 +290,37 @@ def create_w3c_test_group_case(filename, group_elem, group_num, xsd_version='1.0
class TestGroupCase(unittest.TestCase):
@unittest.skipIf(not any(g['source'].endswith('.xsd') for g in group_tests), 'No schema tests')
@unittest.skipIf(group_tests[0]['source'].endswith('.xml'), 'No schema test')
def test_xsd_schema(self):
for item in filter(lambda x: x['source'].endswith('.xsd'), group_tests):
source = item['source']
rel_path = os.path.relpath(source)
for version, expected in sorted(filter(lambda x: x[0] != 'source', item.items())):
for version, expected in sorted(filter(lambda x: not x[0].startswith('source'), item.items())):
schema_class = XMLSchema11 if version == '1.1' else XMLSchema10
if expected == 'invalid':
message = "schema %s should be invalid with XSD %s" % (rel_path, version)
with self.assertRaises(XMLSchemaException, msg=message):
with warnings.catch_warnings():
warnings.simplefilter('ignore')
schema_class(source, use_meta=False)
if len(item['sources']) <= 1:
schema_class(source, use_meta=False)
else:
schema = schema_class(source, use_meta=False, build=False)
for other in item['sources'][1:]:
schema_class(other, global_maps=schema.maps, build=False)
schema.build()
else:
try:
with warnings.catch_warnings():
warnings.simplefilter('ignore')
schema = schema_class(source, use_meta=False)
if len(item['sources']) <= 1:
schema = schema_class(source, use_meta=False)
else:
schema = schema_class(source, use_meta=False, build=False)
for other in item['sources'][1:]:
schema_class(other, global_maps=schema.maps, build=False)
schema.build()
except XMLSchemaException as err:
schema = None
message = "schema %s should be valid with XSD %s, but an error is raised:" \
@ -292,12 +330,14 @@ def create_w3c_test_group_case(filename, group_elem, group_num, xsd_version='1.0
self.assertIsInstance(schema, schema_class, msg=message)
@unittest.skipIf(not any(g['source'].endswith('.xml') for g in group_tests), 'No instance tests')
@unittest.skipIf(group_tests[0]['source'].endswith('.xsd') and len(group_tests) == 1, 'No instance tests')
def test_xml_instances(self):
if group_tests[0]['source'].endswith('.xsd'):
schema = group_tests[0]['source']
schemas = group_tests[0]['sources']
else:
schema = None
schemas = []
for item in filter(lambda x: not x['source'].endswith('.xsd'), group_tests):
source = item['source']
@ -310,12 +350,27 @@ def create_w3c_test_group_case(filename, group_elem, group_num, xsd_version='1.0
with self.assertRaises((XMLSchemaException, ElementTree.ParseError), msg=message):
with warnings.catch_warnings():
warnings.simplefilter('ignore')
validate(source, schema=schema, cls=schema_class)
if len(schemas) <= 1:
validate(source, schema=schema, cls=schema_class)
else:
xs = schema_class(schemas[0], use_meta=False, build=False)
for other in schemas[1:]:
schema_class(other, global_maps=xs.maps, build=False)
xs.build()
xs.validate(source)
else:
try:
with warnings.catch_warnings():
warnings.simplefilter('ignore')
validate(source, schema=schema, cls=schema_class)
if len(schemas) <= 1:
validate(source, schema=schema, cls=schema_class)
else:
xs = schema_class(schemas[0], use_meta=False, build=False)
for other in schemas[1:]:
schema_class(other, global_maps=xs.maps, build=False)
xs.build()
xs.validate(source)
except (XMLSchemaException, ElementTree.ParseError) as err:
error = "instance %s should be valid with XSD %s, but an error " \
"is raised:\n\n%s" % (rel_path, version, str(err))

View File

@ -45,43 +45,43 @@ class XsdXPathTest(unittest.TestCase):
self.assertTrue(self.xs1.findall('.'))
self.assertTrue(isinstance(self.xs1.find('.'), XMLSchema))
self.assertTrue(sorted(self.xs1.findall("*"), key=lambda x: x.name) == elements)
self.assertTrue(self.xs1.findall("*") == self.xs1.findall("./*"))
self.assertTrue(self.xs1.find("./vh:bikes") == self.xs1.elements['bikes'])
self.assertTrue(self.xs1.find("./vh:vehicles/vh:cars").name == self.xs1.elements['cars'].name)
self.assertFalse(self.xs1.find("./vh:vehicles/vh:cars") == self.xs1.elements['cars'])
self.assertFalse(self.xs1.find("/vh:vehicles/vh:cars") == self.xs1.elements['cars'])
self.assertTrue(self.xs1.find("vh:vehicles/vh:cars/..") == self.xs1.elements['vehicles'])
self.assertTrue(self.xs1.find("vh:vehicles/*/..") == self.xs1.elements['vehicles'])
self.assertTrue(self.xs1.find("vh:vehicles/vh:cars/../vh:cars") == self.xs1.find("vh:vehicles/vh:cars"))
self.assertListEqual(self.xs1.findall("*"), self.xs1.findall("./*"))
self.assertEqual(self.xs1.find("./vh:bikes"), self.xs1.elements['bikes'])
self.assertEqual(self.xs1.find("./vh:vehicles/vh:cars").name, self.xs1.elements['cars'].name)
self.assertNotEqual(self.xs1.find("./vh:vehicles/vh:cars"), self.xs1.elements['cars'])
self.assertNotEqual(self.xs1.find("/vh:vehicles/vh:cars"), self.xs1.elements['cars'])
self.assertEqual(self.xs1.find("vh:vehicles/vh:cars/.."), self.xs1.elements['vehicles'])
self.assertEqual(self.xs1.find("vh:vehicles/*/.."), self.xs1.elements['vehicles'])
self.assertEqual(self.xs1.find("vh:vehicles/vh:cars/../vh:cars"), self.xs1.find("vh:vehicles/vh:cars"))
def test_xpath_axis(self):
self.assertTrue(self.xs1.find("vh:vehicles/child::vh:cars/..") == self.xs1.elements['vehicles'])
self.assertEqual(self.xs1.find("vh:vehicles/child::vh:cars/.."), self.xs1.elements['vehicles'])
def test_xpath_subscription(self):
self.assertTrue(len(self.xs1.findall("./vh:vehicles/*")) == 2)
self.assertTrue(self.xs1.findall("./vh:vehicles/*[2]") == [self.bikes])
self.assertTrue(self.xs1.findall("./vh:vehicles/*[3]") == [])
self.assertTrue(self.xs1.findall("./vh:vehicles/*[last()-1]") == [self.cars])
self.assertTrue(self.xs1.findall("./vh:vehicles/*[position()=last()]") == [self.bikes])
self.assertEqual(len(self.xs1.findall("./vh:vehicles/*")), 2)
self.assertListEqual(self.xs1.findall("./vh:vehicles/*[2]"), [self.bikes])
self.assertListEqual(self.xs1.findall("./vh:vehicles/*[3]"), [])
self.assertListEqual(self.xs1.findall("./vh:vehicles/*[last()-1]"), [self.cars])
self.assertListEqual(self.xs1.findall("./vh:vehicles/*[position()=last()]"), [self.bikes])
def test_xpath_group(self):
self.assertTrue(self.xs1.findall("/(vh:vehicles/*/*)") == self.xs1.findall("/vh:vehicles/*/*"))
self.assertTrue(self.xs1.findall("/(vh:vehicles/*/*)[1]") == self.xs1.findall("/vh:vehicles/*/*[1]"))
self.assertEqual(self.xs1.findall("/(vh:vehicles/*/*)"), self.xs1.findall("/vh:vehicles/*/*"))
self.assertEqual(self.xs1.findall("/(vh:vehicles/*/*)[1]"), self.xs1.findall("/vh:vehicles/*/*[1]")[:1])
def test_xpath_predicate(self):
car = self.xs1.elements['cars'].type.content_type[0]
self.assertTrue(self.xs1.findall("./vh:vehicles/vh:cars/vh:car[@make]") == [car])
self.assertTrue(self.xs1.findall("./vh:vehicles/vh:cars/vh:car[@make]") == [car])
self.assertTrue(self.xs1.findall("./vh:vehicles/vh:cars['ciao']") == [self.cars])
self.assertTrue(self.xs1.findall("./vh:vehicles/*['']") == [])
self.assertListEqual(self.xs1.findall("./vh:vehicles/vh:cars/vh:car[@make]"), [car])
self.assertListEqual(self.xs1.findall("./vh:vehicles/vh:cars/vh:car[@make]"), [car])
self.assertListEqual(self.xs1.findall("./vh:vehicles/vh:cars['ciao']"), [self.cars])
self.assertListEqual(self.xs1.findall("./vh:vehicles/*['']"), [])
def test_xpath_descendants(self):
selector = Selector('.//xs:element', self.xs2.namespaces, parser=XPath1Parser)
elements = list(selector.iter_select(self.xs2.root))
self.assertTrue(len(elements) == 14)
self.assertEqual(len(elements), 14)
selector = Selector('.//xs:element|.//xs:attribute|.//xs:keyref', self.xs2.namespaces, parser=XPath1Parser)
elements = list(selector.iter_select(self.xs2.root))
self.assertTrue(len(elements) == 17)
self.assertEqual(len(elements), 17)
def test_xpath_issues(self):
namespaces = {'ps': "http://schemas.microsoft.com/powershell/2004/04"}

View File

@ -316,6 +316,52 @@ class TestDecoding(XsdValidatorTestCase):
xml_dict = xmlschema.to_dict(col_xml_string, self.col_schema.url, namespaces=self.col_namespaces)
self.assertTrue(xml_dict, COLLECTION_DICT)
def test_date_decoding(self):
# Issue #136
schema = xmlschema.XMLSchema("""<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0">
<xs:element name="Date">
<xs:simpleType>
<xs:restriction base="xs:date">
<xs:minInclusive value="2000-01-01"/>
<xs:maxInclusive value="2099-12-31"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:schema>""")
self.assertEqual(schema.to_dict("<Date>2019-01-01</Date>"), '2019-01-01')
self.assertEqual(schema.to_dict("<Date>2019-01-01</Date>", datetime_types=True),
datatypes.Date10.fromstring('2019-01-01'))
data, errors = schema.to_dict("<Date>2019-01-01</Date>", validation='lax')
self.assertEqual(data, '2019-01-01')
self.assertEqual(errors, [])
data, errors = schema.to_dict("<Date>2019-01-01</Date>", validation='lax', datetime_types=True)
self.assertEqual(data, datatypes.Date10.fromstring('2019-01-01'))
self.assertEqual(errors, [])
data, errors = schema.to_dict("<Date>1999-12-31</Date>", validation='lax')
self.assertEqual(data, '1999-12-31')
self.assertEqual(len(errors), 1)
self.assertIn('value has to be greater or equal than', unicode_type(errors[0]))
data, errors = schema.to_dict("<Date>1999-12-31</Date>", validation='lax', datetime_types=True)
self.assertEqual(data, datatypes.Date10.fromstring('1999-12-31'))
self.assertEqual(len(errors), 1)
data, errors = schema.to_dict("<Date>2019</Date>", validation='lax')
self.assertIsNone(data)
self.assertEqual(len(errors), 1)
with self.assertRaises(XMLSchemaValidationError):
schema.to_dict("<Date>2019</Date>")
data, errors = schema.to_dict("<Date>2019</Date>", validation='lax')
self.assertIsNone(data)
self.assertEqual(len(errors), 1)
def test_json_dump_and_load(self):
vh_xml_tree = ElementTree.parse(self.vh_xml_file)
col_xml_tree = ElementTree.parse(self.col_xml_file)

View File

@ -15,9 +15,10 @@ import unittest
from xmlschema import XMLSchemaEncodeError, XMLSchemaValidationError
from xmlschema.converters import UnorderedConverter
from xmlschema.compat import unicode_type, ordered_dict_class
from xmlschema.etree import etree_element, etree_tostring, is_etree_element, ElementTree
from xmlschema.qnames import local_name
from xmlschema.etree import etree_element, etree_tostring, ElementTree
from xmlschema.validators.exceptions import XMLSchemaChildrenValidationError
from xmlschema.helpers import local_name
from xmlschema.helpers import is_etree_element
from xmlschema.tests import XsdValidatorTestCase
from xmlschema.validators import XMLSchema11
@ -373,8 +374,8 @@ class TestEncoding(XsdValidatorTestCase):
</xs:element>
""")
with self.assertRaises(XMLSchemaChildrenValidationError):
schema.to_etree({"A": [1, 2], "B": [3, 4]})
root = schema.to_etree(ordered_dict_class([('A', [1, 2]), ('B', [3, 4])]))
self.assertListEqual([e.text for e in root], ['1', '3', '2', '4'])
root = schema.to_etree({"A": [1, 2], "B": [3, 4]}, converter=UnorderedConverter)
self.assertListEqual([e.text for e in root], ['1', '3', '2', '4'])

View File

@ -273,6 +273,32 @@ class TestXsdComplexType(XsdValidatorTestCase):
</xs:sequence>
</xs:complexType>""")
def test_upa_violation_with_wildcard(self):
self.check_schema("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="tns" xmlns:ns="tns" elementFormDefault="unqualified">
<xs:complexType name="baseType">
<xs:sequence>
<xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"></xs:any>
</xs:sequence>
</xs:complexType>
<xs:complexType name="addressType">
<xs:complexContent>
<xs:extension base="ns:baseType">
<xs:sequence>
<xs:element name="state" type="xs:string" />
<xs:element name="currency" type="xs:string" />
<xs:element name="zip" type="xs:int" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
""", XMLSchemaModelError if self.schema_class.XSD_VERSION == '1.0' else None)
class TestXsd11ComplexType(TestXsdComplexType):

View File

@ -110,6 +110,22 @@ class TestXsdWildcards(XsdValidatorTestCase):
</xs:complexType>""")
self.assertEqual(schema.types['taggedType'].attributes[None].namespace, [''])
def test_namespace_variants(self):
schema = self.schema_class("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="tns1">
<xs:group name="group1">
<xs:sequence>
<xs:any namespace="urn:a" processContents="skip"/>
<xs:any namespace="" processContents="lax"/>
</xs:sequence>
</xs:group>
</xs:schema>""")
any1 = schema.groups['group1'][0]
self.assertEqual(any1.namespace, ['urn:a'])
any2 = schema.groups['group1'][1]
self.assertEqual(any2.namespace, [])
class TestXsd11Wildcards(TestXsdWildcards):
@ -158,31 +174,136 @@ class TestXsd11Wildcards(TestXsdWildcards):
self.assertFalse(any2.is_restriction(any1))
self.assertTrue(any3.is_restriction(any1))
def test_extend(self):
def test_wildcard_union(self):
schema = self.schema_class("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="tns1">
<xs:group name="group1">
<xs:sequence>
<xs:any namespace="tns1"/>
<xs:any namespace="tns1 tns2"/>
<xs:any notNamespace="tns1"/>
<xs:any notNamespace="tns1 tns2"/>
<xs:any namespace="tns1"/> <xs:any namespace="tns1 tns2"/>
<xs:any notNamespace="tns1"/> <xs:any notNamespace="tns1 tns2"/>
<xs:any namespace="##any"/> <xs:any notNamespace="tns1"/>
<xs:any namespace="##other"/> <xs:any notNamespace="tns1"/>
<xs:any notNamespace="tns1"/> <xs:any namespace="##other"/>
<xs:any namespace="##other"/> <xs:any notNamespace="##local tns1"/>
<xs:any namespace="##other"/> <xs:any notNamespace="tns2"/>
</xs:sequence>
</xs:group>
</xs:schema>""")
any1, any2, any3, any4 = schema.groups['group1'][:]
# <xs:any namespace="tns1"/> <xs:any namespace="tns1 tns2"/>
any1, any2 = schema.groups['group1'][:2]
self.assertListEqual(any1.namespace, ['tns1'])
any1.extend(any2)
any1.union(any2)
self.assertListEqual(any1.namespace, ['tns1', 'tns2'])
self.assertListEqual(any3.namespace, [])
self.assertListEqual(any3.not_namespace, ['tns1'])
any3.extend(any4)
self.assertListEqual(any3.not_namespace, ['tns1'])
any4.extend(any3)
self.assertListEqual(any4.not_namespace, ['tns1'])
# <xs:any notNamespace="tns1"/> <xs:any notNamespace="tns1 tns2"/>
any1, any2 = schema.groups['group1'][2:4]
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['tns1'])
any1.union(any2)
self.assertListEqual(any1.not_namespace, ['tns1'])
any2.union(any1)
self.assertListEqual(any2.not_namespace, ['tns1'])
# <xs:any namespace="##any"/> <xs:any notNamespace="tns1"/>
any1, any2 = schema.groups['group1'][4:6]
any1.union(any2)
self.assertEqual(any1.namespace, ('##any',))
self.assertEqual(any1.not_namespace, ())
# <xs:any namespace="##other"/> <xs:any notNamespace="tns1"/>
any1, any2 = schema.groups['group1'][6:8]
any1.union(any2)
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['tns1'])
# <xs:any notNamespace="tns1"/> <xs:any namespace="##other"/>
any1, any2 = schema.groups['group1'][8:10]
any1.union(any2)
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['tns1'])
# <xs:any namespace="##other"/> <xs:any notNamespace="##local tns1"/>
any1, any2 = schema.groups['group1'][10:12]
any1.union(any2)
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['', 'tns1'])
# <xs:any namespace="##other"/> <xs:any notNamespace="tns2"/>
any1, any2 = schema.groups['group1'][12:14]
any1.union(any2)
self.assertListEqual(any1.namespace, ['##any'])
self.assertListEqual(any1.not_namespace, [])
def test_wildcard_intersection(self):
schema = self.schema_class("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="tns1">
<xs:group name="group1">
<xs:sequence>
<xs:any namespace="tns1"/> <xs:any namespace="tns1 tns2"/>
<xs:any notNamespace="tns1"/> <xs:any notNamespace="tns1 tns2"/>
<xs:any namespace="##any"/> <xs:any notNamespace="tns1"/>
<xs:any namespace="##other"/> <xs:any notNamespace="tns1"/>
<xs:any notNamespace="tns1"/> <xs:any namespace="##other"/>
<xs:any namespace="##other"/> <xs:any notNamespace="##local tns1"/>
<xs:any namespace="##other"/> <xs:any notNamespace="tns2"/>
<xs:any namespace="##any" notQName="##defined qn1"/>
<xs:any namespace="##local" notQName="##defined"/>
</xs:sequence>
</xs:group>
</xs:schema>""")
# <xs:any namespace="tns1"/> <xs:any namespace="tns1 tns2"/>
any1, any2 = schema.groups['group1'][:2]
self.assertListEqual(any1.namespace, ['tns1'])
any1.intersection(any2)
self.assertListEqual(any1.namespace, ['tns1'])
# <xs:any notNamespace="tns1"/> <xs:any notNamespace="tns1 tns2"/>
any1, any2 = schema.groups['group1'][2:4]
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['tns1'])
any1.intersection(any2)
self.assertListEqual(any1.not_namespace, ['tns1', 'tns2'])
any2.intersection(any1)
self.assertListEqual(any2.not_namespace, ['tns1', 'tns2'])
# <xs:any namespace="##any"/> <xs:any notNamespace="tns1"/>
any1, any2 = schema.groups['group1'][4:6]
any1.intersection(any2)
self.assertEqual(any1.namespace, [])
self.assertEqual(any1.not_namespace, ['tns1'])
# <xs:any namespace="##other"/> <xs:any notNamespace="tns1"/>
any1, any2 = schema.groups['group1'][6:8]
any1.intersection(any2)
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['tns1', ''])
# <xs:any notNamespace="tns1"/> <xs:any namespace="##other"/>
any1, any2 = schema.groups['group1'][8:10]
any1.intersection(any2)
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['tns1', ''])
# <xs:any namespace="##other"/> <xs:any notNamespace="##local tns1"/>
any1, any2 = schema.groups['group1'][10:12]
any1.intersection(any2)
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['', 'tns1'])
# <xs:any namespace="##other"/> <xs:any notNamespace="tns2"/>
any1, any2 = schema.groups['group1'][12:14]
any1.intersection(any2)
self.assertListEqual(any1.namespace, [])
self.assertListEqual(any1.not_namespace, ['tns2', 'tns1', ''])
# <xs:any namespace="##any" notQName="##defined qn1"/>
# <xs:any namespace="##local" notQName="##defined"/>
any1, any2 = schema.groups['group1'][14:16]
any1.intersection(any2)
self.assertListEqual(any1.namespace, [''])
self.assertListEqual(any1.not_qname, ['##defined', 'qn1'])
def test_open_content_mode_interleave(self):
schema = self.check_schema("""

View File

@ -9,7 +9,8 @@
# @author Davide Brunato <brunato@sissa.it>
#
from __future__ import unicode_literals
from elementpath import datatypes, XPath2Parser, XPathContext, ElementPathError
from elementpath import XPath2Parser, XPathContext, ElementPathError
from elementpath.datatypes import XSD_BUILTIN_TYPES
from ..qnames import XSD_ASSERT
from ..xpath import ElementPathMixin, XMLSchemaProxy
@ -32,58 +33,83 @@ class XsdAssert(XsdComponent, ElementPathMixin):
"""
_ADMITTED_TAGS = {XSD_ASSERT}
token = None
parser = None
path = 'true()'
def __init__(self, elem, schema, parent, base_type):
self.base_type = base_type
super(XsdAssert, self).__init__(elem, schema, parent)
def __repr__(self):
return '%s(test=%r)' % (self.__class__.__name__, self.path)
def _parse(self):
super(XsdAssert, self)._parse()
if self.base_type.is_complex():
if self.base_type.is_simple():
self.parse_error("base_type=%r is not a complexType definition" % self.base_type)
else:
try:
self.path = self.elem.attrib['test']
self.path = self.elem.attrib['test'].strip()
except KeyError as err:
self.parse_error(str(err), elem=self.elem)
self.path = 'true()'
if not self.base_type.has_simple_content():
variables = {'value': datatypes.XSD_BUILTIN_TYPES['anyType'].value}
else:
try:
builtin_type_name = self.base_type.content_type.primitive_type.local_name
except AttributeError:
variables = {'value': datatypes.XSD_BUILTIN_TYPES['anySimpleType'].value}
else:
variables = {'value': datatypes.XSD_BUILTIN_TYPES[builtin_type_name].value}
else:
self.parse_error("base_type=%r is not a complexType definition" % self.base_type)
self.path = 'true()'
variables = None
if 'xpathDefaultNamespace' in self.elem.attrib:
self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
else:
self.xpath_default_namespace = self.schema.xpath_default_namespace
self.parser = XPath2Parser(self.namespaces, strict=False, variables=variables,
default_namespace=self.xpath_default_namespace)
@property
def built(self):
return self.token is not None and (self.base_type.is_global or self.base_type.built)
return self.token is not None and (self.base_type.parent is None or self.base_type.built)
def parse_xpath_test(self):
self.parser.schema = XMLSchemaProxy(self.schema, self)
if not self.base_type.has_simple_content():
variables = {'value': XSD_BUILTIN_TYPES['anyType'].value}
else:
try:
builtin_type_name = self.base_type.content_type.primitive_type.local_name
except AttributeError:
variables = {'value': XSD_BUILTIN_TYPES['anySimpleType'].value}
else:
variables = {'value': XSD_BUILTIN_TYPES[builtin_type_name].value}
self.parser = XPath2Parser(
namespaces=self.namespaces,
variables=variables,
strict=False,
default_namespace=self.xpath_default_namespace,
schema=XMLSchemaProxy(self.schema, self)
)
try:
self.token = self.parser.parse(self.path)
except ElementPathError as err:
self.parse_error(err, elem=self.elem)
self.token = self.parser.parse('true()')
def __call__(self, elem):
if not self.token.evaluate(XPathContext(root=elem)):
msg = "expression is not true with test path %r."
yield XMLSchemaValidationError(self, obj=elem, reason=msg % self.path)
def __call__(self, elem, value=None, source=None, namespaces=None, **kwargs):
if value is not None:
self.parser.variables['value'] = self.base_type.text_decode(value)
if not self.parser.is_schema_bound():
self.parser.schema.bind_parser(self.parser)
if source is None:
context = XPathContext(root=elem)
else:
context = XPathContext(root=source.root, item=elem)
default_namespace = self.parser.namespaces['']
if namespaces and '' in namespaces:
self.parser.namespaces[''] = namespaces['']
try:
if not self.token.evaluate(context.copy()):
msg = "expression is not true with test path %r."
yield XMLSchemaValidationError(self, obj=elem, reason=msg % self.path)
except ElementPathError as err:
yield XMLSchemaValidationError(self, obj=elem, reason=str(err))
self.parser.namespaces[''] = default_namespace
# For implementing ElementPathMixin
def __iter__(self):
@ -98,3 +124,7 @@ class XsdAssert(XsdComponent, ElementPathMixin):
@property
def type(self):
return self.parent
@property
def xpath_proxy(self):
return XMLSchemaProxy(self.schema, self)

View File

@ -19,8 +19,9 @@ from ..compat import MutableMapping, ordered_dict_class
from ..exceptions import XMLSchemaAttributeError, XMLSchemaTypeError, XMLSchemaValueError
from ..qnames import XSD_ANNOTATION, XSD_ANY_SIMPLE_TYPE, XSD_SIMPLE_TYPE, \
XSD_ATTRIBUTE_GROUP, XSD_COMPLEX_TYPE, XSD_RESTRICTION, XSD_EXTENSION, \
XSD_SEQUENCE, XSD_ALL, XSD_CHOICE, XSD_ATTRIBUTE, XSD_ANY_ATTRIBUTE
from ..helpers import get_namespace, get_qname, get_xsd_form_attribute
XSD_SEQUENCE, XSD_ALL, XSD_CHOICE, XSD_ATTRIBUTE, XSD_ANY_ATTRIBUTE, \
get_namespace, get_qname
from ..helpers import get_xsd_form_attribute
from ..namespaces import XSI_NAMESPACE
from .exceptions import XMLSchemaValidationError
@ -88,6 +89,9 @@ class XsdAttribute(XsdComponent, ValidationMixin):
if 'default' in attrib:
self.default = attrib['default']
if 'fixed' in attrib:
self.fixed = attrib['fixed']
if self._parse_reference():
try:
xsd_attribute = self.maps.lookup_attribute(self.name)
@ -104,9 +108,11 @@ class XsdAttribute(XsdComponent, ValidationMixin):
self.default = xsd_attribute.default
if xsd_attribute.fixed is not None:
self.fixed = xsd_attribute.fixed
if 'fixed' in attrib and attrib['fixed'] != self.fixed:
self.parse_error("referenced attribute has a different fixed value %r" % xsd_attribute.fixed)
if self.fixed is None:
self.fixed = xsd_attribute.fixed
elif xsd_attribute.fixed != self.fixed:
msg = "referenced attribute has a different fixed value %r"
self.parse_error(msg % xsd_attribute.fixed)
for attribute in ('form', 'type'):
if attribute in self.elem.attrib:
@ -117,9 +123,6 @@ class XsdAttribute(XsdComponent, ValidationMixin):
self.parse_error("not allowed type definition for XSD attribute reference")
return
if 'fixed' in attrib:
self.fixed = attrib['fixed']
try:
form = get_xsd_form_attribute(self.elem, 'form')
except ValueError as err:
@ -379,7 +382,7 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
def _parse(self):
super(XsdAttributeGroup, self)._parse()
elem = self.elem
any_attribute = False
any_attribute = None
attribute_group_refs = []
if elem.tag == XSD_ATTRIBUTE_GROUP:
@ -390,18 +393,25 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
except KeyError:
self.parse_error("an attribute group declaration requires a 'name' attribute.")
return
else:
if self.schema.default_attributes == self.name and self.xsd_version > '1.0':
self.schema.default_attributes = self
attributes = ordered_dict_class()
for child in filter(lambda x: x.tag != XSD_ANNOTATION, elem):
if any_attribute:
if any_attribute is not None:
if child.tag == XSD_ANY_ATTRIBUTE:
self.parse_error("more anyAttribute declarations in the same attribute group")
else:
self.parse_error("another declaration after anyAttribute")
elif child.tag == XSD_ANY_ATTRIBUTE:
any_attribute = True
attributes[None] = self.schema.BUILDERS.any_attribute_class(child, self.schema, self)
any_attribute = self.schema.BUILDERS.any_attribute_class(child, self.schema, self)
if None in attributes:
attributes[None] = attributes[None].copy()
attributes[None].intersection(any_attribute)
else:
attributes[None] = any_attribute
elif child.tag == XSD_ATTRIBUTE:
attribute = self.schema.BUILDERS.attribute_class(child, self.schema, self)
@ -447,10 +457,14 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
else:
if not isinstance(base_attributes, tuple):
for name, attr in base_attributes.items():
if name is not None and name in attributes:
if name not in attributes:
attributes[name] = attr
elif name is not None:
self.parse_error("multiple declaration for attribute {!r}".format(name))
else:
attributes[name] = attr
attributes[None] = attributes[None].copy()
attributes[None].intersection(attr)
elif self.xsd_version == '1.0':
self.parse_error("Circular reference found between attribute groups "
"{!r} and {!r}".format(self.name, attribute_group_qname))
@ -474,7 +488,7 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
if name is None:
if self.derivation == 'extension':
try:
attr.extend(base_attr)
attr.union(base_attr)
except ValueError as err:
self.parse_error(err)
elif not attr.is_restriction(base_attr):
@ -490,7 +504,11 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
attr.type.normalize(attr.fixed) != base_attr.type.normalize(base_attr.fixed):
self.parse_error("Attribute %r: derived attribute has a different fixed value" % name)
self._attribute_group.update(self.base_attributes.items())
if self.redefine is not None:
pass # In case of redefinition do not copy base attributes
else:
self._attribute_group.update(self.base_attributes.items())
elif self.redefine is not None and not attribute_group_refs:
for name, attr in self._attribute_group.items():
if name is None:
@ -519,6 +537,10 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
self.clear()
self._attribute_group.update(attributes)
if None in self._attribute_group and None not in attributes and self.derivation == 'restriction':
wildcard = self._attribute_group[None].copy()
wildcard.namespace = wildcard.not_namespace = wildcard.not_qname = ()
self._attribute_group[None] = wildcard
if self.xsd_version == '1.0':
has_key = False
@ -601,6 +623,10 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
reason = "%r attribute not allowed for element." % name
yield self.validation_error(validation, reason, attrs, **kwargs)
continue
else:
if xsd_attribute.use == 'prohibited':
reason = "use of attribute %r is prohibited" % name
yield self.validation_error(validation, reason, attrs, **kwargs)
for result in xsd_attribute.iter_decode(value, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):

View File

@ -25,8 +25,21 @@ from elementpath import datatypes
from ..compat import PY3, long_type, unicode_type
from ..exceptions import XMLSchemaValueError
from ..qnames import *
from ..etree import etree_element, is_etree_element
from ..qnames import XSD_LENGTH, XSD_MIN_LENGTH, XSD_MAX_LENGTH, XSD_ENUMERATION, \
XSD_PATTERN, XSD_WHITE_SPACE, XSD_MIN_INCLUSIVE, XSD_MIN_EXCLUSIVE, XSD_MAX_INCLUSIVE, \
XSD_MAX_EXCLUSIVE, XSD_TOTAL_DIGITS, XSD_FRACTION_DIGITS, XSD_EXPLICIT_TIMEZONE, \
XSD_STRING, XSD_NORMALIZED_STRING, XSD_NAME, XSD_NCNAME, XSD_QNAME, XSD_TOKEN, \
XSD_NMTOKEN, XSD_ID, XSD_IDREF, XSD_LANGUAGE, XSD_DECIMAL, XSD_DOUBLE, XSD_FLOAT, \
XSD_INTEGER, XSD_BYTE, XSD_SHORT, XSD_INT, XSD_LONG, XSD_UNSIGNED_BYTE, \
XSD_UNSIGNED_SHORT, XSD_UNSIGNED_INT, XSD_UNSIGNED_LONG, XSD_POSITIVE_INTEGER, \
XSD_NEGATIVE_INTEGER, XSD_NON_NEGATIVE_INTEGER, XSD_NON_POSITIVE_INTEGER, \
XSD_GDAY, XSD_GMONTH, XSD_GMONTH_DAY, XSD_GYEAR, XSD_GYEAR_MONTH, XSD_TIME, XSD_DATE, \
XSD_DATETIME, XSD_DATE_TIME_STAMP, XSD_ENTITY, XSD_ANY_URI, XSD_BOOLEAN, \
XSD_DURATION, XSD_DAY_TIME_DURATION, XSD_YEAR_MONTH_DURATION, XSD_BASE64_BINARY, \
XSD_HEX_BINARY, XSD_NOTATION_TYPE, XSD_ERROR, XSD_ASSERTION, XSD_SIMPLE_TYPE, \
XSD_COMPLEX_TYPE, XSD_ANY_TYPE, XSD_ANY_ATOMIC_TYPE, XSD_ANY_SIMPLE_TYPE
from ..etree import etree_element
from ..helpers import is_etree_element
from .exceptions import XMLSchemaValidationError
from .facets import XSD_10_FACETS_BUILDERS, XSD_11_FACETS_BUILDERS
from .simple_types import XsdSimpleType, XsdAtomicBuiltin

View File

@ -11,12 +11,11 @@
from __future__ import unicode_literals
from ..exceptions import XMLSchemaValueError
from ..qnames import XSD_ANNOTATION, XSD_GROUP, XSD_ATTRIBUTE_GROUP, XSD_SEQUENCE, XSD_ALL, \
XSD_CHOICE, XSD_ANY_ATTRIBUTE, XSD_ATTRIBUTE, XSD_COMPLEX_CONTENT, XSD_RESTRICTION, \
XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_ANY_TYPE, XSD_SIMPLE_CONTENT, XSD_ANY_SIMPLE_TYPE, \
XSD_OPEN_CONTENT, XSD_ASSERT
from ..helpers import get_qname, local_name, get_xsd_derivation_attribute
from ..etree import etree_element
from ..qnames import XSD_ANNOTATION, XSD_GROUP, XSD_ATTRIBUTE_GROUP, XSD_SEQUENCE, \
XSD_ALL, XSD_CHOICE, XSD_ANY_ATTRIBUTE, XSD_ATTRIBUTE, XSD_COMPLEX_CONTENT, \
XSD_RESTRICTION, XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_ANY_TYPE, XSD_SIMPLE_CONTENT, \
XSD_ANY_SIMPLE_TYPE, XSD_OPEN_CONTENT, XSD_ASSERT, get_qname, local_name
from ..helpers import get_xsd_derivation_attribute
from .exceptions import XMLSchemaValidationError, XMLSchemaDecodeError
from .xsdbase import XsdType, ValidationMixin
@ -28,8 +27,6 @@ from .wildcards import XsdOpenContent
XSD_MODEL_GROUP_TAGS = {XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}
SEQUENCE_ELEMENT = etree_element(XSD_SEQUENCE)
class XsdComplexType(XsdType, ValidationMixin):
"""
@ -55,11 +52,10 @@ class XsdComplexType(XsdType, ValidationMixin):
mixed = False
assertions = ()
open_content = None
_block = None
_ADMITTED_TAGS = {XSD_COMPLEX_TYPE, XSD_RESTRICTION}
_CONTENT_TAIL_TAGS = {XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_ANY_ATTRIBUTE}
_block = None
_derivation = None
@staticmethod
def normalize(text):
@ -82,7 +78,7 @@ class XsdComplexType(XsdType, ValidationMixin):
def __repr__(self):
if self.name is not None:
return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name)
elif not hasattr(self, 'content_type'):
elif not hasattr(self, 'content_type') or not hasattr(self, 'attributes'):
return '%s(id=%r)' % (self.__class__.__name__, id(self))
else:
return '%s(content=%r, attributes=%r)' % (
@ -137,14 +133,10 @@ class XsdComplexType(XsdType, ValidationMixin):
content_elem = self._parse_child_component(elem, strict=False)
if content_elem is None or content_elem.tag in self._CONTENT_TAIL_TAGS:
#
# complexType with empty content
self.content_type = self.schema.BUILDERS.group_class(SEQUENCE_ELEMENT, self.schema, self)
self.content_type = self.schema.create_empty_content_group(self)
self._parse_content_tail(elem)
elif content_elem.tag in {XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
#
# complexType with child elements
self.content_type = self.schema.BUILDERS.group_class(content_elem, self.schema, self)
self._parse_content_tail(elem)
@ -156,11 +148,11 @@ class XsdComplexType(XsdType, ValidationMixin):
if derivation_elem is None:
return
self.base_type = self._parse_base_type(derivation_elem)
self.base_type = base_type = self._parse_base_type(derivation_elem)
if derivation_elem.tag == XSD_RESTRICTION:
self._parse_simple_content_restriction(derivation_elem, self.base_type)
self._parse_simple_content_restriction(derivation_elem, base_type)
else:
self._parse_simple_content_extension(derivation_elem, self.base_type)
self._parse_simple_content_extension(derivation_elem, base_type)
if content_elem is not elem[-1]:
k = 2 if content_elem is not elem[0] else 1
@ -183,7 +175,6 @@ class XsdComplexType(XsdType, ValidationMixin):
return
base_type = self._parse_base_type(derivation_elem, complex_content=True)
if base_type is not self:
self.base_type = base_type
elif self.redefine:
@ -202,7 +193,7 @@ class XsdComplexType(XsdType, ValidationMixin):
self.open_content = XsdOpenContent(content_elem, self.schema, self)
if content_elem is elem[-1]:
self.content_type = self.schema.BUILDERS.group_class(SEQUENCE_ELEMENT, self.schema, self)
self.content_type = self.schema.create_empty_content_group(self)
else:
for index, child in enumerate(elem):
if content_elem is not child:
@ -210,7 +201,7 @@ class XsdComplexType(XsdType, ValidationMixin):
elif elem[index + 1].tag in {XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
self.content_type = self.schema.BUILDERS.group_class(elem[index + 1], self.schema, self)
else:
self.content_type = self.schema.BUILDERS.group_class(SEQUENCE_ELEMENT, self.schema, self)
self.content_type = self.schema.self.schema.create_empty_content_group(self)
break
self._parse_content_tail(elem)
@ -239,8 +230,8 @@ class XsdComplexType(XsdType, ValidationMixin):
return
derivation = local_name(derivation_elem.tag)
if self._derivation is None:
self._derivation = derivation == 'extension'
if self.derivation is None:
self.derivation = derivation
elif self.redefine is None:
raise XMLSchemaValueError("%r is expected to have a redefined/overridden component" % self)
@ -340,9 +331,9 @@ class XsdComplexType(XsdType, ValidationMixin):
self.parse_error(msg.format(base_type.content_type.model, content_type.model))
break
else:
# Empty content model
content_type = self.schema.BUILDERS.group_class(elem, self.schema, self)
content_type.model = base_type.content_type.model
content_type = self.schema.create_empty_content_group(self, base_type.content_type.model)
content_type.restriction = base_type.content_type
if base_type.is_element_only() and content_type.mixed:
self.parse_error(
@ -371,103 +362,80 @@ class XsdComplexType(XsdType, ValidationMixin):
if 'extension' in base_type.final:
self.parse_error("the base type is not derivable by extension")
# Parse openContent
for group_elem in filter(lambda x: x.tag != XSD_ANNOTATION, elem):
if group_elem.tag != XSD_OPEN_CONTENT:
break
self.open_content = XsdOpenContent(group_elem, self.schema, self)
try:
self.open_content.any_element.extend(base_type.open_content.any_element)
except AttributeError:
pass
break
else:
group_elem = None
if not self.open_content:
if self.schema.default_open_content:
self.open_content = self.schema.default_open_content
elif getattr(base_type, 'open_content', None):
self.open_content = base_type.open_content
try:
if self.open_content and not base_type.open_content.is_restriction(self.open_content):
msg = "{!r} is not an extension of the base type {!r}"
self.parse_error(msg.format(self.open_content, base_type.open_content))
except AttributeError:
pass
if base_type.is_empty():
# Empty model extension: don't create a nested group.
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
self.content_type = self.schema.BUILDERS.group_class(group_elem, self.schema, self)
else:
# Empty content model
self.content_type = self.schema.BUILDERS.group_class(elem, self.schema, self)
else:
# Create a dummy sequence content type if the base type has not empty content model
sequence_elem = etree_element(XSD_SEQUENCE)
sequence_elem.text = '\n '
content_type = self.schema.BUILDERS.group_class(sequence_elem, self.schema, self)
if not base_type.mixed:
# Empty element-only model extension: don't create a nested group.
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
self.content_type = self.schema.BUILDERS.group_class(group_elem, self.schema, self)
elif base_type.is_simple() or base_type.has_simple_content():
self.content_type = self.schema.create_empty_content_group(self)
else:
self.content_type = self.schema.create_empty_content_group(
parent=self, model=base_type.content_type.model
)
elif base_type.mixed:
# Empty mixed model extension
self.content_type = self.schema.create_empty_content_group(self)
self.content_type.append(self.schema.create_empty_content_group(self.content_type))
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
# Illegal derivation from a simple content. Always forbidden in XSD 1.1
# for XSD 1.0 applies only with not empty base and not empty extension.
if base_type.is_simple() or base_type.has_simple_content() and self.xsd_version == '1.0':
self.parse_error("base %r is simple or has a simple content." % base_type, elem)
base_type = self.maps.types[XSD_ANY_TYPE]
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
group = self.schema.BUILDERS.group_class(group_elem, self.schema, self.content_type)
if not self.mixed:
self.parse_error("base has a different content type (mixed=%r) and the "
"extension group is not empty." % base_type.mixed, elem)
else:
group = self.schema.create_empty_content_group(self)
group = self.schema.BUILDERS.group_class(group_elem, self.schema, self)
self.content_type.append(group)
self.content_type.elem.append(base_type.content_type.elem)
self.content_type.elem.append(group.elem)
if self.xsd_version == '1.0':
if group.model == 'all':
self.parse_error("cannot extend a complex content with xs:all")
if base_type.content_type.model == 'all' and group.model == 'sequence':
self.parse_error("xs:sequence cannot extend xs:all")
elif group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
# Derivation from a simple content is forbidden if base type is not empty.
if base_type.is_simple() or base_type.has_simple_content():
self.parse_error("base %r is simple or has a simple content." % base_type, elem)
base_type = self.any_type
elif base_type.content_type.model == 'all':
if group.model == 'sequence':
self.parse_error("xs:sequence cannot extend xs:all")
elif group.model == 'all':
if base_type.content_type.min_occurs != group.min_occurs:
self.parse_error(
"when xs:all extends xs:all the minOccurs must be the same"
)
if base_type.content_type.mixed and not base_type.content_type:
self.parse_error(
"xs:all cannot extend an xs:all with mixed empty content"
)
group = self.schema.BUILDERS.group_class(group_elem, self.schema, self)
elif base_type.content_type.model == 'sequence':
if group.model == 'all':
self.parse_error("xs:all cannot extend a not empty xs:sequence")
elif group.model == 'all':
self.parse_error("xs:all cannot extend a not empty xs:choice")
if group.model == 'all':
self.parse_error("cannot extend a complex content with xs:all")
if base_type.content_type.model == 'all' and group.model == 'sequence':
self.parse_error("xs:sequence cannot extend xs:all")
content_type.append(base_type.content_type)
content_type.append(group)
sequence_elem.append(base_type.content_type.elem)
sequence_elem.append(group.elem)
if base_type.content_type.model == 'all' and base_type.content_type and group:
if self.xsd_version == '1.0':
self.parse_error("XSD 1.0 does not allow extension of a not empty 'all' model group")
elif group.model != 'all':
self.parse_error("cannot extend a not empty 'all' model group with a different model")
if base_type.mixed != self.mixed and base_type.name != XSD_ANY_TYPE:
self.parse_error("base has a different content type (mixed=%r) and the "
"extension group is not empty." % base_type.mixed, elem)
elif not base_type.is_simple() and not base_type.has_simple_content():
content_type.append(base_type.content_type)
sequence_elem.append(base_type.content_type.elem)
if base_type.mixed != self.mixed and base_type.name != XSD_ANY_TYPE and self.mixed:
self.parse_error("extended type has a mixed content but the base is element-only", elem)
content_type = self.schema.create_empty_content_group(self)
content_type.append(base_type.content_type)
content_type.append(group)
content_type.elem.append(base_type.content_type.elem)
content_type.elem.append(group.elem)
if base_type.content_type.model == 'all' and base_type.content_type and group:
self.parse_error("XSD 1.0 does not allow extension of a not empty 'all' model group")
if base_type.mixed != self.mixed and base_type.name != XSD_ANY_TYPE:
self.parse_error("base has a different content type (mixed=%r) and the "
"extension group is not empty." % base_type.mixed, elem)
self.content_type = content_type
elif not base_type.is_simple() and not base_type.has_simple_content():
self.content_type = self.schema.create_empty_content_group(self)
self.content_type.append(base_type.content_type)
self.content_type.elem.append(base_type.content_type.elem)
if base_type.mixed != self.mixed and base_type.name != XSD_ANY_TYPE and self.mixed:
self.parse_error("extended type has a mixed content but the base is element-only", elem)
else:
self.content_type = self.schema.create_empty_content_group(self)
self._parse_content_tail(elem, derivation='extension', base_attributes=base_type.attributes)
@property
def block(self):
return self.schema.block_default if self._block is None else self._block
@property
def built(self):
return self.content_type.parent is not None or self.content_type.built
@ -476,10 +444,6 @@ class XsdComplexType(XsdType, ValidationMixin):
def validation_attempted(self):
return 'full' if self.built else self.content_type.validation_attempted
@property
def block(self):
return self.schema.block_default if self._block is None else self._block
@staticmethod
def is_simple():
return False
@ -532,14 +496,15 @@ class XsdComplexType(XsdType, ValidationMixin):
self.base_type.is_valid(source, use_defaults, namespaces)
def is_derived(self, other, derivation=None):
if derivation and derivation == self.derivation:
derivation = None # derivation mode checked
if self is other:
return True
elif derivation and self.derivation and derivation != self.derivation and other.is_complex():
return False
return derivation is None
elif other.name == XSD_ANY_TYPE:
return True
elif self.base_type is other:
return True
return derivation is None or self.base_type.derivation == derivation
elif hasattr(other, 'member_types'):
return any(self.is_derived(m, derivation) for m in other.member_types)
elif self.base_type is None:
@ -564,7 +529,7 @@ class XsdComplexType(XsdType, ValidationMixin):
for obj in self.base_type.iter_components(xsd_classes):
yield obj
for obj in self.assertions:
for obj in filter(lambda x: x.base_type is self, self.assertions):
if xsd_classes is None or isinstance(obj, xsd_classes):
yield obj
@ -578,15 +543,11 @@ class XsdComplexType(XsdType, ValidationMixin):
else:
return self.has_simple_content() or self.mixed and self.is_emptiable()
@property
def derivation(self):
return 'extension' if self._derivation else 'restriction' if self._derivation is False else None
def has_restriction(self):
return self._derivation is False
return self.derivation == 'restriction'
def has_extension(self):
return self._derivation is True
return self.derivation == 'extension'
def text_decode(self, text):
if self.has_simple_content():
@ -614,7 +575,7 @@ class XsdComplexType(XsdType, ValidationMixin):
"""
# XSD 1.1 assertions
for assertion in self.assertions:
for error in assertion(elem):
for error in assertion(elem, **kwargs):
yield self.validation_error(validation, error, **kwargs)
for result in self.attributes.iter_decode(elem.attrib, validation, **kwargs):
@ -729,21 +690,29 @@ class Xsd11ComplexType(XsdComplexType):
# Add inheritable attributes
if hasattr(self.base_type, 'attributes'):
for name, attr in self.base_type.attributes.items():
if name and attr.inheritable:
if attr.inheritable:
if name not in self.attributes:
self.attributes[name] = attr
elif not self.attributes[name].inheritable:
self.parse_error("attribute %r must be inheritable")
if self.elem.get('defaultAttributesApply') in {'false', '0'}:
self.default_attributes_apply = False
if 'defaultAttributesApply' in self.elem.attrib:
if self.elem.attrib['defaultAttributesApply'].strip() in {'false', '0'}:
self.default_attributes_apply = False
# Add default attributes
if self.default_attributes_apply and isinstance(self.schema.default_attributes, XsdAttributeGroup):
if self.redefine is None and any(k in self.attributes for k in self.schema.default_attributes):
if self.redefine is None:
default_attributes = self.schema.default_attributes
else:
default_attributes = self.redefine.schema.default_attributes
if default_attributes is None:
pass
elif self.default_attributes_apply and not self.is_override():
if self.redefine is None and any(k in self.attributes for k in default_attributes):
self.parse_error("at least a default attribute is already declared in the complex type")
self.attributes.update(
(k, v) for k, v in self.schema.default_attributes.items() if k not in self.attributes
(k, v) for k, v in default_attributes.items() if k not in self.attributes
)
def _parse_complex_content_extension(self, elem, base_type):
@ -752,12 +721,115 @@ class Xsd11ComplexType(XsdComplexType):
# https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#sec-cos-ct-extends
if base_type.is_simple() or base_type.has_simple_content():
self.parse_error("base %r is simple or has a simple content." % base_type, elem)
base_type = self.maps.types[XSD_ANY_TYPE]
super(Xsd11ComplexType, self)._parse_complex_content_extension(elem, base_type)
base_type = self.any_type
if 'extension' in base_type.final:
self.parse_error("the base type is not derivable by extension")
# Parse openContent
for group_elem in filter(lambda x: x.tag != XSD_ANNOTATION, elem):
if group_elem.tag != XSD_OPEN_CONTENT:
break
self.open_content = XsdOpenContent(group_elem, self.schema, self)
try:
self.open_content.any_element.union(base_type.open_content.any_element)
except AttributeError:
pass
else:
group_elem = None
if not self.open_content:
if self.schema.default_open_content:
self.open_content = self.schema.default_open_content
elif getattr(base_type, 'open_content', None):
self.open_content = base_type.open_content
try:
if self.open_content and not base_type.open_content.is_restriction(self.open_content):
msg = "{!r} is not an extension of the base type {!r}"
self.parse_error(msg.format(self.open_content, base_type.open_content))
except AttributeError:
pass
if not base_type.content_type:
if not base_type.mixed:
# Empty element-only model extension: don't create a nested sequence group.
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
self.content_type = self.schema.BUILDERS.group_class(group_elem, self.schema, self)
else:
self.content_type = self.schema.create_empty_content_group(
parent=self, model=base_type.content_type.model
)
elif base_type.mixed:
# Empty mixed model extension
self.content_type = self.schema.create_empty_content_group(self)
self.content_type.append(self.schema.create_empty_content_group(self.content_type))
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
group = self.schema.BUILDERS.group_class(group_elem, self.schema, self.content_type)
if not self.mixed:
self.parse_error("base has a different content type (mixed=%r) and the "
"extension group is not empty." % base_type.mixed, elem)
if group.model == 'all':
self.parse_error("cannot extend an empty mixed content with an xs:all")
else:
group = self.schema.create_empty_content_group(self)
self.content_type.append(group)
self.content_type.elem.append(base_type.content_type.elem)
self.content_type.elem.append(group.elem)
elif group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
group = self.schema.BUILDERS.group_class(group_elem, self.schema, self)
if base_type.content_type.model != 'all':
content_type = self.schema.create_empty_content_group(self)
content_type.append(base_type.content_type)
content_type.elem.append(base_type.content_type.elem)
if group.model == 'all':
msg = "xs:all cannot extend a not empty xs:%s"
self.parse_error(msg % base_type.content_type.model)
else:
content_type.append(group)
content_type.elem.append(group.elem)
else:
content_type = self.schema.create_empty_content_group(self, model='all')
content_type.extend(base_type.content_type)
content_type.elem.extend(base_type.content_type.elem)
if not group:
pass
elif group.model != 'all':
self.parse_error("cannot extend a not empty 'all' model group with a different model")
elif base_type.content_type.min_occurs != group.min_occurs:
self.parse_error("when extend an xs:all group minOccurs must be the same")
elif base_type.mixed and not base_type.content_type:
self.parse_error("cannot extend an xs:all group with mixed empty content")
else:
content_type.extend(group)
content_type.elem.extend(group.elem)
if base_type.mixed != self.mixed and base_type.name != XSD_ANY_TYPE:
self.parse_error("base has a different content type (mixed=%r) and the "
"extension group is not empty." % base_type.mixed, elem)
self.content_type = content_type
elif not base_type.is_simple() and not base_type.has_simple_content():
self.content_type = self.schema.create_empty_content_group(self)
self.content_type.append(base_type.content_type)
self.content_type.elem.append(base_type.content_type.elem)
if base_type.mixed != self.mixed and base_type.name != XSD_ANY_TYPE and self.mixed:
self.parse_error("extended type has a mixed content but the base is element-only", elem)
else:
self.content_type = self.schema.create_empty_content_group(self)
self._parse_content_tail(elem, derivation='extension', base_attributes=base_type.attributes)
def _parse_content_tail(self, elem, **kwargs):
self.attributes = self.schema.BUILDERS.attribute_group_class(elem, self.schema, self, **kwargs)
self.assertions = []
for child in filter(lambda x: x.tag != XSD_ANNOTATION, elem):
if child.tag == XSD_ASSERT:
self.assertions.append(XsdAssert(child, self.schema, self, self))
self.assertions = [XsdAssert(e, self.schema, self, self) for e in elem if e.tag == XSD_ASSERT]
if getattr(self.base_type, 'assertions', None):
self.assertions.extend(assertion for assertion in self.base_type.assertions)

View File

@ -18,13 +18,13 @@ from elementpath import XPath2Parser, ElementPathError, XPathContext
from elementpath.datatypes import AbstractDateTime, Duration
from ..exceptions import XMLSchemaAttributeError
from ..qnames import XSD_ANNOTATION, XSD_GROUP, \
XSD_SEQUENCE, XSD_ALL, XSD_CHOICE, XSD_ATTRIBUTE_GROUP, XSD_COMPLEX_TYPE, \
XSD_SIMPLE_TYPE, XSD_ALTERNATIVE, XSD_ELEMENT, XSD_ANY_TYPE, XSD_UNIQUE, \
XSD_KEY, XSD_KEYREF, XSI_NIL, XSI_TYPE, XSD_ID, XSD_ERROR
from ..helpers import get_qname, get_xsd_derivation_attribute, \
get_xsd_form_attribute, ParticleCounter
from ..qnames import XSD_ANNOTATION, XSD_GROUP, XSD_SEQUENCE, XSD_ALL, \
XSD_CHOICE, XSD_ATTRIBUTE_GROUP, XSD_COMPLEX_TYPE, XSD_SIMPLE_TYPE, \
XSD_ALTERNATIVE, XSD_ELEMENT, XSD_ANY_TYPE, XSD_UNIQUE, XSD_KEY, \
XSD_KEYREF, XSI_NIL, XSI_TYPE, XSD_ID, XSD_ERROR, get_qname
from ..etree import etree_element
from ..helpers import get_xsd_derivation_attribute, get_xsd_form_attribute, \
ParticleCounter, strictly_equal
from ..converters import ElementData, raw_xml_encode, XMLSchemaConverter
from ..xpath import XMLSchemaProxy, ElementPathMixin
@ -66,7 +66,8 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
"""
type = None
qualified = False
attributes = None
alternatives = ()
inheritable = ()
_ADMITTED_TAGS = {XSD_ELEMENT}
_abstract = False
@ -78,7 +79,10 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
def __init__(self, elem, schema, parent):
super(XsdElement, self).__init__(elem, schema, parent)
self.names = (self.qualified_name,) if self.qualified else (self.qualified_name, self.local_name)
if self.qualified or self.ref is not None or 'targetNamespace' in elem.attrib:
self.names = (self.qualified_name,)
else:
self.names = (self.qualified_name, self.local_name)
if self.type is None:
raise XMLSchemaAttributeError("undefined 'type' attribute for %r." % self)
if self.qualified is None:
@ -92,13 +96,11 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
def __setattr__(self, name, value):
if name == "type":
assert value is None or isinstance(value, XsdType), "Wrong value %r for attribute 'type'." % value
if hasattr(value, 'attributes'):
assert value is None or isinstance(value, XsdType)
try:
self.attributes = value.attributes
else:
self.attributes = self.schema.BUILDERS.attribute_group_class(
XSD_ATTRIBUTE_GROUP_ELEMENT, self.schema, self
)
except AttributeError:
self.attributes = self.schema.create_empty_attribute_group(self)
super(XsdElement, self).__setattr__(name, value)
def __iter__(self):
@ -106,6 +108,10 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
for e in self.type.content_type.iter_elements():
yield e
@property
def xpath_proxy(self):
return XMLSchemaProxy(self.schema, self)
def _parse(self):
XsdComponent._parse(self)
self._parse_attributes()
@ -113,7 +119,6 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
self._parse_identity_constraints(index)
if self.parent is None and 'substitutionGroup' in self.elem.attrib:
self._parse_substitution_group(self.elem.attrib['substitutionGroup'])
self.xpath_proxy = XMLSchemaProxy(self.schema, self)
def _parse_attributes(self):
self._parse_particle(self.elem)
@ -253,6 +258,10 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
return 0
def _parse_identity_constraints(self, index=0):
if self.ref is not None:
self.identities = self.ref.identities
return
self.identities = {}
for child in filter(lambda x: x.tag != XSD_ANNOTATION, self.elem[index:]):
if child.tag == XSD_UNIQUE:
@ -349,11 +358,19 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
@property
def final(self):
return self._final or self.schema.final_default if self.ref is None else self.ref.final
if self.ref is not None:
return self.ref.final
elif self._final is not None:
return self._final
return self.schema.final_default
@property
def block(self):
return self._block or self.schema.block_default if self.ref is None else self.ref.block
if self.ref is not None:
return self.ref.block
elif self._block is not None:
return self._block
return self.schema.block_default
@property
def nillable(self):
@ -380,9 +397,15 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
return self.type.attributes[get_qname(self.type.target_namespace, name)]
return self.type.attributes[name]
def get_type(self, elem):
def get_type(self, elem, inherited=None):
return self.type
def get_attributes(self, xsd_type):
try:
return xsd_type.attributes
except AttributeError:
return self.attributes
def get_path(self, ancestor=None, reverse=False):
"""
Returns the XPath expression of the element. The path is relative to the schema instance
@ -422,9 +445,11 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
def iter_substitutes(self):
for xsd_element in self.maps.substitution_groups.get(self.name, ()):
yield xsd_element
if not xsd_element.abstract:
yield xsd_element
for e in xsd_element.iter_substitutes():
yield e
if not e.abstract:
yield e
def data_value(self, elem):
"""Returns the decoded data value of the provided element as XPath fn:data()."""
@ -445,47 +470,65 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
:return: yields a decoded object, eventually preceded by a sequence of \
validation or decoding errors.
"""
if self.abstract:
yield self.validation_error(validation, "cannot use an abstract element for validation", elem, **kwargs)
if not isinstance(converter, XMLSchemaConverter):
converter = self.schema.get_converter(converter, level=level, **kwargs)
inherited = kwargs.get('inherited')
value = content = attributes = None
# Get the instance type: xsi:type or the schema's declaration
if XSI_TYPE not in elem.attrib:
xsd_type = self.get_type(elem)
else:
xsi_type = elem.attrib[XSI_TYPE]
# Get the instance effective type
xsd_type = self.get_type(elem, inherited)
if XSI_TYPE in elem.attrib:
type_name = elem.attrib[XSI_TYPE].strip()
try:
xsd_type = self.maps.lookup_type(converter.unmap_qname(xsi_type))
except KeyError:
yield self.validation_error(validation, "unknown type %r" % xsi_type, elem, **kwargs)
xsd_type = self.get_type(elem)
xsd_type = self.maps.get_instance_type(type_name, xsd_type, converter)
except (KeyError, TypeError) as err:
yield self.validation_error(validation, err, elem, **kwargs)
if xsd_type.is_blocked(self):
yield self.validation_error(validation, "usage of %r is blocked" % xsd_type, elem, **kwargs)
# Decode attributes
attribute_group = getattr(xsd_type, 'attributes', self.attributes)
for result in attribute_group.iter_decode(elem.attrib, validation, **kwargs):
attribute_group = self.get_attributes(xsd_type)
for result in attribute_group.iter_decode(elem.attrib, validation, level=level, **kwargs):
if isinstance(result, XMLSchemaValidationError):
yield self.validation_error(validation, result, elem, **kwargs)
else:
attributes = result
if self.inheritable and any(name in self.inheritable for name in elem.attrib):
if inherited:
inherited = inherited.copy()
inherited.update((k, v) for k, v in elem.attrib.items() if k in self.inheritable)
else:
inherited = {k: v for k, v in elem.attrib.items() if k in self.inheritable}
kwargs['inherited'] = inherited
# Checks the xsi:nil attribute of the instance
if validation != 'skip' and XSI_NIL in elem.attrib:
if XSI_NIL in elem.attrib:
xsi_nil = elem.attrib[XSI_NIL].strip()
if not self.nillable:
yield self.validation_error(validation, "element is not nillable.", elem, **kwargs)
try:
if elem.attrib[XSI_NIL].strip() in ('true', '1'):
if elem.text is not None:
reason = "xsi:nil='true' but the element is not empty."
yield self.validation_error(validation, reason, elem, **kwargs)
else:
element_data = ElementData(elem.tag, None, None, attributes)
yield converter.element_decode(element_data, self, level)
return
except TypeError:
elif xsi_nil not in {'0', '1', 'false', 'true'}:
reason = "xsi:nil attribute must has a boolean value."
yield self.validation_error(validation, reason, elem, **kwargs)
elif xsi_nil in ('0', 'false'):
pass
elif elem.text is not None or len(elem):
reason = "xsi:nil='true' but the element is not empty."
yield self.validation_error(validation, reason, elem, **kwargs)
else:
element_data = ElementData(elem.tag, None, None, attributes)
yield converter.element_decode(element_data, self, level)
return
if not xsd_type.has_simple_content():
for assertion in xsd_type.assertions:
for error in assertion(elem, **kwargs):
yield self.validation_error(validation, error, **kwargs)
for result in xsd_type.content_type.iter_decode(
elem, validation, converter, level + 1, **kwargs):
if isinstance(result, XMLSchemaValidationError):
@ -503,23 +546,35 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
text = self.fixed
elif text == self.fixed or validation == 'skip':
pass
elif xsd_type.text_decode(text) != xsd_type.text_decode(self.fixed):
elif not strictly_equal(xsd_type.text_decode(text), xsd_type.text_decode(self.fixed)):
reason = "must has the fixed value %r." % self.fixed
yield self.validation_error(validation, reason, elem, **kwargs)
elif not text and kwargs.get('use_defaults') and self.default is not None:
text = self.default
if not xsd_type.is_simple():
if xsd_type.is_complex():
for assertion in xsd_type.assertions:
for error in assertion(elem, value=text, **kwargs):
yield self.validation_error(validation, error, **kwargs)
if text and xsd_type.content_type.is_list():
value = text.split()
else:
value = text
xsd_type = xsd_type.content_type
if text is None:
for result in xsd_type.iter_decode('', validation, **kwargs):
for result in xsd_type.iter_decode('', validation, _skip_id=True, **kwargs):
if isinstance(result, XMLSchemaValidationError):
yield self.validation_error(validation, result, elem, **kwargs)
if 'filler' in kwargs:
value = kwargs['filler'](self)
else:
if level == 0 or self.xsd_version != '1.0':
kwargs['_skip_id'] = True
for result in xsd_type.iter_decode(text, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):
yield self.validation_error(validation, result, elem, **kwargs)
@ -549,7 +604,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
for constraint in self.identities.values():
if isinstance(constraint, XsdKeyref) and '_no_deep' in kwargs: # TODO: Complete lazy validation
continue
for error in constraint(elem):
for error in constraint(elem, converter):
yield self.validation_error(validation, error, elem, **kwargs)
def iter_encode(self, obj, validation='lax', converter=None, level=0, **kwargs):
@ -575,30 +630,30 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
children = element_data.content
attributes = ()
if element_data.attributes and XSI_TYPE in element_data.attributes:
xsi_type = element_data.attributes[XSI_TYPE]
xsd_type = self.get_type(element_data)
if XSI_TYPE in element_data.attributes:
type_name = element_data.attributes[XSI_TYPE].strip()
try:
xsd_type = self.maps.lookup_type(converter.unmap_qname(xsi_type))
except KeyError:
errors.append("unknown type %r" % xsi_type)
xsd_type = self.get_type(element_data)
else:
xsd_type = self.get_type(element_data)
xsd_type = self.maps.get_instance_type(type_name, xsd_type, converter)
except (KeyError, TypeError) as err:
errors.append(err)
attribute_group = getattr(xsd_type, 'attributes', self.attributes)
attribute_group = self.get_attributes(xsd_type)
for result in attribute_group.iter_encode(element_data.attributes, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):
errors.append(result)
else:
attributes = result
if validation != 'skip' and XSI_NIL in element_data.attributes:
if XSI_NIL in element_data.attributes:
xsi_nil = element_data.attributes[XSI_NIL].strip()
if not self.nillable:
errors.append("element is not nillable.")
xsi_nil = element_data.attributes[XSI_NIL]
if xsi_nil.strip() not in ('0', '1', 'true', 'false'):
elif xsi_nil not in {'0', '1', 'true', 'false'}:
errors.append("xsi:nil attribute must has a boolean value.")
if element_data.text is not None:
elif xsi_nil in ('0', 'false'):
pass
elif element_data.text is not None or element_data.content:
errors.append("xsi:nil='true' but the element is not empty.")
else:
elem = converter.etree_element(element_data.tag, attrib=attributes, level=level)
@ -655,7 +710,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
return True
return False
def match(self, name, default_namespace=None, group=None):
def match(self, name, default_namespace=None, **kwargs):
if default_namespace and name[0] != '{':
name = '{%s}%s' % (default_namespace, name)
@ -666,8 +721,6 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
if name in xsd_element.names:
return xsd_element
matched_element = match
def is_restriction(self, other, check_occurs=True):
if isinstance(other, XsdAnyElement):
if self.min_occurs == self.max_occurs == 0:
@ -791,7 +844,6 @@ class Xsd11Element(XsdElement):
Content: (annotation?, ((simpleType | complexType)?, alternative*, (unique | key | keyref)*))
</element>
"""
alternatives = ()
_target_namespace = None
def _parse(self):
@ -800,11 +852,15 @@ class Xsd11Element(XsdElement):
index = self._parse_type()
index = self._parse_alternatives(index)
self._parse_identity_constraints(index)
if self.parent is None and 'substitutionGroup' in self.elem.attrib:
for substitution_group in self.elem.attrib['substitutionGroup'].split():
self._parse_substitution_group(substitution_group)
self._parse_target_namespace()
self.xpath_proxy = XMLSchemaProxy(self.schema, self)
if any(v.inheritable for v in self.attributes.values()):
self.inheritable = {k: v for k, v in self.attributes.items() if v.inheritable}
def _parse_alternatives(self, index=0):
if self.ref is not None:
@ -838,7 +894,33 @@ class Xsd11Element(XsdElement):
return self.schema.target_namespace
return self._target_namespace
def get_type(self, elem):
def iter_components(self, xsd_classes=None):
if xsd_classes is None:
yield self
for obj in self.identities.values():
yield obj
else:
if isinstance(self, xsd_classes):
yield self
for obj in self.identities.values():
if isinstance(obj, xsd_classes):
yield obj
for alt in self.alternatives:
for obj in alt.iter_components(xsd_classes):
yield obj
if self.ref is None and self.type.parent is not None:
for obj in self.type.iter_components(xsd_classes):
yield obj
def iter_substitutes(self):
for xsd_element in self.maps.substitution_groups.get(self.name, ()):
yield xsd_element
for e in xsd_element.iter_substitutes():
yield e
def get_type(self, elem, inherited=None):
if not self.alternatives:
return self.type
@ -849,10 +931,17 @@ class Xsd11Element(XsdElement):
else:
elem = etree_element(elem.tag)
for alt in self.alternatives:
if alt.type is not None and \
alt.token.boolean_value(list(alt.token.select(context=XPathContext(root=elem)))):
return alt.type
if inherited:
dummy = etree_element('_dummy_element', attrib=inherited)
for alt in filter(lambda x: x.type is not None, self.alternatives):
if alt.token is None or alt.test(elem) or alt.test(dummy):
return alt.type
else:
for alt in filter(lambda x: x.type is not None, self.alternatives):
if alt.token is None or alt.test(elem):
return alt.type
return self.type
def is_overlap(self, other):
@ -865,14 +954,21 @@ class Xsd11Element(XsdElement):
for e in self.iter_substitutes():
if other.name == e.name or any(x is e for x in other.iter_substitutes()):
return True
elif isinstance(other, XsdAnyElement):
if other.is_matching(self.name, self.default_namespace):
return True
for e in self.maps.substitution_groups.get(self.name, ()):
if other.is_matching(e.name, self.default_namespace):
return True
return False
def is_consistent(self, other, strict=True):
if isinstance(other, XsdAnyElement):
if other.process_contents == 'skip':
return True
xsd_element = other.matched_element(self.name, self.default_namespace)
return xsd_element is None or self.is_consistent(xsd_element, False)
xsd_element = other.match(self.name, self.default_namespace, resolve=True)
return xsd_element is None or self.is_consistent(xsd_element, strict=False)
if self.name == other.name:
e = self
@ -935,7 +1031,9 @@ class XsdAlternative(XsdComponent):
self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
else:
self.xpath_default_namespace = self.schema.xpath_default_namespace
parser = XPath2Parser(self.namespaces, strict=False, default_namespace=self.xpath_default_namespace)
parser = XPath2Parser(
self.namespaces, strict=False, default_namespace=self.xpath_default_namespace
)
try:
self.path = attrib['test']
@ -986,3 +1084,16 @@ class XsdAlternative(XsdComponent):
@property
def validation_attempted(self):
return 'full' if self.built else self.type.validation_attempted
def iter_components(self, xsd_classes=None):
if xsd_classes is None or isinstance(self, xsd_classes):
yield self
if self.type is not None and self.type.parent is not None:
for obj in self.type.iter_components(xsd_classes):
yield obj
def test(self, elem):
try:
return self.token.boolean_value(list(self.token.select(context=XPathContext(elem))))
except (TypeError, ValueError):
return False

View File

@ -13,10 +13,11 @@ This module contains exception and warning classes for the 'xmlschema.validators
"""
from __future__ import unicode_literals
from ..compat import PY3
from ..compat import PY3, string_base_type
from ..exceptions import XMLSchemaException, XMLSchemaWarning, XMLSchemaValueError
from ..etree import etree_tostring, is_etree_element, etree_getpath
from ..helpers import qname_to_prefixed
from ..qnames import qname_to_prefixed
from ..etree import etree_tostring, etree_getpath
from ..helpers import is_etree_element
from ..resources import XMLResource
@ -198,9 +199,14 @@ class XMLSchemaValidationError(XMLSchemaValidatorError, ValueError):
:type namespaces: dict
"""
def __init__(self, validator, obj, reason=None, source=None, namespaces=None):
if not isinstance(obj, string_base_type):
_obj = obj
else:
_obj = obj.encode('ascii', 'xmlcharrefreplace').decode('utf-8')
super(XMLSchemaValidationError, self).__init__(
validator=validator,
message="failed validating {!r} with {!r}".format(obj, validator),
message="failed validating {!r} with {!r}".format(_obj, validator),
elem=obj if is_etree_element(obj) else None,
source=source,
namespaces=namespaces,
@ -218,8 +224,12 @@ class XMLSchemaValidationError(XMLSchemaValidatorError, ValueError):
msg.append('Reason: %s\n' % self.reason)
if hasattr(self.validator, 'tostring'):
msg.append("Schema:\n\n%s\n" % self.validator.tostring(' ', 20))
if self.elem is not None:
elem_as_string = etree_tostring(self.elem, self.namespaces, ' ', 20)
if is_etree_element(self.elem):
try:
elem_as_string = etree_tostring(self.elem, self.namespaces, ' ', 20)
except (ValueError, TypeError):
elem_as_string = repr(self.elem)
if hasattr(self.elem, 'sourceline'):
msg.append("Instance (line %r):\n\n%s\n" % (self.elem.sourceline, elem_as_string))
else:

View File

@ -13,13 +13,16 @@ This module contains declarations and classes for XML Schema constraint facets.
"""
from __future__ import unicode_literals
import re
from elementpath import XPath2Parser, ElementPathError, datatypes
import operator
from elementpath import XPath2Parser, ElementPathError
from elementpath.datatypes import XSD_BUILTIN_TYPES
from ..compat import unicode_type, MutableSequence
from ..qnames import XSD_LENGTH, XSD_MIN_LENGTH, XSD_MAX_LENGTH, XSD_ENUMERATION, XSD_WHITE_SPACE, \
XSD_PATTERN, XSD_MAX_INCLUSIVE, XSD_MAX_EXCLUSIVE, XSD_MIN_INCLUSIVE, XSD_MIN_EXCLUSIVE, \
XSD_TOTAL_DIGITS, XSD_FRACTION_DIGITS, XSD_ASSERTION, XSD_EXPLICIT_TIMEZONE, XSD_NOTATION_TYPE, \
XSD_BASE64_BINARY, XSD_HEX_BINARY
from ..qnames import XSD_LENGTH, XSD_MIN_LENGTH, XSD_MAX_LENGTH, XSD_ENUMERATION, \
XSD_WHITE_SPACE, XSD_PATTERN, XSD_MAX_INCLUSIVE, XSD_MAX_EXCLUSIVE, XSD_MIN_INCLUSIVE, \
XSD_MIN_EXCLUSIVE, XSD_TOTAL_DIGITS, XSD_FRACTION_DIGITS, XSD_ASSERTION, \
XSD_EXPLICIT_TIMEZONE, XSD_NOTATION_TYPE, XSD_BASE64_BINARY, XSD_HEX_BINARY, XSD_QNAME
from ..helpers import count_digits
from ..regex import get_python_regex
from .exceptions import XMLSchemaValidationError, XMLSchemaDecodeError
@ -40,8 +43,11 @@ class XsdFacet(XsdComponent):
return '%s(value=%r, fixed=%r)' % (self.__class__.__name__, self.value, self.fixed)
def __call__(self, value):
for error in self.validator(value):
yield error
try:
for error in self.validator(value):
yield error
except (TypeError, ValueError) as err:
yield XMLSchemaValidationError(self, value, unicode_type(err))
def _parse(self):
super(XsdFacet, self)._parse()
@ -150,6 +156,8 @@ class XsdLengthFacet(XsdFacet):
self.validator = self.hex_length_validator
elif primitive_type.name == XSD_BASE64_BINARY:
self.validator = self.base64_length_validator
elif primitive_type.name == XSD_QNAME:
pass # See: https://www.w3.org/Bugs/Public/show_bug.cgi?id=4009
else:
self.validator = self.length_validator
@ -193,7 +201,7 @@ class XsdMinLengthFacet(XsdFacet):
self.validator = self.hex_min_length_validator
elif primitive_type.name == XSD_BASE64_BINARY:
self.validator = self.base64_min_length_validator
else:
elif primitive_type.name != XSD_QNAME:
self.validator = self.min_length_validator
def min_length_validator(self, x):
@ -236,7 +244,7 @@ class XsdMaxLengthFacet(XsdFacet):
self.validator = self.hex_max_length_validator
elif primitive_type.name == XSD_BASE64_BINARY:
self.validator = self.base64_max_length_validator
else:
elif primitive_type.name != XSD_QNAME:
self.validator = self.max_length_validator
def max_length_validator(self, x):
@ -286,9 +294,13 @@ class XsdMinInclusiveFacet(XsdFacet):
if facet is not None and facet.value < self.value:
self.parse_error("maximum value of base_type is lesser")
def validator(self, x):
if x < self.value:
yield XMLSchemaValidationError(self, x, "value has to be greater or equal than %r." % self.value)
def __call__(self, value):
try:
if value < self.value:
reason = "value has to be greater or equal than %r." % self.value
yield XMLSchemaValidationError(self, value, reason)
except (TypeError, ValueError) as err:
yield XMLSchemaValidationError(self, value, unicode_type(err))
class XsdMinExclusiveFacet(XsdFacet):
@ -324,9 +336,13 @@ class XsdMinExclusiveFacet(XsdFacet):
if facet is not None and facet.value <= self.value:
self.parse_error("maximum value of base_type is lesser")
def validator(self, x):
if x <= self.value:
yield XMLSchemaValidationError(self, x, "value has to be greater than %r." % self.value)
def __call__(self, value):
try:
if value <= self.value:
reason = "value has to be greater than %r." % self.value
yield XMLSchemaValidationError(self, value, reason)
except (TypeError, ValueError) as err:
yield XMLSchemaValidationError(self, value, unicode_type(err))
class XsdMaxInclusiveFacet(XsdFacet):
@ -362,9 +378,13 @@ class XsdMaxInclusiveFacet(XsdFacet):
if facet is not None and facet.value < self.value:
self.parse_error("maximum value of base_type is lesser")
def validator(self, x):
if x > self.value:
yield XMLSchemaValidationError(self, x, "value has to be lesser or equal than %r." % self.value)
def __call__(self, value):
try:
if value > self.value:
reason = "value has to be lesser or equal than %r." % self.value
yield XMLSchemaValidationError(self, value, reason)
except (TypeError, ValueError) as err:
yield XMLSchemaValidationError(self, value, unicode_type(err))
class XsdMaxExclusiveFacet(XsdFacet):
@ -400,9 +420,13 @@ class XsdMaxExclusiveFacet(XsdFacet):
if facet is not None and facet.value < self.value:
self.parse_error("maximum value of base_type is lesser")
def validator(self, x):
if x >= self.value:
yield XMLSchemaValidationError(self, x, "value has to be lesser than %r" % self.value)
def __call__(self, value):
try:
if value >= self.value:
reason = "value has to be lesser than %r" % self.value
yield XMLSchemaValidationError(self, value, reason)
except (TypeError, ValueError) as err:
yield XMLSchemaValidationError(self, value, unicode_type(err))
class XsdTotalDigitsFacet(XsdFacet):
@ -426,8 +450,10 @@ class XsdTotalDigitsFacet(XsdFacet):
self.validator = self.total_digits_validator
def total_digits_validator(self, x):
if len([d for d in str(x).strip('0') if d.isdigit()]) > self.value:
yield XMLSchemaValidationError(self, x, "the number of digits is greater than %r." % self.value)
if operator.add(*count_digits(x)) > self.value:
yield XMLSchemaValidationError(
self, x, "the number of digits is greater than %r." % self.value
)
class XsdFractionDigitsFacet(XsdFacet):
@ -458,8 +484,10 @@ class XsdFractionDigitsFacet(XsdFacet):
self.validator = self.fraction_digits_validator
def fraction_digits_validator(self, x):
if len(str(x).strip('0').partition('.')[2]) > self.value:
yield XMLSchemaValidationError(self, x, "the number of fraction digits is greater than %r." % self.value)
if count_digits(x)[1] > self.value:
yield XMLSchemaValidationError(
self, x, "the number of fraction digits is greater than %r." % self.value
)
class XsdExplicitTimezoneFacet(XsdFacet):
@ -626,15 +654,39 @@ class XsdPatternFacets(MutableSequence, XsdFacet):
return '%s(%s...\'])' % (self.__class__.__name__, s[:70])
def __call__(self, text):
if all(pattern.match(text) is None for pattern in self.patterns):
msg = "value doesn't match any pattern of %r."
yield XMLSchemaValidationError(self, text, reason=msg % self.regexps)
try:
if all(pattern.match(text) is None for pattern in self.patterns):
msg = "value doesn't match any pattern of %r."
yield XMLSchemaValidationError(self, text, reason=msg % self.regexps)
except TypeError as err:
yield XMLSchemaValidationError(self, text, unicode_type(err))
@property
def regexps(self):
return [e.get('value', '') for e in self._elements]
class XsdAssertionXPathParser(XPath2Parser):
"""Parser for XSD 1.1 assertion facets."""
XsdAssertionXPathParser.unregister('last')
XsdAssertionXPathParser.unregister('position')
@XsdAssertionXPathParser.method(XsdAssertionXPathParser.function('last', nargs=0))
def evaluate(self, context=None):
self.missing_context("Context item size is undefined")
@XsdAssertionXPathParser.method(XsdAssertionXPathParser.function('position', nargs=0))
def evaluate(self, context=None):
self.missing_context("Context item position is undefined")
XsdAssertionXPathParser.build_tokenizer()
class XsdAssertionFacet(XsdFacet):
"""
XSD 1.1 *assertion* facet for simpleType definitions.
@ -662,16 +714,16 @@ class XsdAssertionFacet(XsdFacet):
try:
builtin_type_name = self.base_type.primitive_type.local_name
variables = {'value': datatypes.XSD_BUILTIN_TYPES[builtin_type_name].value}
variables = {'value': XSD_BUILTIN_TYPES[builtin_type_name].value}
except AttributeError:
variables = {'value': datatypes.XSD_BUILTIN_TYPES['anySimpleType'].value}
variables = {'value': XSD_BUILTIN_TYPES['anySimpleType'].value}
if 'xpathDefaultNamespace' in self.elem.attrib:
self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
else:
self.xpath_default_namespace = self.schema.xpath_default_namespace
self.parser = XPath2Parser(self.namespaces, strict=False, variables=variables,
default_namespace=self.xpath_default_namespace)
self.parser = XsdAssertionXPathParser(self.namespaces, strict=False, variables=variables,
default_namespace=self.xpath_default_namespace)
try:
self.token = self.parser.parse(self.path)
@ -681,9 +733,12 @@ class XsdAssertionFacet(XsdFacet):
def __call__(self, value):
self.parser.variables['value'] = value
if not self.token.evaluate():
msg = "value is not true with test path %r."
yield XMLSchemaValidationError(self, value, reason=msg % self.path)
try:
if not self.token.evaluate():
msg = "value is not true with test path %r."
yield XMLSchemaValidationError(self, value, reason=msg % self.path)
except ElementPathError as err:
yield XMLSchemaValidationError(self, value, reason=str(err))
XSD_10_FACETS_BUILDERS = {

View File

@ -15,12 +15,12 @@ from __future__ import unicode_literals
import warnings
from collections import Counter
from ..compat import string_base_type
from ..exceptions import XMLSchemaKeyError, XMLSchemaTypeError, XMLSchemaValueError, XMLSchemaWarning
from ..namespaces import XSD_NAMESPACE
from ..qnames import XSD_REDEFINE, XSD_OVERRIDE, XSD_NOTATION, XSD_ANY_TYPE, XSD_SIMPLE_TYPE, \
XSD_COMPLEX_TYPE, XSD_GROUP, XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_ELEMENT
from ..helpers import get_qname, local_name
from ..namespaces import NamespaceResourcesMap
from ..namespaces import XSD_NAMESPACE, NamespaceResourcesMap
from ..qnames import XSD_REDEFINE, XSD_OVERRIDE, XSD_NOTATION, XSD_ANY_TYPE, \
XSD_SIMPLE_TYPE, XSD_COMPLEX_TYPE, XSD_GROUP, XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, \
XSD_ELEMENT, XSI_TYPE, get_qname, local_name, qname_to_extended
from . import XMLSchemaNotBuiltError, XMLSchemaModelError, XMLSchemaModelDepthError, \
XsdValidator, XsdComponent, XsdAttribute, XsdSimpleType, XsdComplexType, XsdElement, \
@ -119,13 +119,13 @@ def create_lookup_function(xsd_classes):
else:
types_desc = xsd_classes.__name__
def lookup(global_map, qname, tag_map):
def lookup(qname, global_map, tag_map):
try:
obj = global_map[qname]
except KeyError:
if '{' in qname:
raise XMLSchemaKeyError("missing a %s component for %r!" % (types_desc, qname))
raise XMLSchemaKeyError("missing a %s component for %r! As the name has no namespace "
raise XMLSchemaKeyError("missing an %s component for %r!" % (types_desc, qname))
raise XMLSchemaKeyError("missing an %s component for %r! As the name has no namespace "
"maybe a missing default namespace declaration." % (types_desc, qname))
else:
if isinstance(obj, xsd_classes):
@ -236,22 +236,22 @@ class XsdGlobals(XsdValidator):
__copy__ = copy
def lookup_notation(self, qname):
return lookup_notation(self.notations, qname, self.validator.BUILDERS_MAP)
return lookup_notation(qname, self.notations, self.validator.BUILDERS_MAP)
def lookup_type(self, qname):
return lookup_type(self.types, qname, self.validator.BUILDERS_MAP)
return lookup_type(qname, self.types, self.validator.BUILDERS_MAP)
def lookup_attribute(self, qname):
return lookup_attribute(self.attributes, qname, self.validator.BUILDERS_MAP)
return lookup_attribute(qname, self.attributes, self.validator.BUILDERS_MAP)
def lookup_attribute_group(self, qname):
return lookup_attribute_group(self.attribute_groups, qname, self.validator.BUILDERS_MAP)
return lookup_attribute_group(qname, self.attribute_groups, self.validator.BUILDERS_MAP)
def lookup_group(self, qname):
return lookup_group(self.groups, qname, self.validator.BUILDERS_MAP)
return lookup_group(qname, self.groups, self.validator.BUILDERS_MAP)
def lookup_element(self, qname):
return lookup_element(self.elements, qname, self.validator.BUILDERS_MAP)
return lookup_element(qname, self.elements, self.validator.BUILDERS_MAP)
def lookup(self, tag, qname):
"""
@ -280,6 +280,23 @@ class XsdGlobals(XsdValidator):
else:
raise XMLSchemaValueError("wrong tag {!r} for an XSD global definition/declaration".format(tag))
def get_instance_type(self, type_name, base_type, namespaces):
"""
Returns the instance XSI type from global maps, validating it with the reference base type.
:param type_name: the XSI type attribute value, a QName in prefixed format.
:param base_type: the XSD from which the instance type has to be derived.
:param namespaces: a map from prefixes to namespaces.
"""
if base_type.is_complex() and XSI_TYPE in base_type.attributes:
base_type.attributes[XSI_TYPE].validate(type_name)
extended_name = qname_to_extended(type_name, namespaces)
xsi_type = lookup_type(extended_name, self.types, self.validator.BUILDERS_MAP)
if not xsi_type.is_derived(base_type):
raise XMLSchemaTypeError("%r is not a derived type of %r" % (xsi_type, self))
return xsi_type
@property
def built(self):
return all(schema.built for schema in self.iter_schemas())
@ -314,6 +331,10 @@ class XsdGlobals(XsdValidator):
def xsd_version(self):
return self.validator.XSD_VERSION
@property
def builders_map(self):
return self.validator.BUILDERS_MAP
@property
def all_errors(self):
errors = []
@ -455,8 +476,23 @@ class XsdGlobals(XsdValidator):
self.lookup_notation(qname)
for qname in self.attributes:
self.lookup_attribute(qname)
for qname in self.attribute_groups:
self.lookup_attribute_group(qname)
for schema in filter(
lambda x: isinstance(x.default_attributes, string_base_type),
not_built_schemas):
try:
schema.default_attributes = schema.maps.attribute_groups[schema.default_attributes]
except KeyError:
schema.default_attributes = None
msg = "defaultAttributes={!r} doesn't match an attribute group of {!r}"
schema.parse_error(
error=msg.format(schema.root.get('defaultAttributes'), schema),
elem=schema.root,
validation=schema.validation
)
for qname in self.types:
self.lookup_type(qname)
for qname in self.elements:
@ -469,8 +505,12 @@ class XsdGlobals(XsdValidator):
for group in schema.iter_components(XsdGroup):
group.build()
# Builds xs:keyref's key references
for constraint in filter(lambda x: isinstance(x, XsdKeyref), self.identities.values()):
constraint.parse_refer()
# Build XSD 1.1 identity references and assertions
if self.validator.XSD_VERSION != '1.0':
if self.xsd_version != '1.0':
for schema in filter(lambda x: x.meta_schema is not None, not_built_schemas):
for e in schema.iter_components(Xsd11Element):
for constraint in filter(lambda x: x.ref is not None, e.identities.values()):
@ -490,10 +530,6 @@ class XsdGlobals(XsdValidator):
for assertion in schema.iter_components(XsdAssert):
assertion.parse_xpath_test()
# Builds xs:keyref's key references
for constraint in filter(lambda x: isinstance(x, XsdKeyref), self.identities.values()):
constraint.parse_refer()
self.check(filter(lambda x: x.meta_schema is not None, not_built_schemas), self.validation)
def check(self, schemas=None, validation='strict'):
@ -513,21 +549,9 @@ class XsdGlobals(XsdValidator):
if e is xsd_element:
msg = "circularity found for substitution group with head element %r"
e.parse_error(msg.format(e), validation=validation)
elif e.abstract and e.name not in self.substitution_groups and self.validator.XSD_VERSION > '1.0':
elif e.abstract and e.name not in self.substitution_groups and self.xsd_version > '1.0':
self.parse_error("in XSD 1.1 an abstract element cannot be member of a substitution group")
if self.validator.XSD_VERSION > '1.0':
for s in filter(lambda x: x.default_attributes is not None, schemas):
if isinstance(s.default_attributes, XsdAttributeGroup):
continue
try:
s.default_attributes = s.maps.attribute_groups[s.default_attributes]
except KeyError:
s.default_attributes = None
msg = "defaultAttributes={!r} doesn't match an attribute group of {!r}"
s.parse_error(msg.format(s.root.get('defaultAttributes'), s), s.root, validation)
if validation == 'strict' and not self.built:
raise XMLSchemaNotBuiltError(self, "global map has unbuilt components: %r" % self.unbuilt)

View File

@ -12,15 +12,16 @@
This module contains classes for XML Schema model groups.
"""
from __future__ import unicode_literals
import warnings
from ..compat import unicode_type
from ..exceptions import XMLSchemaValueError
from ..etree import etree_element
from ..qnames import XSD_ANNOTATION, XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE, \
XSD_COMPLEX_TYPE, XSD_ELEMENT, XSD_ANY, XSD_RESTRICTION, XSD_EXTENSION
from xmlschema.helpers import get_qname, local_name
from ..qnames import XSD_ANNOTATION, XSD_GROUP, XSD_SEQUENCE, XSD_ALL, \
XSD_CHOICE, XSD_ELEMENT, XSD_ANY, XSI_TYPE, get_qname, local_name
from .exceptions import XMLSchemaValidationError, XMLSchemaChildrenValidationError
from .exceptions import XMLSchemaValidationError, XMLSchemaChildrenValidationError, \
XMLSchemaTypeTableWarning
from .xsdbase import ValidationMixin, XsdComponent, XsdType
from .elements import XsdElement
from .wildcards import XsdAnyElement, Xsd11AnyElement
@ -77,12 +78,11 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
mixed = False
model = None
redefine = None
restriction = None
interleave = None # an Xsd11AnyElement in case of XSD 1.1 openContent with mode='interleave'
suffix = None # an Xsd11AnyElement in case of openContent with mode='suffix' or 'interleave'
_ADMITTED_TAGS = {
XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_RESTRICTION, XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE
}
_ADMITTED_TAGS = {XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}
def __init__(self, elem, schema, parent):
self._group = []
@ -114,49 +114,53 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
def _parse(self):
super(XsdGroup, self)._parse()
self.clear()
elem = self.elem
self._parse_particle(elem)
self._parse_particle(self.elem)
if elem.tag == XSD_GROUP:
# Global group or reference
if self._parse_reference():
try:
xsd_group = self.schema.maps.lookup_group(self.name)
except KeyError:
self.parse_error("missing group %r" % self.prefixed_name)
xsd_group = self.schema.create_any_content_group(self, self.name)
if isinstance(xsd_group, tuple):
# Disallowed circular definition, substitute with any content group.
self.parse_error("Circular definitions detected for group %r:" % self.name, xsd_group[0])
self.model = 'sequence'
self.mixed = True
self.append(self.schema.BUILDERS.any_element_class(ANY_ELEMENT, self.schema, self))
else:
self.model = xsd_group.model
if self.model == 'all':
if self.max_occurs != 1:
self.parse_error("maxOccurs must be 1 for 'all' model groups")
if self.min_occurs not in (0, 1):
self.parse_error("minOccurs must be (0 | 1) for 'all' model groups")
if self.xsd_version == '1.0' and isinstance(self.parent, XsdGroup):
self.parse_error("in XSD 1.0 the 'all' model group cannot be nested")
self.append(xsd_group)
self.ref = xsd_group
return
if self.elem.tag != XSD_GROUP:
# Local group (sequence|all|choice)
if 'name' in self.elem.attrib:
self.parse_error("attribute 'name' not allowed for a local group")
self._parse_content_model(self.elem)
elif self._parse_reference():
try:
self.name = get_qname(self.target_namespace, elem.attrib['name'])
xsd_group = self.schema.maps.lookup_group(self.name)
except KeyError:
return
self.parse_error("missing group %r" % self.prefixed_name)
xsd_group = self.schema.create_any_content_group(self, self.name)
if isinstance(xsd_group, tuple):
# Disallowed circular definition, substitute with any content group.
self.parse_error("Circular definitions detected for group %r:" % self.name, xsd_group[0])
self.model = 'sequence'
self.mixed = True
self.append(self.schema.BUILDERS.any_element_class(ANY_ELEMENT, self.schema, self))
else:
content_model = self._parse_child_component(elem, strict=True)
self.model = xsd_group.model
if self.model == 'all':
if self.max_occurs != 1:
self.parse_error("maxOccurs must be 1 for 'all' model groups")
if self.min_occurs not in (0, 1):
self.parse_error("minOccurs must be (0 | 1) for 'all' model groups")
if self.xsd_version == '1.0' and isinstance(self.parent, XsdGroup):
self.parse_error("in XSD 1.0 the 'all' model group cannot be nested")
self.append(xsd_group)
self.ref = xsd_group
else:
attrib = self.elem.attrib
try:
self.name = get_qname(self.target_namespace, attrib['name'])
except KeyError:
pass
else:
content_model = self._parse_child_component(self.elem, strict=True)
if self.parent is not None:
self.parse_error("attribute 'name' not allowed for a local group")
else:
if 'minOccurs' in elem.attrib:
if 'minOccurs' in attrib:
self.parse_error("attribute 'minOccurs' not allowed for a global group")
if 'maxOccurs' in elem.attrib:
if 'maxOccurs' in attrib:
self.parse_error("attribute 'maxOccurs' not allowed for a global group")
if 'minOccurs' in content_model.attrib:
self.parse_error(
@ -166,26 +170,13 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
self.parse_error(
"attribute 'maxOccurs' not allowed for the model of a global group", content_model
)
if content_model.tag not in {XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
self.parse_error('unexpected tag %r' % content_model.tag, content_model)
return
elif elem.tag in {XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
# Local group (sequence|all|choice)
if 'name' in elem.attrib:
self.parse_error("attribute 'name' not allowed for a local group")
content_model = elem
self.name = None
elif elem.tag in {XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_RESTRICTION}:
self.name = self.model = None
return
else:
self.parse_error('unexpected tag %r' % elem.tag)
return
if content_model.tag in {XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
self._parse_content_model(content_model)
else:
self.parse_error('unexpected tag %r' % content_model.tag, content_model)
self._parse_content_model(elem, content_model)
def _parse_content_model(self, elem, content_model):
def _parse_content_model(self, content_model):
self.model = local_name(content_model.tag)
if self.model == 'all':
if self.max_occurs != 1:
@ -198,7 +189,7 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
# Builds inner elements and reference groups later, for avoids circularity.
self.append((child, self.schema))
elif content_model.tag == XSD_ALL:
self.parse_error("'all' model can contains only elements.", elem)
self.parse_error("'all' model can contains only elements.")
elif child.tag == XSD_ANY:
self.append(XsdAnyElement(child, self.schema, self))
elif child.tag in (XSD_SEQUENCE, XSD_CHOICE):
@ -220,11 +211,11 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
else:
self.append(xsd_group)
elif self.redefine is None:
self.parse_error("Circular definition detected for group %r:" % self.name, elem)
self.parse_error("Circular definition detected for group %r:" % self.name)
else:
if child.get('minOccurs', '1') != '1' or child.get('maxOccurs', '1') != '1':
self.parse_error(
"Redefined group reference cannot have minOccurs/maxOccurs other than 1:", elem
"Redefined group reference cannot have minOccurs/maxOccurs other than 1:"
)
self.append(self.redefine)
else:
@ -489,6 +480,81 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
else:
return other_max_occurs >= max_occurs * self.max_occurs
def check_dynamic_context(self, elem, xsd_element, model_element, converter):
if model_element is not xsd_element:
if 'substitution' in model_element.block \
or xsd_element.type.is_blocked(model_element):
raise XMLSchemaValidationError(
model_element, "substitution of %r is blocked" % model_element
)
alternatives = ()
if isinstance(xsd_element, XsdAnyElement):
if xsd_element.process_contents == 'skip':
return
try:
xsd_element = self.maps.lookup_element(elem.tag)
except LookupError:
try:
type_name = elem.attrib[XSI_TYPE].strip()
except KeyError:
return
else:
xsd_type = self.maps.get_instance_type(type_name, self.any_type, converter)
else:
alternatives = xsd_element.alternatives
try:
type_name = elem.attrib[XSI_TYPE].strip()
except KeyError:
xsd_type = xsd_element.type
else:
xsd_type = self.maps.get_instance_type(type_name, xsd_element.type, converter)
else:
if XSI_TYPE not in elem.attrib:
xsd_type = xsd_element.type
else:
alternatives = xsd_element.alternatives
try:
type_name = elem.attrib[XSI_TYPE].strip()
except KeyError:
xsd_type = xsd_element.type
else:
xsd_type = self.maps.get_instance_type(type_name, xsd_element.type, converter)
if model_element is not xsd_element and model_element.block:
for derivation in model_element.block.split():
if xsd_type.is_derived(model_element.type, derivation):
reason = "usage of %r with type %s is blocked by head element"
raise XMLSchemaValidationError(self, reason % (xsd_element, derivation))
if XSI_TYPE not in elem.attrib:
return
# If it's a restriction the context is the base_type's group
group = self.restriction if self.restriction is not None else self
# Dynamic EDC check of matched element
for e in filter(lambda x: isinstance(x, XsdElement), group.iter_elements()):
if e.name == elem.tag:
other = e
else:
for other in e.iter_substitutes():
if other.name == elem.tag:
break
else:
continue
if len(other.alternatives) != len(alternatives) or \
not xsd_type.is_dynamic_consistent(other.type):
reason = "%r that matches %r is not consistent with local declaration %r"
raise XMLSchemaValidationError(self, reason % (elem, xsd_element, other))
elif not all(any(a == x for x in alternatives) for a in other.alternatives) or \
not all(any(a == x for x in other.alternatives) for a in alternatives):
msg = "Maybe a not equivalent type table between elements %r and %r." % (self, xsd_element)
warnings.warn(msg, XMLSchemaTypeTableWarning, stacklevel=3)
def iter_decode(self, elem, validation='lax', converter=None, level=0, **kwargs):
"""
Creates an iterator for decoding an Element content.
@ -538,38 +604,47 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
if callable(child.tag):
continue # child is a <class 'lxml.etree._Comment'>
if self.interleave and self.interleave.is_matching(child.tag, default_namespace, self):
xsd_element = self.interleave
else:
while model.element is not None:
xsd_element = model.element.match(child.tag, default_namespace, self)
if xsd_element is None:
for particle, occurs, expected in model.advance(False):
errors.append((index, particle, occurs, expected))
model.clear()
model_broken = True # the model is broken, continues with raw decoding.
break
else:
continue
while model.element is not None:
xsd_element = model.element.match(
child.tag, default_namespace, group=self, occurs=model.occurs
)
if xsd_element is None:
if self.interleave is not None and \
self.interleave.is_matching(child.tag, default_namespace, self, model.occurs):
xsd_element = self.interleave
break
for particle, occurs, expected in model.advance(True):
for particle, occurs, expected in model.advance(False):
errors.append((index, particle, occurs, expected))
break
else:
if self.suffix and self.suffix.is_matching(child.tag, default_namespace, self):
xsd_element = self.suffix
model.clear()
model_broken = True # the model is broken, continues with raw decoding.
break
else:
for xsd_element in self.iter_elements():
if xsd_element.is_matching(child.tag, default_namespace, self):
if not model_broken:
errors.append((index, xsd_element, 0, []))
model_broken = True
break
else:
errors.append((index, self, 0, None))
xsd_element = None
model_broken = True
continue
break
try:
self.check_dynamic_context(child, xsd_element, model.element, converter)
except XMLSchemaValidationError as err:
yield self.validation_error(validation, err, elem, **kwargs)
for particle, occurs, expected in model.advance(True):
errors.append((index, particle, occurs, expected))
break
else:
if self.suffix is not None and self.suffix.is_matching(child.tag, default_namespace, self):
xsd_element = self.suffix
else:
for xsd_element in self.iter_elements():
if xsd_element.is_matching(child.tag, default_namespace, group=self):
if not model_broken:
errors.append((index, xsd_element, 0, []))
model_broken = True
break
else:
errors.append((index, self, 0, None))
xsd_element = None
model_broken = True
if xsd_element is None or kwargs.get('no_depth'):
# TODO: use a default decoder str-->str??
@ -637,10 +712,12 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
cdata_index = 0
if isinstance(element_data.content, dict) or kwargs.get('unordered'):
content = model.iter_unordered_content(element_data.content)
elif not isinstance(element_data.content, list):
content = []
elif converter.losslessly:
content = element_data.content
else:
content = model.iter_collapsed_content(element_data.content)
content = ModelVisitor(self).iter_collapsed_content(element_data.content)
for index, (name, value) in enumerate(content):
if isinstance(name, int):
@ -653,12 +730,14 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
cdata_index += 1
continue
if self.interleave and self.interleave.is_matching(name, default_namespace, self):
if self.interleave and self.interleave.is_matching(name, default_namespace, group=self):
xsd_element = self.interleave
value = get_qname(default_namespace, name), value
else:
while model.element is not None:
xsd_element = model.element.match(name, default_namespace, self)
xsd_element = model.element.match(
name, default_namespace, group=self, occurs=model.occurs
)
if xsd_element is None:
for particle, occurs, expected in model.advance():
errors.append((index - cdata_index, particle, occurs, expected))
@ -670,13 +749,13 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
errors.append((index - cdata_index, particle, occurs, expected))
break
else:
if self.suffix and self.suffix.is_matching(name, default_namespace, self):
if self.suffix and self.suffix.is_matching(name, default_namespace, group=self):
xsd_element = self.suffix
value = get_qname(default_namespace, name), value
else:
errors.append((index - cdata_index, self, 0, []))
for xsd_element in self.iter_elements():
if not xsd_element.is_matching(name, default_namespace, self):
if not xsd_element.is_matching(name, default_namespace, group=self):
continue
elif isinstance(xsd_element, XsdAnyElement):
value = get_qname(default_namespace, name), value
@ -705,7 +784,7 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
else:
children[-1].tail = children[-1].tail.strip() + (padding[:-indent] or '\n')
if validation != 'skip' and errors:
if validation != 'skip' and (errors or not content):
attrib = {k: unicode_type(v) for k, v in element_data.attributes.items()}
if validation == 'lax' and converter.etree_element_class is not etree_element:
child_tags = [converter.etree_element(e.tag, attrib=e.attrib) for e in children]
@ -713,6 +792,10 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
else:
elem = converter.etree_element(element_data.tag, text, children, attrib)
if not content:
reason = "wrong content type {!r}".format(type(element_data.content))
yield self.validation_error(validation, reason, elem, **kwargs)
for index, particle, occurs, expected in errors:
yield self.children_validation_error(validation, elem, index, particle, occurs, expected, **kwargs)
@ -732,7 +815,7 @@ class Xsd11Group(XsdGroup):
Content: (annotation?, (element | any | group)*)
</all>
"""
def _parse_content_model(self, elem, content_model):
def _parse_content_model(self, content_model):
self.model = local_name(content_model.tag)
if self.model == 'all':
if self.max_occurs not in (0, 1):
@ -766,11 +849,11 @@ class Xsd11Group(XsdGroup):
self.pop()
elif self.redefine is None:
self.parse_error("Circular definition detected for group %r:" % self.name, elem)
self.parse_error("Circular definition detected for group %r:" % self.name)
else:
if child.get('minOccurs', '1') != '1' or child.get('maxOccurs', '1') != '1':
self.parse_error(
"Redefined group reference cannot have minOccurs/maxOccurs other than 1:", elem
"Redefined group reference cannot have minOccurs/maxOccurs other than 1:"
)
self.append(self.redefine)
else:
@ -845,7 +928,7 @@ class Xsd11Group(XsdGroup):
for w1 in filter(lambda x: isinstance(x, XsdAnyElement), base_items):
for w2 in wildcards:
if w1.process_contents == w2.process_contents and w1.occurs == w2.occurs:
w2.extend(w1)
w2.union(w1)
w2.extended = True
break
else:

View File

@ -17,8 +17,8 @@ from collections import Counter
from elementpath import Selector, XPath1Parser, ElementPathError
from ..exceptions import XMLSchemaValueError
from ..qnames import XSD_ANNOTATION, XSD_UNIQUE, XSD_KEY, XSD_KEYREF, XSD_SELECTOR, XSD_FIELD
from ..helpers import get_qname, qname_to_prefixed
from ..qnames import XSD_ANNOTATION, XSD_QNAME, XSD_UNIQUE, XSD_KEY, XSD_KEYREF, \
XSD_SELECTOR, XSD_FIELD, get_qname, qname_to_prefixed, qname_to_extended
from ..etree import etree_getpath
from ..regex import get_python_regex
@ -148,13 +148,14 @@ class XsdIdentity(XsdComponent):
for xsd_element in self.selector.xpath_selector.iter_select(self.parent):
yield xsd_element
def get_fields(self, context, decoders=None):
def get_fields(self, context, namespaces=None, decoders=None):
"""
Get fields for a schema or instance context element.
:param context: Context Element or XsdElement
:param decoders: Context schema fields decoders.
:return: A tuple with field values. An empty field is replaced by `None`.
:param context: context Element or XsdElement
:param namespaces: is an optional mapping from namespace prefix to URI.
:param decoders: context schema fields decoders.
:return: a tuple with field values. An empty field is replaced by `None`.
"""
fields = []
for k, field in enumerate(self.fields):
@ -170,6 +171,8 @@ class XsdIdentity(XsdComponent):
fields.append(result[0])
else:
value = decoders[k].data_value(result[0])
if decoders[k].type.root_type.name == XSD_QNAME:
value = qname_to_extended(value, namespaces)
if isinstance(value, list):
fields.append(tuple(value))
else:
@ -178,11 +181,12 @@ class XsdIdentity(XsdComponent):
raise XMLSchemaValueError("%r field selects multiple values!" % field)
return tuple(fields)
def iter_values(self, elem):
def iter_values(self, elem, namespaces):
"""
Iterate field values, excluding empty values (tuples with all `None` values).
:param elem: Instance XML element.
:param elem: instance XML element.
:param namespaces: XML document namespaces.
:return: N-Tuple with value fields.
"""
current_path = ''
@ -193,13 +197,15 @@ class XsdIdentity(XsdComponent):
# Change the XSD context only if the path is changed
current_path = path
xsd_element = self.parent.find(path)
if not hasattr(xsd_element, 'tag'):
yield XMLSchemaValidationError(self, e, "{!r} is not an element".format(xsd_element))
xsd_fields = self.get_fields(xsd_element)
if all(fld is None for fld in xsd_fields):
continue
try:
fields = self.get_fields(e, decoders=xsd_fields)
fields = self.get_fields(e, namespaces, decoders=xsd_fields)
except XMLSchemaValueError as err:
yield XMLSchemaValidationError(self, e, reason=str(err))
else:
@ -208,15 +214,11 @@ class XsdIdentity(XsdComponent):
@property
def built(self):
return bool(self.fields and self.selector)
return self.selector is not None
def __call__(self, *args, **kwargs):
for error in self.validator(*args, **kwargs):
yield error
def validator(self, elem):
def __call__(self, elem, namespaces):
values = Counter()
for v in self.iter_values(elem):
for v in self.iter_values(elem, namespaces):
if isinstance(v, XMLSchemaValidationError):
yield v
else:
@ -267,9 +269,10 @@ class XsdKeyref(XsdIdentity):
elif isinstance(self.refer, (XsdKey, XsdUnique)):
return # referenced key/unique identity constraint already set
try:
self.refer = self.parent.identities[self.refer]
except KeyError:
refer = self.parent.identities.get(self.refer)
if refer is not None and refer.ref is None:
self.refer = refer
else:
try:
self.refer = self.maps.identities[self.refer]
except KeyError:
@ -296,29 +299,29 @@ class XsdKeyref(XsdIdentity):
@property
def built(self):
return bool(self.fields and self.selector and self.refer)
return self.selector is not None and isinstance(self.refer, XsdIdentity)
def get_refer_values(self, elem):
def get_refer_values(self, elem, namespaces):
values = set()
for e in elem.iterfind(self.refer_path):
for v in self.refer.iter_values(e):
for v in self.refer.iter_values(e, namespaces):
if not isinstance(v, XMLSchemaValidationError):
values.add(v)
return values
def validator(self, elem):
def __call__(self, elem, namespaces):
if self.refer is None:
return
refer_values = None
for v in self.iter_values(elem):
for v in self.iter_values(elem, namespaces):
if isinstance(v, XMLSchemaValidationError):
yield v
continue
if refer_values is None:
try:
refer_values = self.get_refer_values(elem)
refer_values = self.get_refer_values(elem, namespaces)
except XMLSchemaValueError as err:
yield XMLSchemaValidationError(self, elem, str(err))
continue

View File

@ -18,6 +18,7 @@ from ..compat import PY3, MutableSequence
from ..exceptions import XMLSchemaValueError
from .exceptions import XMLSchemaModelError, XMLSchemaModelDepthError
from .xsdbase import ParticleMixin
from .wildcards import XsdAnyElement, Xsd11AnyElement
MAX_MODEL_DEPTH = 15
"""Limit depth for safe visiting of models"""
@ -233,15 +234,27 @@ class ModelGroup(MutableSequence, ParticleMixin):
continue
elif pe.parent is e.parent:
if pe.parent.model in {'all', 'choice'}:
msg = "{!r} and {!r} overlap and are in the same {!r} group"
raise XMLSchemaModelError(self, msg.format(pe, e, pe.parent.model))
if isinstance(pe, Xsd11AnyElement) and not isinstance(e, XsdAnyElement):
pe.add_precedence(e, self)
elif isinstance(e, Xsd11AnyElement) and not isinstance(pe, XsdAnyElement):
e.add_precedence(pe, self)
else:
msg = "{!r} and {!r} overlap and are in the same {!r} group"
raise XMLSchemaModelError(self, msg.format(pe, e, pe.parent.model))
elif pe.min_occurs == pe.max_occurs:
continue
if not distinguishable_paths(previous_path + [pe], current_path + [e]):
if distinguishable_paths(previous_path + [pe], current_path + [e]):
continue
elif isinstance(pe, Xsd11AnyElement) and not isinstance(e, XsdAnyElement):
pe.add_precedence(e, self)
elif isinstance(e, Xsd11AnyElement) and not isinstance(pe, XsdAnyElement):
e.add_precedence(pe, self)
else:
raise XMLSchemaModelError(
self, "Unique Particle Attribution violation between {!r} and {!r}".format(pe, e)
)
paths[e.name] = e, current_path[:]
@ -322,8 +335,7 @@ class ModelVisitor(MutableSequence):
:ivar occurs: the Counter instance for keeping track of occurrences of XSD elements and groups.
:ivar element: the current XSD element, initialized to the first element of the model.
:ivar group: the current XSD model group, initialized to *root* argument.
:ivar iterator: the current XSD group iterator.
:ivar items: the current XSD group unmatched items.
:ivar items: the current XSD group's items iterator.
:ivar match: if the XSD group has an effective item match.
"""
def __init__(self, root):
@ -331,7 +343,7 @@ class ModelVisitor(MutableSequence):
self.occurs = Counter()
self._subgroups = []
self.element = None
self.group, self.iterator, self.items, self.match = root, iter(root), root[::-1], False
self.group, self.items, self.match = root, iter(root), False
self._start()
def __str__(self):
@ -367,17 +379,17 @@ class ModelVisitor(MutableSequence):
del self._subgroups[:]
self.occurs.clear()
self.element = None
self.group, self.iterator, self.items, self.match = self.root, iter(self.root), self.root[::-1], False
self.group, self.items, self.match = self.root, iter(self.root), False
def _start(self):
while True:
item = next(self.iterator, None)
item = next(self.items, None)
if item is None or not isinstance(item, ModelGroup):
self.element = item
break
elif item:
self.append((self.group, self.iterator, self.items, self.match))
self.group, self.iterator, self.items, self.match = item, iter(item), item[::-1], False
self.append((self.group, self.items, self.match))
self.group, self.items, self.match = item, iter(item), False
@property
def expected(self):
@ -385,12 +397,19 @@ class ModelVisitor(MutableSequence):
Returns the expected elements of the current and descendant groups.
"""
expected = []
for item in reversed(self.items):
if isinstance(item, ModelGroup):
expected.extend(item.iter_elements())
if self.group.model == 'choice':
items = self.group
elif self.group.model == 'all':
items = (e for e in self.group if e.min_occurs > self.occurs[e])
else:
items = (e for e in self.group if e.min_occurs > self.occurs[e])
for e in items:
if isinstance(e, ModelGroup):
expected.extend(e.iter_elements())
else:
expected.append(item)
expected.extend(item.maps.substitution_groups.get(item.name, ()))
expected.append(e)
expected.extend(e.maps.substitution_groups.get(e.name, ()))
return expected
def restart(self):
@ -417,7 +436,7 @@ class ModelVisitor(MutableSequence):
or for the current group, `False` otherwise.
"""
if isinstance(item, ModelGroup):
self.group, self.iterator, self.items, self.match = self.pop()
self.group, self.items, self.match = self.pop()
item_occurs = occurs[item]
model = self.group.model
@ -426,29 +445,21 @@ class ModelVisitor(MutableSequence):
if model == 'choice':
occurs[item] = 0
occurs[self.group] += 1
self.iterator, self.match = iter(self.group), False
else:
if model == 'all':
self.items.remove(item)
else:
self.items.pop()
if not self.items:
self.occurs[self.group] += 1
self.items, self.match = iter(self.group), False
elif model == 'sequence' and item is self.group[-1]:
self.occurs[self.group] += 1
return item.is_missing(item_occurs)
elif model == 'sequence':
if self.match:
self.items.pop()
if not self.items:
if item is self.group[-1]:
occurs[self.group] += 1
return not item.is_emptiable()
elif item.is_emptiable():
self.items.pop()
return False
elif self.group.min_occurs <= occurs[self.group] or self:
return stop_item(self.group)
else:
self.items.pop()
return True
element, occurs = self.element, self.occurs
@ -460,6 +471,8 @@ class ModelVisitor(MutableSequence):
self.match = True
if not element.is_over(occurs[element]):
return
obj = None
try:
if stop_item(element):
yield element, occurs[element], [element]
@ -468,35 +481,51 @@ class ModelVisitor(MutableSequence):
while self.group.is_over(occurs[self.group]):
stop_item(self.group)
obj = next(self.iterator, None)
obj = next(self.items, None)
if obj is None:
if not self.match:
if self.group.model == 'all' and all(e.min_occurs == 0 for e in self.items):
occurs[self.group] += 1
if self.group.model == 'all':
for e in self.group:
occurs[e] = occurs[(e,)]
if all(e.min_occurs <= occurs[e] for e in self.group):
occurs[self.group] = 1
group, expected = self.group, self.expected
if stop_item(group) and expected:
yield group, occurs[group], expected
elif not self.items:
self.iterator, self.items, self.match = iter(self.group), self.group[::-1], False
elif self.group.model == 'all':
self.iterator, self.match = iter(self.items), False
elif all(e.min_occurs == 0 for e in self.items):
self.iterator, self.items, self.match = iter(self.group), self.group[::-1], False
occurs[self.group] += 1
elif self.group.model != 'all':
self.items, self.match = iter(self.group), False
elif any(not e.is_over(occurs[e]) for e in self.group):
for e in self.group:
occurs[(e,)] += occurs[e]
self.items, self.match = (e for e in self.group if not e.is_over(occurs[e])), False
else:
for e in self.group:
occurs[(e,)] += occurs[e]
occurs[self.group] = 1
elif not isinstance(obj, ModelGroup): # XsdElement or XsdAnyElement
self.element, occurs[obj] = obj, 0
return
else:
self.append((self.group, self.iterator, self.items, self.match))
self.group, self.iterator, self.items, self.match = obj, iter(obj), obj[::-1], False
self.append((self.group, self.items, self.match))
self.group, self.items, self.match = obj, iter(obj), False
occurs[obj] = 0
if obj.model == 'all':
for e in obj:
occurs[(e,)] = 0
except IndexError:
# Model visit ended
self.element = None
if self.group.is_missing(occurs[self.group]) and self.items:
yield self.group, occurs[self.group], self.expected
if self.group.is_missing(occurs[self.group]):
if self.group.model == 'choice':
yield self.group, occurs[self.group], self.expected
elif self.group.model == 'sequence':
if obj is not None:
yield self.group, occurs[self.group], self.expected
elif any(e.min_occurs > occurs[e] for e in self.group):
yield self.group, occurs[self.group], self.expected
def sort_content(self, content, restart=True):
if restart:
@ -578,26 +607,42 @@ class ModelVisitor(MutableSequence):
"""
prev_name = None
unordered_content = defaultdict(deque)
for name, value in content:
if isinstance(name, int) or self.element is None:
yield name, value
elif prev_name != name:
continue
while self.element is not None:
if self.element.is_matching(name):
yield name, value
prev_name = name
for _ in self.advance(True):
pass
break
for key in unordered_content:
if self.element.is_matching(key):
break
else:
if prev_name == name:
unordered_content[name].append(value)
break
for _ in self.advance(False):
pass
continue
try:
yield key, unordered_content[key].popleft()
except IndexError:
del unordered_content[key]
else:
for _ in self.advance(True):
pass
else:
yield name, value
prev_name = name
elif self.element.is_matching(name):
yield name, value
else:
unordered_content[name].append(value)
while self.element is not None and unordered_content:
for key in unordered_content:
if self.element.is_matching(key):
try:
yield name, unordered_content[key].popleft()
except IndexError:
del unordered_content[key]
break
else:
break
# Add the remaining consumable content onto the end of the data.
for name, values in unordered_content.items():

View File

@ -10,10 +10,7 @@
#
from __future__ import unicode_literals
from ..exceptions import XMLSchemaValueError
from ..qnames import XSD_NOTATION
from ..helpers import get_qname
from ..qnames import XSD_NOTATION, get_qname
from .xsdbase import XsdComponent

View File

@ -18,6 +18,7 @@ the standard.
import os
from collections import namedtuple, Counter
from abc import ABCMeta
import logging
import warnings
import re
@ -27,9 +28,9 @@ from ..exceptions import XMLSchemaTypeError, XMLSchemaURLError, XMLSchemaKeyErro
from ..qnames import VC_MIN_VERSION, VC_MAX_VERSION, VC_TYPE_AVAILABLE, \
VC_TYPE_UNAVAILABLE, VC_FACET_AVAILABLE, VC_FACET_UNAVAILABLE, XSD_SCHEMA, \
XSD_ANNOTATION, XSD_NOTATION, XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_GROUP, \
XSD_SIMPLE_TYPE, XSD_COMPLEX_TYPE, XSD_ELEMENT, XSD_SEQUENCE, XSD_ANY, \
XSD_ANY_ATTRIBUTE, XSD_INCLUDE, XSD_IMPORT, XSD_REDEFINE, XSD_OVERRIDE, \
XSD_DEFAULT_OPEN_CONTENT
XSD_SIMPLE_TYPE, XSD_COMPLEX_TYPE, XSD_ELEMENT, XSD_SEQUENCE, XSD_CHOICE, \
XSD_ALL, XSD_ANY, XSD_ANY_ATTRIBUTE, XSD_INCLUDE, XSD_IMPORT, XSD_REDEFINE, \
XSD_OVERRIDE, XSD_DEFAULT_OPEN_CONTENT
from ..helpers import get_xsd_derivation_attribute, get_xsd_form_attribute
from ..namespaces import XSD_NAMESPACE, XML_NAMESPACE, XSI_NAMESPACE, XHTML_NAMESPACE, \
XLINK_NAMESPACE, VC_NAMESPACE, NamespaceResourcesMap, NamespaceView
@ -43,6 +44,7 @@ from .exceptions import XMLSchemaParseError, XMLSchemaValidationError, XMLSchema
from .xsdbase import XSD_VALIDATION_MODES, XsdValidator, ValidationMixin, XsdComponent
from .notations import XsdNotation
from .identities import XsdKey, XsdKeyref, XsdUnique, Xsd11Key, Xsd11Unique, Xsd11Keyref
from .facets import XSD_11_FACETS
from .simple_types import xsd_simple_type_factory, XsdUnion, XsdAtomicRestriction, \
Xsd11AtomicRestriction, Xsd11Union
from .attributes import XsdAttribute, XsdAttributeGroup, Xsd11Attribute
@ -53,6 +55,9 @@ from .wildcards import XsdAnyElement, XsdAnyAttribute, Xsd11AnyElement, \
Xsd11AnyAttribute, XsdDefaultOpenContent
from .globals_ import XsdGlobals
logger = logging.getLogger('xmlschema')
logging.basicConfig(format='[%(levelname)s] %(message)s')
XSD_VERSION_PATTERN = re.compile(r'^\d+\.\d+$')
# Elements for building dummy groups
@ -70,10 +75,12 @@ ANY_ELEMENT = etree_element(
'maxOccurs': 'unbounded'
})
# XSD schemas of W3C standards
SCHEMAS_DIR = os.path.join(os.path.dirname(__file__), 'schemas/')
XML_SCHEMA_FILE = os.path.join(SCHEMAS_DIR, 'xml_minimal.xsd')
XSI_SCHEMA_FILE = os.path.join(SCHEMAS_DIR, 'XMLSchema-instance_minimal.xsd')
XLINK_SCHEMA_FILE = os.path.join(SCHEMAS_DIR, 'xlink.xsd')
XHTML_SCHEMA_FILE = os.path.join(SCHEMAS_DIR, 'xhtml1-strict.xsd')
VC_SCHEMA_FILE = os.path.join(SCHEMAS_DIR, 'XMLSchema-versioning_minimal.xsd')
@ -151,8 +158,10 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
:param converter: is an optional argument that can be an :class:`XMLSchemaConverter` \
subclass or instance, used for defining the default XML data converter for XML Schema instance.
:type converter: XMLSchemaConverter or None
:param locations: schema location hints for namespace imports. Can be a dictionary or \
a sequence of couples (namespace URI, resource URL).
:param locations: schema location hints, that can include additional namespaces to \
import after processing schema's import statements. Usually filled with the couples \
(namespace, url) extracted from xsi:schemaLocations. Can be a dictionary or a sequence \
of couples (namespace URI, resource URL).
:type locations: dict or list or None
:param base_url: is an optional base URL, used for the normalization of relative paths \
when the URL of the schema resource can't be obtained from the source argument.
@ -168,6 +177,11 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
meta-schema is added at the end. In the latter case the meta-schema is rebuilt if any base \
namespace has been overridden by an import. Ignored if the argument *global_maps* is provided.
:type use_meta: bool
:param loglevel: for setting a different logging level for schema initialization \
and building. For default is WARNING (30). For INFO level set it with 20, for \
DEBUG level with 10. The default loglevel is restored after schema building, \
when exiting the initialization method.
:type loglevel: int
:cvar XSD_VERSION: store the XSD version (1.0 or 1.1).
:vartype XSD_VERSION: str
@ -179,6 +193,8 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
:vartype BUILDERS_MAP: dict
:cvar BASE_SCHEMAS: a dictionary from namespace to schema resource for meta-schema bases.
:vartype BASE_SCHEMAS: dict
:cvar FALLBACK_LOCATIONS: fallback schema location hints for other standard namespaces.
:vartype FALLBACK_LOCATIONS: dict
:cvar meta_schema: the XSD meta-schema instance.
:vartype meta_schema: XMLSchema
:cvar attribute_form_default: the schema's *attributeFormDefault* attribute, defaults to 'unqualified'.
@ -204,9 +220,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
:vartype maps: XsdGlobals
:ivar converter: the default converter used for XML data decoding/encoding.
:vartype converter: XMLSchemaConverter
:ivar xpath_proxy: a proxy for XPath operations on schema components.
:vartype xpath_proxy: XMLSchemaProxy
:ivar locations: schema location hints.
:ivar locations: schemas location hints.
:vartype locations: NamespaceResourcesMap
:ivar namespaces: a dictionary that maps from the prefixes used by the schema into namespace URI.
:vartype namespaces: dict
@ -236,6 +250,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
BUILDERS = None
BUILDERS_MAP = None
BASE_SCHEMAS = None
FALLBACK_LOCATIONS = None
meta_schema = None
# Schema defaults
@ -251,10 +266,18 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
default_open_content = None
override = None
def __init__(self, source, namespace=None, validation='strict', global_maps=None, converter=None,
locations=None, base_url=None, defuse='remote', timeout=300, build=True, use_meta=True):
def __init__(self, source, namespace=None, validation='strict', global_maps=None,
converter=None, locations=None, base_url=None, defuse='remote',
timeout=300, build=True, use_meta=True, loglevel=None):
super(XMLSchemaBase, self).__init__(validation)
if loglevel is not None:
logger.setLevel(loglevel)
elif build and global_maps is None:
logger.setLevel(logging.WARNING)
self.source = XMLResource(source, base_url, defuse, timeout, lazy=False)
logger.debug("Read schema from %r", self.source)
self.imports = {}
self.includes = {}
self.warnings = []
@ -284,6 +307,9 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
if '' not in self.namespaces:
self.namespaces[''] = namespace
logger.debug("Schema targetNamespace is %r", self.target_namespace)
logger.debug("Declared namespaces: %r", self.namespaces)
# Parses the schema defaults
if 'attributeFormDefault' in root.attrib:
try:
@ -298,12 +324,15 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
self.parse_error(err, root)
if 'blockDefault' in root.attrib:
try:
self.block_default = get_xsd_derivation_attribute(
root, 'blockDefault', {'extension', 'restriction', 'substitution'}
)
except ValueError as err:
self.parse_error(err, root)
if self.meta_schema is None:
pass # Skip XSD 1.0 meta-schema that has blockDefault="#all"
else:
try:
self.block_default = get_xsd_derivation_attribute(
root, 'blockDefault', {'extension', 'restriction', 'substitution'}
)
except ValueError as err:
self.parse_error(err, root)
if 'finalDefault' in root.attrib:
try:
@ -311,14 +340,9 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
except ValueError as err:
self.parse_error(err, root)
# Set locations hints
self.locations = NamespaceResourcesMap(self.source.get_locations(locations))
if self.meta_schema is not None:
# Add fallback schema location hint for XHTML
self.locations[XHTML_NAMESPACE] = os.path.join(SCHEMAS_DIR, 'xhtml1-strict.xsd')
self.converter = self.get_converter(converter)
self.xpath_proxy = XMLSchemaProxy(self)
self.xpath_tokens = {}
# Create or set the XSD global maps instance
if self.meta_schema is None:
@ -329,7 +353,6 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
elif global_maps is None:
if use_meta is False:
self.maps = XsdGlobals(self, validation)
self.locations.update(self.BASE_SCHEMAS)
elif self.target_namespace not in self.BASE_SCHEMAS:
if not self.meta_schema.maps.types:
self.meta_schema.maps.build()
@ -364,9 +387,13 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
for e in self.meta_schema.iter_errors(root, namespaces=self.namespaces):
self.parse_error(e.reason, elem=e.elem)
# Includes and imports schemas (errors are treated as warnings)
self._include_schemas()
self._import_namespaces()
self._parse_inclusions()
self._parse_imports()
# Imports by argument (usually from xsi:schemaLocation attribute).
for ns in self.locations:
if ns not in self.maps.namespaces:
self._import_namespace(ns, self.locations[ns])
if '' not in self.namespaces:
self.namespaces[''] = '' # For default local names are mapped to no namespace
@ -384,8 +411,22 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
self.default_open_content = XsdDefaultOpenContent(child, self)
break
if build:
self.maps.build()
try:
if build:
self.maps.build()
finally:
if loglevel is not None:
logger.setLevel(logging.WARNING) # Restore default logging
def __getstate__(self):
state = self.__dict__.copy()
del state['xpath_tokens']
state.pop('_xpath_parser', None)
return state
def __setstate__(self, state):
self.__dict__.update(state)
self.xpath_tokens = {}
def __repr__(self):
if self.url:
@ -428,6 +469,10 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
def __len__(self):
return len(self.elements)
@property
def xpath_proxy(self):
return XMLSchemaProxy(self)
@property
def xsd_version(self):
"""Property that returns the class attribute XSD_VERSION."""
@ -551,7 +596,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
for e in xsd_element.iter():
if e is xsd_element or isinstance(e, XsdAnyElement):
continue
elif e.ref or e.is_global:
elif e.ref or e.parent is None:
if e.name in names:
names.discard(e.name)
if not names:
@ -574,9 +619,10 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
:param source: an optional argument referencing to or containing the XSD meta-schema \
resource. Required if the schema class doesn't already have a meta-schema.
:param base_schemas: an optional dictionary that contains namespace URIs and schema locations. \
If provided it's used as substitute for class 's BASE_SCHEMAS. Also a sequence of (namespace, \
location) items can be provided if there are more schema documents for one or more namespaces.
:param base_schemas: an optional dictionary that contains namespace URIs and \
schema locations. If provided it's used as substitute for class 's BASE_SCHEMAS. \
Also a sequence of (namespace, location) items can be provided if there are more \
schema documents for one or more namespaces.
:param global_maps: is an optional argument containing an :class:`XsdGlobals` \
instance for the new meta schema. If not provided a new map is created.
"""
@ -634,18 +680,46 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
return group
def create_empty_content_group(self, parent, model='sequence'):
if model == 'sequence':
group_elem = etree_element(XSD_SEQUENCE)
elif model == 'choice':
group_elem = etree_element(XSD_CHOICE)
elif model == 'all':
group_elem = etree_element(XSD_ALL)
else:
raise XMLSchemaValueError("'model' argument must be (sequence | choice | all)")
group_elem.text = '\n '
return self.BUILDERS.group_class(group_elem, self, parent)
def create_any_attribute_group(self, parent):
"""
Creates an attribute group related to schema instance that accepts any attribute.
:param parent: the parent component to set for the any attribute group.
"""
attribute_group = self.BUILDERS.attribute_group_class(ATTRIBUTE_GROUP_ELEMENT, self, parent)
attribute_group[None] = self.BUILDERS.any_attribute_class(ANY_ATTRIBUTE_ELEMENT, self, attribute_group)
attribute_group = self.BUILDERS.attribute_group_class(
ATTRIBUTE_GROUP_ELEMENT, self, parent
)
attribute_group[None] = self.BUILDERS.any_attribute_class(
ANY_ATTRIBUTE_ELEMENT, self, attribute_group
)
return attribute_group
def create_empty_attribute_group(self, parent):
"""
Creates an empty attribute group related to schema instance.
:param parent: the parent component to set for the any attribute group.
"""
return self.BUILDERS.attribute_group_class(ATTRIBUTE_GROUP_ELEMENT, self, parent)
def copy(self):
"""Makes a copy of the schema instance. The new instance has independent maps of shared XSD components."""
"""
Makes a copy of the schema instance. The new instance has independent maps
of shared XSD components.
"""
schema = object.__new__(self.__class__)
schema.__dict__.update(self.__dict__)
schema.source = self.source.copy()
@ -779,17 +853,19 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
def get_element(self, tag, path=None, namespaces=None):
if not path:
return self.find(tag)
return self.find(tag, namespaces)
elif path[-1] == '*':
return self.find(path[:-1] + tag, namespaces)
else:
return self.find(path, namespaces)
def _include_schemas(self):
def _parse_inclusions(self):
"""Processes schema document inclusions and redefinitions."""
for child in filter(lambda x: x.tag == XSD_INCLUDE, self.root):
try:
self.include_schema(child.attrib['schemaLocation'], self.base_url)
location = child.attrib['schemaLocation'].strip()
logger.info("Include schema from %r", location)
self.include_schema(location, self.base_url)
except KeyError:
pass
except (OSError, IOError) as err:
@ -810,7 +886,9 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
for child in filter(lambda x: x.tag == XSD_REDEFINE, self.root):
try:
schema = self.include_schema(child.attrib['schemaLocation'], self.base_url)
location = child.attrib['schemaLocation'].strip()
logger.info("Redefine schema %r", location)
schema = self.include_schema(location, self.base_url)
except KeyError:
pass # Attribute missing error already found by validation against meta-schema
except (OSError, IOError) as err:
@ -845,8 +923,15 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
break
else:
schema = self.create_schema(
schema_url, self.target_namespace, self.validation, self.maps, self.converter,
self.locations, self.base_url, self.defuse, self.timeout, False
source=schema_url,
namespace=self.target_namespace,
validation=self.validation,
global_maps=self.maps,
converter=self.converter,
base_url=self.base_url,
defuse=self.defuse,
timeout=self.timeout,
build=False,
)
if location not in self.includes:
@ -855,10 +940,10 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
self.includes[schema_url] = schema
return schema
def _import_namespaces(self):
def _parse_imports(self):
"""
Processes namespace imports. Imports are done on namespace basis not on resource: this
is the standard and also avoids import loops that sometimes are hard to detect.
Parse namespace import elements. Imports are done on namespace basis, not on
single resource. A warning is generated for a failure of a namespace import.
"""
namespace_imports = NamespaceResourcesMap(map(
lambda x: (x.get('namespace'), x.get('schemaLocation')),
@ -900,35 +985,44 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
if local_hints:
locations = local_hints + locations
import_error = None
for url in locations:
try:
self.import_schema(namespace, url, self.base_url)
except (OSError, IOError) as err:
# It's not an error if the location access fails (ref. section 4.2.6.2):
# https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#composition-schemaImport
if import_error is None:
import_error = err
except (XMLSchemaURLError, XMLSchemaParseError, XMLSchemaTypeError, ParseError) as err:
if namespace:
msg = "cannot import namespace %r: %s." % (namespace, err)
else:
msg = "cannot import chameleon schema: %s." % err
if isinstance(err, (XMLSchemaParseError, ParseError)):
self.parse_error(msg)
elif self.validation == 'strict':
raise type(err)(msg)
else:
self.errors.append(type(err)(msg))
except XMLSchemaValueError as err:
self.parse_error(err)
if namespace in self.FALLBACK_LOCATIONS:
locations.append(self.FALLBACK_LOCATIONS[namespace])
self._import_namespace(namespace, locations)
def _import_namespace(self, namespace, locations):
import_error = None
for url in locations:
try:
logger.debug("Import namespace %r from %r", namespace, url)
self.import_schema(namespace, url, self.base_url)
except (OSError, IOError) as err:
# It's not an error if the location access fails (ref. section 4.2.6.2):
# https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#composition-schemaImport
logger.debug('%s', err)
if import_error is None:
import_error = err
except (XMLSchemaURLError, XMLSchemaParseError, XMLSchemaTypeError, ParseError) as err:
if namespace:
msg = "cannot import namespace %r: %s." % (namespace, err)
else:
break
msg = "cannot import chameleon schema: %s." % err
if isinstance(err, (XMLSchemaParseError, ParseError)):
self.parse_error(msg)
elif self.validation == 'strict':
raise type(err)(msg)
else:
self.errors.append(type(err)(msg))
except XMLSchemaValueError as err:
self.parse_error(err)
else:
if import_error is not None:
self.warnings.append("Namespace import failed: %s." % str(import_error))
warnings.warn(self.warnings[-1], XMLSchemaImportWarning, stacklevel=3)
self.imports[namespace] = None
logger.info("Namespace %r imported from %r", namespace, url)
break
else:
if import_error is not None:
self.warnings.append("Namespace import failed: %s." % str(import_error))
warnings.warn(self.warnings[-1], XMLSchemaImportWarning, stacklevel=3)
self.imports[namespace] = None
def import_schema(self, namespace, location, base_url=None, force=False):
"""
@ -957,8 +1051,14 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
return schema
schema = self.create_schema(
schema_url, None, self.validation, self.maps, self.converter,
self.locations, self.base_url, self.defuse, self.timeout, False
source=schema_url,
validation=self.validation,
global_maps=self.maps,
converter=self.converter,
base_url=self.base_url,
defuse=self.defuse,
timeout=self.timeout,
build=False,
)
if schema.target_namespace != namespace:
raise XMLSchemaValueError('imported schema %r has an unmatched namespace %r' % (location, namespace))
@ -1013,8 +1113,8 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
if VC_FACET_AVAILABLE in elem.attrib:
for qname in elem.attrib[VC_FACET_AVAILABLE].split():
try:
if self.resolve_qname(qname) in self.maps.types:
pass
if self.resolve_qname(qname) not in XSD_11_FACETS:
return False
except XMLSchemaNamespaceError:
pass
except (KeyError, ValueError) as err:
@ -1023,12 +1123,14 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
if VC_FACET_UNAVAILABLE in elem.attrib:
for qname in elem.attrib[VC_FACET_UNAVAILABLE].split():
try:
if self.resolve_qname(qname) in self.maps.types:
pass
if self.resolve_qname(qname) not in XSD_11_FACETS:
break
except XMLSchemaNamespaceError:
pass
break
except (KeyError, ValueError) as err:
self.parse_error(str(err), elem)
self.parse_error(err, elem)
else:
return False
return True
@ -1113,7 +1215,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
"""
if not self.built:
if self.meta_schema is not None:
raise XMLSchemaNotBuiltError(self, "schema %r is not built." % self)
raise XMLSchemaNotBuiltError(self, "schema %r is not built" % self)
self.build()
if not isinstance(source, XMLResource):
@ -1125,6 +1227,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
namespaces.update(source.get_namespaces())
id_map = Counter()
inherited = {}
if source.is_lazy() and path is None:
# TODO: Document validation in lazy mode.
@ -1135,8 +1238,8 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
yield self.validation_error('lax', "%r is not an element of the schema" % source.root, source.root)
for result in xsd_element.iter_decode(source.root, source=source, namespaces=namespaces,
use_defaults=use_defaults, id_map=id_map,
no_depth=True, drop_results=True):
use_defaults=use_defaults, id_map=id_map, no_depth=True,
inherited=inherited, drop_results=True):
if isinstance(result, XMLSchemaValidationError):
yield result
else:
@ -1147,18 +1250,25 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
schema_path = '/%s/*' % source.root.tag
for elem in source.iterfind(path, namespaces):
xsd_element = self.get_element(elem.tag, schema_path, namespaces)
xsd_element = self.get_element(elem.tag, schema_path, self.namespaces)
if xsd_element is None:
yield self.validation_error('lax', "%r is not an element of the schema" % elem, elem)
for result in xsd_element.iter_decode(elem, source=source, namespaces=namespaces,
use_defaults=use_defaults, id_map=id_map,
drop_results=True):
inherited=inherited, drop_results=True):
if isinstance(result, XMLSchemaValidationError):
yield result
else:
del result
# Check unresolved IDREF values
for k, v in id_map.items():
if isinstance(v, XMLSchemaValidationError):
yield v
elif v == 0:
yield self.validation_error('lax', "IDREF %r not found in XML document" % k, source.root)
def iter_decode(self, source, path=None, schema_path=None, validation='lax', process_namespaces=True,
namespaces=None, use_defaults=True, decimal_type=None, datetime_types=False,
converter=None, filler=None, fill_missing=False, **kwargs):
@ -1195,7 +1305,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
"""
if not self.built:
if self.meta_schema is not None:
raise XMLSchemaNotBuiltError(self, "schema %r is not built." % self)
raise XMLSchemaNotBuiltError(self, "schema %r is not built" % self)
self.build()
if validation not in XSD_VALIDATION_MODES:
@ -1214,6 +1324,8 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
converter = self.get_converter(converter, namespaces, **kwargs)
id_map = Counter()
inherited = {}
if decimal_type is not None:
kwargs['decimal_type'] = decimal_type
if filler is not None:
@ -1227,9 +1339,15 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
for obj in xsd_element.iter_decode(
elem, validation, converter=converter, source=source, namespaces=namespaces,
use_defaults=use_defaults, datetime_types=datetime_types,
fill_missing=fill_missing, id_map=id_map, **kwargs):
fill_missing=fill_missing, id_map=id_map, inherited=inherited, **kwargs):
yield obj
for k, v in id_map.items():
if isinstance(v, XMLSchemaValidationError):
yield v
elif v == 0:
yield self.validation_error('lax', "IDREF %r not found in XML document" % k, source.root)
def decode(self, source, path=None, schema_path=None, validation='strict', *args, **kwargs):
"""
Decodes XML data. Takes the same arguments of the method :func:`XMLSchema.iter_decode`.
@ -1272,7 +1390,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
"""
if not self.built:
if self.meta_schema is not None:
raise XMLSchemaNotBuiltError(self, "schema %r is not built." % self)
raise XMLSchemaNotBuiltError(self, "schema %r is not built" % self)
self.build()
if validation not in XSD_VALIDATION_MODES:
@ -1371,7 +1489,10 @@ class XMLSchema10(XMLSchemaBase):
BASE_SCHEMAS = {
XML_NAMESPACE: XML_SCHEMA_FILE,
XSI_NAMESPACE: XSI_SCHEMA_FILE,
}
FALLBACK_LOCATIONS = {
XLINK_NAMESPACE: XLINK_SCHEMA_FILE,
XHTML_NAMESPACE: XHTML_SCHEMA_FILE,
}
@ -1432,16 +1553,21 @@ class XMLSchema11(XMLSchemaBase):
XSD_NAMESPACE: os.path.join(SCHEMAS_DIR, 'XSD_1.1/xsd11-extra.xsd'),
XML_NAMESPACE: XML_SCHEMA_FILE,
XSI_NAMESPACE: XSI_SCHEMA_FILE,
XLINK_NAMESPACE: XLINK_SCHEMA_FILE,
VC_NAMESPACE: VC_SCHEMA_FILE,
}
FALLBACK_LOCATIONS = {
XLINK_NAMESPACE: XLINK_SCHEMA_FILE,
XHTML_NAMESPACE: XHTML_SCHEMA_FILE,
}
def _include_schemas(self):
super(XMLSchema11, self)._include_schemas()
def _parse_inclusions(self):
super(XMLSchema11, self)._parse_inclusions()
for child in filter(lambda x: x.tag == XSD_OVERRIDE, self.root):
try:
schema = self.include_schema(child.attrib['schemaLocation'], self.base_url)
location = child.attrib['schemaLocation'].strip()
logger.info("Override schema %r", location)
schema = self.include_schema(location, self.base_url)
except KeyError:
pass # Attribute missing error already found by validation against meta-schema
except (OSError, IOError) as err:

View File

@ -3,7 +3,7 @@
Chameleon schema for defining XSD 1.1 list type builtins and to override
openContent/defaultOpenContent declarations for the xmlschema library.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.w3.org/2001/XMLSchema">
<xs:override schemaLocation="XMLSchema.xsd">
<xs:element name="openContent" id="openContent">
@ -104,4 +104,4 @@ openContent/defaultOpenContent declarations for the xmlschema library.
</xs:restriction>
</xs:simpleType>
</xs:schema>
</xs:schema>

View File

@ -17,19 +17,21 @@ from decimal import DecimalException
from ..compat import string_base_type, unicode_type
from ..etree import etree_element
from ..exceptions import XMLSchemaTypeError, XMLSchemaValueError
from ..qnames import (
XSD_ANY_TYPE, XSD_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE, XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP,
XSD_ANY_ATTRIBUTE, XSD_PATTERN, XSD_MIN_INCLUSIVE, XSD_MIN_EXCLUSIVE, XSD_MAX_INCLUSIVE,
XSD_MAX_EXCLUSIVE, XSD_LENGTH, XSD_MIN_LENGTH, XSD_MAX_LENGTH, XSD_WHITE_SPACE, XSD_LIST,
XSD_ANY_SIMPLE_TYPE, XSD_UNION, XSD_RESTRICTION, XSD_ANNOTATION, XSD_ASSERTION, XSD_ID,
XSD_FRACTION_DIGITS, XSD_TOTAL_DIGITS, XSD_EXPLICIT_TIMEZONE, XSD_ERROR, XSD_ASSERT
)
from ..helpers import get_qname, local_name, get_xsd_derivation_attribute
from ..qnames import XSD_ANY_TYPE, XSD_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE, \
XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_ANY_ATTRIBUTE, XSD_PATTERN, \
XSD_MIN_INCLUSIVE, XSD_MIN_EXCLUSIVE, XSD_MAX_INCLUSIVE, XSD_MAX_EXCLUSIVE, \
XSD_LENGTH, XSD_MIN_LENGTH, XSD_MAX_LENGTH, XSD_WHITE_SPACE, XSD_LIST, \
XSD_ANY_SIMPLE_TYPE, XSD_UNION, XSD_RESTRICTION, XSD_ANNOTATION, XSD_ASSERTION, \
XSD_ID, XSD_IDREF, XSD_FRACTION_DIGITS, XSD_TOTAL_DIGITS, XSD_EXPLICIT_TIMEZONE, \
XSD_ERROR, XSD_ASSERT, get_qname, local_name
from ..helpers import get_xsd_derivation_attribute
from .exceptions import XMLSchemaValidationError, XMLSchemaEncodeError, XMLSchemaDecodeError, XMLSchemaParseError
from .exceptions import XMLSchemaValidationError, XMLSchemaEncodeError, \
XMLSchemaDecodeError, XMLSchemaParseError
from .xsdbase import XsdAnnotation, XsdType, ValidationMixin
from .facets import XsdFacet, XsdWhiteSpaceFacet, XSD_10_FACETS_BUILDERS, XSD_11_FACETS_BUILDERS, XSD_10_FACETS, \
XSD_11_FACETS, XSD_10_LIST_FACETS, XSD_11_LIST_FACETS, XSD_10_UNION_FACETS, XSD_11_UNION_FACETS, MULTIPLE_FACETS
from .facets import XsdFacet, XsdWhiteSpaceFacet, XSD_10_FACETS_BUILDERS, \
XSD_11_FACETS_BUILDERS, XSD_10_FACETS, XSD_11_FACETS, XSD_10_LIST_FACETS, \
XSD_11_LIST_FACETS, XSD_10_UNION_FACETS, XSD_11_UNION_FACETS, MULTIPLE_FACETS
def xsd_simple_type_factory(elem, schema, parent):
@ -291,6 +293,10 @@ class XsdSimpleType(XsdType, ValidationMixin):
def is_complex():
return False
@staticmethod
def is_list():
return False
def is_empty(self):
return self.max_length == 0
@ -355,7 +361,7 @@ class XsdSimpleType(XsdType, ValidationMixin):
if isinstance(obj, (string_base_type, bytes)):
obj = self.normalize(obj)
if validation != 'skip':
if validation != 'skip' and obj is not None:
if self.patterns is not None:
for error in self.patterns(obj):
yield error
@ -371,7 +377,7 @@ class XsdSimpleType(XsdType, ValidationMixin):
elif validation != 'skip':
yield self.encode_error(validation, obj, unicode_type)
if validation != 'skip':
if validation != 'skip' and obj is not None:
if self.patterns is not None:
for error in self.patterns(obj):
yield error
@ -394,11 +400,15 @@ class XsdAtomic(XsdSimpleType):
a base_type attribute that refers to primitive or derived atomic
built-in type or another derived simpleType.
"""
to_python = str
_special_types = {XSD_ANY_TYPE, XSD_ANY_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE}
_ADMITTED_TAGS = {XSD_RESTRICTION, XSD_SIMPLE_TYPE}
def __init__(self, elem, schema, parent, name=None, facets=None, base_type=None):
self.base_type = base_type
if base_type is None:
self.primitive_type = self
else:
self.base_type = base_type
super(XsdAtomic, self).__init__(elem, schema, parent, name, facets)
def __repr__(self):
@ -408,38 +418,27 @@ class XsdAtomic(XsdSimpleType):
return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name)
def __setattr__(self, name, value):
if name == 'base_type' and value is not None and not isinstance(value, XsdType):
raise XMLSchemaValueError("%r attribute must be an XsdType instance or None: %r" % (name, value))
super(XsdAtomic, self).__setattr__(name, value)
if name in ('base_type', 'white_space'):
if getattr(self, 'white_space', None) is None:
if name == 'base_type':
assert isinstance(value, XsdType)
if not hasattr(self, 'white_space'):
try:
white_space = self.base_type.white_space
self.white_space = self.base_type.white_space
except AttributeError:
return
pass
try:
if value.is_simple():
self.primitive_type = self.base_type.primitive_type
else:
if white_space is not None:
self.white_space = white_space
self.primitive_type = self.base_type.content_type.primitive_type
except AttributeError:
self.primitive_type = value
@property
def admitted_facets(self):
primitive_type = self.primitive_type
if primitive_type is None or primitive_type.is_complex():
if self.primitive_type.is_complex():
return XSD_10_FACETS if self.xsd_version == '1.0' else XSD_11_FACETS
return primitive_type.admitted_facets
@property
def primitive_type(self):
if self.base_type is None:
return self
try:
if self.base_type.is_simple():
return self.base_type.primitive_type
else:
return self.base_type.content_type.primitive_type
except AttributeError:
# The base_type is XsdList or XsdUnion.
return self.base_type
return self.primitive_type.admitted_facets
def get_facet(self, tag):
try:
@ -454,10 +453,6 @@ class XsdAtomic(XsdSimpleType):
def is_atomic():
return True
@staticmethod
def is_list():
return False
class XsdAtomicBuiltin(XsdAtomic):
"""
@ -470,8 +465,8 @@ class XsdAtomicBuiltin(XsdAtomic):
- to_python(value): Decoding from XML
- from_python(value): Encoding to XML
"""
def __init__(self, elem, schema, name, python_type, base_type=None, admitted_facets=None, facets=None,
to_python=None, from_python=None):
def __init__(self, elem, schema, name, python_type, base_type=None, admitted_facets=None,
facets=None, to_python=None, from_python=None):
"""
:param name: the XSD type's qualified name.
:param python_type: the correspondent Python's type. If a tuple or list of types \
@ -508,6 +503,9 @@ class XsdAtomicBuiltin(XsdAtomic):
def admitted_facets(self):
return self._admitted_facets or self.primitive_type.admitted_facets
def is_datetime(self):
return self.to_python.__name__ == 'fromstring'
def iter_decode(self, obj, validation='lax', **kwargs):
if isinstance(obj, (string_base_type, bytes)):
obj = self.normalize(obj)
@ -521,10 +519,23 @@ class XsdAtomicBuiltin(XsdAtomic):
except KeyError:
pass
else:
id_map[obj] += 1
if id_map[obj] > 1:
try:
id_map[obj] += 1
except TypeError:
id_map[obj] = 1
if id_map[obj] > 1 and '_skip_id' not in kwargs:
yield self.validation_error(validation, "Duplicated xsd:ID value {!r}".format(obj))
elif self.name == XSD_IDREF:
try:
id_map = kwargs['id_map']
except KeyError:
pass
else:
if obj not in id_map:
id_map[obj] = kwargs.get('node', 0)
if validation == 'skip':
try:
yield self.to_python(obj)
@ -542,6 +553,11 @@ class XsdAtomicBuiltin(XsdAtomic):
yield self.decode_error(validation, obj, self.to_python, reason=str(err))
yield None
return
except TypeError:
# xs:error type (eg. an XSD 1.1 type alternative used to catch invalid values)
yield self.validation_error(validation, "Invalid value {!r}".format(obj))
yield None
return
for validator in self.validators:
for error in validator(result):
@ -580,6 +596,10 @@ class XsdAtomicBuiltin(XsdAtomic):
yield self.encode_error(validation, obj, self.from_python)
yield None
return
except TypeError:
yield self.validation_error(validation, "Invalid value {!r}".format(obj))
yield None
return
for validator in self.validators:
for error in validator(obj):
@ -717,7 +737,7 @@ class XsdList(XsdSimpleType):
def iter_components(self, xsd_classes=None):
if xsd_classes is None or isinstance(self, xsd_classes):
yield self
if not self.base_type.is_global:
if self.base_type.parent is not None:
for obj in self.base_type.iter_components(xsd_classes):
yield obj
@ -725,10 +745,6 @@ class XsdList(XsdSimpleType):
if isinstance(obj, (string_base_type, bytes)):
obj = self.normalize(obj)
if validation != 'skip' and self.patterns:
for error in self.patterns(obj):
yield error
items = []
for chunk in obj.split():
for result in self.base_type.iter_decode(chunk, validation, **kwargs):
@ -737,22 +753,12 @@ class XsdList(XsdSimpleType):
else:
items.append(result)
if validation != 'skip':
for validator in self.validators:
for error in validator(items):
yield error
yield items
def iter_encode(self, obj, validation='lax', **kwargs):
if not hasattr(obj, '__iter__') or isinstance(obj, (str, unicode_type, bytes)):
obj = [obj]
if validation != 'skip':
for validator in self.validators:
for error in validator(obj):
yield error
encoded_items = []
for item in obj:
for result in self.base_type.iter_encode(item, validation, **kwargs):
@ -863,6 +869,10 @@ class XsdUnion(XsdSimpleType):
def is_list(self):
return all(mt.is_list() for mt in self.member_types)
def is_dynamic_consistent(self, other):
return other.is_derived(self) or hasattr(other, 'member_types') and \
any(mt1.is_derived(mt2) for mt1 in other.member_types for mt2 in self.member_types)
def iter_components(self, xsd_classes=None):
if xsd_classes is None or isinstance(self, xsd_classes):
yield self
@ -870,22 +880,15 @@ class XsdUnion(XsdSimpleType):
for obj in mt.iter_components(xsd_classes):
yield obj
def iter_decode(self, obj, validation='lax', **kwargs):
if isinstance(obj, (string_base_type, bytes)):
obj = self.normalize(obj)
if validation != 'skip' and self.patterns:
for error in self.patterns(obj):
yield error
# Try the text as a whole
def iter_decode(self, obj, validation='lax', patterns=None, **kwargs):
# Try decoding the whole text
for member_type in self.member_types:
for result in member_type.iter_decode(obj, validation='lax', **kwargs):
if not isinstance(result, XMLSchemaValidationError):
if validation != 'skip':
for validator in self.validators:
for error in validator(result):
yield error
if validation != 'skip' and patterns:
obj = member_type.normalize(obj)
for error in patterns(obj):
yield error
yield result
return
@ -917,24 +920,12 @@ class XsdUnion(XsdSimpleType):
reason = "no type suitable for decoding the values %r." % not_decodable
yield self.decode_error(validation, obj, self.member_types, reason)
for validator in self.validators:
for error in validator(items):
yield error
yield items if len(items) > 1 else items[0] if items else None
def iter_encode(self, obj, validation='lax', **kwargs):
for member_type in self.member_types:
for result in member_type.iter_encode(obj, validation='lax', **kwargs):
if result is not None and not isinstance(result, XMLSchemaValidationError):
if validation != 'skip':
for validator in self.validators:
for error in validator(obj):
yield error
if self.patterns is not None:
for error in self.patterns(result):
yield error
yield result
return
elif validation == 'strict':
@ -947,14 +938,6 @@ class XsdUnion(XsdSimpleType):
for item in obj:
for result in member_type.iter_encode(item, validation='lax', **kwargs):
if result is not None and not isinstance(result, XMLSchemaValidationError):
if validation != 'skip':
for validator in self.validators:
for error in validator(result):
yield error
if self.patterns is not None:
for error in self.patterns(result):
yield error
results.append(result)
break
elif validation == 'strict':
@ -1128,7 +1111,7 @@ class XsdAtomicRestriction(XsdAtomic):
def iter_components(self, xsd_classes=None):
if xsd_classes is None or isinstance(self, xsd_classes):
yield self
if not self.base_type.is_global:
if self.base_type.parent is not None:
for obj in self.base_type.iter_components(xsd_classes):
yield obj
@ -1136,10 +1119,6 @@ class XsdAtomicRestriction(XsdAtomic):
if isinstance(obj, (string_base_type, bytes)):
obj = self.normalize(obj)
if validation != 'skip' and self.patterns:
for error in self.patterns(obj):
yield error
if self.base_type.is_simple():
base_type = self.base_type
elif self.base_type.has_simple_content():
@ -1151,13 +1130,20 @@ class XsdAtomicRestriction(XsdAtomic):
raise XMLSchemaValueError("wrong base type %r: a simpleType or a complexType with "
"simple or mixed content required." % self.base_type)
if validation != 'skip' and self.patterns:
if not isinstance(self.primitive_type, XsdUnion):
for error in self.patterns(obj):
yield error
elif 'patterns' not in kwargs:
kwargs['patterns'] = self.patterns
for result in base_type.iter_decode(obj, validation, **kwargs):
if isinstance(result, XMLSchemaValidationError):
yield result
if isinstance(result, XMLSchemaDecodeError):
yield unicode_type(obj) if validation == 'skip' else None
else:
if validation != 'skip':
if validation != 'skip' and result is not None:
for validator in self.validators:
for error in validator(result):
yield error
@ -1169,35 +1155,21 @@ class XsdAtomicRestriction(XsdAtomic):
if self.is_list():
if not hasattr(obj, '__iter__') or isinstance(obj, (str, unicode_type, bytes)):
obj = [] if obj is None or obj == '' else [obj]
if validation != 'skip':
for validator in self.validators:
for error in validator(obj):
yield error
for result in self.base_type.iter_encode(obj, validation):
if isinstance(result, XMLSchemaValidationError):
yield result
if isinstance(result, XMLSchemaEncodeError):
yield unicode_type(obj) if validation == 'skip' else None
return
else:
yield result
return
if isinstance(obj, (string_base_type, bytes)):
obj = self.normalize(obj)
if self.base_type.is_simple():
base_type = self.base_type
elif self.base_type.has_simple_content():
base_type = self.base_type.content_type
elif self.base_type.mixed:
yield unicode_type(obj)
return
else:
raise XMLSchemaValueError("wrong base type %r: a simpleType or a complexType with "
"simple or mixed content required." % self.base_type)
if isinstance(obj, (string_base_type, bytes)):
obj = self.normalize(obj)
if self.base_type.is_simple():
base_type = self.base_type
elif self.base_type.has_simple_content():
base_type = self.base_type.content_type
elif self.base_type.mixed:
yield unicode_type(obj)
return
else:
raise XMLSchemaValueError("wrong base type %r: a simpleType or a complexType with "
"simple or mixed content required." % self.base_type)
for result in base_type.iter_encode(obj, validation):
if isinstance(result, XMLSchemaValidationError):
@ -1206,7 +1178,11 @@ class XsdAtomicRestriction(XsdAtomic):
yield unicode_type(obj) if validation == 'skip' else None
return
else:
if validation != 'skip':
if validation != 'skip' and self.validators and obj is not None:
if isinstance(obj, (string_base_type, bytes)):
if self.primitive_type.is_datetime():
obj = self.primitive_type.to_python(obj)
for validator in self.validators:
for error in validator(obj):
yield error

View File

@ -14,9 +14,9 @@ This module contains classes for XML Schema wildcards.
from __future__ import unicode_literals
from ..exceptions import XMLSchemaValueError
from ..qnames import XSD_ANY, XSD_ANY_ATTRIBUTE, XSD_OPEN_CONTENT, XSD_DEFAULT_OPEN_CONTENT
from ..helpers import get_namespace
from ..namespaces import XSI_NAMESPACE
from ..qnames import XSD_ANY, XSD_ANY_ATTRIBUTE, XSD_OPEN_CONTENT, \
XSD_DEFAULT_OPEN_CONTENT, get_namespace
from ..xpath import XMLSchemaProxy, ElementPathMixin
from .exceptions import XMLSchemaNotBuiltError
@ -36,14 +36,14 @@ class XsdWildcard(XsdComponent, ValidationMixin):
super(XsdWildcard, self).__init__(elem, schema, parent)
def __repr__(self):
if self.namespace:
return '%s(namespace=%r, process_contents=%r)' % (
self.__class__.__name__, self.namespace, self.process_contents
)
else:
if self.not_namespace:
return '%s(not_namespace=%r, process_contents=%r)' % (
self.__class__.__name__, self.not_namespace, self.process_contents
)
else:
return '%s(namespace=%r, process_contents=%r)' % (
self.__class__.__name__, self.namespace, self.process_contents
)
def _parse(self):
super(XsdWildcard, self)._parse()
@ -52,6 +52,8 @@ class XsdWildcard(XsdComponent, ValidationMixin):
namespace = self.elem.get('namespace', '##any').strip()
if namespace == '##any':
pass
elif not namespace:
self.namespace = [] # an empty value means no namespace allowed!
elif namespace == '##other':
self.namespace = [namespace]
elif namespace == '##local':
@ -150,7 +152,7 @@ class XsdWildcard(XsdComponent, ValidationMixin):
def built(self):
return True
def is_matching(self, name, default_namespace=None, group=None):
def is_matching(self, name, default_namespace=None, **kwargs):
if name is None:
return False
elif not name or name[0] == '{':
@ -163,9 +165,9 @@ class XsdWildcard(XsdComponent, ValidationMixin):
def is_namespace_allowed(self, namespace):
if self.not_namespace:
return namespace not in self.not_namespace
elif self.namespace[0] == '##any' or namespace == XSI_NAMESPACE:
elif '##any' in self.namespace or namespace == XSI_NAMESPACE:
return True
elif self.namespace[0] == '##other':
elif '##other' in self.namespace:
return namespace and namespace != self.target_namespace
else:
return namespace in self.namespace
@ -209,7 +211,8 @@ class XsdWildcard(XsdComponent, ValidationMixin):
elif other.not_qname:
if not self.deny_qnames(x for x in other.not_qname if not x.startswith('##')):
return False
elif any(not other.is_namespace_allowed(get_namespace(x)) for x in self.not_qname if not x.startswith('##')):
elif any(not other.is_namespace_allowed(get_namespace(x))
for x in self.not_qname if not x.startswith('##')):
return False
if self.not_namespace:
@ -240,36 +243,45 @@ class XsdWildcard(XsdComponent, ValidationMixin):
else:
return all(ns in other.namespace for ns in self.namespace)
def extend(self, other):
"""Extends the XSD wildcard to include the namespace of another XSD wildcard."""
def union(self, other):
"""
Update an XSD wildcard with the union of itself and another XSD wildcard.
"""
if not self.not_qname:
self.not_qname = other.not_qname[:]
else:
self.not_qname = [
x for x in self.not_qname
if x in other.not_qname or not other.is_namespace_allowed(get_namespace(x))
]
if self.not_namespace:
if other.not_namespace:
self.not_namespace = [ns for ns in self.not_namespace if ns in other.not_namespace]
elif other.namespace == '##any':
self.not_namespace = ()
elif other.namespace != '##other':
self.not_namespace = [ns for ns in self.not_namespace if ns not in other.namespace]
elif other.target_namespace in self.not_namespace:
self.not_namespace = ['', other.target_namespace] if other.target_namespace else ['']
elif '##any' in other.namespace:
self.not_namespace = []
self.namespace = ['##any']
return
elif '##other' in other.namespace:
not_namespace = ('', other.target_namespace)
self.not_namespace = [ns for ns in self.not_namespace if ns in not_namespace]
else:
self.not_namespace = ()
self.not_namespace = [ns for ns in self.not_namespace if ns not in other.namespace]
if not self.not_namespace:
self.namespace = ['##any']
return
elif other.not_namespace:
if self.namespace == '##any':
if '##any' in self.namespace:
return
elif self.namespace != '##other':
self.not_namespace = [ns for ns in other.not_namespace if ns not in self.namespace]
elif self.target_namespace in other.not_namespace:
self.not_namespace = ['', self.target_namespace] if self.target_namespace else ['']
elif '##other' in self.namespace:
not_namespace = ('', self.target_namespace)
self.not_namespace = [ns for ns in other.not_namespace if ns in not_namespace]
else:
self.not_namespace = ()
self.not_namespace = [ns for ns in other.not_namespace if ns not in self.namespace]
if not self.not_namespace:
self.namespace = ['##any']
self.namespace = ['##any'] if not self.not_namespace else []
return
if '##any' in self.namespace or self.namespace == other.namespace:
@ -285,12 +297,7 @@ class XsdWildcard(XsdComponent, ValidationMixin):
self.namespace.extend(ns for ns in other.namespace if ns not in self.namespace)
return
if w2.not_namespace:
self.not_namespace = [ns for ns in w2.not_namespace]
if w1.target_namespace not in self.not_namespace:
self.not_namespace.append(w1.target_namespace)
self.namespace = []
elif w1.target_namespace in w2.namespace and '' in w2.namespace:
if w1.target_namespace in w2.namespace and '' in w2.namespace:
self.namespace = ['##any']
elif '' not in w2.namespace and w1.target_namespace == w2.target_namespace:
self.namespace = ['##other']
@ -301,6 +308,61 @@ class XsdWildcard(XsdComponent, ValidationMixin):
self.namespace = []
self.not_namespace = ['', w1.target_namespace] if w1.target_namespace else ['']
def intersection(self, other):
"""
Update an XSD wildcard with the intersection of itself and another XSD wildcard.
"""
if self.not_qname:
self.not_qname.extend(x for x in other.not_qname if x not in self.not_qname)
else:
self.not_qname = [x for x in other.not_qname]
if self.not_namespace:
if other.not_namespace:
self.not_namespace.extend(ns for ns in other.not_namespace if ns not in self.not_namespace)
elif '##any' in other.namespace:
pass
elif '##other' not in other.namespace:
self.namespace = [ns for ns in other.namespace if ns not in self.not_namespace]
self.not_namespace = []
else:
if other.target_namespace not in self.not_namespace:
self.not_namespace.append(other.target_namespace)
if '' not in self.not_namespace:
self.not_namespace.append('')
return
elif other.not_namespace:
if '##any' in self.namespace:
self.not_namespace = [ns for ns in other.not_namespace]
self.namespace = []
elif '##other' not in self.namespace:
self.namespace = [ns for ns in self.namespace if ns not in other.not_namespace]
else:
self.not_namespace = [ns for ns in other.not_namespace]
if self.target_namespace not in self.not_namespace:
self.not_namespace.append(self.target_namespace)
if '' not in self.not_namespace:
self.not_namespace.append('')
self.namespace = []
return
if self.namespace == other.namespace:
return
elif '##any' in other.namespace:
return
elif '##any' in self.namespace:
self.namespace = other.namespace[:]
elif '##other' in self.namespace:
self.namespace = [ns for ns in other.namespace if ns not in ('', self.target_namespace)]
elif '##other' not in other.namespace:
self.namespace = [ns for ns in self.namespace if ns in other.namespace]
else:
if other.target_namespace in self.namespace:
self.namespace.remove(other.target_namespace)
if '' in self.namespace:
self.namespace.remove('')
def iter_decode(self, source, validation='lax', **kwargs):
raise NotImplementedError
@ -323,6 +385,7 @@ class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin):
</any>
"""
_ADMITTED_TAGS = {XSD_ANY}
precedences = ()
def __repr__(self):
if self.namespace:
@ -334,23 +397,38 @@ class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin):
self.__class__.__name__, self.not_namespace, self.process_contents, self.occurs
)
@property
def xpath_proxy(self):
return XMLSchemaProxy(self.schema, self)
def _parse(self):
super(XsdAnyElement, self)._parse()
self._parse_particle(self.elem)
self.xpath_proxy = XMLSchemaProxy(self.schema, self)
def is_emptiable(self):
return self.min_occurs == 0 or self.process_contents != 'strict'
def match(self, name, default_namespace=None, resolve=False, **kwargs):
"""
Returns the element wildcard if name is matching the name provided
as argument, `None` otherwise.
def matched_element(self, name, default_namespace=None, group=None):
if self.is_matching(name, default_namespace, group):
try:
if name[0] != '{' and default_namespace:
return self.maps.lookup_element('{%s}%s' % (default_namespace, name))
else:
return self.maps.lookup_element(name)
except LookupError:
pass
:param name: a local or fully-qualified name.
:param default_namespace: used when it's not `None` and not empty for \
completing local name arguments.
:param resolve: when `True` it doesn't return the wildcard but try to \
resolve and return the element matching the name.
:param kwargs: additional options used by XSD 1.1 xs:any wildcards.
"""
if not self.is_matching(name, default_namespace, **kwargs):
return
elif not resolve:
return self
try:
if name[0] != '{' and default_namespace:
return self.maps.lookup_element('{%s}%s' % (default_namespace, name))
else:
return self.maps.lookup_element(name)
except LookupError:
pass
def __iter__(self):
return iter(())
@ -366,12 +444,11 @@ class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin):
return iter(())
def iter_decode(self, elem, validation='lax', **kwargs):
if self.process_contents == 'skip':
return
if self.is_matching(elem.tag):
if self.process_contents == 'skip':
return
namespace = get_namespace(elem.tag)
if self.is_namespace_allowed(namespace):
self._load_namespace(namespace)
self._load_namespace(get_namespace(elem.tag))
try:
xsd_element = self.maps.lookup_element(elem.tag)
except LookupError:
@ -394,6 +471,7 @@ class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin):
name, value = obj
namespace = get_namespace(name)
if self.is_namespace_allowed(namespace):
self._load_namespace(namespace)
try:
@ -457,14 +535,38 @@ class XsdAnyAttribute(XsdWildcard):
"""
_ADMITTED_TAGS = {XSD_ANY_ATTRIBUTE}
def iter_decode(self, attribute, validation='lax', **kwargs):
if self.process_contents == 'skip':
return
def match(self, name, default_namespace=None, resolve=False, **kwargs):
"""
Returns the attribute wildcard if name is matching the name provided
as argument, `None` otherwise.
:param name: a local or fully-qualified name.
:param default_namespace: used when it's not `None` and not empty for \
completing local name arguments.
:param resolve: when `True` it doesn't return the wildcard but try to \
resolve and return the attribute matching the name.
:param kwargs: additional options that can be used by certain components.
"""
if not self.is_matching(name, default_namespace, **kwargs):
return
elif not resolve:
return self
try:
if name[0] != '{' and default_namespace:
return self.maps.lookup_attribute('{%s}%s' % (default_namespace, name))
else:
return self.maps.lookup_attribute(name)
except LookupError:
pass
def iter_decode(self, attribute, validation='lax', **kwargs):
name, value = attribute
namespace = get_namespace(name)
if self.is_namespace_allowed(namespace):
self._load_namespace(namespace)
if self.is_matching(name):
if self.process_contents == 'skip':
return
self._load_namespace(get_namespace(name))
try:
xsd_attribute = self.maps.lookup_attribute(name)
except LookupError:
@ -523,7 +625,18 @@ class Xsd11AnyElement(XsdAnyElement):
super(Xsd11AnyElement, self)._parse()
self._parse_not_constraints()
def is_matching(self, name, default_namespace=None, group=None):
def is_matching(self, name, default_namespace=None, group=None, occurs=None):
"""
Returns `True` if the component name is matching the name provided as argument,
`False` otherwise. For XSD elements the matching is extended to substitutes.
:param name: a local or fully-qualified name.
:param default_namespace: used if it's not None and not empty for completing \
the name argument in case it's a local name.
:param group: used only by XSD 1.1 any element wildcards to verify siblings in \
case of ##definedSibling value in notQName attribute.
:param occurs: a Counter instance for verify model occurrences counting.
"""
if name is None:
return False
elif not name or name[0] == '{':
@ -534,19 +647,34 @@ class Xsd11AnyElement(XsdAnyElement):
name = '{%s}%s' % (default_namespace, name)
namespace = default_namespace
if '##defined' in self.not_qname and name in self.maps.elements:
if self.maps.elements[name].schema is self.schema:
if group in self.precedences:
if occurs is None:
if any(e.is_matching(name) for e in self.precedences[group]):
return False
elif any(e.is_matching(name) and not e.is_over(occurs[e]) for e in self.precedences[group]):
return False
if '##defined' in self.not_qname and name in self.maps.elements:
return False
if group and '##definedSibling' in self.not_qname:
if any(e is not self and e.match(name, default_namespace) for e in group.iter_elements()):
if any(e.is_matching(name) for e in group.iter_elements()
if not isinstance(e, XsdAnyElement)):
return False
return name not in self.not_qname and self.is_namespace_allowed(namespace)
def is_consistent(self, other):
if isinstance(other, XsdAnyElement) or self.process_contents == 'skip':
return True
xsd_element = self.matched_element(other.name, other.default_namespace)
return xsd_element is None or other.is_consistent(xsd_element, False)
xsd_element = self.match(other.name, other.default_namespace, resolve=True)
return xsd_element is None or other.is_consistent(xsd_element, strict=False)
def add_precedence(self, other, group):
if not self.precedences:
self.precedences = {}
try:
self.precedences[group].append(other)
except KeyError:
self.precedences[group] = [other]
class Xsd11AnyAttribute(XsdAnyAttribute):
@ -563,11 +691,13 @@ class Xsd11AnyAttribute(XsdAnyAttribute):
Content: (annotation?)
</anyAttribute>
"""
inheritable = False # Added for reduce checkings on XSD 1.1 attributes
def _parse(self):
super(Xsd11AnyAttribute, self)._parse()
self._parse_not_constraints()
def is_matching(self, name, default_namespace=None, group=None):
def is_matching(self, name, default_namespace=None, **kwargs):
if name is None:
return False
elif not name or name[0] == '{':
@ -579,8 +709,7 @@ class Xsd11AnyAttribute(XsdAnyAttribute):
namespace = default_namespace
if '##defined' in self.not_qname and name in self.maps.attributes:
if self.maps.attributes[name].schema is self.schema:
return False
return False
return name not in self.not_qname and self.is_namespace_allowed(namespace)

View File

@ -17,10 +17,12 @@ import re
from ..compat import PY3, string_base_type, unicode_type
from ..exceptions import XMLSchemaValueError, XMLSchemaTypeError
from ..qnames import XSD_ANNOTATION, XSD_APPINFO, XSD_DOCUMENTATION, XML_LANG, \
XSD_ANY_TYPE, XSD_ANY_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE, XSD_ID
from ..helpers import get_qname, local_name, qname_to_prefixed
from ..etree import etree_tostring, is_etree_element
from .exceptions import XMLSchemaParseError, XMLSchemaValidationError, XMLSchemaDecodeError, XMLSchemaEncodeError
XSD_ANY_TYPE, XSD_ANY_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE, XSD_ID, XSD_OVERRIDE, \
get_qname, local_name, qname_to_prefixed
from ..etree import etree_tostring
from ..helpers import is_etree_element
from .exceptions import XMLSchemaParseError, XMLSchemaValidationError, \
XMLSchemaDecodeError, XMLSchemaEncodeError
XSD_VALIDATION_MODES = {'strict', 'lax', 'skip'}
@ -252,11 +254,16 @@ class XsdComponent(XsdValidator):
def xsd_version(self):
return self.schema.XSD_VERSION
@property
def is_global(self):
"""Is `True` if the instance is a global component, `False` if it's local."""
"""Returns `True` if the instance is a global component, `False` if it's local."""
return self.parent is None
def is_override(self):
"""Returns `True` if the instance is an override of a global component."""
if self.parent is not None:
return False
return any(self.elem in x for x in self.schema.root if x.tag == XSD_OVERRIDE)
@property
def schema_elem(self):
"""The reference element of the schema for the component instance."""
@ -395,12 +402,12 @@ class XsdComponent(XsdValidator):
self.parse_error("a declaration contained in a global complexType "
"must has the same namespace as its parent schema")
if not self._target_namespace and self.name[0] == '{':
self.name = local_name(self.name)
elif self.name[0] != '{':
self.name = '{%s}%s' % (self._target_namespace, self.name)
else:
self.name = '{%s}%s' % (self._target_namespace, local_name(self.name))
if not self._target_namespace and self.name[0] == '{':
self.name = local_name(self.name)
elif self.name[0] != '{':
self.name = '{%s}%s' % (self._target_namespace, self.name)
else:
self.name = '{%s}%s' % (self._target_namespace, local_name(self.name))
@property
def local_name(self):
@ -427,7 +434,7 @@ class XsdComponent(XsdValidator):
def built(self):
raise NotImplementedError
def is_matching(self, name, default_namespace=None, group=None):
def is_matching(self, name, default_namespace=None, **kwargs):
"""
Returns `True` if the component name is matching the name provided as argument,
`False` otherwise. For XSD elements the matching is extended to substitutes.
@ -435,8 +442,7 @@ class XsdComponent(XsdValidator):
:param name: a local or fully-qualified name.
:param default_namespace: used if it's not None and not empty for completing the name \
argument in case it's a local name.
:param group: used only by XSD 1.1 any element wildcards to verify siblings in \
case of ##definedSibling value in notQName attribute.
:param kwargs: additional options that can be used by certain components.
"""
if not name:
return self.name == name
@ -448,9 +454,9 @@ class XsdComponent(XsdValidator):
qname = '{%s}%s' % (default_namespace, name)
return self.qualified_name == qname or not self.qualified and self.local_name == name
def match(self, name, default_namespace=None, group=None):
def match(self, name, default_namespace=None, **kwargs):
"""Returns the component if its name is matching the name provided as argument, `None` otherwise."""
return self if self.is_matching(name, default_namespace, group) else None
return self if self.is_matching(name, default_namespace, **kwargs) else None
def get_global(self):
"""Returns the global XSD component that contains the component instance."""
@ -563,6 +569,7 @@ class XsdType(XsdComponent):
"""Common base class for XSD types."""
abstract = False
block = None
base_type = None
derivation = None
redefine = None
@ -576,6 +583,34 @@ class XsdType(XsdComponent):
def built(self):
raise NotImplementedError
@property
def content_type_label(self):
if self.is_empty():
return 'empty'
elif self.has_simple_content():
return 'simple'
elif self.is_element_only():
return 'element-only'
elif self.has_mixed_content():
return 'mixed'
else:
return 'unknown'
@property
def root_type(self):
"""The root type of the type definition hierarchy. Is itself for a root type."""
if self.base_type is None:
return self # Note that a XsdUnion type is always considered a root type
try:
if self.base_type.is_simple():
return self.base_type.primitive_type
else:
return self.base_type.content_type.primitive_type
except AttributeError:
# The type has complex or XsdList content
return self.base_type
@staticmethod
def is_simple():
"""Returns `True` if the instance is a simpleType, `False` otherwise."""
@ -589,7 +624,14 @@ class XsdType(XsdComponent):
@staticmethod
def is_atomic():
"""Returns `True` if the instance is an atomic simpleType, `False` otherwise."""
return None
return False
@staticmethod
def is_datetime():
"""
Returns `True` if the instance is a datetime/duration XSD builtin-type, `False` otherwise.
"""
return False
def is_empty(self):
"""Returns `True` if the instance has an empty value or content, `False` otherwise."""
@ -618,22 +660,28 @@ class XsdType(XsdComponent):
"""
raise NotImplementedError
@property
def content_type_label(self):
if self.is_empty():
return 'empty'
elif self.has_simple_content():
return 'simple'
elif self.is_element_only():
return 'element-only'
elif self.has_mixed_content():
return 'mixed'
else:
return 'unknown'
def is_derived(self, other, derivation=None):
raise NotImplementedError
def is_blocked(self, xsd_element):
"""
Returns `True` if the base type derivation is blocked, `False` otherwise.
"""
xsd_type = xsd_element.type
if self is xsd_type:
return False
block = ('%s %s' % (xsd_element.block, xsd_type.block)).strip()
if not block:
return False
block = {x for x in block.split() if x in ('extension', 'restriction')}
return any(self.is_derived(xsd_type, derivation) for derivation in block)
def is_dynamic_consistent(self, other):
return self.is_derived(other) or hasattr(other, 'member_types') and \
any(self.is_derived(mt) for mt in other.member_types)
def is_key(self):
return self.name == XSD_ID or self.is_derived(self.maps.types[XSD_ID])

View File

@ -36,7 +36,7 @@ class XMLSchemaContext(XPathSchemaContext):
if len(elem):
context.size = len(elem)
for context.position, context.item in enumerate(elem):
if context.item.is_global:
if context.item.parent is None:
for item in safe_iter_descendants(context):
yield item
elif getattr(context.item, 'ref', None) is not None:
@ -64,7 +64,7 @@ class XMLSchemaContext(XPathSchemaContext):
if len(elem):
context.size = len(elem)
for context.position, context.item in enumerate(elem):
if context.item.is_global:
if context.item.parent is None:
for item in safe_iter_context(context):
yield item
elif getattr(context.item, 'ref', None) is not None:
@ -93,6 +93,20 @@ class XMLSchemaProxy(AbstractSchemaProxy):
except AttributeError:
raise XMLSchemaTypeError("%r is not an XsdElement" % base_element)
def bind_parser(self, parser):
if parser.schema is not self:
parser.schema = self
try:
parser.symbol_table = self._schema.xpath_tokens[parser.__class__]
except KeyError:
parser.symbol_table = parser.__class__.symbol_table.copy()
self._schema.xpath_tokens[parser.__class__] = parser.symbol_table
for xsd_type in self.iter_atomic_types():
parser.schema_constructor(xsd_type.name)
parser.tokenizer = parser.create_tokenizer(parser.symbol_table)
def get_context(self):
return XMLSchemaContext(root=self._schema, item=self._base_element)
@ -120,6 +134,9 @@ class XMLSchemaProxy(AbstractSchemaProxy):
except KeyError:
return None
def find(self, path, namespaces=None):
return self._schema.find(path, namespaces)
def is_instance(self, obj, type_qname):
xsd_type = self._schema.maps.types[type_qname]
try:
@ -158,12 +175,18 @@ class ElementPathMixin(Sequence):
:cvar text: The Element text. Its value is always `None`. For compatibility with the ElementTree API.
:cvar tail: The Element tail. Its value is always `None`. For compatibility with the ElementTree API.
"""
_attrib = {}
text = None
tail = None
attributes = {}
namespaces = {}
xpath_default_namespace = None
xpath_proxy = None
_xpath_parser = None # Internal XPath 2.0 parser, instantiated at first use.
def __getstate__(self):
state = self.__dict__.copy()
state.pop('_xpath_parser', None)
return state
@abstractmethod
def __iter__(self):
@ -189,51 +212,62 @@ class ElementPathMixin(Sequence):
@property
def attrib(self):
"""Returns the Element attributes. For compatibility with the ElementTree API."""
return getattr(self, 'attributes', self._attrib)
return self.attributes
def get(self, key, default=None):
"""Gets an Element attribute. For compatibility with the ElementTree API."""
return self.attrib.get(key, default)
return self.attributes.get(key, default)
def iterfind(self, path, namespaces=None):
"""
Creates and iterator for all XSD subelements matching the path.
@property
def xpath_proxy(self):
"""Returns an XPath proxy instance bound with the schema."""
raise NotImplementedError
:param path: an XPath expression that considers the XSD component as the root element.
:param namespaces: is an optional mapping from namespace prefix to full name.
:return: an iterable yielding all matching XSD subelements in document order.
def _rebind_xpath_parser(self):
"""Rebind XPath 2 parser with schema component."""
if self._xpath_parser is not None:
self._xpath_parser.schema.bind_parser(self._xpath_parser)
def _get_xpath_namespaces(self, namespaces=None):
"""
Returns a dictionary with namespaces for XPath selection.
:param namespaces: an optional map from namespace prefix to namespace URI. \
If this argument is not provided the schema's namespaces are used.
"""
if namespaces is None:
namespaces = {k: v for k, v in self.namespaces.items() if k}
namespaces[''] = self.xpath_default_namespace
elif '' not in namespaces:
namespaces[''] = self.xpath_default_namespace
xpath_namespaces = XPath2Parser.DEFAULT_NAMESPACES.copy()
xpath_namespaces.update(namespaces)
return xpath_namespaces
def _xpath_parse(self, path, namespaces=None):
path = path.strip()
if path.startswith('/') and not path.startswith('//'):
path = ''.join(['/', XSD_SCHEMA, path])
if namespaces is None:
namespaces = {k: v for k, v in self.namespaces.items() if k}
parser = XPath2Parser(namespaces, strict=False, schema=self.xpath_proxy,
default_namespace=self.xpath_default_namespace)
root_token = parser.parse(path)
context = XMLSchemaContext(self)
return root_token.select(context)
namespaces = self._get_xpath_namespaces(namespaces)
if self._xpath_parser is None:
self._xpath_parser = XPath2Parser(namespaces, strict=False, schema=self.xpath_proxy)
else:
self._xpath_parser.namespaces = namespaces
return self._xpath_parser.parse(path)
def find(self, path, namespaces=None):
"""
Finds the first XSD subelement matching the path.
:param path: an XPath expression that considers the XSD component as the root element.
:param namespaces: an optional mapping from namespace prefix to full name.
:param namespaces: an optional mapping from namespace prefix to namespace URI.
:return: The first matching XSD subelement or ``None`` if there is not match.
"""
path = path.strip()
if path.startswith('/') and not path.startswith('//'):
path = ''.join(['/', XSD_SCHEMA, path])
if namespaces is None:
namespaces = {k: v for k, v in self.namespaces.items() if k}
parser = XPath2Parser(namespaces, strict=False, schema=self.xpath_proxy,
default_namespace=self.xpath_default_namespace)
root_token = parser.parse(path)
context = XMLSchemaContext(self)
return next(root_token.select(context), None)
return next(self._xpath_parse(path, namespaces).select_results(context), None)
def findall(self, path, namespaces=None):
"""
@ -244,17 +278,19 @@ class ElementPathMixin(Sequence):
:return: a list containing all matching XSD subelements in document order, an empty \
list is returned if there is no match.
"""
path = path.strip()
if path.startswith('/') and not path.startswith('//'):
path = ''.join(['/', XSD_SCHEMA, path])
if namespaces is None:
namespaces = {k: v for k, v in self.namespaces.items() if k}
parser = XPath2Parser(namespaces, strict=False, schema=self.xpath_proxy,
default_namespace=self.xpath_default_namespace)
root_token = parser.parse(path)
context = XMLSchemaContext(self)
return root_token.get_results(context)
return self._xpath_parse(path, namespaces).get_results(context)
def iterfind(self, path, namespaces=None):
"""
Creates and iterator for all XSD subelements matching the path.
:param path: an XPath expression that considers the XSD component as the root element.
:param namespaces: is an optional mapping from namespace prefix to full name.
:return: an iterable yielding all matching XSD subelements in document order.
"""
context = XMLSchemaContext(self)
return self._xpath_parse(path, namespaces).select_results(context)
def iter(self, tag=None):
"""
@ -267,7 +303,7 @@ class ElementPathMixin(Sequence):
if tag is None or elem.is_matching(tag):
yield elem
for child in elem:
if child.is_global:
if child.parent is None:
for e in safe_iter(child):
yield e
elif getattr(child, 'ref', None) is not None: