Merge branch 'develop' for upgrade to v1.0.15
This commit is contained in:
commit
298ceb117a
|
@ -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
|
||||
|
|
|
@ -164,8 +164,6 @@ Resource access API
|
|||
.. autofunction:: xmlschema.normalize_url
|
||||
|
||||
|
||||
|
||||
|
||||
XSD components API
|
||||
------------------
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
setuptools
|
||||
tox
|
||||
coverage
|
||||
elementpath~=1.2.0
|
||||
elementpath~=1.3.0
|
||||
lxml
|
||||
memory_profiler
|
||||
pathlib2 # For Py27 tests on resources
|
||||
|
|
4
setup.py
4
setup.py
|
@ -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={
|
||||
|
|
7
tox.ini
7
tox.ini
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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('\\')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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  (valid in XML 1.1)
|
||||
'../saxonData/XmlVersions/xv006.n02.xml', # 14855: invalid character 𐀀 (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))
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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("""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue