Merge branch 'develop' for updating to release v1.0.11
This commit is contained in:
commit
d0ddbc4d6d
|
@ -13,4 +13,3 @@ build/
|
|||
development/
|
||||
test_cases/
|
||||
!xmlschema/tests/test_cases/
|
||||
!xmlschema/unicode_categories.json
|
||||
|
|
|
@ -2,6 +2,18 @@
|
|||
CHANGELOG
|
||||
*********
|
||||
|
||||
`v1.0.11`_ (2019-05-05)
|
||||
=======================
|
||||
* Added a script for running the W3C XSD test suite.
|
||||
* Check restrictions and model groups UPA violations
|
||||
* Model groups splitted between two modules for more focusing on models basics
|
||||
* Added two new exceptions for model group errors
|
||||
* More control on imported namespaces
|
||||
* Added *use_meta* argument to schema classes
|
||||
* Added *includes* list and *imports* dict to schema classes
|
||||
* Many fixes for passing the W3C's tests for XSD 1.0 schemas
|
||||
* Added a test for issue #105 and a fix for issue #103
|
||||
|
||||
`v1.0.10`_ (2019-02-25)
|
||||
=======================
|
||||
* Fixed Element type mismatch issue when apply *SafeXMLParser* to schema resources
|
||||
|
@ -226,3 +238,4 @@ v0.9.6 (2017-05-05)
|
|||
.. _v1.0.8: https://github.com/brunato/xmlschema/compare/v1.0.7...v1.0.8
|
||||
.. _v1.0.9: https://github.com/brunato/xmlschema/compare/v1.0.8...v1.0.9
|
||||
.. _v1.0.10: https://github.com/brunato/xmlschema/compare/v1.0.9...v1.0.10
|
||||
.. _v1.0.11: https://github.com/brunato/xmlschema/compare/v1.0.10...v1.0.11
|
||||
|
|
21
doc/api.rst
21
doc/api.rst
|
@ -33,25 +33,26 @@ Schema level API
|
|||
.. autoattribute:: tag
|
||||
.. autoattribute:: id
|
||||
.. autoattribute:: version
|
||||
.. autoattribute:: attribute_form_default
|
||||
.. autoattribute:: element_form_default
|
||||
.. autoattribute:: block_default
|
||||
.. autoattribute:: final_default
|
||||
|
||||
.. autoattribute:: schema_location
|
||||
.. autoattribute:: no_namespace_schema_location
|
||||
.. autoattribute:: target_prefix
|
||||
.. autoattribute:: default_namespace
|
||||
.. autoattribute:: base_url
|
||||
.. autoattribute:: root_elements
|
||||
.. autoattribute:: builtin_types
|
||||
.. autoattribute:: root_elements
|
||||
|
||||
.. automethod:: create_meta_schema
|
||||
.. automethod:: create_schema
|
||||
.. automethod:: create_any_content_group
|
||||
.. automethod:: create_any_attribute_group
|
||||
|
||||
.. automethod:: get_locations
|
||||
.. automethod:: include_schema
|
||||
.. automethod:: import_schema
|
||||
.. automethod:: create_schema
|
||||
.. automethod:: create_any_content_group
|
||||
.. automethod:: create_any_attribute_group
|
||||
.. automethod:: resolve_qname
|
||||
.. automethod:: iter_globals
|
||||
.. automethod:: iter_components
|
||||
|
||||
.. automethod:: check_schema
|
||||
.. automethod:: build
|
||||
|
@ -59,8 +60,6 @@ Schema level API
|
|||
.. autoattribute:: validation_attempted
|
||||
.. autoattribute:: validity
|
||||
.. autoattribute:: all_errors
|
||||
.. automethod:: iter_components
|
||||
.. automethod:: iter_globals
|
||||
|
||||
.. automethod:: get_converter
|
||||
.. automethod:: validate
|
||||
|
@ -180,6 +179,8 @@ Errors and exceptions
|
|||
.. autoexception:: xmlschema.XMLSchemaValidatorError
|
||||
.. autoexception:: xmlschema.XMLSchemaNotBuiltError
|
||||
.. autoexception:: xmlschema.XMLSchemaParseError
|
||||
.. autoexception:: xmlschema.XMLSchemaModelError
|
||||
.. autoexception:: xmlschema.XMLSchemaModelDepthError
|
||||
.. autoexception:: xmlschema.XMLSchemaValidationError
|
||||
.. autoexception:: xmlschema.XMLSchemaDecodeError
|
||||
.. autoexception:: xmlschema.XMLSchemaEncodeError
|
||||
|
|
|
@ -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.10'
|
||||
release = '1.0.11'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
|
@ -136,3 +136,17 @@ or:
|
|||
.. code-block:: text
|
||||
|
||||
tox -- -x
|
||||
|
||||
|
||||
Testing with the W3C XML Schema 1.1 test suite
|
||||
----------------------------------------------
|
||||
|
||||
From release v1.0.11, using the script *test_w3c_suite.py*, you can run also tests based on the
|
||||
`W3C XML Schema 1.1 test suite <https://github.com/w3c/xsdtests>`_. To run these tests, actually
|
||||
limited to XSD 1.0 schema tests, clone the W3C repo on the project's parent directory and than
|
||||
run the script:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
git clone https://github.com/w3c/xsdtests.git
|
||||
python xmlschema/xmlschema/tests/test_w3c_suite.py
|
||||
|
|
|
@ -115,9 +115,9 @@ The global maps can be accessed through :attr:`XMLSchema.maps` attribute:
|
|||
'{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-hasFacetAndProperty}hasFacet',
|
||||
'{http://www.w3.org/2001/XMLSchema-hasFacetAndProperty}hasProperty',
|
||||
'{http://www.w3.org/2001/XMLSchema}all']
|
||||
'{http://www.w3.org/2001/XMLSchema}all',
|
||||
'{http://www.w3.org/2001/XMLSchema}annotation',
|
||||
'{http://www.w3.org/2001/XMLSchema}any']
|
||||
|
||||
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
|
||||
|
@ -528,3 +528,19 @@ For default this argument has value *'remote'* that means the protection on XML
|
|||
applied only to data loaded from remote. Other values for this argument can be *'always'*
|
||||
and *'never'*.
|
||||
|
||||
|
||||
Limit on model groups checking
|
||||
------------------------------
|
||||
|
||||
From release v1.0.11 the model groups of the schemas are checked against restriction violations
|
||||
and *Unique Particle Attribution* violations.
|
||||
|
||||
To avoids XSD model recursion attacks a limit of ``MAX_MODEL_DEPTH = 15`` is set. If this limit
|
||||
is exceeded an ``XMLSchemaModelDepthError`` is raised, the error is caught and a warning is generated.
|
||||
If you need to set an higher limit for checking all your groups you can import the library and change
|
||||
the value in the specific module that processes the model checks:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import xmlschema
|
||||
>>> xmlschema.validators.models.MAX_MODEL_DEPTH = 20
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Requirements for setup a development environment for the xmlschema package.
|
||||
setuptools
|
||||
tox
|
||||
elementpath~=1.1.5
|
||||
elementpath~=1.1.7
|
||||
lxml
|
||||
memory_profiler
|
||||
pathlib2 # For Py27 tests on resources
|
||||
|
|
30
setup.py
30
setup.py
|
@ -9,17 +9,43 @@
|
|||
#
|
||||
# @author Davide Brunato <brunato@sissa.it>
|
||||
#
|
||||
import importlib
|
||||
from setuptools import setup
|
||||
from setuptools.command.develop import develop
|
||||
from setuptools.command.install import install
|
||||
|
||||
with open("README.rst") as readme:
|
||||
long_description = readme.read()
|
||||
|
||||
|
||||
class DevelopCommand(develop):
|
||||
|
||||
def run(self):
|
||||
develop.run(self)
|
||||
print("Post-develop: create Unicode categories JSON file")
|
||||
codepoints_module = importlib.import_module('xmlschema.codepoints')
|
||||
codepoints_module.save_unicode_categories()
|
||||
|
||||
|
||||
class InstallCommand(install):
|
||||
|
||||
def run(self):
|
||||
install.run(self)
|
||||
print("Post-install: create Unicode categories JSON file")
|
||||
codepoints_module = importlib.import_module('xmlschema.codepoints')
|
||||
codepoints_module.save_unicode_categories()
|
||||
|
||||
|
||||
setup(
|
||||
name='xmlschema',
|
||||
version='1.0.10',
|
||||
install_requires=['elementpath~=1.1.5'],
|
||||
version='1.0.11',
|
||||
install_requires=['elementpath~=1.1.7'],
|
||||
packages=['xmlschema'],
|
||||
include_package_data=True,
|
||||
cmdclass={
|
||||
'develop': DevelopCommand,
|
||||
'install': InstallCommand
|
||||
},
|
||||
author='Davide Brunato',
|
||||
author_email='brunato@sissa.it',
|
||||
url='https://github.com/brunato/xmlschema',
|
||||
|
|
4
tox.ini
4
tox.ini
|
@ -10,12 +10,12 @@ toxworkdir = {homedir}/.tox/xmlschema
|
|||
[testenv]
|
||||
deps =
|
||||
lxml
|
||||
elementpath~=1.1.5
|
||||
elementpath~=1.1.7
|
||||
commands = python xmlschema/tests/test_all.py {posargs}
|
||||
|
||||
[testenv:py27]
|
||||
deps =
|
||||
lxml
|
||||
elementpath~=1.1.5
|
||||
elementpath~=1.1.7
|
||||
pathlib2
|
||||
commands = python xmlschema/tests/test_all.py {posargs}
|
||||
|
|
|
@ -20,12 +20,13 @@ from .converters import (
|
|||
from .documents import validate, to_dict, to_json, from_json
|
||||
|
||||
from .validators import (
|
||||
XMLSchemaValidatorError, XMLSchemaParseError, XMLSchemaNotBuiltError, XMLSchemaValidationError,
|
||||
XMLSchemaDecodeError, XMLSchemaEncodeError, XMLSchemaChildrenValidationError, XMLSchemaIncludeWarning,
|
||||
XMLSchemaImportWarning, XsdGlobals, XMLSchemaBase, XMLSchema, XMLSchema10
|
||||
XMLSchemaValidatorError, XMLSchemaParseError, XMLSchemaNotBuiltError, XMLSchemaModelError,
|
||||
XMLSchemaModelDepthError, XMLSchemaValidationError, XMLSchemaDecodeError, XMLSchemaEncodeError,
|
||||
XMLSchemaChildrenValidationError, XMLSchemaIncludeWarning, XMLSchemaImportWarning, XsdGlobals,
|
||||
XMLSchemaBase, XMLSchema, XMLSchema10
|
||||
)
|
||||
|
||||
__version__ = '1.0.10'
|
||||
__version__ = '1.0.11'
|
||||
__author__ = "Davide Brunato"
|
||||
__contact__ = "brunato@sissa.it"
|
||||
__copyright__ = "Copyright 2016-2019, SISSA"
|
||||
|
@ -40,6 +41,7 @@ def XMLSchema_v1_0(*args, **kwargs):
|
|||
"and will be removed in 1.1 version", DeprecationWarning, stacklevel=2)
|
||||
return XMLSchema10(*args, **kwargs)
|
||||
|
||||
|
||||
def etree_get_namespaces(*args, **kwargs):
|
||||
import warnings
|
||||
warnings.warn("etree_get_namespaces() function name has been replaced by fetch_namespaces() "
|
||||
|
|
|
@ -16,7 +16,6 @@ from __future__ import unicode_literals
|
|||
import json
|
||||
import os
|
||||
from sys import maxunicode
|
||||
from collections import defaultdict
|
||||
|
||||
from .compat import PY3, unicode_chr, string_base_type, Iterable, MutableSet
|
||||
from .exceptions import XMLSchemaValueError, XMLSchemaTypeError, XMLSchemaRegexError
|
||||
|
@ -135,6 +134,7 @@ def iterparse_character_group(s, expand_ranges=False):
|
|||
:return: yields integers or couples of integers.
|
||||
"""
|
||||
escaped = False
|
||||
on_range = False
|
||||
char = None
|
||||
length = len(s)
|
||||
string_iter = iter(range(len(s)))
|
||||
|
@ -154,19 +154,29 @@ def iterparse_character_group(s, expand_ranges=False):
|
|||
char = s[k]
|
||||
yield ord(char)
|
||||
escaped = False
|
||||
elif on_range:
|
||||
char = s[k]
|
||||
yield ord(char)
|
||||
on_range = False
|
||||
else:
|
||||
# Parse character range
|
||||
on_range = True
|
||||
try:
|
||||
k = next(string_iter)
|
||||
end_char = s[k]
|
||||
if end_char == '\\' and (k < length - 1) and s[k + 1] in r'-|.^?*+{}()[]':
|
||||
k = next(string_iter)
|
||||
end_char = s[k]
|
||||
if end_char == '\\' and (k < length - 1):
|
||||
if s[k+1] in r'-|.^?*+{}()[]':
|
||||
k = next(string_iter)
|
||||
end_char = s[k]
|
||||
elif s[k+1] in r'sSdDiIcCwWpP':
|
||||
msg = "bad character range '%s-\\%s' at position %d: %r" % (char, s[k+1], k-2, s)
|
||||
raise XMLSchemaRegexError(msg)
|
||||
except StopIteration:
|
||||
msg = "bad character range %r-%r at position %d: %r" % (char, s[-1], k - 2, s)
|
||||
msg = "bad character range '%s-%s' at position %d: %r" % (char, s[-1], k-2, s)
|
||||
raise XMLSchemaRegexError(msg)
|
||||
|
||||
if ord(char) > ord(end_char):
|
||||
msg = "bad character range %r-%r at position %d: %r" % (char, end_char, k - 2, s)
|
||||
msg = "bad character range '%s-%s' at position %d: %r" % (char, end_char, k-2, s)
|
||||
raise XMLSchemaRegexError(msg)
|
||||
elif expand_ranges:
|
||||
for cp in range(ord(char) + 1, ord(end_char) + 1):
|
||||
|
@ -176,17 +186,19 @@ def iterparse_character_group(s, expand_ranges=False):
|
|||
elif s[k] in r'|.^?*+{}()':
|
||||
if escaped:
|
||||
escaped = False
|
||||
on_range = False
|
||||
char = s[k]
|
||||
yield ord(char)
|
||||
elif s[k] in r'[]':
|
||||
if not escaped and length > 1:
|
||||
raise XMLSchemaRegexError("bad character %r at position %d" % (s[k], k))
|
||||
escaped = False
|
||||
escaped = on_range = False
|
||||
char = s[k]
|
||||
yield ord(char)
|
||||
if k >= length-1 or s[k+1] != '-':
|
||||
yield ord(char)
|
||||
elif s[k] == '\\':
|
||||
if escaped:
|
||||
escaped = False
|
||||
escaped = on_range = False
|
||||
char = '\\'
|
||||
yield ord(char)
|
||||
else:
|
||||
|
@ -195,8 +207,10 @@ def iterparse_character_group(s, expand_ranges=False):
|
|||
if escaped:
|
||||
escaped = False
|
||||
yield ord('\\')
|
||||
on_range = False
|
||||
char = s[k]
|
||||
yield ord(char)
|
||||
if k >= length-1 or s[k+1] != '-':
|
||||
yield ord(char)
|
||||
if escaped:
|
||||
yield ord('\\')
|
||||
|
||||
|
@ -226,6 +240,12 @@ class UnicodeSubset(MutableSet):
|
|||
self._code_points = list()
|
||||
self.update(args[0])
|
||||
|
||||
@classmethod
|
||||
def fromlist(cls, code_points):
|
||||
subset = cls()
|
||||
subset._code_points = sorted(code_points, key=code_point_order)
|
||||
return subset
|
||||
|
||||
@property
|
||||
def code_points(self):
|
||||
return self._code_points
|
||||
|
@ -470,33 +490,42 @@ class UnicodeSubset(MutableSet):
|
|||
def get_unicodedata_categories():
|
||||
"""
|
||||
Extracts Unicode categories information from unicodedata library. Each category is
|
||||
represented with a list containing distinct code points and code points intervals.
|
||||
represented with an ordered list containing code points and code point ranges.
|
||||
|
||||
:return: a `defaultdict` dictionary with category names as keys and lists as values.
|
||||
:return: a dictionary with category names as keys and lists as values.
|
||||
"""
|
||||
import unicodedata
|
||||
from unicodedata import category
|
||||
|
||||
def append_code_points():
|
||||
diff = cp - start_cp
|
||||
if diff == 1:
|
||||
categories[last_category].append(start_cp)
|
||||
elif diff == 2:
|
||||
categories[last_category].append(start_cp)
|
||||
categories[last_category].append(start_cp + 1)
|
||||
else:
|
||||
categories[last_category].append((start_cp, cp))
|
||||
categories = {k: [] for k in (
|
||||
'C', 'Cc', 'Cf', 'Cs', 'Co', 'Cn',
|
||||
'L', 'Lu', 'Ll', 'Lt', 'Lm', 'Lo',
|
||||
'M', 'Mn', 'Mc', 'Me',
|
||||
'N', 'Nd', 'Nl', 'No',
|
||||
'P', 'Pc', 'Pd', 'Ps', 'Pe', 'Pi', 'Pf', 'Po',
|
||||
'S', 'Sm', 'Sc', 'Sk', 'So',
|
||||
'Z', 'Zs', 'Zl', 'Zp'
|
||||
)}
|
||||
|
||||
categories = defaultdict(list)
|
||||
last_category = 'Cc'
|
||||
start_cp = cp = 0
|
||||
for cp, category in enumerate(map(unicodedata.category, map(unicode_chr, range(maxunicode + 1)))):
|
||||
if category != last_category:
|
||||
append_code_points()
|
||||
last_category = category
|
||||
start_cp = cp
|
||||
minor_category = 'Cc'
|
||||
start_cp, next_cp = 0, 1
|
||||
for cp in range(maxunicode + 1):
|
||||
if category(unicode_chr(cp)) != minor_category:
|
||||
if cp > next_cp:
|
||||
categories[minor_category].append((start_cp, cp))
|
||||
categories[minor_category[0]].append(categories[minor_category][-1])
|
||||
else:
|
||||
categories[minor_category].append(start_cp)
|
||||
categories[minor_category[0]].append(start_cp)
|
||||
|
||||
minor_category = category(unicode_chr(cp))
|
||||
start_cp, next_cp = cp, cp + 1
|
||||
else:
|
||||
cp += 1
|
||||
append_code_points()
|
||||
if next_cp == maxunicode + 1:
|
||||
categories[minor_category].append(start_cp)
|
||||
categories[minor_category[0]].append(start_cp)
|
||||
else:
|
||||
categories[minor_category].append((start_cp, maxunicode + 1))
|
||||
categories[minor_category[0]].append(categories[minor_category][-1])
|
||||
|
||||
return categories
|
||||
|
||||
|
@ -511,6 +540,7 @@ def save_unicode_categories(filename=None):
|
|||
if filename is None:
|
||||
filename = os.path.join(os.path.dirname(__file__), 'unicode_categories.json')
|
||||
|
||||
print("Saving Unicode categories to %r" % filename)
|
||||
with open(filename, 'w') as fp:
|
||||
json.dump(get_unicodedata_categories(), fp)
|
||||
|
||||
|
@ -529,28 +559,22 @@ def build_unicode_categories(filename=None):
|
|||
categories = get_unicodedata_categories() # for Python 2.7
|
||||
else:
|
||||
if filename is None:
|
||||
filename = os.path.join(os.path.dirname(__file__), 'unicode_categoriesxxx.json')
|
||||
filename = os.path.join(os.path.dirname(__file__), 'unicode_categories.json')
|
||||
try:
|
||||
with open(filename, 'r') as fp:
|
||||
categories = json.load(fp)
|
||||
except (IOError, SystemError, ValueError):
|
||||
categories = get_unicodedata_categories()
|
||||
else:
|
||||
if any(not v for v in categories):
|
||||
categories = get_unicodedata_categories()
|
||||
|
||||
# Add general categories code points
|
||||
categories['C'] = categories['Cc'] + categories['Cf'] + categories['Cs'] + categories['Co'] + categories['Cn']
|
||||
categories['L'] = categories['Lu'] + categories['Ll'] + categories['Lt'] + categories['Lm'] + categories['Lo']
|
||||
categories['M'] = categories['Mn'] + categories['Mc'] + categories['Me']
|
||||
categories['N'] = categories['Nd'] + categories['Nl'] + categories['No']
|
||||
categories['P'] = categories['Pc'] + categories['Pd'] + categories['Ps'] + \
|
||||
categories['Pe'] + categories['Pi'] + categories['Pf'] + categories['Po']
|
||||
categories['S'] = categories['Sm'] + categories['Sc'] + categories['Sk'] + categories['So']
|
||||
categories['Z'] = categories['Zs'] + categories['Zl'] + categories['Zp']
|
||||
|
||||
return {k: UnicodeSubset(v) for k, v in categories.items()}
|
||||
return {k: UnicodeSubset.fromlist(v) for k, v in categories.items()}
|
||||
|
||||
|
||||
UNICODE_CATEGORIES = build_unicode_categories()
|
||||
|
||||
|
||||
UNICODE_BLOCKS = {
|
||||
'IsBasicLatin': UnicodeSubset('\u0000-\u007F'),
|
||||
'IsLatin-1Supplement': UnicodeSubset('\u0080-\u00FF'),
|
||||
|
|
|
@ -21,6 +21,7 @@ try:
|
|||
from urllib.error import URLError
|
||||
from io import StringIO, BytesIO
|
||||
from collections.abc import Iterable, MutableSet, Sequence, MutableSequence, Mapping, MutableMapping
|
||||
from functools import lru_cache
|
||||
except ImportError:
|
||||
# Python 2.7 imports
|
||||
from urllib import pathname2url
|
||||
|
@ -29,6 +30,18 @@ except ImportError:
|
|||
from StringIO import StringIO # the io.StringIO accepts only unicode type
|
||||
from io import BytesIO
|
||||
from collections import Iterable, MutableSet, Sequence, MutableSequence, Mapping, MutableMapping
|
||||
from functools import wraps
|
||||
|
||||
def lru_cache(maxsize=128, typed=False):
|
||||
"""
|
||||
A fake lru_cache decorator function for Python 2.7 compatibility until support ends.
|
||||
"""
|
||||
def lru_cache_decorator(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
return f(*args, **kwargs)
|
||||
return wrapper
|
||||
return lru_cache_decorator
|
||||
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
|
|
@ -16,7 +16,6 @@ from .compat import URLError
|
|||
|
||||
class XMLSchemaException(Exception):
|
||||
"""The base exception that let you catch all the errors generated by the library."""
|
||||
pass
|
||||
|
||||
|
||||
class XMLSchemaOSError(XMLSchemaException, OSError):
|
||||
|
@ -53,9 +52,8 @@ class XMLSchemaURLError(XMLSchemaException, URLError):
|
|||
|
||||
class XMLSchemaRegexError(XMLSchemaException, ValueError):
|
||||
"""Raised when an error is found when parsing an XML Schema regular expression."""
|
||||
pass
|
||||
|
||||
|
||||
class XMLSchemaWarning(Warning):
|
||||
"""Base warning class for the XMLSchema package."""
|
||||
pass
|
||||
|
||||
|
|
|
@ -9,14 +9,14 @@
|
|||
# @author Davide Brunato <brunato@sissa.it>
|
||||
#
|
||||
"""
|
||||
This module contains various helper functions for XML/XSD processing and parsing.
|
||||
This module contains various helper functions and classes.
|
||||
"""
|
||||
import re
|
||||
|
||||
from .exceptions import XMLSchemaValueError, XMLSchemaTypeError, XMLSchemaKeyError
|
||||
from .qnames import XSD_ANNOTATION
|
||||
|
||||
|
||||
XSD_FINAL_ATTRIBUTE_VALUES = {'restriction', 'extension', 'list', 'union'}
|
||||
NAMESPACE_PATTERN = re.compile(r'{([^}]*)}')
|
||||
|
||||
|
||||
|
@ -29,8 +29,8 @@ def get_namespace(name):
|
|||
|
||||
def get_qname(uri, name):
|
||||
"""
|
||||
Returns a fully qualified name from URI and local part. If any argument has boolean value
|
||||
`False` or if the name is already a fully qualified name, returns the *name* argument.
|
||||
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
|
||||
|
@ -44,10 +44,10 @@ def get_qname(uri, name):
|
|||
|
||||
def local_name(qname):
|
||||
"""
|
||||
Return the local part of a qualified name. If the name is `None` or empty
|
||||
Return the local part of an expanded QName. If the name is `None` or empty
|
||||
returns the *name* argument.
|
||||
|
||||
:param qname: QName or universal name formatted string, or `None`.
|
||||
:param qname: an expanded QName or a local name.
|
||||
"""
|
||||
try:
|
||||
if qname[0] != '{':
|
||||
|
@ -63,40 +63,6 @@ def local_name(qname):
|
|||
raise XMLSchemaTypeError("required a string-like object or None! %r" % qname)
|
||||
|
||||
|
||||
def prefixed_to_qname(name, namespaces):
|
||||
"""
|
||||
Transforms a prefixed name into a fully qualified name using a namespace map. Returns
|
||||
the *name* argument if it's not a prefixed name or if it has boolean value `False`.
|
||||
|
||||
:param name: a local name or a prefixed name or a fully qualified name or `None`.
|
||||
:param namespaces: a map from prefixes to namespace URIs.
|
||||
:return: string with a FQN or a local name or the name argument.
|
||||
"""
|
||||
if not name or name[0] == '{':
|
||||
return name
|
||||
|
||||
try:
|
||||
prefix, name = name.split(':')
|
||||
except ValueError:
|
||||
if ':' in name:
|
||||
raise XMLSchemaValueError("wrong format for reference name %r" % name)
|
||||
try:
|
||||
uri = namespaces['']
|
||||
except KeyError:
|
||||
return name
|
||||
else:
|
||||
return '{%s}%s' % (uri, name) if uri else name
|
||||
else:
|
||||
if not prefix or not name:
|
||||
raise XMLSchemaValueError("wrong format for reference name %r" % name)
|
||||
try:
|
||||
uri = namespaces[prefix]
|
||||
except KeyError:
|
||||
raise XMLSchemaValueError("prefix %r not found in namespace map" % prefix)
|
||||
else:
|
||||
return '{%s}%s' % (uri, name) if uri else name
|
||||
|
||||
|
||||
def qname_to_prefixed(qname, namespaces):
|
||||
"""
|
||||
Transforms a fully qualified name into a prefixed name using a namespace map. Returns the
|
||||
|
@ -212,7 +178,7 @@ def get_xml_bool_attribute(elem, attribute, default=None):
|
|||
raise XMLSchemaTypeError("an XML boolean value is required for attribute %r" % attribute)
|
||||
|
||||
|
||||
def get_xsd_derivation_attribute(elem, attribute, values):
|
||||
def get_xsd_derivation_attribute(elem, attribute, values=None):
|
||||
"""
|
||||
Get a derivation attribute (maybe 'block', 'blockDefault', 'final' or 'finalDefault')
|
||||
checking the items with the values arguments. Returns a string.
|
||||
|
@ -222,10 +188,69 @@ def get_xsd_derivation_attribute(elem, attribute, values):
|
|||
:param values: sequence of admitted values when the attribute value is not '#all'.
|
||||
:return: a string.
|
||||
"""
|
||||
value = elem.get(attribute, '')
|
||||
value = elem.get(attribute)
|
||||
if value is None:
|
||||
return ''
|
||||
|
||||
if values is None:
|
||||
values = XSD_FINAL_ATTRIBUTE_VALUES
|
||||
|
||||
items = value.split()
|
||||
if len(items) == 1 and items[0] == '#all':
|
||||
return ' '.join(values)
|
||||
elif not all([s in values for s in items]):
|
||||
raise XMLSchemaValueError("wrong value %r for attribute %r." % (value, attribute))
|
||||
return value
|
||||
|
||||
|
||||
def get_xsd_form_attribute(elem, attribute):
|
||||
"""
|
||||
Get an XSD form attribute, checking the value. If the attribute is missing returns `None`
|
||||
|
||||
:param elem: the Element instance.
|
||||
:param attribute: the attribute name (maybe 'form', or 'elementFormDefault' or 'attributeFormDefault').
|
||||
:return: a string.
|
||||
"""
|
||||
value = elem.get(attribute)
|
||||
if value is None:
|
||||
return
|
||||
elif value not in ('qualified', 'unqualified'):
|
||||
raise XMLSchemaValueError(
|
||||
"wrong value %r for attribute %r, it must be 'qualified' or 'unqualified'." % (value, attribute)
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class ParticleCounter(object):
|
||||
"""
|
||||
An helper class for counting total min/max occurrences of XSD particles.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.min_occurs = self.max_occurs = 0
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (self.__class__.__name__, self.min_occurs, self.max_occurs)
|
||||
|
||||
def __add__(self, other):
|
||||
self.min_occurs += other.min_occurs
|
||||
if self.max_occurs is not None:
|
||||
if other.max_occurs is None:
|
||||
self.max_occurs = None
|
||||
else:
|
||||
self.max_occurs += other.max_occurs
|
||||
return self
|
||||
|
||||
def __mul__(self, other):
|
||||
self.min_occurs *= other.min_occurs
|
||||
if self.max_occurs is None:
|
||||
if other.max_occurs == 0:
|
||||
self.max_occurs = 0
|
||||
elif other.max_occurs is None:
|
||||
if self.max_occurs != 0:
|
||||
self.max_occurs = None
|
||||
else:
|
||||
self.max_occurs *= other.max_occurs
|
||||
return self
|
||||
|
||||
def reset(self):
|
||||
self.min_occurs = self.max_occurs = 0
|
||||
|
|
|
@ -57,7 +57,7 @@ class NamespaceResourcesMap(MutableMapping):
|
|||
|
||||
def __setitem__(self, uri, value):
|
||||
if isinstance(value, list):
|
||||
self._store[uri] = value
|
||||
self._store[uri] = value[:]
|
||||
else:
|
||||
try:
|
||||
self._store[uri].append(value)
|
||||
|
|
|
@ -99,10 +99,11 @@ XSD_TOTAL_DIGITS = xsd_qname('totalDigits')
|
|||
XSD_FRACTION_DIGITS = xsd_qname('fractionDigits')
|
||||
|
||||
# XSD 1.1 elements
|
||||
XSD_OPEN_CONTENT = xsd_qname('openContent') # open content model
|
||||
XSD_ALTERNATIVE = xsd_qname('alternative') # conditional type assignment
|
||||
XSD_ASSERT = xsd_qname('assert') # complex type assertions
|
||||
XSD_ASSERTION = xsd_qname('assertion') # facets
|
||||
XSD_OPEN_CONTENT = xsd_qname('openContent') # open content model
|
||||
XSD_DEFAULT_OPEN_CONTENT = xsd_qname('defaultOpenContent') # default open content model (schema level)
|
||||
XSD_ALTERNATIVE = xsd_qname('alternative') # conditional type assignment
|
||||
XSD_ASSERT = xsd_qname('assert') # complex type assertions
|
||||
XSD_ASSERTION = xsd_qname('assertion') # facets
|
||||
XSD_EXPLICIT_TIMEZONE = xsd_qname('explicitTimezone')
|
||||
|
||||
# Identity constraints
|
||||
|
@ -195,5 +196,5 @@ __all__ = [
|
|||
'XSD_NON_NEGATIVE_INTEGER', 'XSD_POSITIVE_INTEGER', 'XSD_UNSIGNED_LONG', 'XSD_UNSIGNED_INT',
|
||||
'XSD_UNSIGNED_SHORT', 'XSD_UNSIGNED_BYTE', 'XSD_NON_POSITIVE_INTEGER', 'XSD_NEGATIVE_INTEGER',
|
||||
'XSD_IDREFS', 'XSD_ENTITIES', 'XSD_NMTOKENS', 'XSD_DATE_TIME_STAMP', 'XSD_DAY_TIME_DURATION',
|
||||
'XSD_YEAR_MONTH_DURATION',
|
||||
'XSD_YEAR_MONTH_DURATION', 'XSD_DEFAULT_OPEN_CONTENT', 'XSD_OVERRIDE',
|
||||
]
|
||||
|
|
|
@ -19,6 +19,11 @@ from .compat import PY3, unicode_type, string_base_type, MutableSet
|
|||
from .exceptions import XMLSchemaValueError, XMLSchemaRegexError
|
||||
from .codepoints import UNICODE_CATEGORIES, UNICODE_BLOCKS, UnicodeSubset
|
||||
|
||||
_RE_QUANTIFIER = re.compile(r'{\d+(,(\d+)?)?}')
|
||||
_RE_FORBIDDEN_ESCAPES = re.compile(
|
||||
r'(?<!\\)\\(U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|o{\d+}|\d+|A|Z|z|B|b|o)'
|
||||
)
|
||||
|
||||
_UNICODE_SUBSETS = UNICODE_CATEGORIES.copy()
|
||||
_UNICODE_SUBSETS.update(UNICODE_BLOCKS)
|
||||
|
||||
|
@ -36,7 +41,7 @@ I_SHORTCUT_REPLACE = (
|
|||
)
|
||||
|
||||
C_SHORTCUT_REPLACE = (
|
||||
"\-.0-9:A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-"
|
||||
"-.0-9:A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-"
|
||||
"\u200D\u203F\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"
|
||||
)
|
||||
|
||||
|
@ -47,7 +52,7 @@ C_SHORTCUT_SET = UnicodeSubset(C_SHORTCUT_REPLACE)
|
|||
W_SHORTCUT_SET = UnicodeSubset()
|
||||
W_SHORTCUT_SET._code_points = sorted(
|
||||
UNICODE_CATEGORIES['P'].code_points + UNICODE_CATEGORIES['Z'].code_points +
|
||||
UNICODE_CATEGORIES['C'].code_points, key=lambda x: x[0] if isinstance(x, tuple) else x
|
||||
UNICODE_CATEGORIES['C'].code_points, key=lambda x: x if isinstance(x, int) else x[0]
|
||||
)
|
||||
|
||||
# Single and Multi character escapes
|
||||
|
@ -89,8 +94,8 @@ class XsdRegexCharGroup(MutableSet):
|
|||
"""
|
||||
A set subclass to represent XML Schema regex character groups.
|
||||
"""
|
||||
_re_char_group = re.compile(r'(\\[nrt\\|.\-^?*+{}()\[\]sSdDiIcCwW]|\\[pP]{[a-zA-Z\-0-9]+})')
|
||||
_re_unicode_ref = re.compile(r'\\([pP]){([a-zA-Z\-0-9]+)}')
|
||||
_re_char_group = re.compile(r'(?<!.-)(\\[nrt|.\-^?*+{}()\]sSdDiIcCwW]|\\[pP]{[a-zA-Z\-0-9]+})')
|
||||
_re_unicode_ref = re.compile(r'\\([pP]){([\w\d-]+)}')
|
||||
|
||||
def __init__(self, *args):
|
||||
self.positive = UnicodeSubset()
|
||||
|
@ -154,11 +159,14 @@ class XsdRegexCharGroup(MutableSet):
|
|||
self.positive |= value
|
||||
else:
|
||||
self.negative |= value
|
||||
elif self._re_unicode_ref.search(part) is not None:
|
||||
if part.startswith('\\p'):
|
||||
self.positive |= get_unicode_subset(part[3:-1])
|
||||
else:
|
||||
self.negative |= get_unicode_subset(part[3:-1])
|
||||
elif part.startswith('\\p'):
|
||||
if self._re_unicode_ref.search(part) is None:
|
||||
raise XMLSchemaValueError("wrong Unicode subset specification %r" % part)
|
||||
self.positive |= get_unicode_subset(part[3:-1])
|
||||
elif part.startswith('\\P'):
|
||||
if self._re_unicode_ref.search(part) is None:
|
||||
raise XMLSchemaValueError("wrong Unicode subset specification %r" % part)
|
||||
self.negative |= get_unicode_subset(part[3:-1])
|
||||
else:
|
||||
self.positive.update(part)
|
||||
|
||||
|
@ -172,11 +180,14 @@ class XsdRegexCharGroup(MutableSet):
|
|||
self.positive -= value
|
||||
else:
|
||||
self.negative -= value
|
||||
elif self._re_unicode_ref.search(part) is not None:
|
||||
if part.startswith('\\p'):
|
||||
self.positive -= get_unicode_subset(part[3:-1])
|
||||
else:
|
||||
self.negative -= get_unicode_subset(part[3:-1])
|
||||
elif part.startswith('\\p'):
|
||||
if self._re_unicode_ref.search(part) is None:
|
||||
raise XMLSchemaValueError("wrong Unicode subset specification %r" % part)
|
||||
self.positive -= get_unicode_subset(part[3:-1])
|
||||
elif part.startswith('\\P'):
|
||||
if self._re_unicode_ref.search(part) is None:
|
||||
raise XMLSchemaValueError("wrong Unicode subset specification %r" % part)
|
||||
self.negative -= get_unicode_subset(part[3:-1])
|
||||
else:
|
||||
self.positive.difference_update(part)
|
||||
|
||||
|
@ -209,7 +220,9 @@ def parse_character_class(xml_regex, class_pos):
|
|||
|
||||
group_pos = pos
|
||||
while True:
|
||||
if xml_regex[pos] == '\\':
|
||||
if xml_regex[pos] == '[':
|
||||
raise XMLSchemaRegexError("'[' is invalid in a character class: %r" % xml_regex)
|
||||
elif xml_regex[pos] == '\\':
|
||||
pos += 2
|
||||
elif xml_regex[pos] == ']' or xml_regex[pos:pos + 2] == '-[':
|
||||
if pos == group_pos:
|
||||
|
@ -239,7 +252,16 @@ def get_python_regex(xml_regex):
|
|||
"""
|
||||
regex = ['^(']
|
||||
pos = 0
|
||||
while pos < len(xml_regex):
|
||||
xml_regex_len = len(xml_regex)
|
||||
nested_groups = 0
|
||||
|
||||
match = _RE_FORBIDDEN_ESCAPES.search(xml_regex)
|
||||
if match:
|
||||
raise XMLSchemaRegexError(
|
||||
"not allowed escape sequence %r at position %d: %r" % (match.group(), match.span()[0], xml_regex)
|
||||
)
|
||||
|
||||
while pos < xml_regex_len:
|
||||
ch = xml_regex[pos]
|
||||
if ch == '.':
|
||||
regex.append('[^\r\n]')
|
||||
|
@ -248,14 +270,56 @@ def get_python_regex(xml_regex):
|
|||
elif ch == '[':
|
||||
try:
|
||||
char_group, pos = parse_character_class(xml_regex, pos)
|
||||
regex.append(unicode_type(char_group))
|
||||
except IndexError:
|
||||
raise XMLSchemaRegexError(
|
||||
"unterminated character group at position %d: %r" % (pos, xml_regex)
|
||||
)
|
||||
else:
|
||||
char_group_repr = unicode_type(char_group)
|
||||
if char_group_repr == '[^]':
|
||||
regex.append(r'[\w\W]')
|
||||
elif char_group_repr == '[]':
|
||||
regex.append(r'[^\w\W]')
|
||||
else:
|
||||
regex.append(char_group_repr)
|
||||
|
||||
elif ch == '{':
|
||||
if pos == 0:
|
||||
raise XMLSchemaRegexError("unexpected quantifier %r at position %d: %r" % (ch, pos, xml_regex))
|
||||
match = _RE_QUANTIFIER.match(xml_regex[pos:])
|
||||
if match is None:
|
||||
raise XMLSchemaRegexError("invalid quantifier %r at position %d: %r" % (ch, pos, xml_regex))
|
||||
regex.append(match.group())
|
||||
pos += len(match.group())
|
||||
if pos < xml_regex_len and xml_regex[pos] in ('?', '+', '*'):
|
||||
raise XMLSchemaRegexError(
|
||||
"unexpected meta character %r at position %d: %r" % (xml_regex[pos], pos, xml_regex)
|
||||
)
|
||||
continue
|
||||
|
||||
elif ch == '(':
|
||||
if xml_regex[pos:pos + 2] == '(?':
|
||||
raise XMLSchemaRegexError("'(?...)' extension notation is not allowed: %r" % xml_regex)
|
||||
nested_groups += 1
|
||||
regex.append(ch)
|
||||
elif ch == ']':
|
||||
raise XMLSchemaRegexError("unexpected meta character %r at position %d: %r" % (ch, pos, xml_regex))
|
||||
elif ch == ')':
|
||||
if nested_groups == 0:
|
||||
raise XMLSchemaRegexError("unbalanced parenthesis ')' at position %d: %r" % (pos, xml_regex))
|
||||
nested_groups -= 1
|
||||
regex.append(ch)
|
||||
elif ch in ('?', '+', '*'):
|
||||
if pos == 0:
|
||||
raise XMLSchemaRegexError("unexpected quantifier %r at position %d: %r" % (ch, pos, xml_regex))
|
||||
elif pos < xml_regex_len - 1 and xml_regex[pos+1] in ('?', '+', '*', '{'):
|
||||
raise XMLSchemaRegexError(
|
||||
"unexpected meta character %r at position %d: %r" % (xml_regex[pos+1], pos+1, xml_regex)
|
||||
)
|
||||
regex.append(ch)
|
||||
elif ch == '\\':
|
||||
pos += 1
|
||||
if pos >= len(xml_regex):
|
||||
if pos >= xml_regex_len:
|
||||
regex.append('\\')
|
||||
elif xml_regex[pos] == 'i':
|
||||
regex.append('[%s]' % I_SHORTCUT_REPLACE)
|
||||
|
@ -287,5 +351,7 @@ def get_python_regex(xml_regex):
|
|||
regex.append(ch)
|
||||
pos += 1
|
||||
|
||||
if nested_groups > 0:
|
||||
raise XMLSchemaRegexError("unterminated subpattern in expression: %r" % xml_regex)
|
||||
regex.append(r')$')
|
||||
return ''.join(regex)
|
||||
|
|
|
@ -75,9 +75,13 @@ class XMLSchemaTestCase(unittest.TestCase):
|
|||
def setUpClass(cls):
|
||||
cls.errors = []
|
||||
cls.xsd_types = cls.schema_class.builtin_types()
|
||||
cls.content_pattern = re.compile(r'(xs:sequence|xs:choice|xs:all)')
|
||||
cls.content_pattern = re.compile(r'(<|<xs:)(sequence|choice|all)')
|
||||
|
||||
cls.default_namespaces = {'ns': 'ns', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
|
||||
cls.default_namespaces = {
|
||||
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
'tns': 'http://xmlschema.test/ns',
|
||||
'ns': 'ns',
|
||||
}
|
||||
|
||||
cls.vh_dir = cls.casepath('examples/vehicles')
|
||||
cls.vh_xsd_file = cls.casepath('examples/vehicles/vehicles.xsd')
|
||||
|
@ -179,7 +183,7 @@ class XMLSchemaTestCase(unittest.TestCase):
|
|||
self.check_namespace_prefixes(error_string)
|
||||
|
||||
if not self.errors and expected:
|
||||
raise ValueError("found no errors when %d expected." % expected)
|
||||
raise ValueError("{!r}: found no errors when {} expected.".format(path, expected))
|
||||
elif len(self.errors) != expected:
|
||||
num_errors = len(self.errors)
|
||||
if num_errors == 1:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/vehicles"
|
||||
targetNamespace="http://example.com/vehicles">
|
||||
|
||||
<xs:include schemaLocation="cars.xsd" />
|
||||
|
|
|
@ -124,4 +124,24 @@
|
|||
<xs:pattern value="(word|other)|(alternative)"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Issue #103 -->
|
||||
<xs:simpleType name="myType">
|
||||
<xs:union memberTypes="typeA typeB"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="typeA">
|
||||
<xs:restriction base="xs:decimal">
|
||||
<xs:minInclusive value="0.0"/>
|
||||
<xs:maxInclusive value="100.0"/>
|
||||
<xs:fractionDigits value="3"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="typeB">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern value="NaN"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Schema test for invalid restricted models: UPA violation restricting a substitution group head. -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:element type="xs:string" name="elem1"/>
|
||||
<xs:element type="xs:string" name="elem2" substitutionGroup="elem1" />
|
||||
|
||||
<xs:complexType name="basicType1">
|
||||
<xs:sequence>
|
||||
<xs:element ref="elem1" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- UPA violation minOccurs < maxOccurs -->
|
||||
<xs:complexType name="restrictedType1">
|
||||
<xs:complexContent>
|
||||
<xs:restriction base="basicType1">
|
||||
<xs:sequence>
|
||||
<xs:element ref="elem2" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- No UPA violation if the restricted element is empty -->
|
||||
<xs:complexType name="restrictedType2">
|
||||
<xs:complexContent>
|
||||
<xs:restriction base="basicType1">
|
||||
<xs:sequence>
|
||||
<xs:element ref="elem2" minOccurs="0" maxOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element type="xs:string" name="elem3" abstract="true"/>
|
||||
<xs:element type="xs:string" name="elem4" substitutionGroup="elem3" />
|
||||
|
||||
<xs:complexType name="basicType3">
|
||||
<xs:sequence>
|
||||
<xs:element ref="elem3" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- No UPA violation if the head element is abstract (it cannot be used in an instance). -->
|
||||
<xs:complexType name="restrictedType3">
|
||||
<xs:complexContent>
|
||||
<xs:restriction base="basicType3">
|
||||
<xs:sequence>
|
||||
<xs:element ref="elem4" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Schema test for invalid models: UPA violation restricting a substitution group head. -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:complexType name="baseType1">
|
||||
<xs:sequence>
|
||||
<xs:element name="elem1" />
|
||||
<xs:element minOccurs="0" name="elem2" />
|
||||
<xs:choice>
|
||||
<xs:element name="elem3" type="xs:string" />
|
||||
<xs:element name="elem4" type="xs:string" />
|
||||
</xs:choice>
|
||||
<xs:element minOccurs="0" name="elem5" />
|
||||
<xs:element minOccurs="0" name="elem6" type="xs:string" />
|
||||
<xs:element minOccurs="0" name="elem7" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="restrictedType1">
|
||||
<xs:complexContent>
|
||||
<xs:restriction base="baseType1">
|
||||
<xs:sequence>
|
||||
<xs:sequence>
|
||||
<xs:element name="elem1" />
|
||||
<xs:element minOccurs="0" name="elem2" />
|
||||
<xs:choice>
|
||||
<xs:element name="elem3" type="xs:token" />
|
||||
<xs:element name="elem4" type="xs:string" />
|
||||
</xs:choice>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" name="elem6" type="xs:string" />
|
||||
</xs:sequence>
|
||||
</xs:sequence>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" name="elem7" />
|
||||
</xs:sequence>
|
||||
</xs:sequence>
|
||||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Schema test for invalid models: UPA violations with a substitution group head and element. -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:element type="xs:int" name="a"/>
|
||||
<xs:element type="xs:int" name="b" substitutionGroup="a" />
|
||||
|
||||
<xs:complexType name="wrong_type1">
|
||||
<xs:all>
|
||||
<xs:element ref="a"/>
|
||||
<xs:element ref="b"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="wrong_type2">
|
||||
<xs:all>
|
||||
<xs:element ref="b"/>
|
||||
<xs:element ref="a"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="wrong_type3">
|
||||
<xs:choice>
|
||||
<xs:element ref="a"/>
|
||||
<xs:element ref="b"/>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="good_type1">
|
||||
<xs:sequence>
|
||||
<xs:element ref="a"/>
|
||||
<xs:element ref="b"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="wrong_type4">
|
||||
<xs:sequence>
|
||||
<xs:element ref="a" maxOccurs="unbounded"/>
|
||||
<xs:element ref="b"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Schema test for invalid XSD 1.0 models: UPA violations that involve wildcards and elements. -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://xmlschema.test/ns"
|
||||
xmlns="http://xmlschema.test/ns"
|
||||
xmlns:other-ns="http://xmlschema.test/other-ns">
|
||||
|
||||
<xs:import namespace='http://xmlschema.test/other-ns' schemaLocation='other-ns.xsd'/>
|
||||
|
||||
<xs:element name="elem0" type="xs:string"/>
|
||||
|
||||
<!-- UPA violation (any with minOccurs < maxOccurs and ambiguity with elem0 validation) -->
|
||||
<xs:element name='elem1'>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:any namespace='##targetNamespace' minOccurs='0'/>
|
||||
<xs:element ref='elem0'/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<!-- UPA violation (any and elem0 overlap and choice model) -->
|
||||
<xs:element name='elem2'>
|
||||
<xs:complexType>
|
||||
<xs:choice>
|
||||
<xs:any namespace='##targetNamespace'/>
|
||||
<xs:element ref='elem0'/>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<!-- UPA violation (any with minOccurs < maxOccurs and ambiguity with other-ns:elem0) -->
|
||||
<xs:element name='elem3'>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:any namespace='##other' minOccurs='0'/>
|
||||
<xs:element ref='other-ns:elem0'/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
</xs:schema>
|
|
@ -14,15 +14,15 @@
|
|||
<!-- Groups for model validation tests -->
|
||||
<xs:group name="group1">
|
||||
<xs:sequence>
|
||||
<xs:element name="elem1" minOccurs="0" />
|
||||
<xs:element name="elem2" minOccurs="0" />
|
||||
<xs:element name="elem3" minOccurs="0" />
|
||||
<xs:element name="elem1" type="xs:string" minOccurs="0" />
|
||||
<xs:element name="elem2" type="xs:string" minOccurs="0" />
|
||||
<xs:element name="elem3" type="xs:string" minOccurs="0" />
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
|
||||
<xs:group name="group2">
|
||||
<xs:sequence>
|
||||
<xs:element name="elem1" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="elem1" type="xs:string" minOccurs="0" />
|
||||
<xs:choice minOccurs="0">
|
||||
<xs:group ref="group1"/>
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://xmlschema.test/other-ns">
|
||||
<xs:element name="elem0" type="xs:string"/>
|
||||
</xs:schema>
|
|
@ -0,0 +1,8 @@
|
|||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="rootType"/>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:ns="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="ns:rootType"/>
|
||||
</xs:schema>
|
|
@ -0,0 +1,8 @@
|
|||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:ns="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:import namespace="http://example.com/xmlschema/namespaces"/>
|
||||
<xs:element name="root" type="ns:rootType"/>
|
||||
</xs:schema>
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
An invalid usage of the default namespace: it isn't mapped to targetNamespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="rootType"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A valid usage of the default namespace: mapped by an explicit declaration.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="rootType"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A valid usage of the default namespace: for default is mapped to no namespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="rootType"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
An invalid import case: the default namespace is mapped to targetNamespace but
|
||||
the imported chameleon schema still maps default namespace to no namespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:import schemaLocation="chameleon1.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
An invalid import case: the default namespace is mapped to targetNamespace but
|
||||
the imported chameleon schema still maps default namespace to no namespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:import schemaLocation="chameleon2.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A valid import case: imported chameleon schema imports the target namespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:import schemaLocation="chameleon3.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A valid include of a chameleon schema using an explicit declaration of the default namespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:include schemaLocation="chameleon1.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A valid include using the default namespace, including a chameleon schema.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:include schemaLocation="chameleon1.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A valid include of a chameleon schema using an explicit declaration of the default namespace.
|
||||
In this case the chameleon schema uses another prefix for the same target namespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:include schemaLocation="chameleon2.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A valid include of a chameleon schema using an implicit declaration of the default namespace.
|
||||
In this case the chameleon schema uses another prefix for the same target namespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:include schemaLocation="chameleon2.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
An valid include from a schema with a targetNamespace, with the default namespace explicitly mapped.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:include schemaLocation="included3-valid.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
An invalid include: including a schema with a different targetNamespace.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:include schemaLocation="included4-invalid.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
An valid include from a schema with a targetNamespace, with the default namespace explicitly mapped.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:nsx="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:include schemaLocation="included5-valid.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
An invalid include from a schema with a targetNamespace not mapped to any prefix.
|
||||
-->
|
||||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:include schemaLocation="included6-invalid.xsd"/>
|
||||
|
||||
<xs:simpleType name="rootType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="rootType"/>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://example.com/xmlschema/elements"
|
||||
targetNamespace="http://example.com/xmlschema/elements"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="rootType"/>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:nsy="http://example.com/xmlschema/namespaces"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="nsy:rootType"/>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://example.com/xmlschema/namespaces"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="root" type="rootType"/>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:element name="project" type="TProject">
|
||||
<xs:key name="widgetId">
|
||||
<xs:selector xpath=".//widgets/widget | .//logic/widget"/>
|
||||
<xs:field xpath="@id"/>
|
||||
</xs:key>
|
||||
</xs:element>
|
||||
|
||||
<xs:complexType name="TProject">
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="protocols"/>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
|
@ -36,6 +36,7 @@ features/derivations/complex-extensions.xsd --errors=1
|
|||
features/derivations/complex-with-simple-content-restriction.xsd
|
||||
features/derivations/list_types.xsd --errors=1
|
||||
features/derivations/list_types.xml --errors=2
|
||||
features/derivations/invalid_restrictions1.xsd --errors=1 # UPA violations with substitution group head element
|
||||
|
||||
features/elements/type_alternatives.xsd --errors=3
|
||||
features/elements/type_alternatives.xsd --version=1.1
|
||||
|
@ -47,10 +48,27 @@ features/models/circular_model.xsd --errors=1
|
|||
features/models/illegal-attributes.xsd --errors=1
|
||||
features/models/illegal-declarations.xsd --errors=3
|
||||
features/models/illegal-occurs.xsd --errors=2
|
||||
features/models/invalid_models1.xsd --errors=4
|
||||
features/models/invalid_models2.xsd --errors=3
|
||||
features/models/model1.xml --errors=1
|
||||
features/models/models.xsd
|
||||
features/models/recursive-groups.xsd --errors=2 # Two circular definitions
|
||||
|
||||
features/namespaces/default_ns_invalid.xsd --errors=1
|
||||
features/namespaces/default_ns_valid1.xsd
|
||||
features/namespaces/default_ns_valid2.xsd
|
||||
features/namespaces/import-case1.xsd --errors=1 # Unknown type
|
||||
features/namespaces/import-case2.xsd --errors=1 # Missing namespace import in imported chameleon schema
|
||||
features/namespaces/import-case3.xsd
|
||||
features/namespaces/include-case1.xsd
|
||||
features/namespaces/include-case1bis.xsd
|
||||
features/namespaces/include-case2.xsd
|
||||
features/namespaces/include-case2bis.xsd
|
||||
features/namespaces/include-case3.xsd
|
||||
features/namespaces/include-case4.xsd --errors=2
|
||||
features/namespaces/include-case5.xsd
|
||||
features/namespaces/include-case6.xsd --errors=1
|
||||
|
||||
features/patterns/patterns.xsd
|
||||
features/patterns/patterns.xml --errors=5
|
||||
|
||||
|
@ -81,4 +99,5 @@ issues/issue_051/issue_051.xml
|
|||
issues/issue_073/issue_073-1.xml
|
||||
issues/issue_073/issue_073-2.xml --errors=1
|
||||
issues/issue_086/issue_086-1.xml
|
||||
issues/issue_086/issue_086-2.xml
|
||||
issues/issue_086/issue_086-2.xml
|
||||
issues/issue_105/issue_105.xsd
|
|
@ -36,6 +36,8 @@ TEST_FACTORY_OPTIONS = {
|
|||
}
|
||||
"""Command line options for test factory."""
|
||||
|
||||
RUN_W3C_TEST_SUITE = '-w' in sys.argv or '--w3c' in sys.argv
|
||||
|
||||
sys.argv = [a for a in sys.argv if a not in {'-x', '--extra', '-l', '--lxml'}] # Clean sys.argv for unittest
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import unittest
|
|||
from xmlschema.etree import etree_element
|
||||
from xmlschema.namespaces import XSD_NAMESPACE, XSI_NAMESPACE
|
||||
from xmlschema.helpers import get_xsd_annotation, iter_xsd_components, get_namespace, get_qname, \
|
||||
local_name, prefixed_to_qname, qname_to_prefixed, has_xsd_components, get_xsd_component, \
|
||||
local_name, qname_to_prefixed, has_xsd_components, get_xsd_component, \
|
||||
get_xml_bool_attribute, get_xsd_derivation_attribute
|
||||
from xmlschema.qnames import XSI_TYPE, XSD_SCHEMA, XSD_ELEMENT, XSD_SIMPLE_TYPE, XSD_ANNOTATION
|
||||
from xmlschema.tests import XMLSchemaTestCase
|
||||
|
@ -57,19 +57,6 @@ class TestHelpers(XMLSchemaTestCase):
|
|||
self.assertRaises(TypeError, local_name, 1.0)
|
||||
self.assertRaises(TypeError, local_name, 0)
|
||||
|
||||
def test_prefixed_to_qname_functions(self):
|
||||
namespaces = {'xs': XSD_NAMESPACE, 'xsi': XSI_NAMESPACE}
|
||||
self.assertEqual(prefixed_to_qname('xs:element', namespaces), XSD_ELEMENT)
|
||||
self.assertEqual(prefixed_to_qname('xsi:type', namespaces), XSI_TYPE)
|
||||
|
||||
self.assertEqual(prefixed_to_qname(XSI_TYPE, namespaces), XSI_TYPE)
|
||||
self.assertEqual(prefixed_to_qname('element', namespaces), 'element')
|
||||
self.assertEqual(prefixed_to_qname('', namespaces), '')
|
||||
self.assertEqual(prefixed_to_qname(None, namespaces), None)
|
||||
|
||||
self.assertRaises(ValueError, prefixed_to_qname, 'xsi:type', {})
|
||||
self.assertRaises(ValueError, prefixed_to_qname, 'xml:lang', namespaces)
|
||||
|
||||
def test_qname_to_prefixed_functions(self):
|
||||
namespaces = {'xs': XSD_NAMESPACE, 'xsi': XSI_NAMESPACE}
|
||||
self.assertEqual(qname_to_prefixed(XSD_ELEMENT, namespaces), 'xs:element')
|
||||
|
|
|
@ -265,12 +265,12 @@ class TestGlobalMaps(unittest.TestCase):
|
|||
|
||||
def test_xsd_10_globals(self):
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.notations), 2)
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.types), 105)
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.types), 108)
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.attributes), 18)
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.attribute_groups), 9)
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.groups), 18)
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.elements), 47)
|
||||
self.assertEqual(len([e.is_global for e in xsd_10_meta_schema.maps.iter_globals()]), 199)
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.elements), 45)
|
||||
self.assertEqual(len([e.is_global for e in xsd_10_meta_schema.maps.iter_globals()]), 200)
|
||||
self.assertEqual(len(xsd_10_meta_schema.maps.substitution_groups), 0)
|
||||
|
||||
def test_xsd_11_globals(self):
|
||||
|
@ -279,13 +279,13 @@ class TestGlobalMaps(unittest.TestCase):
|
|||
self.assertEqual(len(xsd_11_meta_schema.maps.attributes), 18)
|
||||
self.assertEqual(len(xsd_11_meta_schema.maps.attribute_groups), 10)
|
||||
self.assertEqual(len(xsd_11_meta_schema.maps.groups), 19)
|
||||
self.assertEqual(len(xsd_11_meta_schema.maps.elements), 53)
|
||||
self.assertEqual(len([e.is_global for e in xsd_11_meta_schema.maps.iter_globals()]), 220)
|
||||
self.assertEqual(len(xsd_11_meta_schema.maps.elements), 51)
|
||||
self.assertEqual(len([e.is_global for e in xsd_11_meta_schema.maps.iter_globals()]), 218)
|
||||
self.assertEqual(len(xsd_11_meta_schema.maps.substitution_groups), 1)
|
||||
|
||||
def test_xsd_10_build(self):
|
||||
xsd_10_meta_schema.maps.build()
|
||||
self.assertEqual(len([e for e in xsd_10_meta_schema.maps.iter_globals()]), 199)
|
||||
self.assertEqual(len([e for e in xsd_10_meta_schema.maps.iter_globals()]), 200)
|
||||
self.assertTrue(xsd_10_meta_schema.maps.built)
|
||||
xsd_10_meta_schema.maps.clear()
|
||||
xsd_10_meta_schema.maps.build()
|
||||
|
@ -293,7 +293,7 @@ class TestGlobalMaps(unittest.TestCase):
|
|||
|
||||
def test_xsd_11_build(self):
|
||||
xsd_11_meta_schema.maps.build()
|
||||
self.assertEqual(len([e for e in xsd_11_meta_schema.maps.iter_globals()]), 220)
|
||||
self.assertEqual(len([e for e in xsd_11_meta_schema.maps.iter_globals()]), 218)
|
||||
self.assertTrue(xsd_11_meta_schema.maps.built)
|
||||
xsd_11_meta_schema.maps.clear()
|
||||
xsd_11_meta_schema.maps.build()
|
||||
|
@ -307,8 +307,8 @@ class TestGlobalMaps(unittest.TestCase):
|
|||
total_counter += 1
|
||||
if c.is_global:
|
||||
global_counter += 1
|
||||
self.assertEqual(global_counter, 199)
|
||||
self.assertEqual(total_counter, 894)
|
||||
self.assertEqual(global_counter, 200)
|
||||
self.assertEqual(total_counter, 901)
|
||||
|
||||
def test_xsd_11_components(self):
|
||||
total_counter = 0
|
||||
|
@ -318,8 +318,8 @@ class TestGlobalMaps(unittest.TestCase):
|
|||
total_counter += 1
|
||||
if c.is_global:
|
||||
global_counter += 1
|
||||
self.assertEqual(global_counter, 220)
|
||||
self.assertEqual(total_counter, 1031)
|
||||
self.assertEqual(global_counter, 218)
|
||||
self.assertEqual(total_counter, 1018)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -14,7 +14,7 @@ This module runs tests concerning model groups validation.
|
|||
"""
|
||||
import unittest
|
||||
|
||||
from xmlschema.validators import XsdModelVisitor
|
||||
from xmlschema.validators import ModelVisitor
|
||||
from xmlschema.tests import XMLSchemaTestCase
|
||||
|
||||
|
||||
|
@ -26,7 +26,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
"""
|
||||
Advances a model with a match condition and checks the expected error list or exception.
|
||||
|
||||
:param model: an XsdModelVisitor instance.
|
||||
:param model: an ModelGroupVisitor instance.
|
||||
:param expected: can be an exception class or a list. Leaving `None` means that an empty \
|
||||
list is expected.
|
||||
"""
|
||||
|
@ -39,7 +39,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
"""
|
||||
Advances a model with a no-match condition and checks the expected error list or or exception.
|
||||
|
||||
:param model: an XsdModelVisitor instance.
|
||||
:param model: an ModelGroupVisitor instance.
|
||||
:param expected: can be an exception class or a list. Leaving `None` means that an empty \
|
||||
list is expected.
|
||||
"""
|
||||
|
@ -52,7 +52,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
"""
|
||||
Advances a model with an argument match condition and checks the expected error list.
|
||||
|
||||
:param model: an XsdModelVisitor instance.
|
||||
:param model: an ModelGroupVisitor instance.
|
||||
:param match: the matching boolean condition.
|
||||
:param expected: can be an exception class or a list. Leaving `None` means that an empty \
|
||||
list is expected.
|
||||
|
@ -66,7 +66,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
"""
|
||||
Stops a model and checks the expected errors list.
|
||||
|
||||
:param model: an XsdModelVisitor instance.
|
||||
:param model: an ModelGroupVisitor instance.
|
||||
:param expected: can be an exception class or a list. Leaving `None` means that an empty \
|
||||
list is expected.
|
||||
"""
|
||||
|
@ -81,12 +81,12 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
# Sequence with two not-emptiable single-occurs elements
|
||||
group = self.vh_schema.elements['vehicles'].type.content_type
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <cars>
|
||||
self.check_advance_true(model) # <bikes>
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <cars>
|
||||
self.check_advance_true(model) # <bikes>
|
||||
self.check_advance_true(model, ValueError) # <bikes>
|
||||
|
@ -96,14 +96,14 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
# Emptiable 1:1 sequence with one emptiable and unlimited element.
|
||||
group = self.vh_schema.elements['cars'].type.content_type
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <car>
|
||||
self.check_advance_true(model) # <car>
|
||||
self.check_advance_true(model) # <car>
|
||||
self.check_advance_false(model) # (end)
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_false(model) # <not-a-car>
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
|
@ -113,7 +113,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
# Sequence with one not-emptiable and unlimited element.
|
||||
group = self.col_schema.elements['collection'].type.content_type
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <car>
|
||||
self.check_advance_true(model) # <car>
|
||||
self.check_advance_true(model) # <car>
|
||||
|
@ -121,7 +121,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
self.check_advance_false(model) # (end)
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_false(model, [(group[0], 0, [group[0]])]) # <not-a-car>
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
|
@ -129,20 +129,20 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
# Sequence with four single elements, last two are also emptiable.
|
||||
group = self.col_schema.types['personType'].content_type
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <name>
|
||||
self.check_advance_true(model) # <born>
|
||||
self.check_advance_true(model) # <dead>
|
||||
self.check_advance_true(model) # <qualification>
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <name>
|
||||
self.check_advance_true(model) # <born>
|
||||
self.check_stop(model)
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <name> match
|
||||
self.check_advance_false(model, [(group[1], 0, [group[1]])]) # <born> missing!
|
||||
self.check_advance_true(model) # <dead> match
|
||||
|
@ -163,22 +163,22 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
"""
|
||||
group = self.schema_class.meta_schema.groups['simpleDerivation']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <restriction> match
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_false(model) # <list> not match with <restriction>
|
||||
self.check_advance_true(model) # <list> match
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_false(model) # <union> not match with <restriction>
|
||||
self.check_advance_false(model) # <union> not match with <list>
|
||||
self.check_advance_true(model) # <union> match
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_false(model) # <other> not match with <restriction>
|
||||
self.check_advance_false(model) # <other> not match with <list>
|
||||
self.check_advance_false(model, [(group, 0, group[:])]) # <other> not match with <union>
|
||||
|
@ -213,7 +213,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
# Sequence with an optional single element and an optional unlimited choice.
|
||||
group = self.schema_class.meta_schema.groups['simpleRestrictionModel']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0])
|
||||
self.check_advance_true(model) # <simpleType> match
|
||||
self.assertEqual(model.element, group[1][0][0])
|
||||
|
@ -249,7 +249,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
"""
|
||||
group = self.schema_class.meta_schema.groups['schemaTop']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0][0][0])
|
||||
self.check_advance_false(model) # <simpleType> don't match
|
||||
self.assertEqual(model.element, group[0][0][1])
|
||||
|
@ -301,22 +301,22 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
"""
|
||||
group = self.schema_class.meta_schema.groups['attrDecls']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
for match in [False, False, True]:
|
||||
self.check_advance(model, match)
|
||||
self.assertIsNone(model.element)
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_false(model)
|
||||
self.check_advance_true(model)
|
||||
self.assertEqual(model.element, group[0][0])
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
for match in [False, True, False, False]:
|
||||
self.check_advance(model, match)
|
||||
self.assertEqual(model.element, group[1])
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
for match in [False, True, True, False, True, False, False]:
|
||||
self.check_advance(model, match)
|
||||
self.assertEqual(model.element, group[1])
|
||||
|
@ -345,7 +345,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
"""
|
||||
group = self.schema_class.meta_schema.groups['complexTypeModel']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0])
|
||||
self.check_advance_true(model) # <simpleContent> match
|
||||
self.assertIsNone(model.element)
|
||||
|
@ -373,7 +373,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
group = self.schema_class.meta_schema.elements['schema'].type.content_type
|
||||
|
||||
# A schema model with a wrong tag
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0][0])
|
||||
self.check_advance_false(model) # eg. anyAttribute
|
||||
self.check_stop(model)
|
||||
|
@ -383,7 +383,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
def test_model_group1(self):
|
||||
group = self.models_schema.groups['group1']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0])
|
||||
self.check_stop(model)
|
||||
|
||||
|
@ -401,7 +401,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
def test_model_group2(self):
|
||||
group = self.models_schema.groups['group2']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0])
|
||||
for _ in range(3):
|
||||
self.check_advance_false(model) # group1 do not match
|
||||
|
@ -417,7 +417,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
def test_model_group3(self):
|
||||
group = self.models_schema.groups['group3']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0])
|
||||
for match in [True, False, True]:
|
||||
self.check_advance(model, match)
|
||||
|
@ -426,7 +426,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
def test_model_group4(self):
|
||||
group = self.models_schema.groups['group4']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0])
|
||||
for match in [True, False, True]:
|
||||
self.check_advance(model, match)
|
||||
|
@ -435,7 +435,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
def test_model_group5(self):
|
||||
group = self.models_schema.groups['group5']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0][0])
|
||||
for _ in range(5): # match [<elem1> .. <elem5>]
|
||||
self.check_advance_true(model)
|
||||
|
@ -446,7 +446,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
def test_model_group6(self):
|
||||
group = self.models_schema.groups['group6']
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0][0])
|
||||
self.check_advance_true(model) # match choice with <elem1>
|
||||
self.check_advance_true(model) # match choice with <elem2>
|
||||
|
@ -455,13 +455,13 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
def test_model_group7(self):
|
||||
group = self.models_schema.types['complexType7'].content_type
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0][0])
|
||||
self.check_stop(model, [(group[0][0], 0, [group[0][0]])])
|
||||
|
||||
group = self.models_schema.types['complexType7_emptiable'].content_type
|
||||
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0][0])
|
||||
self.check_stop(model)
|
||||
|
||||
|
@ -473,7 +473,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
group = schema.types['Foo'].content_type
|
||||
|
||||
# issue_086-1.xml sequence simulation
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.assertEqual(model.element, group[0])
|
||||
self.check_advance_true(model) # <header> matching
|
||||
self.assertEqual(model.element, group[1][0][0]) # 'a' element
|
||||
|
@ -499,7 +499,7 @@ class TestModelValidation(XMLSchemaTestCase):
|
|||
self.check_stop(model)
|
||||
|
||||
# issue_086-2.xml sequence simulation
|
||||
model = XsdModelVisitor(group)
|
||||
model = ModelVisitor(group)
|
||||
self.check_advance_true(model) # <header> matching
|
||||
self.assertEqual(model.element, group[1][0][0]) # 'a' element
|
||||
self.check_advance_false(model)
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
import unittest
|
||||
import glob
|
||||
import fileinput
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import importlib
|
||||
import platform
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
|
@ -113,6 +115,7 @@ class TestPackaging(unittest.TestCase):
|
|||
# Exclude explicit debug statements written in the code
|
||||
exclude = {
|
||||
'regex.py': [240, 241],
|
||||
'codepoints.py': [543],
|
||||
}
|
||||
|
||||
message = "\nFound a debug missing statement at line %d or file %r: %r"
|
||||
|
@ -160,12 +163,28 @@ class TestPackaging(unittest.TestCase):
|
|||
message % (lineno, filename, match.group(1).strip('\'\"'), version)
|
||||
)
|
||||
|
||||
def test_json_unicode_categories(self):
|
||||
filename = os.path.join(self.source_dir, 'unicode_categories.json')
|
||||
self.assertTrue(os.path.isfile(filename), msg="file %r is missing!" % filename)
|
||||
with open(filename, 'r') as fp:
|
||||
self.assertIsInstance(json.load(fp), dict, msg="file %r is not encoded in JSON format!" % filename)
|
||||
|
||||
# TODO: Add tests for checking base schemas files and other package files.
|
||||
def test_base_schema_files(self):
|
||||
et = importlib.import_module('xml.etree.ElementTree')
|
||||
schemas_dir = os.path.join(self.source_dir, 'validators/schemas')
|
||||
base_schemas = [
|
||||
'XSD_1.0/XMLSchema.xsd', 'XSD_1.1/XMLSchema.xsd', 'xhtml1-strict.xsd', 'xlink.xsd',
|
||||
'xml_minimal.xsd', 'XMLSchema-hasFacetAndProperty_minimal.xsd', 'XMLSchema-instance_minimal.xsd'
|
||||
]
|
||||
for rel_path in base_schemas:
|
||||
filename = os.path.join(schemas_dir, rel_path)
|
||||
self.assertTrue(os.path.isfile(filename), msg="schema file %r is missing!" % filename)
|
||||
self.assertIsInstance(et.parse(filename), et.ElementTree)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from xmlschema.tests import print_test_header
|
||||
header1 = "Test package %r installation" % os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
header2 = "with Python {} on platform {}".format(platform.python_version(), platform.platform())
|
||||
print('{0}\n{1}\n{2}\n{0}'.format("*" * max(len(header1), len(header2)), header1, header2))
|
||||
|
||||
print_test_header()
|
||||
unittest.main()
|
||||
|
|
|
@ -18,10 +18,11 @@ import sys
|
|||
import re
|
||||
from unicodedata import category
|
||||
|
||||
from xmlschema.exceptions import XMLSchemaValueError
|
||||
from xmlschema.exceptions import XMLSchemaValueError, XMLSchemaRegexError
|
||||
from xmlschema.compat import unicode_chr
|
||||
from xmlschema.codepoints import iter_code_points, UnicodeSubset, build_unicode_categories, UNICODE_CATEGORIES
|
||||
from xmlschema.regex import get_python_regex
|
||||
from xmlschema.codepoints import code_point_repr, iterparse_character_group, iter_code_points, \
|
||||
UnicodeSubset, build_unicode_categories, UNICODE_CATEGORIES
|
||||
from xmlschema.regex import get_python_regex, XsdRegexCharGroup
|
||||
|
||||
|
||||
class TestCodePoints(unittest.TestCase):
|
||||
|
@ -111,6 +112,15 @@ class TestUnicodeSubset(unittest.TestCase):
|
|||
cds = UnicodeSubset([0, 2, (80, 200), 10000])
|
||||
self.assertEqual(cds - {2, 120, 121, (150, 260)}, [0, (80, 120), (122, 150), 10000])
|
||||
|
||||
def test_code_point_repr_function(self):
|
||||
self.assertEqual(code_point_repr((ord('2'), ord('\\') + 1)), r'2-\\')
|
||||
|
||||
|
||||
class TestXsdRegexCharGroup(unittest.TestCase):
|
||||
|
||||
def test_char_group_split(self):
|
||||
self.assertListEqual(XsdRegexCharGroup._re_char_group.split(r'2-\\'), [r'2-\\'])
|
||||
|
||||
|
||||
class TestUnicodeCategories(unittest.TestCase):
|
||||
"""
|
||||
|
@ -131,7 +141,7 @@ class TestUnicodeCategories(unittest.TestCase):
|
|||
base_sets = [set(v) for k, v in UNICODE_CATEGORIES.items() if len(k) > 1]
|
||||
self.assertFalse(any([s.intersection(t) for s in base_sets for t in base_sets if s != t]))
|
||||
|
||||
@unittest.skipIf(not ((3, 6) <= sys.version_info < (3, 7)), "Test only for Python 3.6")
|
||||
@unittest.skipIf(not ((3, 7) <= sys.version_info < (3, 8)), "Test only for Python 3.7")
|
||||
def test_unicodedata_category(self):
|
||||
for key in UNICODE_CATEGORIES:
|
||||
for cp in UNICODE_CATEGORIES[key]:
|
||||
|
@ -273,6 +283,12 @@ class TestPatterns(unittest.TestCase):
|
|||
self.assertEqual(pattern.search('zk:xy-9s').group(0), 'zk:xy-9s')
|
||||
self.assertIsNone(pattern.search('xx:y'))
|
||||
|
||||
def test_iterparse_character_group(self):
|
||||
self.assertListEqual(list(iterparse_character_group('a-c-1-4x-z-7-9')),
|
||||
[(ord('a'), ord('c') + 1), ord('-'), (ord('1'), ord('4') + 1),
|
||||
(ord('x'), ord('z') + 1), ord('-'), (55, 58)])
|
||||
self.assertListEqual(list(iterparse_character_group('2-\\')), [(ord('2'), ord('\\') + 1)])
|
||||
|
||||
def test_occurrences_qualifiers(self):
|
||||
regex = get_python_regex('#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?')
|
||||
self.assertEqual(regex, '^(#[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?)$')
|
||||
|
@ -321,6 +337,11 @@ class TestPatterns(unittest.TestCase):
|
|||
self.assertEqual(pattern.search('x11').group(0), 'x11')
|
||||
self.assertIsNone(pattern.search('3a'))
|
||||
|
||||
def test_empty_character_group_repr(self):
|
||||
regex = get_python_regex('[a-[a-f]]')
|
||||
self.assertEqual(regex, r'^([^\w\W])$')
|
||||
self.assertRaises(XMLSchemaRegexError, get_python_regex, '[]')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from xmlschema.tests import print_test_header
|
||||
|
|
|
@ -21,10 +21,11 @@ import time
|
|||
import warnings
|
||||
|
||||
import xmlschema
|
||||
from xmlschema import XMLSchemaBase, XMLSchemaParseError, XMLSchemaIncludeWarning, XMLSchemaImportWarning
|
||||
from xmlschema import XMLSchemaBase, XMLSchemaParseError, XMLSchemaModelError, \
|
||||
XMLSchemaIncludeWarning, XMLSchemaImportWarning
|
||||
from xmlschema.compat import PY3, unicode_type
|
||||
from xmlschema.etree import lxml_etree, etree_element, py_etree_element
|
||||
from xmlschema.qnames import XSD_LIST, XSD_UNION
|
||||
from xmlschema.qnames import XSD_LIST, XSD_UNION, XSD_ELEMENT, XSI_TYPE
|
||||
from xmlschema.tests import tests_factory, SchemaObserver, XMLSchemaTestCase
|
||||
from xmlschema.validators import XsdValidator, XMLSchema11
|
||||
from xmlschema.xpath import ElementPathContext
|
||||
|
@ -57,7 +58,7 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
</complexType>
|
||||
<complexType name="restrictedType">
|
||||
<{1}Content>
|
||||
<restriction base="targetType">
|
||||
<restriction base="ns:targetType">
|
||||
{2}
|
||||
</restriction>
|
||||
</{1}Content>
|
||||
|
@ -65,6 +66,28 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
""".format(base.strip(), content, restriction.strip())
|
||||
self.check_schema(source, expected, **kwargs)
|
||||
|
||||
def test_schema_copy(self):
|
||||
schema = self.vh_schema.copy()
|
||||
self.assertNotEqual(id(self.vh_schema), id(schema))
|
||||
self.assertNotEqual(id(self.vh_schema.namespaces), id(schema.namespaces))
|
||||
self.assertNotEqual(id(self.vh_schema.maps), id(schema.maps))
|
||||
|
||||
def test_resolve_qname(self):
|
||||
schema = self.schema_class("""<xs:schema
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<xs:element name="root" />
|
||||
</xs:schema>""")
|
||||
self.assertEqual(schema.resolve_qname('xs:element'), XSD_ELEMENT)
|
||||
self.assertEqual(schema.resolve_qname('xsi:type'), XSI_TYPE)
|
||||
|
||||
self.assertEqual(schema.resolve_qname(XSI_TYPE), XSI_TYPE)
|
||||
self.assertEqual(schema.resolve_qname('element'), 'element')
|
||||
self.assertRaises(ValueError, schema.resolve_qname, '')
|
||||
self.assertRaises(ValueError, schema.resolve_qname, 'xsi:a type ')
|
||||
self.assertRaises(ValueError, schema.resolve_qname, 'xml::lang')
|
||||
|
||||
def test_simple_types(self):
|
||||
# Issue #54: set list or union schema element.
|
||||
xs = self.check_schema("""
|
||||
|
@ -94,16 +117,13 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
<import namespace="http://missing.example.test/" />
|
||||
<import/>
|
||||
""")
|
||||
self.assertEqual(len(context), 4, "Wrong number of include/import warnings")
|
||||
self.assertEqual(len(context), 3, "Wrong number of include/import warnings")
|
||||
self.assertEqual(context[0].category, XMLSchemaIncludeWarning)
|
||||
self.assertEqual(context[1].category, XMLSchemaIncludeWarning)
|
||||
self.assertEqual(context[2].category, XMLSchemaImportWarning)
|
||||
self.assertEqual(context[3].category, XMLSchemaImportWarning)
|
||||
self.assertTrue(str(context[0].message).startswith("Include"))
|
||||
self.assertTrue(str(context[1].message).startswith("Redefine"))
|
||||
self.assertTrue(str(context[2].message).startswith("Namespace import"))
|
||||
self.assertTrue(str(context[3].message).startswith("Namespace import"))
|
||||
self.assertTrue(str(context[3].message).endswith("no schema location provided."))
|
||||
|
||||
def test_wrong_references(self):
|
||||
# Wrong namespace for element type's reference
|
||||
|
@ -167,7 +187,6 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
</simpleType>
|
||||
""")
|
||||
|
||||
@unittest.skip("The feature is still under development")
|
||||
def test_element_restrictions(self):
|
||||
base = """
|
||||
<sequence>
|
||||
|
@ -221,7 +240,6 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
</sequence>
|
||||
""", expected=XMLSchemaParseError)
|
||||
|
||||
@unittest.skip("The feature is still under development")
|
||||
def test_sequence_group_restriction(self):
|
||||
# Meaningless sequence group
|
||||
base = """
|
||||
|
@ -263,7 +281,6 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
XMLSchemaParseError
|
||||
)
|
||||
|
||||
@unittest.skip("The feature is still under development")
|
||||
def test_all_group_restriction(self):
|
||||
base = """
|
||||
<all>
|
||||
|
@ -274,22 +291,29 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
"""
|
||||
self.check_complex_restriction(base, '<all><element name="A"/><element name="C"/></all>')
|
||||
self.check_complex_restriction(
|
||||
base, '<all><element name="C" minOccurs="0"/><element name="A"/></all>',
|
||||
XMLSchemaParseError
|
||||
base, '<all><element name="C" minOccurs="0"/><element name="A"/></all>', XMLSchemaParseError
|
||||
)
|
||||
self.check_complex_restriction(
|
||||
base, '<sequence><element name="A"/><element name="C"/></sequence>'
|
||||
)
|
||||
self.check_complex_restriction(
|
||||
base, '<sequence><element name="C" minOccurs="0"/><element name="A"/></sequence>',
|
||||
)
|
||||
self.check_complex_restriction(
|
||||
base, '<sequence><element name="C" minOccurs="0"/><element name="A" minOccurs="0"/></sequence>',
|
||||
XMLSchemaParseError
|
||||
)
|
||||
self.check_complex_restriction(
|
||||
base, '<sequence><element name="A"/><element name="X"/></sequence>',
|
||||
XMLSchemaParseError
|
||||
base, '<sequence><element name="A"/><element name="X"/></sequence>', XMLSchemaParseError
|
||||
)
|
||||
|
||||
@unittest.skip("The feature is still under development")
|
||||
base = """
|
||||
<all>
|
||||
<element name="A" minOccurs="0" maxOccurs="0"/>
|
||||
</all>
|
||||
"""
|
||||
self.check_complex_restriction(base, '<all><element name="A"/></all>', XMLSchemaParseError)
|
||||
|
||||
def test_choice_group_restriction(self):
|
||||
base = """
|
||||
<choice maxOccurs="2">
|
||||
|
@ -308,7 +332,6 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
base, '<choice maxOccurs="2"><element name="A"/><element name="C"/></choice>',
|
||||
)
|
||||
|
||||
@unittest.skip("The feature is still under development")
|
||||
def test_occurs_restriction(self):
|
||||
base = """
|
||||
<sequence minOccurs="3" maxOccurs="10">
|
||||
|
@ -330,7 +353,7 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
|
||||
def test_union_restrictions(self):
|
||||
# Wrong union restriction (not admitted facets, see issue #67)
|
||||
self.check_schema("""
|
||||
self.check_schema(r"""
|
||||
<simpleType name="Percentage">
|
||||
<restriction base="ns:Integer">
|
||||
<minInclusive value="0"/>
|
||||
|
@ -388,7 +411,7 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
</restriction>
|
||||
</simpleType>""")
|
||||
|
||||
schema = self.check_schema("""
|
||||
self.check_schema("""
|
||||
<simpleType name="restricted_year">
|
||||
<restriction base="gYear">
|
||||
<minInclusive value="1900"/>
|
||||
|
@ -396,6 +419,45 @@ class TestXMLSchema10(XMLSchemaTestCase):
|
|||
</restriction>
|
||||
</simpleType>""")
|
||||
|
||||
def test_base_schemas(self):
|
||||
from xmlschema.validators.schema import XML_SCHEMA_FILE
|
||||
schema = self.schema_class(XML_SCHEMA_FILE)
|
||||
|
||||
def test_recursive_complex_type(self):
|
||||
schema = self.schema_class("""
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="elemA" type="typeA"/>
|
||||
<xs:complexType name="typeA">
|
||||
<xs:sequence>
|
||||
<xs:element ref="elemA" minOccurs="0" maxOccurs="5"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>""")
|
||||
self.assertEqual(schema.elements['elemA'].type, schema.types['typeA'])
|
||||
|
||||
def test_upa_violations(self):
|
||||
self.check_schema("""
|
||||
<complexType name="typeA">
|
||||
<sequence>
|
||||
<sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<element name="A"/>
|
||||
<element name="B"/>
|
||||
</sequence>
|
||||
<element name="A" minOccurs="0"/>
|
||||
</sequence>
|
||||
</complexType>""", XMLSchemaModelError)
|
||||
|
||||
self.check_schema("""
|
||||
<complexType name="typeA">
|
||||
<sequence>
|
||||
<sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<element name="B"/>
|
||||
<element name="A"/>
|
||||
</sequence>
|
||||
<element name="A" minOccurs="0"/>
|
||||
</sequence>
|
||||
</complexType>""")
|
||||
|
||||
|
||||
class TestXMLSchema11(TestXMLSchema10):
|
||||
|
||||
|
@ -522,10 +584,10 @@ def make_schema_test_class(test_file, test_args, test_num, schema_class, check_w
|
|||
xs = schema_class(xsd_file, validation='lax', locations=locations, defuse=defuse)
|
||||
else:
|
||||
xs = schema_class(xsd_file, locations=locations, defuse=defuse)
|
||||
self.errors.extend(xs.all_errors)
|
||||
self.errors.extend(xs.maps.all_errors)
|
||||
|
||||
if inspect:
|
||||
components_ids = set([id(c) for c in xs.iter_components()])
|
||||
components_ids = set([id(c) for c in xs.maps.iter_components()])
|
||||
missing = [c for c in SchemaObserver.components if id(c) not in components_ids]
|
||||
if any([c for c in missing]):
|
||||
raise ValueError("schema missing %d components: %r" % (len(missing), missing))
|
||||
|
@ -589,7 +651,8 @@ def make_schema_test_class(test_file, test_args, test_num, schema_class, check_w
|
|||
with warnings.catch_warnings(record=True) as ctx:
|
||||
warnings.simplefilter("always")
|
||||
self.check_schema()
|
||||
self.assertEqual(len(ctx), expected_warnings, "Wrong number of include/import warnings")
|
||||
self.assertEqual(len(ctx), expected_warnings,
|
||||
"%r: Wrong number of include/import warnings" % xsd_file)
|
||||
else:
|
||||
self.check_schema()
|
||||
|
||||
|
|
|
@ -906,6 +906,12 @@ class TestDecoding(XMLSchemaTestCase):
|
|||
self.assertFalse(xs.is_valid('<value>alpha</value>'))
|
||||
self.assertEqual(xs.decode('<value>10</value>'), 10)
|
||||
|
||||
def test_union_types(self):
|
||||
# For testing issue #103
|
||||
decimal_or_nan = self.st_schema.types['myType']
|
||||
self.check_decode(decimal_or_nan, '95.0', Decimal('95.0'))
|
||||
self.check_decode(decimal_or_nan, 'NaN', u'NaN')
|
||||
|
||||
|
||||
class TestDecoding11(TestDecoding):
|
||||
schema_class = XMLSchema11
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2016-2019, SISSA (International School for Advanced Studies).
|
||||
# All rights reserved.
|
||||
# This file is distributed under the terms of the MIT License.
|
||||
# See the file 'LICENSE' in the root directory of the present
|
||||
# distribution, or http://opensource.org/licenses/MIT.
|
||||
#
|
||||
# @author Davide Brunato <brunato@sissa.it>
|
||||
#
|
||||
"""
|
||||
This module runs tests concerning the W3C XML Schema 1.1 test suite.
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
import unittest
|
||||
import os.path
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
import xmlschema
|
||||
from xmlschema import XMLSchemaException
|
||||
|
||||
TEST_SUITE_NAMESPACE = "http://www.w3.org/XML/2004/xml-schema-test-suite/"
|
||||
XLINK_NAMESPACE = "http://www.w3.org/1999/xlink"
|
||||
ADMITTED_VALIDITY = {'valid', 'invalid', 'indeterminate'}
|
||||
|
||||
####
|
||||
# Tests that are incompatible with XSD meta-schema validation or that are postponed
|
||||
SKIPPED_TESTS = {
|
||||
# Signed as valid that have to be checked
|
||||
'../msData/additional/addB194.xsd', # 4826: invalid xml:lang='enu'
|
||||
'../msData/particles/particlesZ001.xsd', # 10957: Invalid in XSD 1.0
|
||||
'../msData/simpleType/stE110.xsd', # 13892: Circular xs:union declaration
|
||||
'../saxonData/Missing/missing001.xsd', # 14405: missing type (this may be valid in 'lax' mode?)
|
||||
'../saxonData/Missing/missing002.xsd', # 14406: missing substitution group
|
||||
'../saxonData/Missing/missing003.xsd', # 14406: missing type and substitution group
|
||||
'../saxonData/Missing/missing006.xsd', # 14410: missing list item type
|
||||
'../saxonData/VC/vc001.xsd', # 14411: VC namespace required
|
||||
'../saxonData/VC/vc002.xsd', # 14412: VC namespace required
|
||||
'../saxonData/VC/vc014.xsd', # 14413: VC namespace required
|
||||
'../saxonData/VC/vc024.xsd', # 14414: VC 1.1? required
|
||||
'../saxonData/XmlVersions/xv004.xsd', # 14419: non-BMP chars allowed in names in XML 1.1+
|
||||
|
||||
# Invalid that may be valid
|
||||
'../sunData/combined/xsd003b/xsd003b.e.xsd', # 3981: Redefinition that may be valid
|
||||
'../msData/additional/adhocAddC002.xsd', # 4642: Lack of the processor on XML namespace knowledge
|
||||
'../msData/additional/test65026.xsd', # 4712: Lack of the processor on XML namespace knowledge
|
||||
'../msData/annotations/annotF001.xsd', # 4989: Annotation contains xml:lang="" ?? (but xml.xsd allows '')
|
||||
'../msData/datatypes/Facets/base64Binary/base64Binary_enumeration003.xsd', # 7277: check base64 invalid values
|
||||
'../msData/datatypes/Facets/anyURI/anyURI_a001.xsd', # 7292: XSD 1.0 limited URI (see RFC 2396 + RFC 2732)
|
||||
'../msData/datatypes/Facets/anyURI/anyURI_a003.xsd', # 7294: XSD 1.0 limited URI (see RFC 2396 + RFC 2732)
|
||||
'../msData/datatypes/Facets/anyURI/anyURI_b004.xsd', # 7310: XSD 1.0 limited URI (see RFC 2396 + RFC 2732)
|
||||
'../msData/datatypes/Facets/anyURI/anyURI_b006.xsd', # 7312: XSD 1.0 limited URI (see RFC 2396 + RFC 2732)
|
||||
'../msData/element/elemZ026.xsd', # 8541: This is good because the head element is abstract
|
||||
'../msData/element/elemZ031.xsd', # 8557: Valid in Python that has arbitrary large integers
|
||||
'../msData/errata10/errC005.xsd', # 8558: Typo: abstract attribute must be set to "true" to fail
|
||||
'../msData/group/groupH021.xsd', # 8679: TODO: wrong in XSD 1.0, good in XSD 1.1
|
||||
'../msData/identityConstraint/idC019.xsd', # 8936: TODO: is it an error?
|
||||
'../msData/identityConstraint/idI148.xsd', # 9291: FIXME attribute::* in a selector (restrict XPath parser)
|
||||
'../msData/identityConstraint/idJ016.xsd', # 9311: FIXME xpath="xpns: *" not allowed??
|
||||
'../msData/modelGroups/mgE006.xsd', # 9712: Is valid (is mg007.xsd invalid for the same reason)
|
||||
|
||||
# Invalid that are valid because depend by implementation choices
|
||||
'../msData/schema/schG6_a.xsd', # 13639: Schema is valid because the ns import is done once, validation fails.
|
||||
'../msData/schema/schG11_a.xsd', # 13544: Schema is valid because the ns import is done once, validation fails.
|
||||
}
|
||||
|
||||
|
||||
def fetch_xsd_test_suite():
|
||||
parent = os.path.dirname
|
||||
xmlschema_test_dir = parent(os.path.abspath(__file__))
|
||||
xmlschema_base_dir = parent(parent(xmlschema_test_dir))
|
||||
|
||||
suite_file = os.path.join(parent(xmlschema_base_dir), 'xsdtests/suite.xml')
|
||||
if os.path.isfile(suite_file):
|
||||
return suite_file
|
||||
else:
|
||||
raise FileNotFoundError("can't find the XSD suite index file suite.xml ...")
|
||||
|
||||
|
||||
def create_w3c_test_group_case(filename, group_elem, group_number, xsd_version='1.0'):
|
||||
"""
|
||||
Creates a test class for a W3C test group.
|
||||
|
||||
:param filename: the filename of the testSet that owns the testGroup.
|
||||
:param group_elem: the Element instance of the test group.
|
||||
:param group_number: a positive integer to distinguish and order test groups.
|
||||
:param xsd_version: if '1.1' uses XSD 1.1 validator class, otherwise uses the XSD 1.0 validator.
|
||||
"""
|
||||
name = group_elem.attrib['name']
|
||||
|
||||
if xsd_version == '1.1':
|
||||
schema_class = xmlschema.validators.XMLSchema11
|
||||
if group_elem.get('version') == '1.0':
|
||||
raise ValueError("testGroup %r is not suited for XSD 1.1" % name)
|
||||
elif group_elem.get('version') == '1.1':
|
||||
pass # raise ValueError("testGroup %r is not suited for XSD 1.0" % name)
|
||||
else:
|
||||
schema_class = xmlschema.XMLSchema
|
||||
|
||||
schema_elem = group_elem.find('{%s}schemaTest' % TEST_SUITE_NAMESPACE)
|
||||
if schema_elem is not None:
|
||||
schema_document = schema_elem.find('{%s}schemaDocument' % TEST_SUITE_NAMESPACE)
|
||||
schema_path = schema_document.get('{%s}href' % XLINK_NAMESPACE)
|
||||
if schema_path in SKIPPED_TESTS:
|
||||
return
|
||||
|
||||
schema_path = os.path.normpath(os.path.join(os.path.dirname(filename), schema_path))
|
||||
|
||||
if not os.path.isfile(schema_path):
|
||||
raise ValueError("Schema file %r not found!" % schema_path)
|
||||
|
||||
expected = elem = None
|
||||
for elem in schema_elem.findall('{%s}expected' % TEST_SUITE_NAMESPACE):
|
||||
if 'version' not in elem.attrib:
|
||||
expected = elem.attrib['validity']
|
||||
elif elem.attrib['version'] in (xsd_version, 'full-xpath-in-CTA'):
|
||||
expected = elem.attrib['validity']
|
||||
break
|
||||
|
||||
if expected is None:
|
||||
raise ValueError("Missing expected validity for XSD %s" % xsd_version)
|
||||
elif expected not in ADMITTED_VALIDITY:
|
||||
raise ValueError("Wrong validity=%r attribute for %r" % (expected, elem))
|
||||
|
||||
else:
|
||||
schema_path = expected = None
|
||||
|
||||
if expected == 'invalid':
|
||||
class TestGroupCase(unittest.TestCase):
|
||||
def test_invalid_schema(self):
|
||||
with self.assertRaises(XMLSchemaException, msg="Schema %r may be invalid" % schema_path) as _:
|
||||
schema_class(schema_path, use_meta=False)
|
||||
|
||||
elif expected == 'valid':
|
||||
class TestGroupCase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
try:
|
||||
cls.schema = schema_class(schema_path, use_meta=False) if schema_path else None
|
||||
except TypeError:
|
||||
cls.schema = None
|
||||
|
||||
def test_valid_schema(self):
|
||||
if schema_path:
|
||||
self.assertIsInstance(schema_class(schema_path, use_meta=False), schema_class)
|
||||
else:
|
||||
return # expected is None or 'indeterminate'
|
||||
|
||||
TestGroupCase.__name__ = TestGroupCase.__qualname__ = str(
|
||||
'TestGroupCase{0:05}_{1}'.format(group_number, name.replace('-', '_'))
|
||||
)
|
||||
return TestGroupCase
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
index_path = fetch_xsd_test_suite()
|
||||
index_dir = os.path.dirname(index_path)
|
||||
|
||||
suite_xml = ElementTree.parse(index_path)
|
||||
HREF_ATTRIBUTE = "{%s}href" % XLINK_NAMESPACE
|
||||
test_classes = {}
|
||||
testgroup_num = 1
|
||||
|
||||
for testset_elem in suite_xml.iter("{%s}testSetRef" % TEST_SUITE_NAMESPACE):
|
||||
testset_file = os.path.join(index_dir, testset_elem.attrib.get(HREF_ATTRIBUTE, ''))
|
||||
|
||||
testset_xml = ElementTree.parse(testset_file)
|
||||
testset_version = testset_xml.getroot().get('version')
|
||||
if testset_version is not None and '1.0' not in testset_version:
|
||||
continue
|
||||
|
||||
# print("*** {} ***".format(testset_file))
|
||||
|
||||
for testgroup_elem in testset_xml.iter("{%s}testGroup" % TEST_SUITE_NAMESPACE):
|
||||
if testgroup_elem.get('version') == '1.1':
|
||||
continue
|
||||
|
||||
cls = create_w3c_test_group_case(testset_file, testgroup_elem, testgroup_num)
|
||||
if cls is not None:
|
||||
test_classes[cls.__name__] = cls
|
||||
testgroup_num += 1
|
||||
|
||||
globals().update(test_classes)
|
||||
|
||||
# print_test_header()
|
||||
unittest.main()
|
File diff suppressed because one or more lines are too long
|
@ -11,11 +11,11 @@
|
|||
"""
|
||||
XML Schema validators subpackage.
|
||||
"""
|
||||
from .exceptions import XMLSchemaValidatorError, XMLSchemaParseError, XMLSchemaValidationError, \
|
||||
XMLSchemaDecodeError, XMLSchemaEncodeError, XMLSchemaNotBuiltError, XMLSchemaChildrenValidationError, \
|
||||
XMLSchemaIncludeWarning, XMLSchemaImportWarning
|
||||
from .exceptions import XMLSchemaValidatorError, XMLSchemaParseError, XMLSchemaModelError, \
|
||||
XMLSchemaModelDepthError, XMLSchemaValidationError, XMLSchemaDecodeError, XMLSchemaEncodeError, \
|
||||
XMLSchemaNotBuiltError, XMLSchemaChildrenValidationError, XMLSchemaIncludeWarning, XMLSchemaImportWarning
|
||||
|
||||
from .xsdbase import XsdValidator, XsdComponent, XsdAnnotation, XsdType, ParticleMixin, ValidationMixin
|
||||
from .xsdbase import XsdValidator, XsdComponent, XsdAnnotation, XsdType, ValidationMixin, ParticleMixin
|
||||
|
||||
from .assertions import XsdAssert
|
||||
from .notations import XsdNotation
|
||||
|
@ -26,7 +26,8 @@ from .attributes import XsdAttribute, Xsd11Attribute, XsdAttributeGroup
|
|||
from .simple_types import xsd_simple_type_factory, XsdSimpleType, XsdAtomic, XsdAtomicBuiltin, \
|
||||
XsdAtomicRestriction, Xsd11AtomicRestriction, XsdList, XsdUnion
|
||||
from .complex_types import XsdComplexType, Xsd11ComplexType
|
||||
from .groups import XsdModelVisitor, XsdGroup, Xsd11Group
|
||||
from .models import ModelGroup, ModelVisitor
|
||||
from .groups import XsdGroup, Xsd11Group
|
||||
from .elements import XsdElement, Xsd11Element
|
||||
|
||||
from .globals_ import XsdGlobals
|
||||
|
|
|
@ -30,7 +30,7 @@ class XsdAssert(XsdComponent, ElementPathMixin):
|
|||
Content: (annotation?)
|
||||
</assert>
|
||||
"""
|
||||
admitted_tags = {XSD_ASSERT}
|
||||
_admitted_tags = {XSD_ASSERT}
|
||||
token = None
|
||||
|
||||
def __init__(self, elem, schema, parent, base_type):
|
||||
|
|
|
@ -15,11 +15,11 @@ from __future__ import unicode_literals
|
|||
from decimal import Decimal
|
||||
from elementpath.datatypes import AbstractDateTime, Duration
|
||||
|
||||
from ..compat import MutableMapping
|
||||
from ..exceptions import XMLSchemaAttributeError, XMLSchemaValueError
|
||||
from ..compat import MutableMapping, ordered_dict_class
|
||||
from ..exceptions import XMLSchemaAttributeError, XMLSchemaTypeError, XMLSchemaValueError
|
||||
from ..qnames import 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, local_name, prefixed_to_qname
|
||||
from ..helpers import get_namespace, get_qname, get_xsd_form_attribute
|
||||
from ..namespaces import XSI_NAMESPACE
|
||||
|
||||
from .exceptions import XMLSchemaValidationError
|
||||
|
@ -45,7 +45,8 @@ class XsdAttribute(XsdComponent, ValidationMixin):
|
|||
Content: (annotation?, simpleType?)
|
||||
</attribute>
|
||||
"""
|
||||
admitted_tags = {XSD_ATTRIBUTE}
|
||||
_admitted_tags = {XSD_ATTRIBUTE}
|
||||
qualified = False
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None, xsd_type=None):
|
||||
if xsd_type is not None:
|
||||
|
@ -63,50 +64,91 @@ class XsdAttribute(XsdComponent, ValidationMixin):
|
|||
|
||||
def __setattr__(self, name, value):
|
||||
if name == "type":
|
||||
assert isinstance(value, XsdSimpleType), "An XSD attribute's type must be a simpleType."
|
||||
if not isinstance(value, XsdSimpleType):
|
||||
raise XMLSchemaTypeError("An XSD attribute's type must be a simpleType.")
|
||||
super(XsdAttribute, self).__setattr__(name, value)
|
||||
|
||||
def _parse(self):
|
||||
super(XsdAttribute, self)._parse()
|
||||
elem = self.elem
|
||||
self.qualified = elem.attrib.get('form', self.schema.attribute_form_default) == 'qualified'
|
||||
|
||||
if 'default' in elem.attrib and 'fixed' in elem.attrib:
|
||||
self.parse_error("'default' and 'fixed' attributes are mutually exclusive")
|
||||
self._parse_properties('form', 'use')
|
||||
|
||||
try:
|
||||
if self.is_global or self.qualified:
|
||||
self.name = get_qname(self.target_namespace, elem.attrib['name'])
|
||||
form = self.form
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
else:
|
||||
if form is None:
|
||||
self.qualified = self.schema.attribute_form_default == 'qualified'
|
||||
elif self.parent is None:
|
||||
self.parse_error("attribute 'form' not allowed in a global attribute.")
|
||||
else:
|
||||
self.name = elem.attrib['name']
|
||||
except KeyError:
|
||||
# No 'name' attribute, must be a reference
|
||||
self.qualified = form == 'qualified'
|
||||
|
||||
self.use = elem.get('use')
|
||||
if self.use is None:
|
||||
self.use = 'optional'
|
||||
elif self.parent is None:
|
||||
self.parse_error("attribute 'use' not allowed in a global attribute.")
|
||||
elif self.use not in {'optional', 'prohibited', 'required'}:
|
||||
self.parse_error("wrong value %r for 'use' attribute." % self.use)
|
||||
self.use = 'optional'
|
||||
|
||||
name = elem.get('name')
|
||||
if name is not None:
|
||||
if 'ref' in elem.attrib:
|
||||
self.parse_error("both 'name' and 'ref' in attribute declaration")
|
||||
elif name == 'xmlns':
|
||||
self.parse_error("an attribute name must be different from 'xmlns'")
|
||||
|
||||
if self.parent is None or self.qualified:
|
||||
if self.target_namespace == XSI_NAMESPACE and \
|
||||
name not in {'nil', 'type', 'schemaLocation', 'noNamespaceSchemaLocation'}:
|
||||
self.parse_error("Cannot add attributes in %r namespace" % XSI_NAMESPACE)
|
||||
self.name = get_qname(self.target_namespace, name)
|
||||
else:
|
||||
self.name = name
|
||||
elif self.parent is None:
|
||||
self.parse_error("missing 'name' in global attribute declaration")
|
||||
else:
|
||||
try:
|
||||
attribute_name = prefixed_to_qname(elem.attrib['ref'], self.namespaces)
|
||||
attribute_qname = self.schema.resolve_qname(elem.attrib['ref'])
|
||||
except KeyError:
|
||||
# Missing also the 'ref' attribute
|
||||
self.parse_error("missing both 'name' and 'ref' in attribute declaration")
|
||||
self.xsd_type = self.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
return
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
self.xsd_type = self.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
return
|
||||
else:
|
||||
try:
|
||||
xsd_attribute = self.maps.lookup_attribute(attribute_name)
|
||||
xsd_attribute = self.maps.lookup_attribute(attribute_qname)
|
||||
except LookupError:
|
||||
self.parse_error("unknown attribute %r" % elem.attrib['ref'])
|
||||
self.type = self.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
else:
|
||||
self.type = xsd_attribute.type
|
||||
self.qualified = xsd_attribute.qualified
|
||||
if xsd_attribute.fixed is not None and 'fixed' in elem.attrib and \
|
||||
elem.get('fixed') != xsd_attribute.fixed:
|
||||
self.parse_error("referenced attribute has a different fixed value %r" % xsd_attribute.fixed)
|
||||
|
||||
self.name = attribute_name
|
||||
self.name = attribute_qname
|
||||
for attribute in ('form', 'type'):
|
||||
if attribute in self.elem.attrib:
|
||||
self.parse_error("attribute %r is not allowed when attribute reference is used." % attribute)
|
||||
xsd_declaration = self._parse_component(elem, required=False)
|
||||
|
||||
if xsd_declaration is not None and xsd_declaration.tag == XSD_SIMPLE_TYPE:
|
||||
self.parse_error("not allowed type declaration for XSD attribute reference")
|
||||
return
|
||||
|
||||
xsd_declaration = self._parse_component(elem, required=False)
|
||||
try:
|
||||
type_qname = prefixed_to_qname(elem.attrib['type'], self.namespaces)
|
||||
type_qname = self.schema.resolve_qname(elem.attrib['type'])
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
xsd_type = self.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
except KeyError:
|
||||
if xsd_declaration is not None:
|
||||
# No 'type' attribute in declaration, parse for child local simpleType
|
||||
|
@ -117,19 +159,41 @@ class XsdAttribute(XsdComponent, ValidationMixin):
|
|||
else:
|
||||
try:
|
||||
xsd_type = self.maps.lookup_type(type_qname)
|
||||
except LookupError:
|
||||
self.parse_error("unknown type %r." % elem.attrib['type'], elem)
|
||||
except LookupError as err:
|
||||
self.parse_error(err, elem)
|
||||
xsd_type = self.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
|
||||
if xsd_declaration is not None and xsd_declaration.tag == XSD_SIMPLE_TYPE:
|
||||
self.parse_error("ambiguous type declaration for XSD attribute")
|
||||
elif xsd_declaration:
|
||||
self.parse_error("not allowed element in XSD attribute declaration: %r" % xsd_declaration[0])
|
||||
self.type = xsd_type
|
||||
|
||||
try:
|
||||
self.type = xsd_type
|
||||
except TypeError as err:
|
||||
self.parse_error(err)
|
||||
|
||||
# Check value constraints
|
||||
if 'default' in elem.attrib:
|
||||
if 'fixed' in elem.attrib:
|
||||
self.parse_error("'default' and 'fixed' attributes are mutually exclusive")
|
||||
if self.use != 'optional':
|
||||
self.parse_error("the attribute 'use' must be 'optional' if the attribute 'default' is present")
|
||||
if not self.type.is_valid(elem.attrib['default']):
|
||||
msg = "'default' value {!r} is not compatible with the type {!r}"
|
||||
self.parse_error(msg.format(elem.attrib['default'], self.type))
|
||||
elif self.type.is_key():
|
||||
self.parse_error("'xs:ID' or a type derived from 'xs:ID' cannot has a 'default'")
|
||||
elif 'fixed' in elem.attrib:
|
||||
if not self.type.is_valid(elem.attrib['fixed']):
|
||||
msg = "'fixed' value {!r} is not compatible with the type {!r}"
|
||||
self.parse_error(msg.format(elem.attrib['fixed'], self.type))
|
||||
elif self.type.is_key():
|
||||
self.parse_error("'xs:ID' or a type derived from 'xs:ID' cannot has a 'default'")
|
||||
|
||||
@property
|
||||
def built(self):
|
||||
return self.type.is_global or self.type.built
|
||||
return self.type.parent is None or self.type.built
|
||||
|
||||
@property
|
||||
def validation_attempted(self):
|
||||
|
@ -153,17 +217,7 @@ class XsdAttribute(XsdComponent, ValidationMixin):
|
|||
|
||||
@property
|
||||
def form(self):
|
||||
value = self.elem.get('form')
|
||||
if value not in {None, 'qualified', 'unqualified'}:
|
||||
raise XMLSchemaValueError("wrong value %r for 'form' attribute." % value)
|
||||
return value
|
||||
|
||||
@property
|
||||
def use(self):
|
||||
value = self.elem.get('use', 'optional')
|
||||
if value not in {'optional', 'prohibited', 'required'}:
|
||||
raise XMLSchemaValueError("wrong value %r for 'use' attribute." % value)
|
||||
return value
|
||||
return get_xsd_form_attribute(self.elem, 'form')
|
||||
|
||||
def is_optional(self):
|
||||
return self.use == 'optional'
|
||||
|
@ -171,12 +225,12 @@ class XsdAttribute(XsdComponent, ValidationMixin):
|
|||
def iter_components(self, xsd_classes=None):
|
||||
if xsd_classes is None or isinstance(self, xsd_classes):
|
||||
yield self
|
||||
if self.ref is None and not self.type.is_global:
|
||||
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_decode(self, text, validation='lax', **kwargs):
|
||||
if not text and kwargs.get('use_defaults', True):
|
||||
if not text and kwargs.get('use_defaults', True) and self.default is not None:
|
||||
text = self.default
|
||||
if self.fixed is not None and text != self.fixed and validation != 'skip':
|
||||
yield self.validation_error(validation, "value differs from fixed value", text, **kwargs)
|
||||
|
@ -252,14 +306,15 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
Content: (annotation?, ((attribute | attributeGroup)*, anyAttribute?))
|
||||
</attributeGroup>
|
||||
"""
|
||||
admitted_tags = {
|
||||
redefine = None
|
||||
_admitted_tags = {
|
||||
XSD_ATTRIBUTE_GROUP, XSD_COMPLEX_TYPE, XSD_RESTRICTION, XSD_EXTENSION,
|
||||
XSD_SEQUENCE, XSD_ALL, XSD_CHOICE, XSD_ATTRIBUTE, XSD_ANY_ATTRIBUTE
|
||||
}
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None, derivation=None, base_attributes=None):
|
||||
self.derivation = derivation
|
||||
self._attribute_group = dict()
|
||||
self._attribute_group = ordered_dict_class()
|
||||
self.base_attributes = base_attributes
|
||||
XsdComponent.__init__(self, elem, schema, parent, name)
|
||||
|
||||
|
@ -274,20 +329,9 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
else:
|
||||
return '%s()' % self.__class__.__name__
|
||||
|
||||
# Implements the abstract methods of MutableMapping
|
||||
# Implementation of abstract methods
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self._attribute_group[key]
|
||||
except KeyError:
|
||||
if key is None or key[:1] != '{' or get_namespace(key) != self.target_namespace:
|
||||
raise
|
||||
else:
|
||||
# Unqualified form lookup if key is in targetNamespace
|
||||
try:
|
||||
return self._attribute_group[local_name(key)]
|
||||
except KeyError:
|
||||
pass
|
||||
raise
|
||||
return self._attribute_group[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key is None:
|
||||
|
@ -305,12 +349,9 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
raise XMLSchemaValueError("%r name and key %r mismatch." % (value.name, key))
|
||||
|
||||
self._attribute_group[key] = value
|
||||
if value.use == 'required':
|
||||
self.required.add(key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._attribute_group[key]
|
||||
self.required.discard(key)
|
||||
|
||||
def __iter__(self):
|
||||
if None in self._attribute_group:
|
||||
|
@ -332,17 +373,12 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
assert isinstance(value, XsdAnyAttribute), 'An XsdAnyAttribute instance is required.'
|
||||
else:
|
||||
assert isinstance(value, XsdAttribute), 'An XsdAttribute instance is required.'
|
||||
self.required = {
|
||||
k for k, v in self.items() if k is not None and v.use == 'required'
|
||||
}
|
||||
|
||||
def _parse(self):
|
||||
super(XsdAttributeGroup, self)._parse()
|
||||
elem = self.elem
|
||||
any_attribute = False
|
||||
self.clear()
|
||||
if self.base_attributes is not None:
|
||||
self._attribute_group.update(self.base_attributes.items())
|
||||
attribute_group_refs = []
|
||||
|
||||
if elem.tag == XSD_ATTRIBUTE_GROUP:
|
||||
if self.parent is not None:
|
||||
|
@ -353,6 +389,7 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
self.parse_error("an attribute group declaration requires a 'name' attribute.")
|
||||
return
|
||||
|
||||
attributes = ordered_dict_class()
|
||||
for child in self._iterparse_components(elem):
|
||||
if any_attribute:
|
||||
if child.tag == XSD_ANY_ATTRIBUTE:
|
||||
|
@ -362,30 +399,131 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
|
||||
elif child.tag == XSD_ANY_ATTRIBUTE:
|
||||
any_attribute = True
|
||||
self.update([(None, XsdAnyAttribute(child, self.schema, self))])
|
||||
attributes.update([(None, XsdAnyAttribute(child, self.schema, self))])
|
||||
|
||||
elif child.tag == XSD_ATTRIBUTE:
|
||||
attribute = self.schema.BUILDERS.attribute_class(child, self.schema, self)
|
||||
self[attribute.name] = attribute
|
||||
if attribute.name in attributes:
|
||||
self.parse_error("multiple declaration for attribute {!r}".format(attribute.name))
|
||||
else:
|
||||
attributes[attribute.name] = attribute
|
||||
|
||||
elif child.tag == XSD_ATTRIBUTE_GROUP:
|
||||
try:
|
||||
name = child.attrib['ref']
|
||||
except KeyError as err:
|
||||
self.parse_error(str(err), elem)
|
||||
ref = child.attrib['ref']
|
||||
attribute_group_qname = self.schema.resolve_qname(ref)
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
except KeyError:
|
||||
self.parse_error("the attribute 'ref' is required in a local attributeGroup", elem)
|
||||
else:
|
||||
qname = prefixed_to_qname(name, self.namespaces)
|
||||
if attribute_group_qname in attribute_group_refs:
|
||||
self.parse_error("duplicated attributeGroup %r" % ref)
|
||||
elif self.redefine is not None:
|
||||
if attribute_group_qname == self.name:
|
||||
if attribute_group_refs:
|
||||
self.parse_error("in a redefinition the reference to itself must be the first")
|
||||
attribute_group_refs.append(attribute_group_qname)
|
||||
attributes.update(self._attribute_group.items())
|
||||
continue
|
||||
elif not attribute_group_refs:
|
||||
# May be an attributeGroup restriction with a ref to another group
|
||||
if not any(e.tag == XSD_ATTRIBUTE_GROUP and ref == e.get('ref')
|
||||
for e in self.redefine.elem):
|
||||
self.parse_error("attributeGroup ref=%r is not in the redefined group" % ref)
|
||||
elif attribute_group_qname == self.name and self.schema.XSD_VERSION == '1.0':
|
||||
self.parse_error("Circular attribute groups not allowed in XSD 1.0")
|
||||
attribute_group_refs.append(attribute_group_qname)
|
||||
|
||||
try:
|
||||
attribute_group = self.maps.lookup_attribute_group(qname)
|
||||
base_attributes = self.maps.lookup_attribute_group(attribute_group_qname)
|
||||
except LookupError:
|
||||
self.parse_error("unknown attribute group %r" % name, elem)
|
||||
self.parse_error("unknown attribute group %r" % child.attrib['ref'], elem)
|
||||
else:
|
||||
self.update(attribute_group.items())
|
||||
if isinstance(base_attributes, tuple):
|
||||
self.parse_error("Circular reference found between attribute groups "
|
||||
"{!r} and {!r}".format(self.name, attribute_group_qname))
|
||||
|
||||
for name, attr in base_attributes.items():
|
||||
if name is not None and name in attributes:
|
||||
self.parse_error("multiple declaration for attribute {!r}".format(name))
|
||||
else:
|
||||
attributes[name] = attr
|
||||
|
||||
elif self.name is not None:
|
||||
self.parse_error("(attribute | attributeGroup) expected, found %r." % child)
|
||||
|
||||
if self.parent is None and getattr(self.schema, 'default_attributes', None) == self.name:
|
||||
# Check and copy base attributes
|
||||
if self.base_attributes is not None:
|
||||
wildcard = self.base_attributes.get(None)
|
||||
for name, attr in attributes.items():
|
||||
if name not in self.base_attributes:
|
||||
if self.derivation != 'restriction':
|
||||
continue
|
||||
elif wildcard is None or not wildcard.is_matching(name, self.default_namespace):
|
||||
self.parse_error("Unexpected attribute %r in restriction" % name)
|
||||
continue
|
||||
|
||||
base_attr = self.base_attributes[name]
|
||||
|
||||
if name is None:
|
||||
if self.derivation == 'extension':
|
||||
try:
|
||||
attr.extend_namespace(base_attr)
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
elif not attr.is_restriction(base_attr):
|
||||
self.parse_error("Attribute wildcard is not a restriction of the base wildcard")
|
||||
continue
|
||||
if self.derivation == 'restriction' and attr.type.name != XSD_ANY_SIMPLE_TYPE and \
|
||||
not attr.type.is_derived(base_attr.type, 'restriction'):
|
||||
self.parse_error("Attribute type is not a restriction of the base attribute type")
|
||||
if base_attr.use != 'optional' and attr.use == 'optional' or \
|
||||
base_attr.use == 'required' and attr.use != 'required':
|
||||
self.parse_error("Attribute %r: unmatched attribute use in restriction" % name)
|
||||
if base_attr.fixed is not None and \
|
||||
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())
|
||||
elif self.redefine is not None and not attribute_group_refs:
|
||||
for name, attr in self._attribute_group.items():
|
||||
if name is None:
|
||||
continue
|
||||
elif name not in attributes:
|
||||
if attr.use == 'required':
|
||||
self.parse_error("Missing required attribute %r in redefinition restriction" % name)
|
||||
continue
|
||||
if attr.use != 'optional' and attributes[name].use != attr.use:
|
||||
self.parse_error("Attribute %r: unmatched attribute use in redefinition" % name)
|
||||
if attr.fixed is not None and attributes[name].fixed is None:
|
||||
self.parse_error("Attribute %r: redefinition remove fixed constraint" % name)
|
||||
|
||||
pos = 0
|
||||
keys = list(self._attribute_group.keys())
|
||||
for name in attributes:
|
||||
try:
|
||||
next_pos = keys.index(name)
|
||||
except ValueError:
|
||||
self.parse_error("Redefinition restriction contains additional attribute %r" % name)
|
||||
else:
|
||||
if next_pos < pos:
|
||||
self.parse_error("Wrong attribute order in redefinition restriction")
|
||||
break
|
||||
pos = next_pos
|
||||
self.clear()
|
||||
|
||||
self._attribute_group.update(attributes)
|
||||
|
||||
if self.schema.XSD_VERSION == '1.0':
|
||||
has_key = False
|
||||
for attr in self._attribute_group.values():
|
||||
if attr.name is not None and attr.type.is_key():
|
||||
if has_key:
|
||||
self.parse_error("multiple key attributes in a group not allowed in XSD 1.0")
|
||||
has_key = True
|
||||
|
||||
elif self.parent is None and self.schema.default_attributes == self.name:
|
||||
self.schema.default_attributes = self
|
||||
|
||||
@property
|
||||
|
@ -405,12 +543,17 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
def ref(self):
|
||||
return self.elem.get('ref')
|
||||
|
||||
def iter_required(self):
|
||||
for k, v in self._attribute_group.items():
|
||||
if k is not None and v.use == 'required':
|
||||
yield k
|
||||
|
||||
def iter_components(self, xsd_classes=None):
|
||||
if xsd_classes is None or isinstance(self, xsd_classes):
|
||||
yield self
|
||||
if self.ref is None:
|
||||
for attr in self.values():
|
||||
if not attr.is_global:
|
||||
if attr.parent is not None:
|
||||
for obj in attr.iter_components(xsd_classes):
|
||||
yield obj
|
||||
|
||||
|
@ -419,7 +562,7 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
return
|
||||
|
||||
result_list = []
|
||||
required_attributes = self.required.copy()
|
||||
required_attributes = {a for a in self.iter_required()}
|
||||
for name, value in attrs.items():
|
||||
try:
|
||||
xsd_attribute = self[name]
|
||||
|
@ -459,7 +602,7 @@ class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
|
|||
|
||||
def iter_encode(self, attrs, validation='lax', **kwargs):
|
||||
result_list = []
|
||||
required_attributes = self.required.copy()
|
||||
required_attributes = {a for a in self.iter_required()}
|
||||
try:
|
||||
attrs = attrs.items()
|
||||
except AttributeError:
|
||||
|
|
|
@ -19,6 +19,7 @@ from __future__ import unicode_literals
|
|||
import re
|
||||
import base64
|
||||
from decimal import Decimal
|
||||
from math import isinf, isnan
|
||||
|
||||
from elementpath import datatypes
|
||||
|
||||
|
@ -61,7 +62,20 @@ DATETIME_FACETS = (
|
|||
|
||||
|
||||
#
|
||||
# XSD numerical built-in types validator functions
|
||||
# XSD built-in types validator functions
|
||||
def finite_number_validator(x):
|
||||
try:
|
||||
if isinf(x) or isnan(x):
|
||||
yield XMLSchemaValidationError(finite_number_validator, x, "value {!r} is not an xs:decimal".format(x))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
|
||||
def qname_validator(x):
|
||||
if datatypes.QNAME_PATTERN.match(x) is None:
|
||||
yield XMLSchemaValidationError(qname_validator, x, "value {!r} is not an xs:QName".format(x))
|
||||
|
||||
|
||||
def byte_validator(x):
|
||||
if not (-2**7 <= x < 2**7):
|
||||
yield XMLSchemaValidationError(int_validator, x, "value must be -128 <= x < 128.")
|
||||
|
@ -123,11 +137,13 @@ def non_negative_int_validator(x):
|
|||
|
||||
|
||||
def hex_binary_validator(x):
|
||||
if len(x) % 2 or HEX_BINARY_PATTERN.match(x) is None:
|
||||
if x and (len(x) % 2 or HEX_BINARY_PATTERN.match(x) is None):
|
||||
yield XMLSchemaValidationError(hex_binary_validator, x, "not an hexadecimal number.")
|
||||
|
||||
|
||||
def base64_binary_validator(x):
|
||||
if not x:
|
||||
return
|
||||
match = NOT_BASE64_BINARY_PATTERN.search(x)
|
||||
if match is not None:
|
||||
reason = "not a base64 encoding: illegal character %r at position %d." % (match.group(0), match.span()[0])
|
||||
|
@ -156,9 +172,9 @@ def python_to_boolean(obj):
|
|||
|
||||
#
|
||||
# Element facets instances for builtin types.
|
||||
PRESERVE_WHITE_SPACE_ELEMENT = etree_element(XSD_WHITE_SPACE, attrib={'value': 'preserve'})
|
||||
COLLAPSE_WHITE_SPACE_ELEMENT = etree_element(XSD_WHITE_SPACE, attrib={'value': 'collapse'})
|
||||
REPLACE_WHITE_SPACE_ELEMENT = etree_element(XSD_WHITE_SPACE, attrib={'value': 'replace'})
|
||||
PRESERVE_WHITE_SPACE_ELEMENT = etree_element(XSD_WHITE_SPACE, value='preserve')
|
||||
COLLAPSE_WHITE_SPACE_ELEMENT = etree_element(XSD_WHITE_SPACE, value='collapse')
|
||||
REPLACE_WHITE_SPACE_ELEMENT = etree_element(XSD_WHITE_SPACE, value='replace')
|
||||
|
||||
|
||||
XSD_COMMON_BUILTIN_TYPES = (
|
||||
|
@ -179,7 +195,7 @@ XSD_COMMON_BUILTIN_TYPES = (
|
|||
'name': XSD_DECIMAL,
|
||||
'python_type': (Decimal, str, unicode_type, int, float),
|
||||
'admitted_facets': DECIMAL_FACETS,
|
||||
'facets': [COLLAPSE_WHITE_SPACE_ELEMENT],
|
||||
'facets': [finite_number_validator, COLLAPSE_WHITE_SPACE_ELEMENT],
|
||||
}, # decimal number
|
||||
{
|
||||
'name': XSD_DOUBLE,
|
||||
|
@ -236,8 +252,8 @@ XSD_COMMON_BUILTIN_TYPES = (
|
|||
'name': XSD_QNAME,
|
||||
'python_type': (unicode_type, str),
|
||||
'admitted_facets': STRING_FACETS,
|
||||
'facets': [COLLAPSE_WHITE_SPACE_ELEMENT],
|
||||
}, # prf:name (the prefix needs to be qualified with an in scope namespace)
|
||||
'facets': [COLLAPSE_WHITE_SPACE_ELEMENT, qname_validator],
|
||||
}, # prf:name (the prefix needs to be qualified with an in-scope namespace)
|
||||
{
|
||||
'name': XSD_NOTATION_TYPE,
|
||||
'python_type': (unicode_type, str),
|
||||
|
@ -293,22 +309,20 @@ XSD_COMMON_BUILTIN_TYPES = (
|
|||
'python_type': (unicode_type, str),
|
||||
'base_type': XSD_TOKEN,
|
||||
'facets': [
|
||||
etree_element(XSD_PATTERN, attrib={
|
||||
'value': r"([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*"
|
||||
})
|
||||
etree_element(XSD_PATTERN, value=r"([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*")
|
||||
]
|
||||
}, # language codes
|
||||
{
|
||||
'name': XSD_NAME,
|
||||
'python_type': (unicode_type, str),
|
||||
'base_type': XSD_TOKEN,
|
||||
'facets': [etree_element(XSD_PATTERN, attrib={'value': r"\i\c*"})]
|
||||
'facets': [etree_element(XSD_PATTERN, value=r"\i\c*")]
|
||||
}, # not starting with a digit
|
||||
{
|
||||
'name': XSD_NCNAME,
|
||||
'python_type': (unicode_type, str),
|
||||
'base_type': XSD_NAME,
|
||||
'facets': [etree_element(XSD_PATTERN, attrib={'value': r"[\i-[:]][\c-[:]]*"})]
|
||||
'facets': [etree_element(XSD_PATTERN, value=r"[\i-[:]][\c-[:]]*")]
|
||||
}, # cannot contain colons
|
||||
{
|
||||
'name': XSD_ID,
|
||||
|
@ -329,7 +343,7 @@ XSD_COMMON_BUILTIN_TYPES = (
|
|||
'name': XSD_NMTOKEN,
|
||||
'python_type': (unicode_type, str),
|
||||
'base_type': XSD_TOKEN,
|
||||
'facets': [etree_element(XSD_PATTERN, attrib={'value': r"\c+"})]
|
||||
'facets': [etree_element(XSD_PATTERN, value=r"\c+")]
|
||||
}, # should not contain whitespace (attribute only)
|
||||
|
||||
# --- Numerical derived types ---
|
||||
|
@ -342,73 +356,81 @@ XSD_COMMON_BUILTIN_TYPES = (
|
|||
'name': XSD_LONG,
|
||||
'python_type': int if PY3 else (long_type, int),
|
||||
'base_type': XSD_INTEGER,
|
||||
'facets': [long_validator]
|
||||
'facets': [long_validator,
|
||||
etree_element(XSD_MIN_INCLUSIVE, value='-9223372036854775808'),
|
||||
etree_element(XSD_MAX_INCLUSIVE, value='9223372036854775807')]
|
||||
}, # signed 128 bit value
|
||||
{
|
||||
'name': XSD_INT,
|
||||
'python_type': int,
|
||||
'base_type': XSD_LONG,
|
||||
'facets': [int_validator]
|
||||
'facets': [int_validator,
|
||||
etree_element(XSD_MIN_INCLUSIVE, value='-2147483648'),
|
||||
etree_element(XSD_MAX_INCLUSIVE, value='2147483647')]
|
||||
}, # signed 64 bit value
|
||||
{
|
||||
'name': XSD_SHORT,
|
||||
'python_type': int,
|
||||
'base_type': XSD_INT,
|
||||
'facets': [short_validator]
|
||||
'facets': [short_validator,
|
||||
etree_element(XSD_MIN_INCLUSIVE, value='-32768'),
|
||||
etree_element(XSD_MAX_INCLUSIVE, value='32767')]
|
||||
}, # signed 32 bit value
|
||||
{
|
||||
'name': XSD_BYTE,
|
||||
'python_type': int,
|
||||
'base_type': XSD_SHORT,
|
||||
'facets': [byte_validator]
|
||||
'facets': [byte_validator,
|
||||
etree_element(XSD_MIN_INCLUSIVE, value='-128'),
|
||||
etree_element(XSD_MAX_INCLUSIVE, value='127')]
|
||||
}, # signed 8 bit value
|
||||
{
|
||||
'name': XSD_NON_NEGATIVE_INTEGER,
|
||||
'python_type': int if PY3 else (long_type, int),
|
||||
'base_type': XSD_INTEGER,
|
||||
'facets': [non_negative_int_validator]
|
||||
'facets': [non_negative_int_validator, etree_element(XSD_MIN_INCLUSIVE, value='0')]
|
||||
}, # only zero and more value allowed [>= 0]
|
||||
{
|
||||
'name': XSD_POSITIVE_INTEGER,
|
||||
'python_type': int if PY3 else (long_type, int),
|
||||
'base_type': XSD_NON_NEGATIVE_INTEGER,
|
||||
'facets': [positive_int_validator]
|
||||
'facets': [positive_int_validator, etree_element(XSD_MIN_INCLUSIVE, value='1')]
|
||||
}, # only positive value allowed [> 0]
|
||||
{
|
||||
'name': XSD_UNSIGNED_LONG,
|
||||
'python_type': int if PY3 else (long_type, int),
|
||||
'base_type': XSD_NON_NEGATIVE_INTEGER,
|
||||
'facets': [unsigned_long_validator]
|
||||
'facets': [unsigned_long_validator, etree_element(XSD_MAX_INCLUSIVE, value='18446744073709551615')]
|
||||
}, # unsigned 128 bit value
|
||||
{
|
||||
'name': XSD_UNSIGNED_INT,
|
||||
'python_type': int,
|
||||
'base_type': XSD_UNSIGNED_LONG,
|
||||
'facets': [unsigned_int_validator]
|
||||
'facets': [unsigned_int_validator, etree_element(XSD_MAX_INCLUSIVE, value='4294967295')]
|
||||
}, # unsigned 64 bit value
|
||||
{
|
||||
'name': XSD_UNSIGNED_SHORT,
|
||||
'python_type': int,
|
||||
'base_type': XSD_UNSIGNED_INT,
|
||||
'facets': [unsigned_short_validator]
|
||||
'facets': [unsigned_short_validator, etree_element(XSD_MAX_INCLUSIVE, value='65535')]
|
||||
}, # unsigned 32 bit value
|
||||
{
|
||||
'name': XSD_UNSIGNED_BYTE,
|
||||
'python_type': int,
|
||||
'base_type': XSD_UNSIGNED_SHORT,
|
||||
'facets': [unsigned_byte_validator]
|
||||
'facets': [unsigned_byte_validator, etree_element(XSD_MAX_INCLUSIVE, value='255')]
|
||||
}, # unsigned 8 bit value
|
||||
{
|
||||
'name': XSD_NON_POSITIVE_INTEGER,
|
||||
'python_type': (long_type, int),
|
||||
'base_type': XSD_INTEGER,
|
||||
'facets': [non_positive_int_validator]
|
||||
'facets': [non_positive_int_validator, etree_element(XSD_MAX_INCLUSIVE, value='0')]
|
||||
}, # only zero and smaller value allowed [<= 0]
|
||||
{
|
||||
'name': XSD_NEGATIVE_INTEGER,
|
||||
'python_type': (long_type, int),
|
||||
'base_type': XSD_NON_POSITIVE_INTEGER,
|
||||
'facets': [negative_int_validator]
|
||||
'facets': [negative_int_validator, etree_element(XSD_MAX_INCLUSIVE, value='-1')]
|
||||
}, # only negative value allowed [< 0]
|
||||
)
|
||||
|
||||
|
@ -480,7 +502,7 @@ XSD_11_BUILTIN_TYPES = XSD_COMMON_BUILTIN_TYPES + (
|
|||
'python_type': (unicode_type, str),
|
||||
'base_type': XSD_DATETIME,
|
||||
'to_python': datatypes.DateTime.fromstring,
|
||||
'facets': [etree_element(XSD_EXPLICIT_TIMEZONE, attrib={'value': 'required'})],
|
||||
'facets': [etree_element(XSD_EXPLICIT_TIMEZONE, value='required')],
|
||||
}, # [-][Y*]YYYY-MM-DD[Thh:mm:ss] with required timezone
|
||||
{
|
||||
'name': XSD_DAY_TIME_DURATION,
|
||||
|
@ -515,7 +537,7 @@ def xsd_builtin_types_factory(meta_schema, xsd_types, atomic_builtin_class=None)
|
|||
# xs:anyType
|
||||
# Ref: https://www.w3.org/TR/xmlschema11-1/#builtin-ctd
|
||||
any_type = meta_schema.BUILDERS.complex_type_class(
|
||||
elem=etree_element(XSD_COMPLEX_TYPE, attrib={'name': XSD_ANY_TYPE}),
|
||||
elem=etree_element(XSD_COMPLEX_TYPE, name=XSD_ANY_TYPE),
|
||||
schema=meta_schema,
|
||||
parent=None,
|
||||
mixed=True
|
||||
|
@ -527,7 +549,7 @@ def xsd_builtin_types_factory(meta_schema, xsd_types, atomic_builtin_class=None)
|
|||
# xs:anySimpleType
|
||||
# Ref: https://www.w3.org/TR/xmlschema11-2/#builtin-stds
|
||||
xsd_types[XSD_ANY_SIMPLE_TYPE] = XsdSimpleType(
|
||||
elem=etree_element(XSD_SIMPLE_TYPE, attrib={'name': XSD_ANY_SIMPLE_TYPE}),
|
||||
elem=etree_element(XSD_SIMPLE_TYPE, name=XSD_ANY_SIMPLE_TYPE),
|
||||
schema=meta_schema,
|
||||
parent=None,
|
||||
name=XSD_ANY_SIMPLE_TYPE
|
||||
|
@ -536,7 +558,7 @@ def xsd_builtin_types_factory(meta_schema, xsd_types, atomic_builtin_class=None)
|
|||
# xs:anyAtomicType
|
||||
# Ref: https://www.w3.org/TR/xmlschema11-2/#builtin-stds
|
||||
xsd_types[XSD_ANY_ATOMIC_TYPE] = meta_schema.BUILDERS.restriction_class(
|
||||
elem=etree_element(XSD_SIMPLE_TYPE, attrib={'name': XSD_ANY_ATOMIC_TYPE}),
|
||||
elem=etree_element(XSD_SIMPLE_TYPE, name=XSD_ANY_ATOMIC_TYPE),
|
||||
schema=meta_schema,
|
||||
parent=None,
|
||||
name=XSD_ANY_ATOMIC_TYPE,
|
||||
|
@ -551,14 +573,13 @@ def xsd_builtin_types_factory(meta_schema, xsd_types, atomic_builtin_class=None)
|
|||
except KeyError:
|
||||
# If builtin type element is missing create a dummy element. Necessary for the
|
||||
# meta-schema XMLSchema.xsd of XSD 1.1, that not includes builtins declarations.
|
||||
elem = etree_element(XSD_SIMPLE_TYPE, attrib={'name': name, 'id': name})
|
||||
elem = etree_element(XSD_SIMPLE_TYPE, name=name, id=name)
|
||||
else:
|
||||
if schema is not meta_schema:
|
||||
raise XMLSchemaValueError("loaded entry schema doesn't match meta_schema!")
|
||||
|
||||
if 'base_type' in item:
|
||||
base_type = item.get('base_type')
|
||||
item['base_type'] = xsd_types[base_type]
|
||||
base_type = item['base_type'] = xsd_types[item['base_type']]
|
||||
else:
|
||||
base_type = None
|
||||
|
||||
|
|
|
@ -10,10 +10,11 @@
|
|||
#
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..exceptions import XMLSchemaValueError
|
||||
from ..qnames import 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, prefixed_to_qname, get_xml_bool_attribute, get_xsd_derivation_attribute
|
||||
from ..helpers import get_qname, local_name, get_xml_bool_attribute, get_xsd_derivation_attribute
|
||||
from ..etree import etree_element
|
||||
|
||||
from .exceptions import XMLSchemaValidationError, XMLSchemaDecodeError
|
||||
|
@ -44,32 +45,47 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
((group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?))))
|
||||
</complexType>
|
||||
"""
|
||||
admitted_tags = {XSD_COMPLEX_TYPE, XSD_RESTRICTION}
|
||||
_admitted_tags = {XSD_COMPLEX_TYPE, XSD_RESTRICTION}
|
||||
assertions = ()
|
||||
mixed = False
|
||||
_block = None
|
||||
_derivation = None
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None, content_type=None, attributes=None, mixed=None):
|
||||
self.base_type = None
|
||||
self.content_type = content_type
|
||||
self.attributes = attributes
|
||||
self.mixed = mixed
|
||||
self._derivation = None
|
||||
@staticmethod
|
||||
def normalize(text):
|
||||
return text.decode('utf-8') if isinstance(text, bytes) else text
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None, **kwargs):
|
||||
if kwargs:
|
||||
if 'content_type' in kwargs:
|
||||
self.content_type = kwargs['content_type']
|
||||
if 'attributes' in kwargs:
|
||||
self.attributes = kwargs['attributes']
|
||||
if 'mixed' in kwargs:
|
||||
self.mixed = kwargs['mixed']
|
||||
if 'block' in kwargs:
|
||||
self._block = kwargs['block']
|
||||
if 'final' in kwargs:
|
||||
self._final = kwargs['final']
|
||||
super(XsdComplexType, self).__init__(elem, schema, parent, name)
|
||||
|
||||
def __repr__(self):
|
||||
if self.name is None:
|
||||
if self.name is not None:
|
||||
return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name)
|
||||
elif not hasattr(self, 'content_type'):
|
||||
return '%s(id=%r)' % (self.__class__.__name__, id(self))
|
||||
else:
|
||||
return '%s(content=%r, attributes=%r)' % (
|
||||
self.__class__.__name__, self.content_type_label,
|
||||
[a if a.name is None else a.prefixed_name for a in self.attributes.values()]
|
||||
)
|
||||
else:
|
||||
return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name == 'content_type':
|
||||
assert value is None or isinstance(value, (XsdSimpleType, XsdGroup)), \
|
||||
assert isinstance(value, (XsdSimpleType, XsdGroup)), \
|
||||
"The attribute 'content_type' must be a XsdSimpleType or an XsdGroup instance."
|
||||
elif name == 'attributes':
|
||||
assert value is None or isinstance(value, XsdAttributeGroup), \
|
||||
assert isinstance(value, XsdAttributeGroup), \
|
||||
"The attribute 'attributes' must be an XsdAttributeGroup."
|
||||
super(XsdComplexType, self).__setattr__(name, value)
|
||||
|
||||
|
@ -79,15 +95,36 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
if elem.tag == XSD_RESTRICTION:
|
||||
return # a local restriction is already parsed by the caller
|
||||
|
||||
self.mixed = get_xml_bool_attribute(elem, 'mixed', default=False)
|
||||
self._parse_properties('abstract', 'block', 'final')
|
||||
if 'abstract' in elem.attrib:
|
||||
try:
|
||||
self.abstract = get_xml_bool_attribute(elem, 'abstract')
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
|
||||
if 'block' in elem.attrib:
|
||||
try:
|
||||
self._block = get_xsd_derivation_attribute(elem, 'block', ('extension', 'restriction'))
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
|
||||
if 'final' in elem.attrib:
|
||||
try:
|
||||
self._final = get_xsd_derivation_attribute(elem, 'final', ('extension', 'restriction'))
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
|
||||
if 'mixed' in elem.attrib:
|
||||
try:
|
||||
self.mixed = get_xml_bool_attribute(elem, 'mixed')
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
|
||||
try:
|
||||
self.name = get_qname(self.target_namespace, elem.attrib['name'])
|
||||
except KeyError:
|
||||
self.name = None
|
||||
else:
|
||||
if not self.is_global:
|
||||
if self.parent is not None:
|
||||
self.parse_error("attribute 'name' not allowed for a local complexType", elem)
|
||||
|
||||
content_elem = self._parse_component(elem, required=False, strict=False)
|
||||
|
@ -141,9 +178,25 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
if content_elem is not elem[-1]:
|
||||
k = 2 if content_elem is not elem[0] else 1
|
||||
self.parse_error("unexpected tag %r after complexContent declaration:" % elem[k].tag, elem)
|
||||
if base_type is not self:
|
||||
if self.redefine or base_type is not self:
|
||||
self.base_type = base_type
|
||||
|
||||
elif content_elem.tag == XSD_OPEN_CONTENT and self.schema.XSD_VERSION != '1.0':
|
||||
self.open_content = None
|
||||
|
||||
if content_elem is elem[-1]:
|
||||
self.content_type = self.schema.BUILDERS.group_class(SEQUENCE_ELEMENT, self.schema, self)
|
||||
else:
|
||||
for child, index in enumerate(elem):
|
||||
if content_elem is not child:
|
||||
continue
|
||||
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)
|
||||
break
|
||||
self._parse_content_tail(elem)
|
||||
|
||||
else:
|
||||
if self.schema.validation == 'skip':
|
||||
# Also generated by meta-schema validation for 'lax' and 'strict' modes
|
||||
|
@ -151,6 +204,12 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
self.content_type = self.schema.create_any_content_group(self)
|
||||
self.attributes = self.schema.create_any_attribute_group(self)
|
||||
|
||||
if self.redefine is None:
|
||||
if self.base_type is not None and self.base_type.name == self.name:
|
||||
self.parse_error("wrong definition with self-reference", elem)
|
||||
elif self.base_type is None or self.base_type.name != self.name:
|
||||
self.parse_error("wrong redefinition without self-reference", elem)
|
||||
|
||||
def _parse_content_tail(self, elem, **kwargs):
|
||||
self.attributes = self.schema.BUILDERS.attribute_group_class(elem, self.schema, self, **kwargs)
|
||||
|
||||
|
@ -163,33 +222,41 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
return
|
||||
|
||||
derivation = local_name(derivation_elem.tag)
|
||||
self._derivation = derivation == 'extension'
|
||||
if self._derivation is None:
|
||||
self._derivation = derivation == 'extension'
|
||||
elif self.redefine is None:
|
||||
raise XMLSchemaValueError("%r is expected to have a redefined/overridden component" % self)
|
||||
|
||||
if self.base_type is not None and derivation in self.base_type.final:
|
||||
self.parse_error("%r derivation not allowed for %r." % (derivation, self))
|
||||
return derivation_elem
|
||||
|
||||
def _parse_base_type(self, elem, complex_content=False):
|
||||
try:
|
||||
content_base = elem.attrib['base']
|
||||
base_qname = self.schema.resolve_qname(elem.attrib['base'])
|
||||
except KeyError:
|
||||
self.parse_error("'base' attribute required", elem)
|
||||
return self.maps.lookup_type(XSD_ANY_TYPE)
|
||||
return self.maps.types[XSD_ANY_TYPE]
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
return self.maps.types[XSD_ANY_TYPE]
|
||||
|
||||
base_qname = prefixed_to_qname(content_base, self.namespaces)
|
||||
try:
|
||||
base_type = self.maps.lookup_type(base_qname)
|
||||
except KeyError:
|
||||
self.parse_error("missing base type %r" % base_qname, elem)
|
||||
if complex_content:
|
||||
return self.maps.lookup_type(XSD_ANY_TYPE)
|
||||
return self.maps.types[XSD_ANY_TYPE]
|
||||
else:
|
||||
return self.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
return self.maps.types[XSD_ANY_SIMPLE_TYPE]
|
||||
else:
|
||||
if complex_content and base_type.is_simple():
|
||||
if isinstance(base_type, tuple):
|
||||
self.parse_error("circularity definition found between %r and %r" % (self, base_qname), elem)
|
||||
return self.maps.types[XSD_ANY_TYPE]
|
||||
elif complex_content and base_type.is_simple():
|
||||
self.parse_error("a complexType ancestor required: %r" % base_type, elem)
|
||||
return self.maps.lookup_type(XSD_ANY_TYPE)
|
||||
else:
|
||||
return base_type
|
||||
return self.maps.types[XSD_ANY_TYPE]
|
||||
return base_type
|
||||
|
||||
def _parse_simple_content_restriction(self, elem, base_type):
|
||||
# simpleContent restriction: the base type must be a complexType with a simple
|
||||
|
@ -199,7 +266,12 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
self.content_type = self.schema.create_any_content_group(self)
|
||||
self._parse_content_tail(elem)
|
||||
else:
|
||||
if base_type.has_simple_content() or base_type.mixed and base_type.is_emptiable():
|
||||
if base_type.has_simple_content():
|
||||
self.content_type = self.schema.BUILDERS.restriction_class(elem, self.schema, self)
|
||||
if not self.content_type.is_derived(base_type.content_type, 'restriction'):
|
||||
self.parse_error("Content type is not a restriction of base content type", elem)
|
||||
|
||||
elif base_type.mixed and base_type.is_emptiable():
|
||||
self.content_type = self.schema.BUILDERS.restriction_class(elem, self.schema, self)
|
||||
else:
|
||||
self.parse_error("with simple content cannot restrict an empty or "
|
||||
|
@ -226,18 +298,19 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
self.parse_error("base type %r has not simple content." % base_type, elem)
|
||||
self.content_type = self.schema.create_any_content_group(self)
|
||||
|
||||
self._parse_content_tail(elem, derivation='restriction', base_attributes=base_type.attributes)
|
||||
self._parse_content_tail(elem, derivation='extension', base_attributes=base_type.attributes)
|
||||
|
||||
def _parse_complex_content_restriction(self, elem, base_type):
|
||||
if 'restriction' in base_type.final:
|
||||
self.parse_error("the base type is not derivable by restriction")
|
||||
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]
|
||||
|
||||
# complexContent restriction: the base type must be a complexType with a complex content.
|
||||
group_elem = self._parse_component(elem, required=False, strict=False)
|
||||
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
|
||||
content_type = self.schema.BUILDERS.group_class(group_elem, self.schema, self)
|
||||
model = content_type.model
|
||||
if model != 'sequence' and model != base_type.content_type.model:
|
||||
self.parse_error(
|
||||
"cannot restrict a %r model to %r." % (base_type.content_type.model, model), elem
|
||||
)
|
||||
else:
|
||||
# Empty content model
|
||||
content_type = self.schema.BUILDERS.group_class(elem, self.schema, self)
|
||||
|
@ -252,17 +325,15 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
)
|
||||
|
||||
if base_type.name != XSD_ANY_TYPE and not base_type.is_empty() and False:
|
||||
if not content_type.is_restriction(base_type.content_type):
|
||||
if not content_type.has_occurs_restriction(base_type.content_type):
|
||||
self.parse_error("The derived group %r is not a restriction of the base group." % elem, elem)
|
||||
|
||||
self.content_type = content_type
|
||||
self._parse_content_tail(elem, derivation='restriction', base_attributes=base_type.attributes)
|
||||
|
||||
def _parse_complex_content_extension(self, elem, base_type):
|
||||
# complexContent extension: base type must be a complex type with complex content.
|
||||
# A dummy sequence group is added if the base type has not empty content model.
|
||||
if getattr(base_type.content_type, 'model', None) == 'all' and self.schema.XSD_VERSION == '1.0':
|
||||
self.parse_error("XSD 1.0 does not allow extension of an 'ALL' model group.", elem)
|
||||
if 'extension' in base_type.final:
|
||||
self.parse_error("the base type is not derivable by extension")
|
||||
|
||||
group_elem = self._parse_component(elem, required=False, strict=False)
|
||||
if base_type.is_empty():
|
||||
|
@ -284,21 +355,33 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
# 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.lookup_type(XSD_ANY_TYPE)
|
||||
base_type = self.maps.types[XSD_ANY_TYPE]
|
||||
|
||||
group = self.schema.BUILDERS.group_class(group_elem, self.schema, self)
|
||||
if group.model == 'all':
|
||||
self.parse_error("Cannot extend a complex content with an all model")
|
||||
|
||||
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.mixed != self.mixed and not group.is_empty():
|
||||
# complexContent extension: base type must be a complex type with complex content.
|
||||
# A dummy sequence group is added if the base type has not empty content model.
|
||||
if base_type.content_type.model == 'all' and base_type.content_type and group \
|
||||
and self.schema.XSD_VERSION == '1.0':
|
||||
self.parse_error("XSD 1.0 does not allow extension of a not empty 'ALL' model 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.mixed = base_type.mixed
|
||||
|
||||
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)
|
||||
|
||||
self.content_type = content_type
|
||||
|
||||
self._parse_content_tail(elem, derivation='extension', base_attributes=base_type.attributes)
|
||||
|
@ -321,6 +404,10 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
else:
|
||||
return 'none'
|
||||
|
||||
@property
|
||||
def block(self):
|
||||
return self.schema.block_default if self._block is None else self._block
|
||||
|
||||
@staticmethod
|
||||
def is_simple():
|
||||
return False
|
||||
|
@ -363,17 +450,33 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
def is_list(self):
|
||||
return self.has_simple_content() and self.content_type.is_list()
|
||||
|
||||
@property
|
||||
def abstract(self):
|
||||
return get_xml_bool_attribute(self.elem, 'abstract', default=False)
|
||||
def is_valid(self, source, use_defaults=True):
|
||||
if hasattr(source, 'tag'):
|
||||
return super(XsdComplexType, self).is_valid(source, use_defaults)
|
||||
elif isinstance(self.content_type, XsdSimpleType):
|
||||
return self.content_type.is_valid(source)
|
||||
else:
|
||||
return self.base_type is not None and self.base_type.is_valid(source) or self.mixed
|
||||
|
||||
@property
|
||||
def block(self):
|
||||
return get_xsd_derivation_attribute(self.elem, 'block', ('extension', 'restriction'))
|
||||
|
||||
@property
|
||||
def final(self):
|
||||
return get_xsd_derivation_attribute(self.elem, 'final', ('extension', 'restriction'))
|
||||
def is_derived(self, other, derivation=None):
|
||||
if self is other:
|
||||
return True
|
||||
elif derivation and self.derivation and derivation != self.derivation and other.is_complex():
|
||||
return False
|
||||
elif other.name == XSD_ANY_TYPE:
|
||||
return True
|
||||
elif self.base_type is other:
|
||||
return True
|
||||
elif hasattr(other, 'member_types'):
|
||||
return any(self.is_derived(m, derivation) for m in other.member_types)
|
||||
elif self.base_type is None:
|
||||
if not self.has_simple_content():
|
||||
return False
|
||||
return self.content_type.is_derived(other, derivation)
|
||||
elif self.has_simple_content():
|
||||
return self.content_type.is_derived(other, derivation) or self.base_type.is_derived(other, derivation)
|
||||
else:
|
||||
return self.base_type.is_derived(other, derivation)
|
||||
|
||||
def iter_components(self, xsd_classes=None):
|
||||
if xsd_classes is None or isinstance(self, xsd_classes):
|
||||
|
@ -387,7 +490,7 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
|
||||
for obj in self.assertions:
|
||||
if xsd_classes is None or isinstance(obj, xsd_classes):
|
||||
yield obj
|
||||
yield obj
|
||||
|
||||
@staticmethod
|
||||
def get_facet(*_args, **_kwargs):
|
||||
|
@ -409,17 +512,6 @@ class XsdComplexType(XsdType, ValidationMixin):
|
|||
def has_extension(self):
|
||||
return self._derivation is True
|
||||
|
||||
def check_restriction(self):
|
||||
if self._derivation is not False:
|
||||
return
|
||||
elif isinstance(self.content_type, XsdGroup):
|
||||
base_type = self.base_type
|
||||
if base_type.name != XSD_ANY_TYPE and base_type.is_complex() and base_type:
|
||||
if not self.content_type.is_restriction(base_type.content_type):
|
||||
self.parse_error(
|
||||
"The derived group is an illegal restriction of the base type group.", self.elem
|
||||
)
|
||||
|
||||
def decode(self, data, *args, **kwargs):
|
||||
if hasattr(data, 'attrib') or self.is_simple():
|
||||
return super(XsdComplexType, self).decode(data, *args, **kwargs)
|
||||
|
|
|
@ -17,17 +17,18 @@ from elementpath import XPath2Parser, ElementPathSyntaxError, XPathContext
|
|||
from elementpath.xpath_helpers import boolean_value
|
||||
from elementpath.datatypes import AbstractDateTime, Duration
|
||||
|
||||
from ..exceptions import XMLSchemaAttributeError, XMLSchemaValueError
|
||||
from ..exceptions import XMLSchemaAttributeError
|
||||
from ..qnames import 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
|
||||
from ..helpers import get_qname, prefixed_to_qname, get_xml_bool_attribute, get_xsd_derivation_attribute
|
||||
XSD_KEY, XSD_KEYREF, XSI_NIL, XSI_TYPE, XSD_ID
|
||||
from ..helpers import get_qname, get_xml_bool_attribute, get_xsd_derivation_attribute, \
|
||||
get_xsd_form_attribute, ParticleCounter
|
||||
from ..etree import etree_element
|
||||
from ..converters import ElementData, raw_xml_encode, XMLSchemaConverter
|
||||
from ..xpath import ElementPathMixin
|
||||
|
||||
from .exceptions import XMLSchemaValidationError
|
||||
from .xsdbase import XsdComponent, XsdType, ParticleMixin, ValidationMixin
|
||||
from .xsdbase import XsdComponent, XsdType, ValidationMixin, ParticleMixin
|
||||
from .identities import XsdUnique, XsdKey, XsdKeyref
|
||||
from .wildcards import XsdAnyElement
|
||||
|
||||
|
@ -59,7 +60,13 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
Content: (annotation?, ((simpleType | complexType)?, (unique | key | keyref)*))
|
||||
</element>
|
||||
"""
|
||||
admitted_tags = {XSD_ELEMENT}
|
||||
_admitted_tags = {XSD_ELEMENT}
|
||||
qualified = False
|
||||
_ref = None
|
||||
_abstract = False
|
||||
_block = None
|
||||
_final = None
|
||||
_substitution_group = None
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None):
|
||||
super(XsdElement, self).__init__(elem, schema, parent, name)
|
||||
|
@ -96,66 +103,120 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
self._parse_attributes()
|
||||
index = self._parse_type()
|
||||
self._parse_identity_constraints(index)
|
||||
self._parse_substitution_group()
|
||||
if self.parent is None:
|
||||
self._parse_substitution_group()
|
||||
|
||||
def _parse_attributes(self):
|
||||
self._parse_particle(self.elem)
|
||||
self.name = None
|
||||
self._ref = None
|
||||
self.qualified = self.elem.get('form', self.schema.element_form_default) == 'qualified'
|
||||
elem = self.elem
|
||||
attrib = elem.attrib
|
||||
self._parse_particle(elem)
|
||||
|
||||
if self.default is not None and self.fixed is not None:
|
||||
self.parse_error("'default' and 'fixed' attributes are mutually exclusive.")
|
||||
self._parse_properties('abstract', 'block', 'final', 'form', 'nillable')
|
||||
|
||||
# Parse element attributes
|
||||
try:
|
||||
element_name = prefixed_to_qname(self.elem.attrib['ref'], self.namespaces)
|
||||
except KeyError:
|
||||
# No 'ref' attribute ==> 'name' attribute required.
|
||||
try:
|
||||
if self.is_global or self.qualified:
|
||||
self.name = get_qname(self.target_namespace, self.elem.attrib['name'])
|
||||
else:
|
||||
self.name = self.elem.attrib['name']
|
||||
except KeyError:
|
||||
self.parse_error("missing both 'name' and 'ref' attributes.")
|
||||
self.qualified = (self.form or self.schema.element_form_default) == 'qualified'
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
|
||||
if self.is_global:
|
||||
if 'minOccurs' in self.elem.attrib:
|
||||
self.parse_error("attribute 'minOccurs' not allowed for a global element.")
|
||||
if 'maxOccurs' in self.elem.attrib:
|
||||
self.parse_error("attribute 'maxOccurs' not allowed for a global element.")
|
||||
else:
|
||||
# Reference to a global element
|
||||
if self.is_global:
|
||||
self.parse_error("an element reference can't be global.")
|
||||
for attribute in ('name', 'type', 'nillable', 'default', 'fixed', 'form', 'block'):
|
||||
if attribute in self.elem.attrib:
|
||||
self.parse_error("attribute %r is not allowed when element reference is used." % attribute)
|
||||
try:
|
||||
xsd_element = self.maps.lookup_element(element_name)
|
||||
except KeyError:
|
||||
self.parse_error('unknown element %r' % element_name)
|
||||
self.name = element_name
|
||||
self.type = self.maps.lookup_type(XSD_ANY_TYPE)
|
||||
name = elem.get('name')
|
||||
if name is not None:
|
||||
if self.parent is None or self.qualified:
|
||||
self.name = get_qname(self.target_namespace, attrib['name'])
|
||||
else:
|
||||
self._ref = xsd_element
|
||||
self.name = xsd_element.name
|
||||
self.type = xsd_element.type
|
||||
self.qualified = xsd_element.qualified
|
||||
self.name = attrib['name']
|
||||
elif self.parent is None:
|
||||
self.parse_error("missing 'name' in a global element declaration")
|
||||
self.name = elem.get('ref', 'nameless_%s' % str(id(self)))
|
||||
elif 'ref' not in attrib:
|
||||
self.parse_error("missing both 'name' and 'ref' attributes")
|
||||
self.name = elem.get('nameless_%s' % str(id(self)))
|
||||
else:
|
||||
try:
|
||||
element_name = self.schema.resolve_qname(attrib['ref'])
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
self.type = self.maps.types[XSD_ANY_TYPE]
|
||||
self.name = elem.get('nameless_%s' % str(id(self)))
|
||||
else:
|
||||
if not element_name:
|
||||
self.parse_error("empty 'ref' attribute")
|
||||
self.type = self.maps.types[XSD_ANY_TYPE]
|
||||
self.name = elem.get('nameless_%s' % str(id(self)))
|
||||
else:
|
||||
try:
|
||||
xsd_element = self.maps.lookup_element(element_name)
|
||||
except KeyError:
|
||||
self.parse_error('unknown element %r' % element_name)
|
||||
self.name = element_name
|
||||
self.type = self.maps.types[XSD_ANY_TYPE]
|
||||
else:
|
||||
self._ref = xsd_element
|
||||
self.name = xsd_element.name
|
||||
self.type = xsd_element.type
|
||||
self.qualified = xsd_element.qualified
|
||||
|
||||
for attr_name in ('name', 'type', 'nillable', 'default', 'fixed', 'form',
|
||||
'block', 'abstract', 'final', 'substitutionGroup'):
|
||||
if attr_name in attrib:
|
||||
self.parse_error("attribute %r is not allowed when element reference is used." % attr_name)
|
||||
return
|
||||
|
||||
if 'default' in attrib and 'fixed' in attrib:
|
||||
self.parse_error("'default' and 'fixed' attributes are mutually exclusive.")
|
||||
|
||||
if 'abstract' in elem.attrib:
|
||||
try:
|
||||
self._abstract = get_xml_bool_attribute(elem, 'abstract')
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
else:
|
||||
if self.parent is not None:
|
||||
self.parse_error("local scope elements cannot have abstract attribute")
|
||||
|
||||
if 'block' in elem.attrib:
|
||||
try:
|
||||
self._block = get_xsd_derivation_attribute(
|
||||
elem, 'block', ('extension', 'restriction', 'substitution')
|
||||
)
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
|
||||
if self.parent is None:
|
||||
self._parse_properties('nillable')
|
||||
|
||||
if 'final' in elem.attrib:
|
||||
try:
|
||||
self._final = get_xsd_derivation_attribute(elem, 'final', ('extension', 'restriction'))
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
|
||||
for attr_name in ('ref', 'form', 'minOccurs', 'maxOccurs'):
|
||||
if attr_name in attrib:
|
||||
self.parse_error("attribute %r not allowed in a global element declaration" % attr_name)
|
||||
else:
|
||||
self._parse_properties('form', 'nillable')
|
||||
|
||||
for attr_name in ('final', 'substitutionGroup'):
|
||||
if attr_name in attrib:
|
||||
self.parse_error("attribute %r not allowed in a local element declaration" % attr_name)
|
||||
|
||||
def _parse_type(self):
|
||||
attrib = self.elem.attrib
|
||||
if self.ref:
|
||||
if self._parse_component(self.elem, required=False, strict=False) is not None:
|
||||
self.parse_error("element reference declaration can't has children.")
|
||||
elif 'type' in self.elem.attrib:
|
||||
type_qname = prefixed_to_qname(self.elem.attrib['type'], self.namespaces)
|
||||
elif 'type' in attrib:
|
||||
try:
|
||||
self.type = self.maps.lookup_type(type_qname)
|
||||
self.type = self.maps.lookup_type(self.schema.resolve_qname(attrib['type']))
|
||||
except KeyError:
|
||||
self.parse_error('unknown type %r' % self.elem.attrib['type'])
|
||||
self.type = self.maps.lookup_type(XSD_ANY_TYPE)
|
||||
self.parse_error('unknown type %r' % attrib['type'])
|
||||
self.type = self.maps.types[XSD_ANY_TYPE]
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
self.type = self.maps.types[XSD_ANY_TYPE]
|
||||
finally:
|
||||
child = self._parse_component(self.elem, required=False, strict=False)
|
||||
if child is not None and child.tag in (XSD_COMPLEX_TYPE, XSD_SIMPLE_TYPE):
|
||||
msg = "the attribute 'type' and the <%s> local declaration are mutually exclusive"
|
||||
self.parse_error(msg % child.tag.split('}')[-1])
|
||||
else:
|
||||
child = self._parse_component(self.elem, required=False, strict=False)
|
||||
if child is not None:
|
||||
|
@ -163,9 +224,39 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
self.type = self.schema.BUILDERS.complex_type_class(child, self.schema, self)
|
||||
elif child.tag == XSD_SIMPLE_TYPE:
|
||||
self.type = self.schema.BUILDERS.simple_type_factory(child, self.schema, self)
|
||||
else:
|
||||
self.type = self.maps.lookup_type(XSD_ANY_TYPE)
|
||||
return 0
|
||||
|
||||
# Check value constraints
|
||||
if 'default' in attrib and not self.type.is_valid(attrib['default']):
|
||||
msg = "'default' value %r is not compatible with the type of the element"
|
||||
self.parse_error(msg % attrib['default'])
|
||||
elif 'fixed' in attrib and not self.type.is_valid(attrib['fixed']):
|
||||
msg = "'fixed' value %r is not compatible with the type of the element"
|
||||
self.parse_error(msg % attrib['fixed'])
|
||||
|
||||
return 1
|
||||
else:
|
||||
self.type = self.maps.lookup_type(XSD_ANY_TYPE)
|
||||
return 0
|
||||
|
||||
# Check value constraints
|
||||
if 'default' in attrib:
|
||||
if not self.type.is_valid(attrib['default']):
|
||||
msg = "'default' value {!r} is not compatible with the type {!r}"
|
||||
self.parse_error(msg.format(attrib['default'], self.type))
|
||||
elif self.schema.XSD_VERSION == '1.0' and (
|
||||
self.type.name == XSD_ID or self.type.is_derived(self.schema.meta_schema.types['ID'])):
|
||||
self.parse_error("'xs:ID' or a type derived from 'xs:ID' cannot has a 'default'")
|
||||
elif 'fixed' in attrib:
|
||||
if not self.type.is_valid(attrib['fixed']):
|
||||
msg = "'fixed' value {!r} is not compatible with the type {!r}"
|
||||
self.parse_error(msg.format(attrib['fixed'], self.type))
|
||||
elif self.schema.XSD_VERSION == '1.0' and (
|
||||
self.type.name == XSD_ID or self.type.is_derived(self.schema.meta_schema.types['ID'])):
|
||||
self.parse_error("'xs:ID' or a type derived from 'xs:ID' cannot has a 'default'")
|
||||
|
||||
return 0
|
||||
|
||||
def _parse_identity_constraints(self, index=0):
|
||||
|
@ -189,44 +280,56 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
self.constraints[constraint.name] = constraint
|
||||
|
||||
def _parse_substitution_group(self):
|
||||
substitution_group = self.substitution_group
|
||||
substitution_group = self.elem.get('substitutionGroup')
|
||||
if substitution_group is None:
|
||||
return
|
||||
|
||||
if not self.is_global:
|
||||
self.parse_error("'substitutionGroup' attribute in a local element declaration")
|
||||
|
||||
qname = prefixed_to_qname(substitution_group, self.namespaces)
|
||||
if qname[0] != '{':
|
||||
qname = get_qname(self.target_namespace, qname)
|
||||
try:
|
||||
head_element = self.maps.lookup_element(qname)
|
||||
substitution_group_qname = self.schema.resolve_qname(substitution_group)
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
return
|
||||
else:
|
||||
if substitution_group_qname[0] != '{':
|
||||
substitution_group_qname = get_qname(self.target_namespace, substitution_group_qname)
|
||||
|
||||
try:
|
||||
head_element = self.maps.lookup_element(substitution_group_qname)
|
||||
except KeyError:
|
||||
self.parse_error("unknown substitutionGroup %r" % substitution_group)
|
||||
return
|
||||
else:
|
||||
final = head_element.final
|
||||
if final is None:
|
||||
final = self.schema.final_default
|
||||
if isinstance(head_element, tuple):
|
||||
self.parse_error("circularity found for substitutionGroup %r" % substitution_group)
|
||||
return
|
||||
elif 'substitution' in head_element.block:
|
||||
return
|
||||
|
||||
if final == '#all' or 'extension' in final and 'restriction' in final:
|
||||
self.parse_error("head element %r cannot be substituted." % head_element)
|
||||
elif self.type == head_element.type or self.type.name == XSD_ANY_TYPE:
|
||||
pass
|
||||
elif 'extension' in final and not self.type.is_derived(head_element.type, 'extension'):
|
||||
self.parse_error(
|
||||
"%r type is not of the same or an extension of the head element %r type."
|
||||
% (self, head_element)
|
||||
)
|
||||
elif 'restriction' in final and not self.type.is_derived(head_element.type, 'restriction'):
|
||||
self.parse_error(
|
||||
"%r type is not of the same or a restriction of the head element %r type."
|
||||
% (self, head_element)
|
||||
)
|
||||
elif not self.type.is_derived(head_element.type):
|
||||
self.parse_error(
|
||||
"%r type is not of the same or a derivation of the head element %r type."
|
||||
% (self, head_element)
|
||||
)
|
||||
final = head_element.final
|
||||
if self.type == head_element.type or self.type.name == XSD_ANY_TYPE:
|
||||
pass
|
||||
elif not self.type.is_derived(head_element.type):
|
||||
msg = "%r type is not of the same or a derivation of the head element %r type."
|
||||
self.parse_error(msg % (self, head_element))
|
||||
elif final == '#all' or 'extension' in final and 'restriction' in final:
|
||||
msg = "head element %r can't be substituted by an element that has a derivation of its type"
|
||||
self.parse_error(msg % head_element)
|
||||
elif 'extension' in final and self.type.is_derived(head_element.type, 'extension'):
|
||||
msg = "head element %r can't be substituted by an element that has an extension of its type"
|
||||
self.parse_error(msg % head_element)
|
||||
elif 'restriction' in final and self.type.is_derived(head_element.type, 'restriction'):
|
||||
msg = "head element %r can't be substituted by an element that has a restriction of its type"
|
||||
self.parse_error(msg % head_element)
|
||||
|
||||
if self.type.name == XSD_ANY_TYPE and 'type' not in self.elem.attrib:
|
||||
self.type = self.maps.elements[substitution_group_qname].type
|
||||
|
||||
try:
|
||||
self.maps.substitution_groups[substitution_group_qname].add(self)
|
||||
except KeyError:
|
||||
self.maps.substitution_groups[substitution_group_qname] = {self}
|
||||
finally:
|
||||
self._substitution_group = substitution_group_qname
|
||||
|
||||
@property
|
||||
def built(self):
|
||||
|
@ -245,24 +348,21 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
return self.elem.get('ref')
|
||||
|
||||
# Global element's exclusive properties
|
||||
@property
|
||||
def abstract(self):
|
||||
return self._abstract if self._ref is None else self._ref.abstract
|
||||
|
||||
@property
|
||||
def final(self):
|
||||
return get_xsd_derivation_attribute(self.elem, 'final', ('extension', 'restriction'))
|
||||
return self._final or self.schema.final_default if self._ref is None else self._ref.final
|
||||
|
||||
@property
|
||||
def block(self):
|
||||
return get_xsd_derivation_attribute(self.elem, 'block', ('extension', 'restriction', 'substitution'))
|
||||
return self._block or self.schema.block_default if self._ref is None else self._ref.block
|
||||
|
||||
@property
|
||||
def substitution_group(self):
|
||||
return self.elem.get('substitutionGroup')
|
||||
|
||||
# Properties inherited by references
|
||||
@property
|
||||
def abstract(self):
|
||||
if self._ref is not None:
|
||||
return self._ref.abstract
|
||||
return get_xml_bool_attribute(self.elem, 'abstract', default=False)
|
||||
return self._substitution_group if self._ref is None else self._ref.substitution_group
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
|
@ -274,12 +374,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
|
||||
@property
|
||||
def form(self):
|
||||
if self._ref is not None:
|
||||
return self._ref.form
|
||||
value = self.elem.get('form')
|
||||
if value not in (None, 'qualified', 'unqualified'):
|
||||
raise XMLSchemaValueError("wrong value %r for 'form' attribute." % value)
|
||||
return value
|
||||
return get_xsd_form_attribute(self.elem, 'form') if self._ref is None else self._ref.form
|
||||
|
||||
@property
|
||||
def nillable(self):
|
||||
|
@ -295,6 +390,27 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
def get_type(self, elem):
|
||||
return self.type
|
||||
|
||||
def get_path(self, ancestor=None, reverse=False):
|
||||
"""
|
||||
Returns the XPath expression of the element. The path is relative to the schema instance
|
||||
in which the element is contained or is relative to a specific ancestor passed as argument.
|
||||
In the latter case returns `None` if the argument is not an ancestor.
|
||||
|
||||
:param ancestor: optional XSD component of the same schema, that may be an ancestor of the element.
|
||||
:param reverse: if set to `True` returns the reverse path, from the element to ancestor.
|
||||
"""
|
||||
path = []
|
||||
xsd_component = self
|
||||
while xsd_component is not None:
|
||||
if xsd_component is ancestor:
|
||||
return '/'.join(reversed(path)) or '.'
|
||||
elif hasattr(xsd_component, 'tag'):
|
||||
path.append('..' if reverse else xsd_component.name)
|
||||
xsd_component = xsd_component.parent
|
||||
else:
|
||||
if ancestor is None:
|
||||
return '/'.join(reversed(path)) or '.'
|
||||
|
||||
def iter_components(self, xsd_classes=None):
|
||||
if xsd_classes is None:
|
||||
yield self
|
||||
|
@ -307,7 +423,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
if isinstance(obj, xsd_classes):
|
||||
yield obj
|
||||
|
||||
if self.ref is None and not self.type.is_global:
|
||||
if self.ref is None and self.type.parent is not None:
|
||||
for obj in self.type.iter_components(xsd_classes):
|
||||
yield obj
|
||||
|
||||
|
@ -527,36 +643,63 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
yield elem
|
||||
del element_data
|
||||
|
||||
def is_restriction(self, other, check_particle=True):
|
||||
def is_restriction(self, other, check_occurs=True):
|
||||
if isinstance(other, XsdAnyElement):
|
||||
return True # TODO
|
||||
if self.min_occurs == self.max_occurs == 0:
|
||||
return True
|
||||
if check_occurs and not self.has_occurs_restriction(other):
|
||||
return False
|
||||
return other.is_matching(self.name, self.default_namespace)
|
||||
elif isinstance(other, XsdElement):
|
||||
if self.name != other.name:
|
||||
if other.name not in self.maps.substitution_groups:
|
||||
substitution_group = self.substitution_group
|
||||
|
||||
if other.name == self.substitution_group and other.min_occurs != other.max_occurs \
|
||||
and self.max_occurs != 0 and not other.abstract:
|
||||
# Base is the head element, it's not abstract and has non deterministic occurs: this
|
||||
# is less restrictive than W3C test group (elemZ026), marked as invalid despite it's
|
||||
# based on an abstract declaration.
|
||||
return False
|
||||
else:
|
||||
return any(self.is_restriction(e) for e in self.maps.substitution_groups[other.name])
|
||||
elif check_particle and not ParticleMixin.is_restriction(self, other):
|
||||
elif self.substitution_group is None:
|
||||
return False
|
||||
elif not any(e.name == self.name for e in self.maps.substitution_groups[substitution_group]):
|
||||
return False
|
||||
|
||||
if check_occurs and not self.has_occurs_restriction(other):
|
||||
return False
|
||||
elif self.type is not other.type and self.type.elem is not other.type.elem and \
|
||||
not self.type.is_derived(other.type):
|
||||
not self.type.is_derived(other.type, 'restriction') and not other.type.abstract:
|
||||
return False
|
||||
elif self.fixed != other.fixed:
|
||||
elif self.fixed != other.fixed and self.type.normalize(self.fixed) != other.type.normalize(other.fixed):
|
||||
return False
|
||||
elif other.nillable is False and self.nillable:
|
||||
return False
|
||||
elif not all(value in other.block for value in self.block):
|
||||
elif any(value not in self.block for value in other.block.split()):
|
||||
return False
|
||||
elif not all(k in other.constraints for k in self.constraints):
|
||||
return False
|
||||
elif other.model == 'choice':
|
||||
if ParticleMixin.is_restriction(self, other):
|
||||
return any(self.is_restriction(e, False) for e in other.iter_group())
|
||||
else:
|
||||
return any(self.is_restriction(e) for e in other.iter_group())
|
||||
return True
|
||||
elif other.model == 'choice':
|
||||
if other.is_empty() and self.max_occurs != 0:
|
||||
return False
|
||||
|
||||
check_group_items_occurs = self.schema.XSD_VERSION == '1.0'
|
||||
counter = ParticleCounter()
|
||||
for e in other.iter_model():
|
||||
if not isinstance(e, (XsdElement, XsdAnyElement)):
|
||||
return False
|
||||
elif not self.is_restriction(e, check_group_items_occurs):
|
||||
continue
|
||||
counter += e
|
||||
counter *= other
|
||||
if self.has_occurs_restriction(counter):
|
||||
return True
|
||||
counter.reset()
|
||||
return False
|
||||
else:
|
||||
match_restriction = False
|
||||
for e in other.iter_group():
|
||||
for e in other.iter_model():
|
||||
if match_restriction:
|
||||
if not e.is_emptiable():
|
||||
return False
|
||||
|
@ -564,7 +707,21 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
match_restriction = True
|
||||
elif not e.is_emptiable():
|
||||
return False
|
||||
return True
|
||||
return True
|
||||
|
||||
def overlap(self, other):
|
||||
if isinstance(other, XsdElement):
|
||||
if self.name == other.name:
|
||||
return True
|
||||
elif other.substitution_group == self.name or other.name == self.substitution_group:
|
||||
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
|
||||
|
||||
|
||||
class Xsd11Element(XsdElement):
|
||||
|
@ -597,7 +754,8 @@ class Xsd11Element(XsdElement):
|
|||
index = self._parse_type()
|
||||
index = self._parse_alternatives(index)
|
||||
self._parse_identity_constraints(index)
|
||||
self._parse_substitution_group()
|
||||
if self.parent is None:
|
||||
self._parse_substitution_group()
|
||||
self._parse_target_namespace()
|
||||
|
||||
def _parse_alternatives(self, index=0):
|
||||
|
@ -636,6 +794,14 @@ class Xsd11Element(XsdElement):
|
|||
return alt.type
|
||||
return self.type
|
||||
|
||||
def overlap(self, other):
|
||||
if isinstance(other, XsdElement):
|
||||
if self.name == other.name:
|
||||
return True
|
||||
elif other.substitution_group == self.name or other.name == self.substitution_group:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class XsdAlternative(XsdComponent):
|
||||
"""
|
||||
|
@ -648,21 +814,22 @@ class XsdAlternative(XsdComponent):
|
|||
Content: (annotation?, (simpleType | complexType)?)
|
||||
</alternative>
|
||||
"""
|
||||
admitted_tags = {XSD_ALTERNATIVE}
|
||||
_admitted_tags = {XSD_ALTERNATIVE}
|
||||
type = None
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(type=%r, test=%r)' % (self.__class__.__name__, self.elem.get('type'), self.elem.get('test'))
|
||||
|
||||
def _parse(self):
|
||||
XsdComponent._parse(self)
|
||||
elem = self.elem
|
||||
attrib = self.elem.attrib
|
||||
try:
|
||||
self.path = elem.attrib['test']
|
||||
self.path = attrib['test']
|
||||
except KeyError as err:
|
||||
self.path = 'true()'
|
||||
self.parse_error(err, elem=elem)
|
||||
self.parse_error(err)
|
||||
|
||||
if 'xpathDefaultNamespace' in self.elem.attrib:
|
||||
if 'xpathDefaultNamespace' in attrib:
|
||||
self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
|
||||
else:
|
||||
self.xpath_default_namespace = self.schema.xpath_default_namespace
|
||||
|
@ -671,23 +838,24 @@ class XsdAlternative(XsdComponent):
|
|||
try:
|
||||
self.token = parser.parse(self.path)
|
||||
except ElementPathSyntaxError as err:
|
||||
self.parse_error(err, elem=elem)
|
||||
self.parse_error(err)
|
||||
self.token = parser.parse('true()')
|
||||
self.path = 'true()'
|
||||
|
||||
try:
|
||||
type_attrib = elem.attrib['type']
|
||||
type_qname = self.schema.resolve_qname(attrib['type'])
|
||||
except KeyError:
|
||||
self.parse_error("missing 'type' attribute from %r" % elem)
|
||||
self.type = None
|
||||
self.parse_error("missing 'type' attribute")
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
else:
|
||||
try:
|
||||
self.type = self.maps.lookup_type(prefixed_to_qname(type_attrib, self.namespaces))
|
||||
self.type = self.maps.lookup_type(type_qname)
|
||||
except KeyError:
|
||||
self.parse_error("unknown type %r" % type_attrib, elem)
|
||||
self.parse_error("unknown type %r" % attrib['type'])
|
||||
else:
|
||||
if not self.type.is_derived(self.parent.type):
|
||||
self.parse_error("type %r ir not derived from %r" % (type_attrib, self.parent.type), elem)
|
||||
self.parse_error("type %r ir not derived from %r" % (attrib['type'], self.parent.type))
|
||||
|
||||
@property
|
||||
def built(self):
|
||||
|
|
|
@ -50,21 +50,28 @@ class XMLSchemaValidatorError(XMLSchemaException):
|
|||
if self.elem is None:
|
||||
return '%s.' % self.message
|
||||
else:
|
||||
elem, path = self.elem, self.path
|
||||
msg = ['%s:\n' % self.message]
|
||||
if elem is not None:
|
||||
if self.elem is not None:
|
||||
elem_as_string = etree_tostring(self.elem, self.namespaces, ' ', 20)
|
||||
if hasattr(elem, 'sourceline'):
|
||||
msg.append("Schema (line %r):\n\n%s\n" % (elem.sourceline, elem_as_string))
|
||||
if hasattr(self.elem, 'sourceline'):
|
||||
msg.append("Schema (line %r):\n\n%s\n" % (self.elem.sourceline, elem_as_string))
|
||||
else:
|
||||
msg.append("Schema:\n\n%s\n" % elem_as_string)
|
||||
if path is not None:
|
||||
msg.append("Path: %s\n" % path)
|
||||
if self.path is not None:
|
||||
msg.append("Path: %s\n" % self.path)
|
||||
if self.schema_url is not None:
|
||||
msg.append("Schema URL: %s\n" % self.schema_url)
|
||||
if self.origin_url not in (None, self.schema_url):
|
||||
msg.append("Origin URL: %s\n" % self.origin_url)
|
||||
return '\n'.join(msg)
|
||||
|
||||
if PY3:
|
||||
__str__ = __unicode__
|
||||
|
||||
@property
|
||||
def msg(self):
|
||||
return self.__unicode__()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name == 'elem' and value is not None and not is_etree_element(value):
|
||||
raise XMLSchemaValueError("'elem' attribute requires an Element, not %r." % type(value))
|
||||
|
@ -88,7 +95,21 @@ class XMLSchemaValidatorError(XMLSchemaException):
|
|||
try:
|
||||
return self.source.root
|
||||
except AttributeError:
|
||||
return None
|
||||
return
|
||||
|
||||
@property
|
||||
def schema_url(self):
|
||||
try:
|
||||
return self.validator.schema.source.url
|
||||
except AttributeError:
|
||||
return
|
||||
|
||||
@property
|
||||
def origin_url(self):
|
||||
try:
|
||||
return self.validator.maps.validator.source.url
|
||||
except AttributeError:
|
||||
return
|
||||
|
||||
|
||||
class XMLSchemaNotBuiltError(XMLSchemaValidatorError, RuntimeError):
|
||||
|
@ -131,6 +152,32 @@ class XMLSchemaParseError(XMLSchemaValidatorError, SyntaxError):
|
|||
)
|
||||
|
||||
|
||||
class XMLSchemaModelError(XMLSchemaValidatorError, ValueError):
|
||||
"""
|
||||
Raised when a model error is found during the checking of a model group.
|
||||
|
||||
:param group: the XSD model group.
|
||||
:type group: XsdGroup
|
||||
:param message: the error message.
|
||||
:type message: str or unicode
|
||||
"""
|
||||
def __init__(self, group, message):
|
||||
super(XMLSchemaModelError, self).__init__(
|
||||
validator=group,
|
||||
message=message,
|
||||
elem=group.elem,
|
||||
source=getattr(group, 'source', None),
|
||||
namespaces=group.namespaces
|
||||
)
|
||||
|
||||
|
||||
class XMLSchemaModelDepthError(XMLSchemaModelError):
|
||||
"""Raised when recursion depth is exceeded while iterating a model group."""
|
||||
def __init__(self, group):
|
||||
msg = "maximum model recursion depth exceeded while iterating group %r" % group
|
||||
super(XMLSchemaModelDepthError, self).__init__(group, message=msg)
|
||||
|
||||
|
||||
class XMLSchemaValidationError(XMLSchemaValidatorError, ValueError):
|
||||
"""
|
||||
Raised when the XML data is not validated with the XSD component or schema.
|
||||
|
@ -165,20 +212,19 @@ class XMLSchemaValidationError(XMLSchemaValidatorError, ValueError):
|
|||
return unicode(self).encode("utf-8")
|
||||
|
||||
def __unicode__(self):
|
||||
elem, path = self.elem, self.path
|
||||
msg = ['%s:\n' % self.message]
|
||||
if self.reason is not None:
|
||||
msg.append('Reason: %s\n' % self.reason)
|
||||
if hasattr(self.validator, 'tostring'):
|
||||
msg.append("Schema:\n\n%s\n" % self.validator.tostring(' ', 20))
|
||||
if elem is not None:
|
||||
elem_as_string = etree_tostring(elem, self.namespaces, ' ', 20)
|
||||
if hasattr(elem, 'sourceline'):
|
||||
msg.append("Instance (line %r):\n\n%s\n" % (elem.sourceline, elem_as_string))
|
||||
if self.elem is not None:
|
||||
elem_as_string = etree_tostring(self.elem, self.namespaces, ' ', 20)
|
||||
if hasattr(self.elem, 'sourceline'):
|
||||
msg.append("Instance (line %r):\n\n%s\n" % (self.elem.sourceline, elem_as_string))
|
||||
else:
|
||||
msg.append("Instance:\n\n%s\n" % elem_as_string)
|
||||
if path is not None:
|
||||
msg.append("Path: %s\n" % path)
|
||||
if self.path is not None:
|
||||
msg.append("Path: %s\n" % self.path)
|
||||
return '\n'.join(msg)
|
||||
|
||||
if PY3:
|
||||
|
@ -298,9 +344,7 @@ class XMLSchemaChildrenValidationError(XMLSchemaValidationError):
|
|||
|
||||
class XMLSchemaIncludeWarning(XMLSchemaWarning):
|
||||
"""A schema include fails."""
|
||||
pass
|
||||
|
||||
|
||||
class XMLSchemaImportWarning(XMLSchemaWarning):
|
||||
"""A schema namespace import fails."""
|
||||
pass
|
||||
|
|
|
@ -19,7 +19,7 @@ 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_DECIMAL, XSD_INTEGER, XSD_BASE64_BINARY, XSD_HEX_BINARY
|
||||
XSD_BASE64_BINARY, XSD_HEX_BINARY
|
||||
from ..regex import get_python_regex
|
||||
|
||||
from .exceptions import XMLSchemaValidationError, XMLSchemaDecodeError
|
||||
|
@ -105,7 +105,7 @@ class XsdWhiteSpaceFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</whiteSpace>
|
||||
"""
|
||||
admitted_tags = XSD_WHITE_SPACE,
|
||||
_admitted_tags = XSD_WHITE_SPACE,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = value = elem.attrib['value']
|
||||
|
@ -141,7 +141,7 @@ class XsdLengthFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</length>
|
||||
"""
|
||||
admitted_tags = XSD_LENGTH,
|
||||
_admitted_tags = XSD_LENGTH,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = int(elem.attrib['value'])
|
||||
|
@ -184,7 +184,7 @@ class XsdMinLengthFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</minLength>
|
||||
"""
|
||||
admitted_tags = XSD_MIN_LENGTH,
|
||||
_admitted_tags = XSD_MIN_LENGTH,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = int(elem.attrib['value'])
|
||||
|
@ -227,7 +227,7 @@ class XsdMaxLengthFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</maxLength>
|
||||
"""
|
||||
admitted_tags = XSD_MAX_LENGTH,
|
||||
_admitted_tags = XSD_MAX_LENGTH,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = int(elem.attrib['value'])
|
||||
|
@ -270,13 +270,25 @@ class XsdMinInclusiveFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</minInclusive>
|
||||
"""
|
||||
admitted_tags = XSD_MIN_INCLUSIVE,
|
||||
_admitted_tags = XSD_MIN_INCLUSIVE,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = self.base_type.decode(elem.attrib['value'])
|
||||
self.validator = self.min_inclusive_validator
|
||||
|
||||
def min_inclusive_validator(self, x):
|
||||
facet = self.base_type.get_facet(XSD_MIN_EXCLUSIVE)
|
||||
if facet is not None and facet.value >= self.value:
|
||||
self.parse_error("minimum value of base_type is greater")
|
||||
facet = self.base_type.get_facet(XSD_MIN_INCLUSIVE)
|
||||
if facet is not None and facet.value > self.value:
|
||||
self.parse_error("minimum value of base_type is greater")
|
||||
facet = self.base_type.get_facet(XSD_MAX_EXCLUSIVE)
|
||||
if facet is not None and facet.value <= self.value:
|
||||
self.parse_error("maximum value of base_type is lesser")
|
||||
facet = self.base_type.get_facet(XSD_MAX_INCLUSIVE)
|
||||
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)
|
||||
|
||||
|
@ -293,13 +305,25 @@ class XsdMinExclusiveFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</minExclusive>
|
||||
"""
|
||||
admitted_tags = XSD_MIN_EXCLUSIVE,
|
||||
_admitted_tags = XSD_MIN_EXCLUSIVE,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = self.base_type.decode(elem.attrib['value'])
|
||||
self.validator = self.min_exclusive_validator
|
||||
|
||||
def min_exclusive_validator(self, x):
|
||||
facet = self.base_type.get_facet(XSD_MIN_EXCLUSIVE)
|
||||
if facet is not None and facet.value > self.value:
|
||||
self.parse_error("minimum value of base_type is greater")
|
||||
facet = self.base_type.get_facet(XSD_MIN_INCLUSIVE)
|
||||
if facet is not None and facet.value > self.value:
|
||||
self.parse_error("minimum value of base_type is greater")
|
||||
facet = self.base_type.get_facet(XSD_MAX_EXCLUSIVE)
|
||||
if facet is not None and facet.value <= self.value:
|
||||
self.parse_error("maximum value of base_type is lesser")
|
||||
facet = self.base_type.get_facet(XSD_MAX_INCLUSIVE)
|
||||
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)
|
||||
|
||||
|
@ -316,13 +340,25 @@ class XsdMaxInclusiveFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</maxInclusive>
|
||||
"""
|
||||
admitted_tags = XSD_MAX_INCLUSIVE,
|
||||
_admitted_tags = XSD_MAX_INCLUSIVE,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = self.base_type.decode(elem.attrib['value'])
|
||||
self.validator = self.max_inclusive_validator
|
||||
|
||||
def max_inclusive_validator(self, x):
|
||||
facet = self.base_type.get_facet(XSD_MIN_EXCLUSIVE)
|
||||
if facet is not None and facet.value >= self.value:
|
||||
self.parse_error("minimum value of base_type is greater")
|
||||
facet = self.base_type.get_facet(XSD_MIN_INCLUSIVE)
|
||||
if facet is not None and facet.value > self.value:
|
||||
self.parse_error("minimum value of base_type is greater")
|
||||
facet = self.base_type.get_facet(XSD_MAX_EXCLUSIVE)
|
||||
if facet is not None and facet.value <= self.value:
|
||||
self.parse_error("maximum value of base_type is lesser")
|
||||
facet = self.base_type.get_facet(XSD_MAX_INCLUSIVE)
|
||||
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)
|
||||
|
||||
|
@ -339,15 +375,27 @@ class XsdMaxExclusiveFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</maxExclusive>
|
||||
"""
|
||||
admitted_tags = XSD_MAX_EXCLUSIVE,
|
||||
_admitted_tags = XSD_MAX_EXCLUSIVE,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = self.base_type.decode(elem.attrib['value'])
|
||||
self.validator = self.max_exclusive_validator
|
||||
|
||||
def max_exclusive_validator(self, x):
|
||||
facet = self.base_type.get_facet(XSD_MIN_EXCLUSIVE)
|
||||
if facet is not None and facet.value >= self.value:
|
||||
self.parse_error("minimum value of base_type is greater")
|
||||
facet = self.base_type.get_facet(XSD_MIN_INCLUSIVE)
|
||||
if facet is not None and facet.value >= self.value:
|
||||
self.parse_error("minimum value of base_type is greater")
|
||||
facet = self.base_type.get_facet(XSD_MAX_EXCLUSIVE)
|
||||
if facet is not None and facet.value < self.value:
|
||||
self.parse_error("maximum value of base_type is lesser")
|
||||
facet = self.base_type.get_facet(XSD_MAX_INCLUSIVE)
|
||||
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)
|
||||
yield XMLSchemaValidationError(self, x, "value has to be lesser than %r" % self.value)
|
||||
|
||||
|
||||
class XsdTotalDigitsFacet(XsdFacet):
|
||||
|
@ -362,7 +410,7 @@ class XsdTotalDigitsFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</totalDigits>
|
||||
"""
|
||||
admitted_tags = XSD_TOTAL_DIGITS,
|
||||
_admitted_tags = XSD_TOTAL_DIGITS,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = int(elem.attrib['value'])
|
||||
|
@ -387,18 +435,18 @@ class XsdFractionDigitsFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</fractionDigits>
|
||||
"""
|
||||
admitted_tags = XSD_FRACTION_DIGITS,
|
||||
_admitted_tags = XSD_FRACTION_DIGITS,
|
||||
|
||||
def __init__(self, elem, schema, parent, base_type):
|
||||
super(XsdFractionDigitsFacet, self).__init__(elem, schema, parent, base_type)
|
||||
if not base_type.is_subtype(XSD_DECIMAL):
|
||||
if not base_type.is_derived(self.schema.builtin_types()['decimal']):
|
||||
self.parse_error("fractionDigits facet can be applied only to types derived from xs:decimal")
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = int(elem.attrib['value'])
|
||||
if self.value < 0:
|
||||
raise ValueError("'value' must be greater or equal than 0")
|
||||
elif self.value > 0 and self.base_type.is_subtype(XSD_INTEGER):
|
||||
elif self.value > 0 and self.base_type.is_derived(self.schema.builtin_types()['integer']):
|
||||
raise ValueError("fractionDigits facet value has to be 0 for types derived from xs:integer.")
|
||||
self.validator = self.fraction_digits_validator
|
||||
|
||||
|
@ -419,7 +467,7 @@ class XsdExplicitTimezoneFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</explicitTimezone>
|
||||
"""
|
||||
admitted_tags = XSD_EXPLICIT_TIMEZONE,
|
||||
_admitted_tags = XSD_EXPLICIT_TIMEZONE,
|
||||
|
||||
def _parse_value(self, elem):
|
||||
self.value = value = elem.attrib['value']
|
||||
|
@ -450,7 +498,7 @@ class XsdEnumerationFacets(MutableSequence, XsdFacet):
|
|||
Content: (annotation?)
|
||||
</enumeration>
|
||||
"""
|
||||
admitted_tags = {XSD_ENUMERATION}
|
||||
_admitted_tags = {XSD_ENUMERATION}
|
||||
|
||||
def __init__(self, elem, schema, parent, base_type):
|
||||
XsdFacet.__init__(self, elem, schema, parent, base_type)
|
||||
|
@ -468,8 +516,15 @@ class XsdEnumerationFacets(MutableSequence, XsdFacet):
|
|||
except XMLSchemaDecodeError as err:
|
||||
self.parse_error(err, elem)
|
||||
else:
|
||||
if self.base_type.name == XSD_NOTATION_TYPE and value not in self.schema.notations:
|
||||
self.parse_error("value must match a notation global declaration", elem)
|
||||
if self.base_type.name == XSD_NOTATION_TYPE:
|
||||
try:
|
||||
notation_qname = self.schema.resolve_qname(value)
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
else:
|
||||
if notation_qname not in self.maps.notations:
|
||||
self.parse_error("value {} must match a notation global declaration".format(value), elem)
|
||||
|
||||
return value
|
||||
|
||||
# Implements the abstract methods of MutableSequence
|
||||
|
@ -517,7 +572,7 @@ class XsdPatternFacets(MutableSequence, XsdFacet):
|
|||
Content: (annotation?)
|
||||
</pattern>
|
||||
"""
|
||||
admitted_tags = {XSD_PATTERN}
|
||||
_admitted_tags = {XSD_PATTERN}
|
||||
|
||||
def __init__(self, elem, schema, parent, base_type):
|
||||
XsdFacet.__init__(self, elem, schema, parent, base_type)
|
||||
|
@ -533,7 +588,7 @@ class XsdPatternFacets(MutableSequence, XsdFacet):
|
|||
except KeyError:
|
||||
self.parse_error("missing 'value' attribute", elem)
|
||||
return re.compile(r'^$')
|
||||
except XMLSchemaDecodeError as err:
|
||||
except (re.error, XMLSchemaDecodeError) as err:
|
||||
self.parse_error(err, elem)
|
||||
return re.compile(r'^$')
|
||||
|
||||
|
@ -585,7 +640,7 @@ class XsdAssertionFacet(XsdFacet):
|
|||
Content: (annotation?)
|
||||
</assertion>
|
||||
"""
|
||||
admitted_tags = {XSD_ASSERTION}
|
||||
_admitted_tags = {XSD_ASSERTION}
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(test=%r)' % (self.__class__.__name__, self.path)
|
||||
|
|
|
@ -14,16 +14,19 @@ XSD declarations/definitions.
|
|||
"""
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
import warnings
|
||||
from collections import Counter
|
||||
|
||||
from ..exceptions import XMLSchemaKeyError, XMLSchemaTypeError, XMLSchemaValueError
|
||||
from ..exceptions import XMLSchemaKeyError, XMLSchemaTypeError, XMLSchemaValueError, XMLSchemaWarning
|
||||
from ..namespaces import XSD_NAMESPACE
|
||||
from ..qnames import XSD_INCLUDE, XSD_IMPORT, XSD_REDEFINE, XSD_OVERRIDE, XSD_NOTATION, XSD_SIMPLE_TYPE, \
|
||||
XSD_COMPLEX_TYPE, XSD_GROUP, XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_ELEMENT, XSD_ANY_TYPE
|
||||
from ..helpers import get_qname, local_name, prefixed_to_qname
|
||||
from ..qnames import XSD_INCLUDE, XSD_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 . import XMLSchemaNotBuiltError, XsdValidator, XsdKeyref, XsdComponent, XsdAttribute, \
|
||||
XsdSimpleType, XsdComplexType, XsdElement, XsdAttributeGroup, XsdGroup, XsdNotation, XsdAssert
|
||||
from . import XMLSchemaNotBuiltError, XMLSchemaModelError, XMLSchemaModelDepthError, XsdValidator, \
|
||||
XsdKeyref, XsdComponent, XsdAttribute, XsdSimpleType, XsdComplexType, XsdElement, XsdAttributeGroup, \
|
||||
XsdGroup, XsdNotation, XsdAssert
|
||||
from .builtins import xsd_builtin_types_factory
|
||||
|
||||
|
||||
|
@ -61,9 +64,12 @@ def create_load_function(filter_function):
|
|||
for schema in schemas:
|
||||
target_namespace = schema.target_namespace
|
||||
for elem in iterchildren_xsd_redefine(schema.root):
|
||||
location = elem.get('schemaLocation')
|
||||
if location is None:
|
||||
continue
|
||||
for child in filter_function(elem):
|
||||
qname = get_qname(target_namespace, child.attrib['name'])
|
||||
redefinitions.append((qname, (child, schema)))
|
||||
redefinitions.append((qname, child, schema, schema.includes[location]))
|
||||
|
||||
for elem in filter_function(schema.root):
|
||||
qname = get_qname(target_namespace, elem.attrib['name'])
|
||||
|
@ -74,17 +80,41 @@ def create_load_function(filter_function):
|
|||
except AttributeError:
|
||||
xsd_globals[qname] = [xsd_globals[qname], (elem, schema)]
|
||||
|
||||
for qname, obj in redefinitions:
|
||||
if qname not in xsd_globals:
|
||||
elem, schema = obj
|
||||
tags = Counter([x[0] for x in redefinitions])
|
||||
for qname, elem, schema, redefined_schema in redefinitions:
|
||||
|
||||
# Checks multiple redefinitions
|
||||
if tags[qname] > 1:
|
||||
tags[qname] = 1
|
||||
|
||||
redefined_schemas = [x[3] for x in redefinitions if x[0] == qname]
|
||||
if any(redefined_schemas.count(x) > 1 for x in redefined_schemas):
|
||||
schema.parse_error(
|
||||
"multiple redefinition for {} {!r}".format(local_name(elem.tag), qname), elem
|
||||
)
|
||||
else:
|
||||
redefined_schemas = {x[3]: x[2] for x in redefinitions if x[0] == qname}
|
||||
for rs, s in redefined_schemas.items():
|
||||
while True:
|
||||
try:
|
||||
s = redefined_schemas[s]
|
||||
except KeyError:
|
||||
break
|
||||
|
||||
if s is rs:
|
||||
schema.parse_error(
|
||||
"circular redefinition for {} {!r}".format(local_name(elem.tag), qname), elem
|
||||
)
|
||||
break
|
||||
|
||||
# Append redefinition
|
||||
try:
|
||||
xsd_globals[qname].append((elem, schema))
|
||||
except KeyError:
|
||||
schema.parse_error("not a redefinition!", elem)
|
||||
else:
|
||||
try:
|
||||
xsd_globals[qname].append(obj)
|
||||
except KeyError:
|
||||
xsd_globals[qname] = obj
|
||||
except AttributeError:
|
||||
xsd_globals[qname] = [xsd_globals[qname], obj]
|
||||
# xsd_globals[qname] = elem, schema
|
||||
except AttributeError:
|
||||
xsd_globals[qname] = [xsd_globals[qname], (elem, schema)]
|
||||
|
||||
return load_xsd_globals
|
||||
|
||||
|
@ -108,7 +138,10 @@ def create_lookup_function(xsd_classes):
|
|||
try:
|
||||
obj = global_map[qname]
|
||||
except KeyError:
|
||||
raise XMLSchemaKeyError("missing a %s object for %r!" % (types_desc, qname))
|
||||
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 "
|
||||
"maybe a missing default namespace declaration." % (types_desc, qname))
|
||||
else:
|
||||
if isinstance(obj, xsd_classes):
|
||||
return obj
|
||||
|
@ -130,28 +163,26 @@ def create_lookup_function(xsd_classes):
|
|||
return global_map[qname]
|
||||
|
||||
elif isinstance(obj, list):
|
||||
if not isinstance(obj[0], xsd_classes):
|
||||
# Not built XSD global component with redefinitions
|
||||
try:
|
||||
elem, schema = obj[0]
|
||||
except ValueError:
|
||||
return obj[0][0] # Circular build, simply return (elem, schema) couple
|
||||
# Not built XSD global component with redefinitions
|
||||
try:
|
||||
elem, schema = obj[0]
|
||||
except ValueError:
|
||||
return obj[0][0] # Circular build, simply return (elem, schema) couple
|
||||
|
||||
try:
|
||||
factory_or_class = tag_map[elem.tag]
|
||||
except KeyError:
|
||||
raise XMLSchemaKeyError("wrong element %r for map %r." % (elem, global_map))
|
||||
try:
|
||||
factory_or_class = tag_map[elem.tag]
|
||||
except KeyError:
|
||||
raise XMLSchemaKeyError("wrong element %r for map %r." % (elem, global_map))
|
||||
|
||||
global_map[qname] = obj[0], # To catch circular builds
|
||||
global_map[qname] = factory_or_class(elem, schema, parent=None)
|
||||
else:
|
||||
# Built-in type
|
||||
global_map[qname] = obj[0]
|
||||
global_map[qname] = obj[0], # To catch circular builds
|
||||
global_map[qname] = component = factory_or_class(elem, schema, parent=None)
|
||||
|
||||
# Apply redefinitions (changing elem involve a re-parsing of the component)
|
||||
for elem, schema in obj[1:]:
|
||||
global_map[qname].schema = schema
|
||||
global_map[qname].elem = elem
|
||||
component.redefine = component.copy()
|
||||
component.schema = schema
|
||||
component.elem = elem
|
||||
|
||||
return global_map[qname]
|
||||
|
||||
else:
|
||||
|
@ -176,13 +207,15 @@ class XsdGlobals(XsdValidator):
|
|||
declarations defined in the registered schemas. Register a schema to
|
||||
add it's declarations to the global maps.
|
||||
|
||||
:param validator: the XMLSchema class to use for global maps.
|
||||
:param validator: the origin schema class/instance used for creating the global maps.
|
||||
:param validation: the XSD validation mode to use, can be 'strict', 'lax' or 'skip'.
|
||||
"""
|
||||
def __init__(self, validator, validation='strict'):
|
||||
super(XsdGlobals, self).__init__(validation)
|
||||
self.validator = validator
|
||||
if not all(hasattr(validator, a) for a in ('meta_schema', 'BUILDERS_MAP')):
|
||||
raise XMLSchemaValueError("The argument {!r} is not an XSD schema validator".format(validator))
|
||||
|
||||
self.validator = validator
|
||||
self.namespaces = NamespaceResourcesMap() # Registered schemas by namespace URI
|
||||
|
||||
self.types = {} # Global types (both complex and simple)
|
||||
|
@ -234,20 +267,28 @@ class XsdGlobals(XsdValidator):
|
|||
def lookup_element(self, qname):
|
||||
return lookup_element(self.elements, qname, self.validator.BUILDERS_MAP)
|
||||
|
||||
def lookup(self, tag, qname):
|
||||
if tag in (XSD_SIMPLE_TYPE, XSD_COMPLEX_TYPE):
|
||||
return self.lookup_type(qname)
|
||||
elif tag == XSD_ELEMENT:
|
||||
return self.lookup_element(qname)
|
||||
elif tag == XSD_GROUP:
|
||||
return self.lookup_group(qname)
|
||||
elif tag == XSD_ATTRIBUTE:
|
||||
return self.lookup_attribute(qname)
|
||||
elif tag == XSD_ATTRIBUTE_GROUP:
|
||||
return self.lookup_attribute_group(qname)
|
||||
elif tag == XSD_NOTATION:
|
||||
return self.lookup_notation(qname)
|
||||
else:
|
||||
raise XMLSchemaValueError("wrong tag {!r} for an XSD global definition/declaration".format(tag))
|
||||
|
||||
@property
|
||||
def built(self):
|
||||
if not self.namespaces:
|
||||
return False
|
||||
xsd_global = None
|
||||
for xsd_global in self.iter_globals():
|
||||
if not isinstance(xsd_global, XsdComponent):
|
||||
for schema in self.iter_schemas():
|
||||
if not schema.built:
|
||||
return False
|
||||
if not xsd_global.built:
|
||||
return False
|
||||
if xsd_global is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def validation_attempted(self):
|
||||
|
@ -273,6 +314,13 @@ class XsdGlobals(XsdValidator):
|
|||
def resources(self):
|
||||
return [(schema.url, schema) for schemas in self.namespaces.values() for schema in schemas]
|
||||
|
||||
@property
|
||||
def all_errors(self):
|
||||
errors = []
|
||||
for schema in self.iter_schemas():
|
||||
errors.extend(schema.all_errors)
|
||||
return errors
|
||||
|
||||
def iter_components(self, xsd_classes=None):
|
||||
if xsd_classes is None or isinstance(self, xsd_classes):
|
||||
yield self
|
||||
|
@ -305,7 +353,7 @@ class XsdGlobals(XsdValidator):
|
|||
else:
|
||||
if schema in ns_schemas:
|
||||
return
|
||||
if not any([schema.url == obj.url for obj in ns_schemas]):
|
||||
elif not any([schema.url == obj.url and schema.__class__ == obj.__class__ for obj in ns_schemas]):
|
||||
ns_schemas.append(schema)
|
||||
|
||||
def clear(self, remove_schemas=False, only_unbuilt=False):
|
||||
|
@ -355,7 +403,30 @@ class XsdGlobals(XsdValidator):
|
|||
try:
|
||||
meta_schema = self.namespaces[XSD_NAMESPACE][0]
|
||||
except KeyError:
|
||||
raise XMLSchemaValueError("%r: %r namespace is not registered." % (self, XSD_NAMESPACE))
|
||||
# Meta-schemas are not registered. If any of base namespaces is already registered
|
||||
# create a new meta-schema, otherwise register the meta-schemas.
|
||||
meta_schema = self.validator.meta_schema
|
||||
if meta_schema is None:
|
||||
raise XMLSchemaValueError("{!r} has not a meta-schema".format(self.validator))
|
||||
|
||||
if any(ns in self.namespaces for ns in meta_schema.BASE_SCHEMAS):
|
||||
base_schemas = {k: v for k, v in meta_schema.BASE_SCHEMAS.items() if k not in self.namespaces}
|
||||
meta_schema = self.validator.create_meta_schema(meta_schema.url, base_schemas, self)
|
||||
for schema in self.iter_schemas():
|
||||
if schema.meta_schema is not None:
|
||||
schema.meta_schema = meta_schema
|
||||
else:
|
||||
for schema in meta_schema.maps.iter_schemas():
|
||||
self.register(schema)
|
||||
|
||||
self.types.update(meta_schema.maps.types)
|
||||
self.attributes.update(meta_schema.maps.attributes)
|
||||
self.attribute_groups.update(meta_schema.maps.attribute_groups)
|
||||
self.groups.update(meta_schema.maps.groups)
|
||||
self.notations.update(meta_schema.maps.notations)
|
||||
self.elements.update(meta_schema.maps.elements)
|
||||
self.substitution_groups.update(meta_schema.maps.substitution_groups)
|
||||
self.constraints.update(meta_schema.maps.constraints)
|
||||
|
||||
not_built_schemas = [schema for schema in self.iter_schemas() if not schema.built]
|
||||
for schema in not_built_schemas:
|
||||
|
@ -387,58 +458,62 @@ class XsdGlobals(XsdValidator):
|
|||
self.lookup_group(qname)
|
||||
|
||||
# Builds element declarations inside model groups.
|
||||
element_class = meta_schema.BUILDERS.element_class
|
||||
for schema in not_built_schemas:
|
||||
for group in schema.iter_components(XsdGroup):
|
||||
for k in range(len(group)):
|
||||
if isinstance(group[k], tuple):
|
||||
elem, schema = group[k]
|
||||
group[k] = element_class(elem, schema, group)
|
||||
group.build()
|
||||
|
||||
for schema in not_built_schemas:
|
||||
# Build substitution groups from global element declarations
|
||||
for xsd_element in schema.elements.values():
|
||||
if xsd_element.substitution_group:
|
||||
qname = prefixed_to_qname(xsd_element.substitution_group, xsd_element.schema.namespaces)
|
||||
if xsd_element.type.name == XSD_ANY_TYPE and 'type' not in xsd_element.elem.attrib:
|
||||
xsd_element.type = self.elements[qname].type
|
||||
try:
|
||||
self.substitution_groups[qname].add(xsd_element)
|
||||
except KeyError:
|
||||
self.substitution_groups[qname] = {xsd_element}
|
||||
|
||||
# Checks substitution groups
|
||||
for qname in self.substitution_groups:
|
||||
try:
|
||||
xsd_element = self.elements[qname]
|
||||
except KeyError:
|
||||
raise XMLSchemaKeyError("missing global element %r in %r." % (qname, schema))
|
||||
else:
|
||||
for e in xsd_element.iter_substitutes():
|
||||
if e is xsd_element:
|
||||
raise XMLSchemaValueError(
|
||||
"circularity found for element %r in substitution groups of %r" % (e, schema)
|
||||
)
|
||||
|
||||
if schema.meta_schema is not None:
|
||||
# Set referenced key/unique constraints for keyrefs
|
||||
for constraint in schema.iter_components(XsdKeyref):
|
||||
constraint.parse_refer()
|
||||
|
||||
for assertion in schema.iter_components(XsdAssert):
|
||||
assertion.parse()
|
||||
|
||||
# Check for illegal restrictions
|
||||
# TODO: Fix for XsdGroup.is_restriction() method is needed before enabling this check
|
||||
# if schema.validation != 'skip':
|
||||
# for xsd_type in schema.iter_components(XsdComplexType):
|
||||
# xsd_type.check_restriction()
|
||||
|
||||
if schema.XSD_VERSION > '1.0' and schema.default_attributes is not None:
|
||||
if not isinstance(schema.default_attributes, XsdAttributeGroup):
|
||||
schema.default_attributes = None
|
||||
schema.parse_error("defaultAttributes={!r} doesn't match an attribute group of {!r}"
|
||||
.format(schema.root.get('defaultAttributes'), schema), schema.root)
|
||||
for schema in filter(lambda x: x.meta_schema is not None, not_built_schemas):
|
||||
# Build key references and assertions (XSD meta-schema doesn't have any of them)
|
||||
for constraint in schema.iter_components(XsdKeyref):
|
||||
constraint.parse_refer()
|
||||
for assertion in schema.iter_components(XsdAssert):
|
||||
assertion.parse()
|
||||
self._check_schema(schema)
|
||||
|
||||
if self.validation == 'strict' and not self.built:
|
||||
raise XMLSchemaNotBuiltError(self, "global map %r not built!" % self)
|
||||
|
||||
def _check_schema(self, schema):
|
||||
# Checks substitution groups circularities
|
||||
for qname in self.substitution_groups:
|
||||
xsd_element = self.elements[qname]
|
||||
for e in xsd_element.iter_substitutes():
|
||||
if e is xsd_element:
|
||||
schema.parse_error("circularity found for substitution group with head element %r" % xsd_element)
|
||||
|
||||
if schema.XSD_VERSION > '1.0' and schema.default_attributes is not None:
|
||||
if not isinstance(schema.default_attributes, XsdAttributeGroup):
|
||||
schema.default_attributes = None
|
||||
schema.parse_error("defaultAttributes={!r} doesn't match an attribute group of {!r}"
|
||||
.format(schema.root.get('defaultAttributes'), schema), schema.root)
|
||||
|
||||
if schema.validation == 'skip':
|
||||
return
|
||||
|
||||
# Check redefined global groups
|
||||
for group in filter(lambda x: x.schema is schema and x.redefine is not None, self.groups.values()):
|
||||
if not any(isinstance(e, XsdGroup) and e.name == group.name for e in group) \
|
||||
and not group.is_restriction(group.redefine):
|
||||
group.parse_error("The redefined group is an illegal restriction of the original group.")
|
||||
|
||||
# Check complex content types models
|
||||
for xsd_type in schema.iter_components(XsdComplexType):
|
||||
if not isinstance(xsd_type.content_type, XsdGroup):
|
||||
continue
|
||||
|
||||
base_type = xsd_type.base_type
|
||||
if xsd_type.derivation == 'restriction':
|
||||
if base_type and base_type.name != XSD_ANY_TYPE and base_type.is_complex():
|
||||
if not xsd_type.content_type.is_restriction(base_type.content_type):
|
||||
xsd_type.parse_error("The derived group is an illegal restriction of the base type group.")
|
||||
|
||||
try:
|
||||
xsd_type.content_type.check_model()
|
||||
except XMLSchemaModelDepthError:
|
||||
msg = "cannot verify the content model of %r due to maximum recursion depth exceeded" % xsd_type
|
||||
schema.warnings.append(msg)
|
||||
warnings.warn(msg, XMLSchemaWarning, stacklevel=4)
|
||||
except XMLSchemaModelError as err:
|
||||
if self.validation == 'strict':
|
||||
raise
|
||||
xsd_type.errors.append(err)
|
||||
|
|
|
@ -12,22 +12,20 @@
|
|||
This module contains classes for XML Schema model groups.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from collections import Counter
|
||||
|
||||
from ..compat import PY3, unicode_type, MutableSequence
|
||||
from ..exceptions import XMLSchemaTypeError, XMLSchemaValueError
|
||||
from ..compat import unicode_type
|
||||
from ..exceptions import XMLSchemaValueError
|
||||
from ..etree import etree_element
|
||||
from ..qnames import 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, prefixed_to_qname
|
||||
from xmlschema.helpers import get_qname, local_name
|
||||
from ..converters import XMLSchemaConverter
|
||||
|
||||
from .exceptions import XMLSchemaValidationError
|
||||
from .xsdbase import ValidationMixin, XsdComponent, ParticleMixin, XsdType
|
||||
from .exceptions import XMLSchemaValidationError, XMLSchemaChildrenValidationError
|
||||
from .xsdbase import ValidationMixin, XsdComponent, XsdType
|
||||
from .elements import XsdElement
|
||||
from .wildcards import XsdAnyElement
|
||||
|
||||
XSD_GROUP_MODELS = {'sequence', 'choice', 'all'}
|
||||
from .models import MAX_MODEL_DEPTH, ParticleMixin, ModelGroup, ModelVisitor
|
||||
|
||||
ANY_ELEMENT = etree_element(
|
||||
XSD_ANY,
|
||||
|
@ -39,198 +37,7 @@ ANY_ELEMENT = etree_element(
|
|||
})
|
||||
|
||||
|
||||
class XsdModelVisitor(MutableSequence):
|
||||
"""
|
||||
A visitor design pattern class that can be used for validating XML data related to an
|
||||
XSD model group. The visit of the model is done using an external match information,
|
||||
counting the occurrences and yielding tuples in case of model's item occurrence errors.
|
||||
Ends setting the current element to `None`.
|
||||
|
||||
:param root: the root XsdGroup instance of the model.
|
||||
: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 broken: a boolean value that records if the model is still usable.
|
||||
:ivar group: the current XSD group, initialized to *root* argument.
|
||||
:ivar iterator: the current XSD group iterator.
|
||||
:ivar items: the current XSD group unmatched items.
|
||||
:ivar match: if the XSD group has an effective item match.
|
||||
"""
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.occurs = Counter()
|
||||
self._subgroups = []
|
||||
self.element = None
|
||||
self.broken = False
|
||||
self.group, self.iterator, self.items, self.match = root, iter(root), root[::-1], False
|
||||
self._start()
|
||||
|
||||
def __str__(self):
|
||||
# noinspection PyCompatibility,PyUnresolvedReferences
|
||||
return unicode(self).encode("utf-8")
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__repr__()
|
||||
|
||||
if PY3:
|
||||
__str__ = __unicode__
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(root=%r)' % (self.__class__.__name__, self.root)
|
||||
|
||||
# Implements the abstract methods of MutableSequence
|
||||
def __getitem__(self, i):
|
||||
return self._subgroups[i]
|
||||
|
||||
def __setitem__(self, i, item):
|
||||
self._subgroups[i] = item
|
||||
|
||||
def __delitem__(self, i):
|
||||
del self._subgroups[i]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._subgroups)
|
||||
|
||||
def insert(self, i, item):
|
||||
self._subgroups.insert(i, item)
|
||||
|
||||
def clear(self):
|
||||
del self._subgroups[:]
|
||||
self.occurs.clear()
|
||||
self.element = None
|
||||
self.broken = False
|
||||
self.group, self.iterator, self.items, self.match = self.root, iter(self.root), self.root[::-1], False
|
||||
|
||||
def _start(self):
|
||||
while True:
|
||||
item = next(self.iterator, None)
|
||||
if item is None or not isinstance(item, XsdGroup):
|
||||
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
|
||||
|
||||
@property
|
||||
def expected(self):
|
||||
"""
|
||||
Returns the expected elements of the current and descendant groups.
|
||||
"""
|
||||
expected = []
|
||||
for item in reversed(self.items):
|
||||
if isinstance(item, XsdGroup):
|
||||
expected.extend(item.iter_elements())
|
||||
else:
|
||||
expected.append(item)
|
||||
expected.extend(item.maps.substitution_groups.get(item.name, ()))
|
||||
return expected
|
||||
|
||||
def restart(self):
|
||||
self.clear()
|
||||
self._start()
|
||||
|
||||
def stop(self):
|
||||
while self.element is not None:
|
||||
for e in self.advance():
|
||||
yield e
|
||||
|
||||
def advance(self, match=False):
|
||||
"""
|
||||
Generator function for advance to the next element. Yields tuples with
|
||||
particles information when occurrence violation is found.
|
||||
|
||||
:param match: provides current element match.
|
||||
"""
|
||||
def stop_item(item):
|
||||
"""
|
||||
Stops element or group matching, incrementing current group counter.
|
||||
|
||||
:return: `True` if the item has violated the minimum occurrences for itself \
|
||||
or for the current group, `False` otherwise.
|
||||
"""
|
||||
if isinstance(item, XsdGroup):
|
||||
self.group, self.iterator, self.items, self.match = self.pop()
|
||||
|
||||
item_occurs = occurs[item]
|
||||
model = self.group.model
|
||||
if item_occurs:
|
||||
self.match = True
|
||||
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
|
||||
return item.is_missing(item_occurs)
|
||||
|
||||
elif model == 'sequence':
|
||||
if self.match:
|
||||
self.items.pop()
|
||||
if not self.items:
|
||||
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
|
||||
if element is None:
|
||||
raise XMLSchemaValueError("cannot advance, %r is ended!" % self)
|
||||
|
||||
if match:
|
||||
occurs[element] += 1
|
||||
self.match = True
|
||||
if not element.is_over(occurs[element]):
|
||||
return
|
||||
try:
|
||||
if stop_item(element):
|
||||
yield element, occurs[element], [element]
|
||||
|
||||
while True:
|
||||
while self.group.is_over(occurs[self.group]):
|
||||
stop_item(self.group)
|
||||
|
||||
obj = next(self.iterator, 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
|
||||
group, expected = self.group, self.items
|
||||
if stop_item(group) and expected:
|
||||
yield group, occurs[group], self.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 not isinstance(obj, XsdGroup): # XsdElement or XsdAnyElement
|
||||
self.element, occurs[obj] = obj, 0
|
||||
return
|
||||
|
||||
elif obj:
|
||||
self.append((self.group, self.iterator, self.items, self.match))
|
||||
self.group, self.iterator, self.items, self.match = obj, iter(obj), obj[::-1], False
|
||||
occurs[obj] = 0
|
||||
|
||||
except IndexError:
|
||||
self.element = None
|
||||
if self.group.is_missing(occurs[self.group]) and self.items:
|
||||
yield self.group, occurs[self.group], self.expected
|
||||
|
||||
|
||||
class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
||||
class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
|
||||
"""
|
||||
A class for XSD 1.0 model group definitions.
|
||||
|
||||
|
@ -268,23 +75,18 @@ class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
|||
Content: (annotation?, (element | group | choice | sequence | any)*)
|
||||
</sequence>
|
||||
"""
|
||||
admitted_tags = {
|
||||
XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_RESTRICTION,
|
||||
XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE
|
||||
mixed = False
|
||||
model = None
|
||||
redefine = None
|
||||
_admitted_tags = {
|
||||
XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_RESTRICTION, XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE
|
||||
}
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None, initlist=None):
|
||||
self.mixed = False if parent is None else parent.mixed
|
||||
self.model = None
|
||||
def __init__(self, elem, schema, parent, name=None):
|
||||
self._group = []
|
||||
if initlist is not None:
|
||||
if isinstance(initlist, type(self._group)):
|
||||
self._group[:] = initlist
|
||||
elif isinstance(initlist, XsdGroup):
|
||||
self._group[:] = initlist._group[:]
|
||||
else:
|
||||
self._group = list(initlist)
|
||||
XsdComponent.__init__(self, elem, schema, parent, name)
|
||||
if parent is not None and parent.mixed:
|
||||
self.mixed = parent.mixed
|
||||
super(XsdGroup, self).__init__(elem, schema, parent, name)
|
||||
|
||||
def __repr__(self):
|
||||
if self.name is None:
|
||||
|
@ -298,41 +100,18 @@ class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
|||
self.__class__.__name__, self.prefixed_name, self.model, self.occurs
|
||||
)
|
||||
|
||||
# Implements the abstract methods of MutableSequence
|
||||
def __getitem__(self, i):
|
||||
return self._group[i]
|
||||
def copy(self):
|
||||
group = object.__new__(self.__class__)
|
||||
group.__dict__.update(self.__dict__)
|
||||
group.errors = self.errors[:]
|
||||
group._group = self._group[:]
|
||||
return group
|
||||
|
||||
def __setitem__(self, i, item):
|
||||
assert isinstance(item, (tuple, ParticleMixin)), \
|
||||
"XsdGroup's items must be tuples or ParticleMixin instances."
|
||||
self._group[i] = item
|
||||
|
||||
def __delitem__(self, i):
|
||||
del self._group[i]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._group)
|
||||
|
||||
def insert(self, i, item):
|
||||
assert isinstance(item, (tuple, ParticleMixin)), \
|
||||
"XsdGroup's items must be tuples or ParticleMixin instances."
|
||||
self._group.insert(i, item)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name == 'model' and value is not None:
|
||||
if value not in XSD_GROUP_MODELS:
|
||||
raise XMLSchemaValueError("invalid model group %r." % value)
|
||||
if self.model is not None and value != self.model:
|
||||
raise XMLSchemaValueError("cannot change a valid group model: %r" % value)
|
||||
elif name == '_group':
|
||||
if not all(isinstance(item, (tuple, ParticleMixin)) for item in value):
|
||||
raise XMLSchemaValueError("XsdGroup's items must be tuples or ParticleMixin instances.")
|
||||
super(XsdGroup, self).__setattr__(name, value)
|
||||
__copy__ = copy
|
||||
|
||||
def _parse(self):
|
||||
super(XsdGroup, self)._parse()
|
||||
if self and not hasattr(self, '_elem'):
|
||||
self.clear()
|
||||
self.clear()
|
||||
elem = self.elem
|
||||
self._parse_particle(elem)
|
||||
|
||||
|
@ -343,14 +122,19 @@ class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
|||
if name is None:
|
||||
if ref is not None:
|
||||
# Reference to a global group
|
||||
if self.is_global:
|
||||
self.parse_error("a group reference cannot be global", elem)
|
||||
self.name = prefixed_to_qname(ref, self.namespaces)
|
||||
if self.parent is None:
|
||||
self.parse_error("a group reference cannot be global")
|
||||
|
||||
try:
|
||||
self.name = self.schema.resolve_qname(ref)
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
return
|
||||
|
||||
try:
|
||||
xsd_group = self.schema.maps.lookup_group(self.name)
|
||||
except KeyError:
|
||||
self.parse_error("missing group %r" % self.prefixed_name, elem)
|
||||
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):
|
||||
|
@ -361,33 +145,47 @@ class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
|||
self.append(XsdAnyElement(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.schema.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)
|
||||
else:
|
||||
self.parse_error("missing both attributes 'name' and 'ref'", elem)
|
||||
self.parse_error("missing both attributes 'name' and 'ref'")
|
||||
return
|
||||
elif ref is None:
|
||||
# Global group
|
||||
self.name = get_qname(self.target_namespace, name)
|
||||
content_model = self._parse_component(elem)
|
||||
if not self.is_global:
|
||||
self.parse_error("attribute 'name' not allowed for a local group", self)
|
||||
if self.parent is not None:
|
||||
self.parse_error("attribute 'name' not allowed for a local group")
|
||||
else:
|
||||
if 'minOccurs' in elem.attrib:
|
||||
self.parse_error(
|
||||
"attribute 'minOccurs' not allowed for a global group", self
|
||||
)
|
||||
self.parse_error("attribute 'minOccurs' not allowed for a global group")
|
||||
if 'maxOccurs' in elem.attrib:
|
||||
self.parse_error("attribute 'maxOccurs' not allowed for a global group")
|
||||
if 'minOccurs' in content_model.attrib:
|
||||
self.parse_error(
|
||||
"attribute 'maxOccurs' not allowed for a global group", self
|
||||
"attribute 'minOccurs' 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
|
||||
if 'maxOccurs' in content_model.attrib:
|
||||
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
|
||||
|
||||
else:
|
||||
self.parse_error("found both attributes 'name' and 'ref'", elem)
|
||||
self.parse_error("found both attributes 'name' and 'ref'")
|
||||
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}:
|
||||
|
@ -418,37 +216,85 @@ class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
|||
elif child.tag in (XSD_SEQUENCE, XSD_CHOICE):
|
||||
self.append(XsdGroup(child, self.schema, self))
|
||||
elif child.tag == XSD_GROUP:
|
||||
xsd_group = XsdGroup(child, self.schema, self)
|
||||
if xsd_group.name != self.name:
|
||||
self.append(xsd_group)
|
||||
elif not hasattr(self, '_elem'):
|
||||
self.parse_error("Circular definitions detected for group %r:" % self.ref, elem)
|
||||
try:
|
||||
ref = self.schema.resolve_qname(child.attrib['ref'])
|
||||
except KeyError:
|
||||
self.parse_error("missing attribute 'ref' in local group", child)
|
||||
continue
|
||||
|
||||
if ref != self.name:
|
||||
xsd_group = XsdGroup(child, self.schema, self)
|
||||
if xsd_group.model == 'all':
|
||||
self.parse_error("'all' model can appears only at 1st level of a model group")
|
||||
else:
|
||||
self.append(xsd_group)
|
||||
elif self.redefine is None:
|
||||
self.parse_error("Circular definition detected for group %r:" % self.ref, elem)
|
||||
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
|
||||
)
|
||||
self.append(self.redefine)
|
||||
else:
|
||||
continue # Error already caught by validation against the meta-schema
|
||||
|
||||
@property
|
||||
def schema_elem(self):
|
||||
return self.elem if self.name else self.parent.elem
|
||||
def children_validation_error(self, validation, elem, index, particle, occurs=0, expected=None,
|
||||
source=None, namespaces=None, **_kwargs):
|
||||
"""
|
||||
Helper method for generating model validation errors. Incompatible with 'skip' validation mode.
|
||||
Il validation mode is 'lax' returns the error, otherwise raise the error.
|
||||
|
||||
:param validation: the validation mode. Can be 'lax' or 'strict'.
|
||||
:param elem: the instance Element.
|
||||
:param index: the child index.
|
||||
:param particle: the XSD component (subgroup or element) associated to the child.
|
||||
:param occurs: the child tag occurs.
|
||||
:param expected: the expected element tags/object names.
|
||||
:param source: the XML resource related to the validation process.
|
||||
:param namespaces: is an optional mapping from namespace prefix to URI.
|
||||
:param _kwargs: keyword arguments of the validation process that are not used.
|
||||
"""
|
||||
if validation == 'skip':
|
||||
raise XMLSchemaValueError("validation mode 'skip' incompatible with error generation.")
|
||||
|
||||
error = XMLSchemaChildrenValidationError(self, elem, index, particle, occurs, expected, source, namespaces)
|
||||
if validation == 'strict':
|
||||
raise error
|
||||
else:
|
||||
return error
|
||||
|
||||
def build(self):
|
||||
element_class = self.schema.BUILDERS.element_class
|
||||
for k in range(len(self._group)):
|
||||
if isinstance(self._group[k], tuple):
|
||||
elem, schema = self._group[k]
|
||||
self._group[k] = element_class(elem, schema, self)
|
||||
|
||||
if self.redefine is not None:
|
||||
for group in self.redefine.iter_components(XsdGroup):
|
||||
group.build()
|
||||
|
||||
@property
|
||||
def built(self):
|
||||
for item in self:
|
||||
if isinstance(item, XsdAnyElement):
|
||||
if not isinstance(item, ParticleMixin):
|
||||
return False
|
||||
elif isinstance(item, XsdAnyElement):
|
||||
if not item.built:
|
||||
return False
|
||||
elif isinstance(item, tuple):
|
||||
return False
|
||||
elif not isinstance(item, (XsdElement, XsdGroup)):
|
||||
raise XMLSchemaTypeError("wrong type for item %r of %r." % (item, self))
|
||||
elif item.parent is None:
|
||||
continue
|
||||
elif item.parent is not self.parent and isinstance(item.parent, XsdType) and item.parent.parent is None:
|
||||
continue
|
||||
elif not item.ref and not item.built:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def schema_elem(self):
|
||||
return self.elem if self.name else self.parent.elem
|
||||
|
||||
@property
|
||||
def validation_attempted(self):
|
||||
if self.built:
|
||||
|
@ -473,111 +319,192 @@ class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
|||
for obj in item.iter_components(xsd_classes):
|
||||
yield obj
|
||||
|
||||
def clear(self):
|
||||
del self._group[:]
|
||||
def admitted_restriction(self, model):
|
||||
if self.model == model:
|
||||
return True
|
||||
elif self.model == 'all' and model == 'choice' and len(self) > 1:
|
||||
return False
|
||||
elif model == 'all' and self.model == 'choice' and len(self) > 1:
|
||||
return False
|
||||
if model == 'sequence' and self.model != 'sequence' and len(self) > 1:
|
||||
return False
|
||||
|
||||
def is_empty(self):
|
||||
return not self.mixed and not self
|
||||
|
||||
def is_emptiable(self):
|
||||
if self.model == 'choice':
|
||||
return self.min_occurs == 0 or not self or any([item.is_emptiable() for item in self])
|
||||
else:
|
||||
return self.min_occurs == 0 or not self or all([item.is_emptiable() for item in self])
|
||||
|
||||
def is_meaningless(self, parent_group=None):
|
||||
"""
|
||||
A group that may be eliminated. A group is meaningless if one of those conditions is verified:
|
||||
|
||||
- the group is empty
|
||||
- minOccurs == maxOccurs == 1 and the group has one child
|
||||
- minOccurs == maxOccurs == 1 and the group and its parent have a sequence model
|
||||
- minOccurs == maxOccurs == 1 and the group and its parent have a choice model
|
||||
"""
|
||||
def is_restriction(self, other, check_occurs=True):
|
||||
if not self:
|
||||
return True
|
||||
elif self.min_occurs != 1 or self.max_occurs != 1:
|
||||
return False
|
||||
elif len(self) == 1:
|
||||
return True
|
||||
elif parent_group is None:
|
||||
return False
|
||||
elif self.model == 'sequence' and parent_group.model != 'sequence':
|
||||
return False
|
||||
elif self.model == 'choice' and parent_group.model != 'choice':
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def is_restriction(self, other, check_particle=True):
|
||||
if not isinstance(other, XsdGroup):
|
||||
return False
|
||||
elif not self:
|
||||
return True
|
||||
elif self.ref is not None:
|
||||
return self[0].is_restriction(other, check_occurs)
|
||||
elif not isinstance(other, ParticleMixin):
|
||||
raise XMLSchemaValueError("the argument 'base' must be a %r instance" % ParticleMixin)
|
||||
elif not isinstance(other, XsdGroup):
|
||||
return self.is_element_restriction(other)
|
||||
elif not other:
|
||||
return False
|
||||
elif other.model == 'sequence' and self.model != 'sequence':
|
||||
return False
|
||||
elif other.model == 'choice' and self.model == 'all':
|
||||
return False
|
||||
elif other.model == 'all' and self.model == 'choice':
|
||||
return False
|
||||
elif check_particle and not super(XsdGroup, self).is_restriction(other):
|
||||
return False
|
||||
elif other.ref:
|
||||
return self.is_restriction(other[0], check_occurs)
|
||||
elif len(other) == other.min_occurs == other.max_occurs == 1:
|
||||
if len(self) > 1:
|
||||
return self.is_restriction(other[0], check_occurs)
|
||||
elif isinstance(self[0], XsdGroup) and self[0].is_pointless(parent=self):
|
||||
return self[0].is_restriction(other[0], check_occurs)
|
||||
|
||||
other_iterator = iter(other.iter_group())
|
||||
for item in self.iter_group():
|
||||
# Compare model with model
|
||||
if self.model != other.model and self.model != 'sequence' and len(self) > 1:
|
||||
return False
|
||||
elif self.model == other.model or other.model == 'sequence':
|
||||
return self.is_sequence_restriction(other)
|
||||
elif other.model == 'all':
|
||||
return self.is_all_restriction(other)
|
||||
elif other.model == 'choice':
|
||||
return self.is_choice_restriction(other)
|
||||
|
||||
def is_element_restriction(self, other):
|
||||
if self.schema.XSD_VERSION == '1.0' and isinstance(other, XsdElement) and \
|
||||
not other.ref and other.name not in self.schema.substitution_groups:
|
||||
return False
|
||||
elif not self.has_occurs_restriction(other):
|
||||
return False
|
||||
elif self.model == 'choice':
|
||||
if other.name in self.maps.substitution_groups and all(
|
||||
isinstance(e, XsdElement) and e.substitution_group == other.name for e in self):
|
||||
return True
|
||||
return any(e.is_restriction(other, False) for e in self)
|
||||
else:
|
||||
min_occurs = max_occurs = 0
|
||||
for item in self.iter_model():
|
||||
if isinstance(item, XsdGroup):
|
||||
return False
|
||||
elif item.min_occurs == 0 or item.is_restriction(other, False):
|
||||
min_occurs += item.min_occurs
|
||||
if max_occurs is not None:
|
||||
if item.max_occurs is None:
|
||||
max_occurs = None
|
||||
else:
|
||||
max_occurs += item.max_occurs
|
||||
continue
|
||||
return False
|
||||
|
||||
if min_occurs < other.min_occurs:
|
||||
return False
|
||||
elif max_occurs is None:
|
||||
return other.max_occurs is None
|
||||
elif other.max_occurs is None:
|
||||
return True
|
||||
else:
|
||||
return max_occurs <= other.max_occurs
|
||||
|
||||
def is_sequence_restriction(self, other):
|
||||
if not self.has_occurs_restriction(other):
|
||||
return False
|
||||
check_occurs = other.max_occurs != 0
|
||||
check_emptiable = other.model != 'choice' # or self.schema.XSD_VERSION == '1.0'
|
||||
|
||||
# Same model: declarations must simply preserve order
|
||||
other_iterator = iter(other.iter_model())
|
||||
for item in self.iter_model():
|
||||
while True:
|
||||
try:
|
||||
other_item = next(other_iterator)
|
||||
except StopIteration:
|
||||
return False
|
||||
|
||||
if other_item is item:
|
||||
if other_item is item or item.is_restriction(other_item, check_occurs):
|
||||
break
|
||||
elif item.is_restriction(other_item):
|
||||
break
|
||||
elif other.model == 'choice':
|
||||
continue
|
||||
elif other_item.is_emptiable():
|
||||
continue
|
||||
elif isinstance(other_item, XsdGroup) and other_item.model == 'choice' and \
|
||||
other_item.max_occurs == 1:
|
||||
if any(item.is_restriction(s) for s in other_item.iter_group()):
|
||||
break
|
||||
else:
|
||||
elif check_emptiable and not other_item.is_emptiable():
|
||||
return False
|
||||
|
||||
return True
|
||||
if not check_emptiable:
|
||||
return True
|
||||
|
||||
def iter_group(self):
|
||||
"""Creates an iterator for sub elements and groups. Skips meaningless groups."""
|
||||
for item in self:
|
||||
if not isinstance(item, XsdGroup):
|
||||
yield item
|
||||
elif item.is_global or not item.is_meaningless(self):
|
||||
yield item
|
||||
while True:
|
||||
try:
|
||||
other_item = next(other_iterator)
|
||||
except StopIteration:
|
||||
return True
|
||||
else:
|
||||
for obj in item.iter_group():
|
||||
yield obj
|
||||
if not other_item.is_emptiable():
|
||||
return False
|
||||
|
||||
def iter_subelements(self):
|
||||
for item in self:
|
||||
if isinstance(item, XsdGroup):
|
||||
for e in item.iter_subelements():
|
||||
yield e
|
||||
else:
|
||||
yield item
|
||||
def is_all_restriction(self, other):
|
||||
if not self.has_occurs_restriction(other):
|
||||
return False
|
||||
|
||||
def iter_elements(self):
|
||||
for item in self:
|
||||
if isinstance(item, XsdGroup):
|
||||
for e in item.iter_elements():
|
||||
yield e
|
||||
check_occurs = other.max_occurs != 0
|
||||
restriction_items = list(self)
|
||||
|
||||
for other_item in other.iter_model():
|
||||
for item in restriction_items:
|
||||
if other_item is item or item.is_restriction(other_item, check_occurs):
|
||||
break
|
||||
else:
|
||||
yield item
|
||||
for e in self.maps.substitution_groups.get(item.name, ()):
|
||||
yield e
|
||||
if not other_item.is_emptiable():
|
||||
return False
|
||||
continue
|
||||
restriction_items.remove(item)
|
||||
|
||||
return not bool(restriction_items)
|
||||
|
||||
def is_choice_restriction(self, other):
|
||||
if self.parent is None and other.parent is not None and self.schema.XSD_VERSION == '1.0':
|
||||
return False
|
||||
|
||||
check_occurs = other.max_occurs != 0
|
||||
restriction_items = list(self)
|
||||
max_occurs = 0
|
||||
other_max_occurs = 0
|
||||
|
||||
for other_item in other.iter_model():
|
||||
for item in restriction_items:
|
||||
|
||||
if other_item is item or item.is_restriction(other_item, check_occurs):
|
||||
if max_occurs is not None:
|
||||
if item.max_occurs is None:
|
||||
max_occurs = None
|
||||
else:
|
||||
max_occurs += item.max_occurs
|
||||
|
||||
if other_max_occurs is not None:
|
||||
if other_item.max_occurs is None:
|
||||
other_max_occurs = None
|
||||
else:
|
||||
other_max_occurs = max(other_max_occurs, other_item.max_occurs)
|
||||
break
|
||||
else:
|
||||
continue
|
||||
restriction_items.remove(item)
|
||||
|
||||
if restriction_items:
|
||||
return False
|
||||
elif other_max_occurs is None:
|
||||
if other.max_occurs:
|
||||
return True
|
||||
other_max_occurs = 0
|
||||
elif other.max_occurs is None:
|
||||
if other_max_occurs:
|
||||
return True
|
||||
other_max_occurs = 0
|
||||
else:
|
||||
other_max_occurs *= other.max_occurs
|
||||
|
||||
if max_occurs is None:
|
||||
return self.max_occurs == 0
|
||||
elif self.max_occurs is None:
|
||||
return max_occurs == 0
|
||||
else:
|
||||
return other_max_occurs >= max_occurs * self.max_occurs
|
||||
|
||||
def iter_elements(self, depth=0):
|
||||
if depth <= MAX_MODEL_DEPTH:
|
||||
for item in self:
|
||||
if isinstance(item, XsdGroup):
|
||||
for e in item.iter_elements(depth+1):
|
||||
yield e
|
||||
else:
|
||||
yield item
|
||||
for e in self.maps.substitution_groups.get(item.name, ()):
|
||||
yield e
|
||||
|
||||
def sort_children(self, elements, default_namespace=None):
|
||||
"""
|
||||
|
@ -626,7 +553,7 @@ class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
|||
result_list.append((cdata_index, text, None))
|
||||
cdata_index += 1
|
||||
|
||||
model = XsdModelVisitor(self)
|
||||
model = ModelVisitor(self)
|
||||
errors = []
|
||||
|
||||
if not isinstance(converter, XMLSchemaConverter):
|
||||
|
@ -734,7 +661,7 @@ class XsdGroup(MutableSequence, XsdComponent, ValidationMixin, ParticleMixin):
|
|||
default_namespace = converter.get('')
|
||||
losslessly = converter.losslessly
|
||||
|
||||
model = XsdModelVisitor(self)
|
||||
model = ModelVisitor(self)
|
||||
cdata_index = 0
|
||||
|
||||
for index, (name, value) in enumerate(element_data.content):
|
||||
|
@ -875,10 +802,21 @@ class Xsd11Group(XsdGroup):
|
|||
elif child.tag in (XSD_SEQUENCE, XSD_CHOICE, XSD_ALL):
|
||||
self.append(XsdGroup(child, self.schema, self))
|
||||
elif child.tag == XSD_GROUP:
|
||||
xsd_group = XsdGroup(child, self.schema, self)
|
||||
if xsd_group.name != self.name:
|
||||
self.append(xsd_group)
|
||||
elif not hasattr(self, '_elem'):
|
||||
self.parse_error("Circular definitions detected for group %r:" % self.ref, elem)
|
||||
try:
|
||||
ref = self.schema.resolve_qname(child.attrib['ref'])
|
||||
except KeyError:
|
||||
self.parse_error("missing attribute 'ref' in local group", child)
|
||||
continue
|
||||
|
||||
if ref != self.name:
|
||||
self.append(XsdGroup(child, self.schema, self))
|
||||
elif self.redefine is None:
|
||||
self.parse_error("Circular definition detected for group %r:" % self.ref, elem)
|
||||
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
|
||||
)
|
||||
self.append(self.redefine)
|
||||
else:
|
||||
continue # Error already caught by validation against the meta-schema
|
||||
|
|
|
@ -12,24 +12,26 @@
|
|||
This module contains classes for other XML Schema identity constraints.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
from collections import Counter
|
||||
from elementpath import Selector, XPath1Parser, ElementPathSyntaxError
|
||||
from elementpath import Selector, XPath1Parser, ElementPathSyntaxError, ElementPathKeyError
|
||||
|
||||
from ..exceptions import XMLSchemaValueError
|
||||
from ..qnames import XSD_UNIQUE, XSD_KEY, XSD_KEYREF, XSD_SELECTOR, XSD_FIELD
|
||||
from ..helpers import get_qname, prefixed_to_qname, qname_to_prefixed
|
||||
from ..helpers import get_qname, qname_to_prefixed
|
||||
from ..etree import etree_getpath
|
||||
from ..regex import get_python_regex
|
||||
|
||||
from .exceptions import XMLSchemaValidationError
|
||||
from .xsdbase import XsdComponent
|
||||
|
||||
XSD_IDENTITY_XPATH_SYMBOLS = {
|
||||
'processing-instruction', 'descendant-or-self', 'following-sibling', 'preceding-sibling',
|
||||
'ancestor-or-self', 'descendant', 'attribute', 'following', 'namespace', 'preceding',
|
||||
'ancestor', 'position', 'comment', 'parent', 'child', 'self', 'false', 'text', 'node',
|
||||
'processing-instruction', 'following-sibling', 'preceding-sibling',
|
||||
'ancestor-or-self', 'attribute', 'following', 'namespace', 'preceding',
|
||||
'ancestor', 'position', 'comment', 'parent', 'child', 'false', 'text', 'node',
|
||||
'true', 'last', 'not', 'and', 'mod', 'div', 'or', '..', '//', '!=', '<=', '>=', '(', ')',
|
||||
'[', ']', '.', '@', ',', '/', '|', '*', '-', '=', '+', '<', '>', ':', '(end)', '(name)',
|
||||
'(string)', '(float)', '(decimal)', '(integer)'
|
||||
'(string)', '(float)', '(decimal)', '(integer)', '::'
|
||||
}
|
||||
|
||||
|
||||
|
@ -42,7 +44,11 @@ XsdIdentityXPathParser.build_tokenizer()
|
|||
|
||||
|
||||
class XsdSelector(XsdComponent):
|
||||
admitted_tags = {XSD_SELECTOR}
|
||||
_admitted_tags = {XSD_SELECTOR}
|
||||
pattern = re.compile(get_python_regex(
|
||||
r"(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*(\|"
|
||||
r"(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*)*"
|
||||
))
|
||||
|
||||
def __init__(self, elem, schema, parent):
|
||||
super(XsdSelector, self).__init__(elem, schema, parent)
|
||||
|
@ -54,10 +60,13 @@ class XsdSelector(XsdComponent):
|
|||
except KeyError:
|
||||
self.parse_error("'xpath' attribute required:", self.elem)
|
||||
self.path = "*"
|
||||
else:
|
||||
if not self.pattern.match(self.path.replace(' ', '')):
|
||||
self.parse_error("Wrong XPath expression for an xs:selector")
|
||||
|
||||
try:
|
||||
self.xpath_selector = Selector(self.path, self.namespaces, parser=XsdIdentityXPathParser)
|
||||
except ElementPathSyntaxError as err:
|
||||
except (ElementPathSyntaxError, ElementPathKeyError) as err:
|
||||
self.parse_error(err)
|
||||
self.xpath_selector = Selector('*', self.namespaces, parser=XsdIdentityXPathParser)
|
||||
|
||||
|
@ -77,7 +86,12 @@ class XsdSelector(XsdComponent):
|
|||
|
||||
|
||||
class XsdFieldSelector(XsdSelector):
|
||||
admitted_tags = {XSD_FIELD}
|
||||
_admitted_tags = {XSD_FIELD}
|
||||
pattern = re.compile(get_python_regex(
|
||||
r"(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|"
|
||||
r"((attribute::|@)((\i\c*:)?(\i\c*|\*))))(\|(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*"
|
||||
r"((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|((attribute::|@)((\i\c*:)?(\i\c*|\*)))))*"
|
||||
))
|
||||
|
||||
|
||||
class XsdIdentity(XsdComponent):
|
||||
|
@ -107,6 +121,10 @@ class XsdIdentity(XsdComponent):
|
|||
else:
|
||||
self.parse_error("element %r not allowed here:" % child.tag, elem)
|
||||
|
||||
def iter_elements(self):
|
||||
for xsd_element in self.selector.xpath_selector.iter_select(self.parent):
|
||||
yield xsd_element
|
||||
|
||||
def get_fields(self, context, decoders=None):
|
||||
"""
|
||||
Get fields for a schema or instance context element.
|
||||
|
@ -191,11 +209,11 @@ class XsdIdentity(XsdComponent):
|
|||
|
||||
|
||||
class XsdUnique(XsdIdentity):
|
||||
admitted_tags = {XSD_UNIQUE}
|
||||
_admitted_tags = {XSD_UNIQUE}
|
||||
|
||||
|
||||
class XsdKey(XsdIdentity):
|
||||
admitted_tags = {XSD_KEY}
|
||||
_admitted_tags = {XSD_KEY}
|
||||
|
||||
|
||||
class XsdKeyref(XsdIdentity):
|
||||
|
@ -205,12 +223,9 @@ class XsdKeyref(XsdIdentity):
|
|||
:ivar refer: reference to a *xs:key* declaration that must be in the same element \
|
||||
or in a descendant element.
|
||||
"""
|
||||
admitted_tags = {XSD_KEYREF}
|
||||
|
||||
def __init__(self, elem, schema, parent):
|
||||
self.refer = None
|
||||
self.refer_path = '.'
|
||||
super(XsdKeyref, self).__init__(elem, schema, parent)
|
||||
_admitted_tags = {XSD_KEYREF}
|
||||
refer = None
|
||||
refer_path = '.'
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(name=%r, refer=%r)' % (
|
||||
|
@ -220,14 +235,16 @@ class XsdKeyref(XsdIdentity):
|
|||
def _parse(self):
|
||||
super(XsdKeyref, self)._parse()
|
||||
try:
|
||||
self.refer = prefixed_to_qname(self.elem.attrib['refer'], self.namespaces)
|
||||
self.refer = self.schema.resolve_qname(self.elem.attrib['refer'])
|
||||
except KeyError:
|
||||
self.parse_error("missing required attribute 'refer'", self.elem)
|
||||
self.parse_error("missing required attribute 'refer'")
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
|
||||
def parse_refer(self):
|
||||
if self.refer is None:
|
||||
return # attribute or key/unique identity constraint missing
|
||||
elif isinstance(self.refer, XsdIdentity):
|
||||
elif isinstance(self.refer, (XsdKey, XsdUnique)):
|
||||
return # referenced key/unique identity constraint already set
|
||||
|
||||
try:
|
||||
|
@ -236,23 +253,26 @@ class XsdKeyref(XsdIdentity):
|
|||
try:
|
||||
self.refer = self.maps.constraints[self.refer]
|
||||
except KeyError:
|
||||
self.parse_error("refer=%r must reference to a key/unique identity "
|
||||
"constraint." % self.elem.get('refer'))
|
||||
self.refer = None
|
||||
else:
|
||||
refer_path = []
|
||||
xsd_component = self.refer.parent
|
||||
while xsd_component is not None:
|
||||
if xsd_component is self.parent:
|
||||
refer_path.reverse()
|
||||
self.refer_path = '/'.join(refer_path)
|
||||
break
|
||||
elif hasattr(xsd_component, 'tag'):
|
||||
refer_path.append(xsd_component.name)
|
||||
xsd_component = xsd_component.parent
|
||||
else:
|
||||
self.parse_error("%r is not defined in a descendant element." % self.elem.get('refer'))
|
||||
self.refer = None
|
||||
self.parse_error("key/unique identity constraint %r is missing" % self.refer)
|
||||
return
|
||||
|
||||
if not isinstance(self.refer, (XsdKey, XsdUnique)):
|
||||
self.parse_error("reference to a non key/unique identity constraint %r" % self.refer)
|
||||
elif len(self.refer.fields) != len(self.fields):
|
||||
self.parse_error("field cardinality mismatch between %r and %r" % (self, self.refer))
|
||||
elif self.parent is not self.refer.parent:
|
||||
refer_path = self.refer.parent.get_path(ancestor=self.parent)
|
||||
if refer_path is None:
|
||||
# From a note in par. 3.11.5 Part 1 of XSD 1.0 spec: "keyref identity-constraints may be
|
||||
# defined on domains distinct from the embedded domain of the identity-constraint they
|
||||
# reference, or the domains may be the same but self-embedding at some depth. In either
|
||||
# case the node table for the referenced identity-constraint needs to propagate upwards,
|
||||
# with conflict resolution."
|
||||
refer_path = self.parent.get_path(ancestor=self.refer.parent, reverse=True)
|
||||
if refer_path is None:
|
||||
refer_path = self.parent.get_path(reverse=True) + '/' + self.refer.parent.get_path()
|
||||
|
||||
self.refer_path = refer_path
|
||||
|
||||
def get_refer_values(self, elem):
|
||||
values = set()
|
||||
|
|
|
@ -0,0 +1,469 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2016-2019, SISSA (International School for Advanced Studies).
|
||||
# All rights reserved.
|
||||
# This file is distributed under the terms of the MIT License.
|
||||
# See the file 'LICENSE' in the root directory of the present
|
||||
# distribution, or http://opensource.org/licenses/MIT.
|
||||
#
|
||||
# @author Davide Brunato <brunato@sissa.it>
|
||||
#
|
||||
"""
|
||||
This module contains classes and functions for processing XSD content models.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from collections import Counter
|
||||
|
||||
from ..compat import PY3, MutableSequence
|
||||
from ..exceptions import XMLSchemaValueError
|
||||
from .exceptions import XMLSchemaModelError, XMLSchemaModelDepthError
|
||||
from .xsdbase import ParticleMixin
|
||||
|
||||
MAX_MODEL_DEPTH = 15
|
||||
"""Limit depth for safe visiting of models"""
|
||||
|
||||
XSD_GROUP_MODELS = {'sequence', 'choice', 'all'}
|
||||
|
||||
|
||||
class ModelGroup(MutableSequence, ParticleMixin):
|
||||
"""
|
||||
Class for XSD model group particles. This class implements only model related methods,
|
||||
schema element parsing and validation methods are implemented in derived classes.
|
||||
"""
|
||||
def __init__(self, model):
|
||||
assert model in XSD_GROUP_MODELS, "Not a valid value for 'model'"
|
||||
self._group = []
|
||||
self.model = model
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(model=%r, occurs=%r)' % (self.__class__.__name__, self.model, self.occurs)
|
||||
|
||||
# Implements the abstract methods of MutableSequence
|
||||
def __getitem__(self, i):
|
||||
return self._group[i]
|
||||
|
||||
def __setitem__(self, i, item):
|
||||
assert isinstance(item, (tuple, ParticleMixin)), "Items must be tuples or XSD particles"
|
||||
self._group[i] = item
|
||||
|
||||
def __delitem__(self, i):
|
||||
del self._group[i]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._group)
|
||||
|
||||
def insert(self, i, item):
|
||||
assert isinstance(item, (tuple, ParticleMixin)), "Items must be tuples or XSD particles"
|
||||
self._group.insert(i, item)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name == 'model' and value is not None:
|
||||
if value not in XSD_GROUP_MODELS:
|
||||
raise XMLSchemaValueError("invalid model group %r." % value)
|
||||
if self.model is not None and value != self.model and self.model != 'all':
|
||||
raise XMLSchemaValueError("cannot change group model from %r to %r" % (self.model, value))
|
||||
elif name == '_group':
|
||||
if not all(isinstance(item, (tuple, ParticleMixin)) for item in value):
|
||||
raise XMLSchemaValueError("XsdGroup's items must be tuples or ParticleMixin instances.")
|
||||
super(ModelGroup, self).__setattr__(name, value)
|
||||
|
||||
def clear(self):
|
||||
del self._group[:]
|
||||
|
||||
def is_emptiable(self):
|
||||
if self.model == 'choice':
|
||||
return self.min_occurs == 0 or not self or any([item.is_emptiable() for item in self])
|
||||
else:
|
||||
return self.min_occurs == 0 or not self or all([item.is_emptiable() for item in self])
|
||||
|
||||
def is_empty(self):
|
||||
return not self._group or self.max_occurs == 0
|
||||
|
||||
def is_pointless(self, parent):
|
||||
"""
|
||||
Returns `True` if the group may be eliminated without affecting the model, `False` otherwise.
|
||||
A group is pointless if one of those conditions is verified:
|
||||
|
||||
- the group is empty
|
||||
- minOccurs == maxOccurs == 1 and the group has one child
|
||||
- minOccurs == maxOccurs == 1 and the group and its parent have a sequence model
|
||||
- minOccurs == maxOccurs == 1 and the group and its parent have a choice model
|
||||
|
||||
Ref: https://www.w3.org/TR/2004/REC-xmlschema-1-20041028/#coss-particle
|
||||
|
||||
:param parent: effective parent of the model group.
|
||||
"""
|
||||
if not self:
|
||||
return True
|
||||
elif self.min_occurs != 1 or self.max_occurs != 1:
|
||||
return False
|
||||
elif len(self) == 1:
|
||||
return True
|
||||
elif not isinstance(parent, ModelGroup):
|
||||
return False
|
||||
elif self.model == 'sequence' and parent.model != 'sequence':
|
||||
return False
|
||||
elif self.model == 'choice' and parent.model != 'choice':
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def has_occurs_restriction(self, other):
|
||||
if not self:
|
||||
return True
|
||||
elif isinstance(other, ModelGroup):
|
||||
return super(ModelGroup, self).has_occurs_restriction(other)
|
||||
|
||||
# Group particle compared to element particle
|
||||
if self.max_occurs is None or any(e.max_occurs is None for e in self):
|
||||
if other.max_occurs is not None:
|
||||
return False
|
||||
elif self.model == 'choice':
|
||||
return self.min_occurs * min(e.min_occurs for e in self) >= other.min_occurs
|
||||
else:
|
||||
return self.min_occurs * sum(e.min_occurs for e in self) >= other.min_occurs
|
||||
|
||||
elif self.model == 'choice':
|
||||
if self.min_occurs * min(e.min_occurs for e in self) < other.min_occurs:
|
||||
return False
|
||||
elif other.max_occurs is None:
|
||||
return True
|
||||
else:
|
||||
return self.max_occurs * max(e.max_occurs for e in self) <= other.max_occurs
|
||||
|
||||
else:
|
||||
if self.min_occurs * sum(e.min_occurs for e in self) < other.min_occurs:
|
||||
return False
|
||||
elif other.max_occurs is None:
|
||||
return True
|
||||
else:
|
||||
return self.max_occurs * sum(e.max_occurs for e in self) <= other.max_occurs
|
||||
|
||||
def iter_model(self, depth=0):
|
||||
"""
|
||||
A generator function iterating elements and groups of a model group. Skips pointless groups,
|
||||
iterating deeper through them. Raises `XMLSchemaModelDepthError` if the argument *depth* is
|
||||
over `MAX_MODEL_DEPTH` value.
|
||||
|
||||
:param depth: guard for protect model nesting bombs, incremented at each deepest recursion.
|
||||
"""
|
||||
if depth > MAX_MODEL_DEPTH:
|
||||
raise XMLSchemaModelDepthError(self)
|
||||
for item in self:
|
||||
if not isinstance(item, ModelGroup):
|
||||
yield item
|
||||
elif not item.is_pointless(parent=self):
|
||||
yield item
|
||||
else:
|
||||
for obj in item.iter_model(depth+1):
|
||||
yield obj
|
||||
|
||||
def iter_elements(self, depth=0):
|
||||
"""
|
||||
A generator function iterating model's elements. Raises `XMLSchemaModelDepthError` if the
|
||||
argument *depth* is over `MAX_MODEL_DEPTH` value.
|
||||
|
||||
:param depth: guard for protect model nesting bombs, incremented at each deepest recursion.
|
||||
"""
|
||||
if depth > MAX_MODEL_DEPTH:
|
||||
raise XMLSchemaModelDepthError(self)
|
||||
for item in self:
|
||||
if isinstance(item, ModelGroup):
|
||||
for e in item.iter_elements(depth+1):
|
||||
yield e
|
||||
else:
|
||||
yield item
|
||||
|
||||
def iter_subelements(self, depth=0):
|
||||
if depth <= MAX_MODEL_DEPTH:
|
||||
for item in self:
|
||||
if isinstance(item, ModelGroup):
|
||||
for e in item.iter_subelements(depth+1):
|
||||
yield e
|
||||
else:
|
||||
yield item
|
||||
|
||||
def check_model(self):
|
||||
"""
|
||||
Checks if the model group is deterministic. Types matching of same elements and Unique Particle
|
||||
Attribution Constraint are checked. Raises an `XMLSchemaModelError` at first violated constraint.
|
||||
"""
|
||||
def safe_iter_path(group, depth):
|
||||
if depth > MAX_MODEL_DEPTH:
|
||||
raise XMLSchemaModelDepthError(group)
|
||||
for item in group:
|
||||
if isinstance(item, ModelGroup):
|
||||
current_path.append(item)
|
||||
for _item in safe_iter_path(item, depth + 1):
|
||||
yield _item
|
||||
current_path.pop()
|
||||
else:
|
||||
yield item
|
||||
|
||||
paths = {}
|
||||
current_path = [self]
|
||||
for e in safe_iter_path(self, 0):
|
||||
for pe, previous_path in paths.values():
|
||||
if pe.name == e.name and pe.name is not None and pe.type is not e.type:
|
||||
raise XMLSchemaModelError(
|
||||
self, "The model has elements with the same name %r but a different type" % e.name
|
||||
)
|
||||
elif not pe.overlap(e):
|
||||
continue
|
||||
elif pe is not e and 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))
|
||||
elif pe.min_occurs == pe.max_occurs:
|
||||
continue
|
||||
|
||||
if not distinguishable_paths(previous_path + [pe], current_path + [e]):
|
||||
raise XMLSchemaModelError(
|
||||
self, "Unique Particle Attribution violation between {!r} and {!r}".format(pe, e)
|
||||
)
|
||||
paths[e.name] = e, current_path[:]
|
||||
|
||||
|
||||
def distinguishable_paths(path1, path2):
|
||||
"""
|
||||
Checks if two model paths are distinguishable in a deterministic way, without looking forward
|
||||
or backtracking. The arguments are lists containing paths from the base group of the model to
|
||||
a couple of leaf elements. Returns `True` if there is a deterministic separation between paths,
|
||||
`False` if the paths are ambiguous.
|
||||
"""
|
||||
e1, e2 = path1[-1], path2[-1]
|
||||
|
||||
for k, e in enumerate(path1):
|
||||
if e not in path2:
|
||||
depth = k - 1
|
||||
break
|
||||
else:
|
||||
depth = 0
|
||||
|
||||
if path1[depth].max_occurs == 0:
|
||||
return True
|
||||
|
||||
univocal1 = univocal2 = True
|
||||
if path1[depth].model == 'sequence':
|
||||
idx1 = path1[depth].index(path1[depth + 1])
|
||||
idx2 = path2[depth].index(path2[depth + 1])
|
||||
before1 = any(not e.is_emptiable() for e in path1[depth][:idx1])
|
||||
after1 = before2 = any(not e.is_emptiable() for e in path1[depth][idx1 + 1:idx2])
|
||||
after2 = any(not e.is_emptiable() for e in path1[depth][idx2 + 1:])
|
||||
else:
|
||||
before1 = after1 = before2 = after2 = False
|
||||
|
||||
for k in range(depth + 1, len(path1) - 1):
|
||||
univocal1 &= path1[k].is_univocal()
|
||||
if path1[k].model == 'sequence':
|
||||
idx = path1[k].index(path1[k + 1])
|
||||
before1 |= any(not e.is_emptiable() for e in path1[k][:idx])
|
||||
after1 |= any(not e.is_emptiable() for e in path1[k][idx + 1:])
|
||||
|
||||
for k in range(depth + 1, len(path2) - 1):
|
||||
univocal2 &= path2[k].is_univocal()
|
||||
if path2[k].model == 'sequence':
|
||||
idx = path2[k].index(path2[k + 1])
|
||||
before2 |= any(not e.is_emptiable() for e in path2[k][:idx])
|
||||
after2 |= any(not e.is_emptiable() for e in path2[k][idx + 1:])
|
||||
|
||||
if path1[depth].model != 'sequence':
|
||||
return before1 and before2 or \
|
||||
(before1 and (univocal1 and e1.is_univocal() or after1 or path1[depth].max_occurs == 1)) or \
|
||||
(before2 and (univocal2 and e2.is_univocal() or after2 or path2[depth].max_occurs == 1))
|
||||
elif path1[depth].max_occurs == 1:
|
||||
return before2 or (before1 or univocal1) and (e1.is_univocal() or after1)
|
||||
else:
|
||||
return (before2 or (before1 or univocal1) and (e1.is_univocal() or after1)) and \
|
||||
(before1 or (before2 or univocal2) and (e2.is_univocal() or after2))
|
||||
|
||||
|
||||
class ModelVisitor(MutableSequence):
|
||||
"""
|
||||
A visitor design pattern class that can be used for validating XML data related to an XSD
|
||||
model group. The visit of the model is done using an external match information,
|
||||
counting the occurrences and yielding tuples in case of model's item occurrence errors.
|
||||
Ends setting the current element to `None`.
|
||||
|
||||
:param root: the root ModelGroup instance of the model.
|
||||
: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 broken: a boolean value that records if the model is still usable.
|
||||
: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 match: if the XSD group has an effective item match.
|
||||
"""
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.occurs = Counter()
|
||||
self._subgroups = []
|
||||
self.element = None
|
||||
self.broken = False
|
||||
self.group, self.iterator, self.items, self.match = root, iter(root), root[::-1], False
|
||||
self._start()
|
||||
|
||||
def __str__(self):
|
||||
# noinspection PyCompatibility,PyUnresolvedReferences
|
||||
return unicode(self).encode("utf-8")
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__repr__()
|
||||
|
||||
if PY3:
|
||||
__str__ = __unicode__
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(root=%r)' % (self.__class__.__name__, self.root)
|
||||
|
||||
# Implements the abstract methods of MutableSequence
|
||||
def __getitem__(self, i):
|
||||
return self._subgroups[i]
|
||||
|
||||
def __setitem__(self, i, item):
|
||||
self._subgroups[i] = item
|
||||
|
||||
def __delitem__(self, i):
|
||||
del self._subgroups[i]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._subgroups)
|
||||
|
||||
def insert(self, i, item):
|
||||
self._subgroups.insert(i, item)
|
||||
|
||||
def clear(self):
|
||||
del self._subgroups[:]
|
||||
self.occurs.clear()
|
||||
self.element = None
|
||||
self.broken = False
|
||||
self.group, self.iterator, self.items, self.match = self.root, iter(self.root), self.root[::-1], False
|
||||
|
||||
def _start(self):
|
||||
while True:
|
||||
item = next(self.iterator, 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
|
||||
|
||||
@property
|
||||
def expected(self):
|
||||
"""
|
||||
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())
|
||||
else:
|
||||
expected.append(item)
|
||||
expected.extend(item.maps.substitution_groups.get(item.name, ()))
|
||||
return expected
|
||||
|
||||
def restart(self):
|
||||
self.clear()
|
||||
self._start()
|
||||
|
||||
def stop(self):
|
||||
while self.element is not None:
|
||||
for e in self.advance():
|
||||
yield e
|
||||
|
||||
def advance(self, match=False):
|
||||
"""
|
||||
Generator function for advance to the next element. Yields tuples with
|
||||
particles information when occurrence violation is found.
|
||||
|
||||
:param match: provides current element match.
|
||||
"""
|
||||
def stop_item(item):
|
||||
"""
|
||||
Stops element or group matching, incrementing current group counter.
|
||||
|
||||
:return: `True` if the item has violated the minimum occurrences for itself \
|
||||
or for the current group, `False` otherwise.
|
||||
"""
|
||||
if isinstance(item, ModelGroup):
|
||||
self.group, self.iterator, self.items, self.match = self.pop()
|
||||
|
||||
item_occurs = occurs[item]
|
||||
model = self.group.model
|
||||
if item_occurs:
|
||||
self.match = True
|
||||
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
|
||||
return item.is_missing(item_occurs)
|
||||
|
||||
elif model == 'sequence':
|
||||
if self.match:
|
||||
self.items.pop()
|
||||
if not self.items:
|
||||
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
|
||||
if element is None:
|
||||
raise XMLSchemaValueError("cannot advance, %r is ended!" % self)
|
||||
|
||||
if match:
|
||||
occurs[element] += 1
|
||||
self.match = True
|
||||
if not element.is_over(occurs[element]):
|
||||
return
|
||||
try:
|
||||
if stop_item(element):
|
||||
yield element, occurs[element], [element]
|
||||
|
||||
while True:
|
||||
while self.group.is_over(occurs[self.group]):
|
||||
stop_item(self.group)
|
||||
|
||||
obj = next(self.iterator, 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
|
||||
group, expected = self.group, self.items
|
||||
if stop_item(group) and expected:
|
||||
yield group, occurs[group], self.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 not isinstance(obj, ModelGroup): # XsdElement or XsdAnyElement
|
||||
self.element, occurs[obj] = obj, 0
|
||||
return
|
||||
|
||||
elif obj:
|
||||
self.append((self.group, self.iterator, self.items, self.match))
|
||||
self.group, self.iterator, self.items, self.match = obj, iter(obj), obj[::-1], False
|
||||
occurs[obj] = 0
|
||||
|
||||
except IndexError:
|
||||
self.element = None
|
||||
if self.group.is_missing(occurs[self.group]) and self.items:
|
||||
yield self.group, occurs[self.group], self.expected
|
|
@ -30,7 +30,7 @@ class XsdNotation(XsdComponent):
|
|||
Content: (annotation?)
|
||||
</notation>
|
||||
"""
|
||||
admitted_tags = {XSD_NOTATION}
|
||||
_admitted_tags = {XSD_NOTATION}
|
||||
|
||||
def __init__(self, elem, schema, parent):
|
||||
if parent is not None:
|
||||
|
@ -50,11 +50,8 @@ class XsdNotation(XsdComponent):
|
|||
except KeyError:
|
||||
self.parse_error("a notation must have a 'name'.", self.elem)
|
||||
|
||||
for key in self.elem.attrib:
|
||||
if key not in {'id', 'name', 'public', 'system'}:
|
||||
self.parse_error("wrong attribute %r for notation definition." % key, self.elem)
|
||||
if 'public' not in self.elem.attrib and 'system' not in self.elem.attrib:
|
||||
self.parse_error("a notation may have 'public' or 'system' attribute.", self.elem)
|
||||
if 'public' not in self.elem.attrib and 'system' not in self.elem.attrib:
|
||||
self.parse_error("a notation must has a 'public' or a 'system' attribute.", self.elem)
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
|
|
|
@ -28,33 +28,37 @@ Those are the differences between XSD 1.0 and XSD 1.1 and their current developm
|
|||
* schema overrides
|
||||
"""
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from collections import namedtuple, Counter
|
||||
from abc import ABCMeta
|
||||
import warnings
|
||||
import elementpath
|
||||
|
||||
from ..compat import add_metaclass
|
||||
from ..exceptions import XMLSchemaTypeError, XMLSchemaURLError, XMLSchemaValueError, XMLSchemaOSError
|
||||
from ..qnames import XSD_SCHEMA, XSD_NOTATION, XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_SIMPLE_TYPE, \
|
||||
XSD_COMPLEX_TYPE, XSD_GROUP, XSD_ELEMENT, XSD_SEQUENCE, XSD_ANY, XSD_ANY_ATTRIBUTE
|
||||
from ..helpers import prefixed_to_qname, has_xsd_components, get_xsd_derivation_attribute
|
||||
from ..namespaces import XSD_NAMESPACE, XML_NAMESPACE, HFP_NAMESPACE, XSI_NAMESPACE, XHTML_NAMESPACE, \
|
||||
from ..qnames import 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_REDEFINE, XSD_OVERRIDE
|
||||
from ..helpers import has_xsd_components, get_xsd_derivation_attribute, get_xsd_form_attribute
|
||||
from ..namespaces import XSD_NAMESPACE, XML_NAMESPACE, XSI_NAMESPACE, XHTML_NAMESPACE, \
|
||||
XLINK_NAMESPACE, NamespaceResourcesMap, NamespaceView
|
||||
from ..etree import etree_element, etree_tostring
|
||||
from ..etree import etree_element, etree_tostring, ParseError
|
||||
from ..resources import is_remote_url, url_path_is_file, fetch_resource, XMLResource
|
||||
from ..converters import XMLSchemaConverter
|
||||
from ..xpath import ElementPathMixin
|
||||
|
||||
from . import (
|
||||
XMLSchemaParseError, XMLSchemaValidationError, XMLSchemaEncodeError, XMLSchemaNotBuiltError,
|
||||
XMLSchemaIncludeWarning, XMLSchemaImportWarning, XsdValidator, ValidationMixin, XsdComponent,
|
||||
XsdNotation, XsdComplexType, XsdAttribute, XsdElement, XsdAttributeGroup, XsdGroup, Xsd11Group,
|
||||
XsdAnyElement, XsdAnyAttribute, Xsd11Attribute, Xsd11Element, Xsd11AnyElement, XsdGlobals,
|
||||
Xsd11AnyAttribute, Xsd11ComplexType, xsd_simple_type_factory,
|
||||
XsdAtomicRestriction, Xsd11AtomicRestriction
|
||||
)
|
||||
from .exceptions import XMLSchemaParseError, XMLSchemaValidationError, XMLSchemaEncodeError, \
|
||||
XMLSchemaNotBuiltError, XMLSchemaIncludeWarning, XMLSchemaImportWarning
|
||||
from .xsdbase import XsdValidator, ValidationMixin, XsdComponent
|
||||
from .notations import XsdNotation
|
||||
from .simple_types import xsd_simple_type_factory, XsdUnion, XsdAtomicRestriction, \
|
||||
Xsd11AtomicRestriction, Xsd11Union
|
||||
from .attributes import XsdAttribute, XsdAttributeGroup, Xsd11Attribute
|
||||
from .complex_types import XsdComplexType, Xsd11ComplexType
|
||||
from .groups import XsdGroup, Xsd11Group
|
||||
from .elements import XsdElement, Xsd11Element
|
||||
from .wildcards import XsdAnyElement, XsdAnyAttribute, Xsd11AnyElement, Xsd11AnyAttribute
|
||||
from .globals_ import iterchildren_xsd_import, iterchildren_xsd_include, \
|
||||
iterchildren_xsd_redefine, iterchildren_xsd_override
|
||||
iterchildren_xsd_redefine, iterchildren_xsd_override, XsdGlobals
|
||||
|
||||
|
||||
# Elements for building dummy groups
|
||||
|
@ -90,7 +94,9 @@ class XMLSchemaMeta(ABCMeta):
|
|||
|
||||
meta_schema = dict_.get('meta_schema') or get_attribute('meta_schema', *bases)
|
||||
if meta_schema is None:
|
||||
# Defining a subclass without a meta-schema (eg. XMLSchemaBase)
|
||||
return super(XMLSchemaMeta, mcs).__new__(mcs, name, bases, dict_)
|
||||
dict_['meta_schema'] = None
|
||||
|
||||
xsd_version = dict_.get('XSD_VERSION') or get_attribute('XSD_VERSION', *bases)
|
||||
if xsd_version not in ('1.0', '1.1'):
|
||||
|
@ -113,23 +119,15 @@ class XMLSchemaMeta(ABCMeta):
|
|||
elif get_attribute('BUILDERS_MAP', *bases) is None:
|
||||
raise XMLSchemaValueError("Validator class doesn't have a builder map for XSD globals.")
|
||||
|
||||
dict_['meta_schema'] = None
|
||||
if isinstance(meta_schema, XMLSchemaBase):
|
||||
meta_schema = meta_schema.url
|
||||
|
||||
# Build the meta-schema class
|
||||
# Build the new meta-schema class
|
||||
meta_schema_class_name = 'Meta' + name
|
||||
meta_schema_class = super(XMLSchemaMeta, mcs).__new__(mcs, meta_schema_class_name, bases, dict_)
|
||||
meta_schema_class.__qualname__ = meta_schema_class_name
|
||||
meta_schema = meta_schema_class(meta_schema, defuse='never', build=False)
|
||||
globals()[meta_schema_class_name] = meta_schema_class
|
||||
|
||||
base_schemas = dict_.get('BASE_SCHEMAS') or get_attribute('BASE_SCHEMAS', *bases)
|
||||
for uri, pathname in list(base_schemas.items()):
|
||||
if uri == XSD_NAMESPACE:
|
||||
meta_schema.include_schema(location=pathname)
|
||||
else:
|
||||
meta_schema.import_schema(namespace=uri, location=pathname)
|
||||
# Build the new meta-schema instance
|
||||
schema_location = meta_schema.url if isinstance(meta_schema, XMLSchemaBase) else meta_schema
|
||||
meta_schema = meta_schema_class.create_meta_schema(schema_location)
|
||||
meta_schema.maps.build()
|
||||
dict_['meta_schema'] = meta_schema
|
||||
|
||||
|
@ -173,6 +171,10 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
:type timeout: int
|
||||
:param build: defines whether build the schema maps. Default is `True`.
|
||||
:type build: bool
|
||||
:param use_meta: if `True` the schema processor uses the package meta-schema, otherwise the \
|
||||
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
|
||||
|
||||
:cvar XSD_VERSION: store the XSD version (1.0 or 1.1).
|
||||
:vartype XSD_VERSION: str
|
||||
|
@ -186,6 +188,16 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
:vartype BASE_SCHEMAS: 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'.
|
||||
:vartype attribute_form_default: str
|
||||
:cvar element_form_default: the schema's *elementFormDefault* attribute, defaults to 'unqualified'
|
||||
:vartype element_form_default: str
|
||||
:cvar block_default: the schema's *blockDefault* attribute, defaults to ''.
|
||||
:vartype block_default: str
|
||||
:cvar final_default: the schema's *finalDefault* attribute, defaults to ''.
|
||||
:vartype final_default: str
|
||||
:cvar default_attributes: the XSD 1.1 schema's *defaultAttributes* attribute, defaults to ``None``.
|
||||
:vartype default_attributes: XsdAttributeGroup
|
||||
|
||||
:ivar target_namespace: is the *targetNamespace* of the schema, the namespace to which \
|
||||
belong the declarations/definitions of the schema. If it's empty no namespace is associated \
|
||||
|
@ -203,8 +215,14 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
:vartype locations: NamespaceResourcesMap
|
||||
:ivar namespaces: a dictionary that maps from the prefixes used by the schema into namespace URI.
|
||||
:vartype namespaces: dict
|
||||
:ivar imports: a dictionary of namespace imports of the schema, that maps namespace URI to imported schema \
|
||||
object, or `None` in case of unsuccessful import.
|
||||
:vartype imports: dict
|
||||
:ivar includes: a dictionary of included schemas, that maps a schema location to an included schema. \
|
||||
It also comprehend schemas included by "xs:redefine" or "xs:override" statements.
|
||||
:vartype warnings: dict
|
||||
:ivar warnings: warning messages about failure of import and include elements.
|
||||
:vartype namespaces: list
|
||||
:vartype warnings: list
|
||||
|
||||
:ivar notations: `xsd:notation` declarations.
|
||||
:vartype notations: NamespaceView
|
||||
|
@ -225,70 +243,105 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
BASE_SCHEMAS = None
|
||||
meta_schema = 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):
|
||||
super(XMLSchemaBase, self).__init__(validation)
|
||||
try:
|
||||
self.source = XMLResource(source, base_url, defuse, timeout, lazy=False)
|
||||
except (XMLSchemaTypeError, OSError, IOError) as err:
|
||||
raise type(err)('cannot create schema: %s' % err)
|
||||
# Schema defaults
|
||||
target_namespace = ''
|
||||
attribute_form_default = 'unqualified'
|
||||
element_form_default = 'unqualified'
|
||||
block_default = ''
|
||||
final_default = ''
|
||||
default_attributes = None # for XSD 1.1
|
||||
|
||||
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):
|
||||
super(XMLSchemaBase, self).__init__(validation)
|
||||
self.source = XMLResource(source, base_url, defuse, timeout, lazy=False)
|
||||
self.imports = {}
|
||||
self.includes = {}
|
||||
self.warnings = []
|
||||
self._root_elements = None
|
||||
root = self.source.root
|
||||
|
||||
# Set and check target namespace
|
||||
self.target_namespace = root.get('targetNamespace', '')
|
||||
if self.target_namespace == XSD_NAMESPACE and self.meta_schema is not None:
|
||||
raise XMLSchemaValueError("The %r cannot be used as target namespace!" % XSD_NAMESPACE)
|
||||
# Parse namespaces and targetNamespace
|
||||
self.namespaces = {'xml': XML_NAMESPACE} # the XML namespace is implicit
|
||||
self.namespaces.update(self.source.get_namespaces())
|
||||
|
||||
try:
|
||||
self.target_namespace = root.attrib['targetNamespace']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if self.target_namespace == '':
|
||||
# Ref: https://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-schema
|
||||
self.parse_error("The attribute 'targetNamespace' cannot be an empty string.", root)
|
||||
|
||||
if namespace is not None and self.target_namespace != namespace:
|
||||
if self.target_namespace:
|
||||
msg = u"wrong namespace (%r instead of %r) for XSD resource %r."
|
||||
self.parse_error(msg % (self.target_namespace, namespace, self.url), root)
|
||||
else:
|
||||
self.target_namespace = namespace # Chameleon schema
|
||||
|
||||
# Chameleon schema case: set the target namespace and the default namespace
|
||||
self.target_namespace = namespace
|
||||
if '' not in self.namespaces:
|
||||
self.namespaces[''] = namespace
|
||||
|
||||
# Parses the schema defaults
|
||||
if 'attributeFormDefault' in root.attrib:
|
||||
try:
|
||||
self.attribute_form_default = get_xsd_form_attribute(root, 'attributeFormDefault')
|
||||
except ValueError as err:
|
||||
self.parse_error(err, root)
|
||||
|
||||
if 'elementFormDefault' in root.attrib:
|
||||
try:
|
||||
self.element_form_default = get_xsd_form_attribute(root, 'elementFormDefault')
|
||||
except ValueError as err:
|
||||
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 'finalDefault' in root.attrib:
|
||||
try:
|
||||
self.final_default = get_xsd_derivation_attribute(root, 'finalDefault')
|
||||
except ValueError as err:
|
||||
self.parse_error(err, root)
|
||||
|
||||
if self.XSD_VERSION > '1.0':
|
||||
# XSD 1.1: "defaultAttributes" and "xpathDefaultNamespace"
|
||||
self.xpath_default_namespace = self._parse_xpath_default_namespace(root)
|
||||
if 'defaultAttributes' in root.attrib:
|
||||
try:
|
||||
self.default_attributes = self.resolve_qname(root.attrib['defaultAttributes'])
|
||||
except XMLSchemaValueError as error:
|
||||
self.parse_error(str(error), root)
|
||||
|
||||
# Set locations hints map and converter
|
||||
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.namespaces = {'xml': XML_NAMESPACE} # the XML namespace is implicit
|
||||
self.namespaces.update(self.source.get_namespaces())
|
||||
if '' not in self.namespaces:
|
||||
# For default local names are mapped to targetNamespace
|
||||
self.namespaces[''] = self.target_namespace
|
||||
|
||||
self.converter = self.get_converter(converter)
|
||||
|
||||
# XSD 1.1 attributes "defaultAttributes" and "xpathDefaultNamespace"
|
||||
if self.XSD_VERSION > '1.0':
|
||||
self.xpath_default_namespace = self._parse_xpath_default_namespace(root)
|
||||
try:
|
||||
self.default_attributes = prefixed_to_qname(root.attrib['defaultAttributes'], self.namespaces)
|
||||
except KeyError:
|
||||
self.default_attributes = None
|
||||
except XMLSchemaValueError as error:
|
||||
self.parse_error(str(error), root)
|
||||
self.default_attributes = None
|
||||
|
||||
# Create or set the XSD global maps instance
|
||||
if global_maps is None:
|
||||
if self.meta_schema is None:
|
||||
self.maps = XsdGlobals(self.__class__)
|
||||
elif self.target_namespace in self.BASE_SCHEMAS:
|
||||
# Change the meta-schema instance
|
||||
meta_schema_class = self.meta_schema.__class__
|
||||
meta_schema = meta_schema_class(self.meta_schema.url, build=False)
|
||||
for uri, pathname in list(self.BASE_SCHEMAS.items()):
|
||||
if uri == self.target_namespace:
|
||||
meta_schema.import_schema(namespace=uri, location=self.url)
|
||||
else:
|
||||
meta_schema.import_schema(namespace=uri, location=pathname)
|
||||
self.meta_schema = meta_schema
|
||||
self.maps = self.meta_schema.maps
|
||||
else:
|
||||
if self.meta_schema is None:
|
||||
self.maps = global_maps or XsdGlobals(self)
|
||||
return # Meta-schemas don't need to be checked or built and don't process include/imports
|
||||
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:
|
||||
self.maps = self.meta_schema.maps.copy(self, validation=validation)
|
||||
else:
|
||||
base_schemas = {k: v for k, v in self.BASE_SCHEMAS.items() if k != self.target_namespace}
|
||||
meta_schema = self.create_meta_schema(base_schemas=base_schemas)
|
||||
self.maps = meta_schema.maps
|
||||
self.meta_schema = meta_schema
|
||||
|
||||
elif isinstance(global_maps, XsdGlobals):
|
||||
self.maps = global_maps
|
||||
|
@ -296,10 +349,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
raise XMLSchemaTypeError("'global_maps' argument must be a %r instance." % XsdGlobals)
|
||||
|
||||
# Validate the schema document
|
||||
if self.meta_schema is None:
|
||||
# Base schemas use single file and don't have to be checked
|
||||
return
|
||||
elif validation == 'strict':
|
||||
if validation == 'strict':
|
||||
self.check_schema(root, self.namespaces)
|
||||
elif validation == 'lax':
|
||||
self.errors.extend([e for e in self.meta_schema.iter_errors(root, namespaces=self.namespaces)])
|
||||
|
@ -308,6 +358,9 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
self._include_schemas()
|
||||
self._import_namespaces()
|
||||
|
||||
if '' not in self.namespaces:
|
||||
self.namespaces[''] = '' # For default local names are mapped to no namespace
|
||||
|
||||
if build:
|
||||
self.maps.build()
|
||||
|
||||
|
@ -321,11 +374,10 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
def __setattr__(self, name, value):
|
||||
if name == 'root' and value.tag not in (XSD_SCHEMA, 'schema'):
|
||||
raise XMLSchemaValueError("schema root element must has %r tag." % XSD_SCHEMA)
|
||||
elif name == 'validation':
|
||||
if value not in ('strict', 'lax', 'skip'):
|
||||
raise XMLSchemaValueError("Wrong value %r for attribute 'validation'." % value)
|
||||
elif name == 'maps':
|
||||
value.register(self)
|
||||
if self.meta_schema is None and hasattr(self, 'maps'):
|
||||
raise XMLSchemaValueError("cannot change the global maps instance of a meta-schema")
|
||||
super(XMLSchemaBase, self).__setattr__(name, value)
|
||||
self.notations = NamespaceView(value.notations, self.target_namespace)
|
||||
self.types = NamespaceView(value.types, self.target_namespace)
|
||||
self.attributes = NamespaceView(value.attributes, self.target_namespace)
|
||||
|
@ -336,7 +388,11 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
self.constraints = NamespaceView(value.constraints, self.target_namespace)
|
||||
self.global_maps = (self.notations, self.types, self.attributes,
|
||||
self.attribute_groups, self.groups, self.elements)
|
||||
super(XMLSchemaBase, self).__setattr__(name, value)
|
||||
value.register(self)
|
||||
elif name == 'validation' and value not in ('strict', 'lax', 'skip'):
|
||||
raise XMLSchemaValueError("Wrong value %r for attribute 'validation'." % value)
|
||||
else:
|
||||
super(XMLSchemaBase, self).__setattr__(name, value)
|
||||
|
||||
def __iter__(self):
|
||||
for xsd_element in sorted(self.elements.values(), key=lambda x: x.name):
|
||||
|
@ -390,6 +446,11 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
"""Timeout in seconds for fetching resources."""
|
||||
return self.source.timeout
|
||||
|
||||
@property
|
||||
def use_meta(self):
|
||||
"""Returns `True` if the meta-schema is imported."""
|
||||
return self.meta_schema is not None and XSD_NAMESPACE in self.maps.namespaces
|
||||
|
||||
# Schema root attributes
|
||||
@property
|
||||
def tag(self):
|
||||
|
@ -406,30 +467,6 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
"""The schema's *version* attribute, defaults to ``None``."""
|
||||
return self.root.get('version')
|
||||
|
||||
@property
|
||||
def attribute_form_default(self):
|
||||
"""The schema's *attributeFormDefault* attribute, defaults to ``'unqualified'``"""
|
||||
return self.root.get('attributeFormDefault', 'unqualified')
|
||||
|
||||
@property
|
||||
def element_form_default(self):
|
||||
"""The schema's *elementFormDefault* attribute, defaults to ``'unqualified'``."""
|
||||
return self.root.get('elementFormDefault', 'unqualified')
|
||||
|
||||
@property
|
||||
def block_default(self):
|
||||
"""The schema's *blockDefault* attribute, defaults to ``None``."""
|
||||
return get_xsd_derivation_attribute(
|
||||
self.root, 'blockDefault', ('extension', 'restriction', 'substitution')
|
||||
)
|
||||
|
||||
@property
|
||||
def final_default(self):
|
||||
"""The schema's *finalDefault* attribute, defaults to ``None``."""
|
||||
return get_xsd_derivation_attribute(
|
||||
self.root, 'finalDefault', ('extension', 'restriction', 'list', 'union')
|
||||
)
|
||||
|
||||
@property
|
||||
def schema_location(self):
|
||||
"""A list of location hints extracted from the *xsi:schemaLocation* attribute of the schema."""
|
||||
|
@ -461,7 +498,9 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
try:
|
||||
return cls.meta_schema.maps.namespaces[XSD_NAMESPACE][0].types
|
||||
except KeyError:
|
||||
raise XMLSchemaNotBuiltError(cls.meta_schema, "missing XSD namespace in meta-schema.")
|
||||
raise XMLSchemaNotBuiltError(cls.meta_schema, "missing XSD namespace in meta-schema")
|
||||
except AttributeError:
|
||||
raise XMLSchemaNotBuiltError(cls.meta_schema, "meta-schema unavailable for %r" % cls)
|
||||
|
||||
@property
|
||||
def root_elements(self):
|
||||
|
@ -489,6 +528,46 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
|
||||
return [e for e in self.elements.values() if e.name in self._root_elements]
|
||||
|
||||
@classmethod
|
||||
def create_meta_schema(cls, source=None, base_schemas=None, global_maps=None):
|
||||
"""
|
||||
Creates a new meta-schema instance.
|
||||
|
||||
: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 global_maps: is an optional argument containing an :class:`XsdGlobals` \
|
||||
instance for the new meta schema. If not provided a new map is created.
|
||||
"""
|
||||
if source is None:
|
||||
try:
|
||||
source = cls.meta_schema.url
|
||||
except AttributeError:
|
||||
raise XMLSchemaValueError(
|
||||
"The argument 'source' is required when the class doesn't already have a meta-schema"
|
||||
)
|
||||
|
||||
if base_schemas is None:
|
||||
base_schemas = cls.BASE_SCHEMAS.items()
|
||||
elif isinstance(base_schemas, dict):
|
||||
base_schemas = base_schemas.items()
|
||||
else:
|
||||
try:
|
||||
base_schemas = [(n, l) for n, l in base_schemas]
|
||||
except ValueError:
|
||||
raise ValueError("The argument 'base_schemas' is not a dictionary nor a sequence of items")
|
||||
|
||||
meta_schema_class = cls if cls.meta_schema is None else cls.meta_schema.__class__
|
||||
meta_schema = meta_schema_class(source, XSD_NAMESPACE, global_maps=global_maps, defuse='never', build=False)
|
||||
for ns, location in base_schemas:
|
||||
if ns == XSD_NAMESPACE:
|
||||
meta_schema.include_schema(location=location)
|
||||
else:
|
||||
meta_schema.import_schema(namespace=ns, location=location)
|
||||
return meta_schema
|
||||
|
||||
@classmethod
|
||||
def create_schema(cls, *args, **kwargs):
|
||||
"""Creates a new schema instance of the same class of the caller."""
|
||||
|
@ -506,6 +585,22 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
attribute_group[None] = XsdAnyAttribute(ANY_ATTRIBUTE_ELEMENT, self, attribute_group)
|
||||
return attribute_group
|
||||
|
||||
def copy(self):
|
||||
"""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()
|
||||
schema.errors = self.errors[:]
|
||||
schema.warnings = self.warnings[:]
|
||||
schema.namespaces = self.namespaces.copy()
|
||||
schema.locations = NamespaceResourcesMap(self.locations)
|
||||
schema.imports = dict(self.imports)
|
||||
schema.includes = dict(self.includes)
|
||||
schema.maps = self.maps.copy(validator=schema)
|
||||
return schema
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
@classmethod
|
||||
def check_schema(cls, schema, namespaces=None):
|
||||
"""
|
||||
|
@ -526,25 +621,38 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
@property
|
||||
def built(self):
|
||||
xsd_global = None
|
||||
for xsd_global in self.iter_globals():
|
||||
for xsd_global in self.iter_globals(self):
|
||||
if not isinstance(xsd_global, XsdComponent):
|
||||
return False
|
||||
if not xsd_global.built:
|
||||
return False
|
||||
|
||||
if xsd_global is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
prefix = '{%s}' % self.target_namespace if self.target_namespace else ''
|
||||
for child in filter(lambda x: x.tag != XSD_ANNOTATION, self.root):
|
||||
if child.tag in (XSD_REDEFINE, XSD_OVERRIDE):
|
||||
for e in filter(lambda x: x.tag in self.BUILDERS_MAP, child):
|
||||
name = e.get('name')
|
||||
if name is not None:
|
||||
try:
|
||||
if not self.maps.lookup(e.tag, prefix + name if prefix else name).built:
|
||||
return False
|
||||
except KeyError:
|
||||
return False
|
||||
elif child.tag in self.BUILDERS_MAP:
|
||||
name = child.get('name')
|
||||
if name is not None:
|
||||
try:
|
||||
if not self.maps.lookup(child.tag, prefix + name if prefix else name).built:
|
||||
return False
|
||||
except KeyError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def validation_attempted(self):
|
||||
"""
|
||||
Property that returns the XSD component validation status. It can be
|
||||
'full', 'partial' or 'none'.
|
||||
|
||||
| https://www.w3.org/TR/xmlschema-1/#e-validation_attempted
|
||||
| https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validation_attempted
|
||||
"""
|
||||
if self.built:
|
||||
return 'full'
|
||||
elif any([comp.validation_attempted == 'partial' for comp in self.iter_globals()]):
|
||||
|
@ -554,9 +662,9 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
|
||||
def iter_globals(self, schema=None):
|
||||
"""
|
||||
Creates an iterator for XSD global definitions/declarations.
|
||||
Creates an iterator for XSD global definitions/declarations related to schema namespace.
|
||||
|
||||
:param schema: Optional schema instance.
|
||||
:param schema: Optional argument for filtering only globals related to a schema instance.
|
||||
"""
|
||||
if schema is None:
|
||||
for global_map in self.global_maps:
|
||||
|
@ -574,7 +682,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
def iter_components(self, xsd_classes=None):
|
||||
if xsd_classes is None or isinstance(self, xsd_classes):
|
||||
yield self
|
||||
for xsd_global in self.iter_globals():
|
||||
for xsd_global in self.iter_globals(self):
|
||||
for obj in xsd_global.iter_components(xsd_classes):
|
||||
yield obj
|
||||
|
||||
|
@ -622,6 +730,14 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
# https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#src-include
|
||||
self.warnings.append("Include schema failed: %s." % str(err))
|
||||
warnings.warn(self.warnings[-1], XMLSchemaIncludeWarning, stacklevel=3)
|
||||
except (XMLSchemaURLError, XMLSchemaParseError, XMLSchemaTypeError, ParseError) as err:
|
||||
msg = 'cannot include schema %r: %s' % (child.attrib['schemaLocation'], 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))
|
||||
|
||||
for child in iterchildren_xsd_redefine(self.root):
|
||||
try:
|
||||
|
@ -635,6 +751,14 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
warnings.warn(self.warnings[-1], XMLSchemaIncludeWarning, stacklevel=3)
|
||||
if has_xsd_components(child):
|
||||
self.parse_error(str(err), child)
|
||||
except (XMLSchemaURLError, XMLSchemaParseError, XMLSchemaTypeError, ParseError) as err:
|
||||
msg = 'cannot redefine schema %r: %s' % (child.attrib['schemaLocation'], 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))
|
||||
|
||||
def include_schema(self, location, base_url=None):
|
||||
"""
|
||||
|
@ -644,40 +768,56 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
:param base_url: is an optional base URL for fetching the schema resource.
|
||||
:return: the included :class:`XMLSchema` instance.
|
||||
"""
|
||||
try:
|
||||
schema_url = fetch_resource(location, base_url)
|
||||
except XMLSchemaURLError as err:
|
||||
raise XMLSchemaOSError("cannot include schema from %r: %s." % (location, err))
|
||||
schema_url = fetch_resource(location, base_url)
|
||||
for schema in self.maps.namespaces[self.target_namespace]:
|
||||
if schema_url == schema.url:
|
||||
break
|
||||
else:
|
||||
for schema in self.maps.namespaces[self.target_namespace]:
|
||||
if schema_url == schema.url:
|
||||
return schema
|
||||
try:
|
||||
return self.create_schema(
|
||||
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
|
||||
)
|
||||
except XMLSchemaParseError as err:
|
||||
err.message = 'cannot include %r: %s' % (schema_url, err.message)
|
||||
raise err
|
||||
except (XMLSchemaTypeError, OSError, IOError) as err:
|
||||
raise type(err)('cannot include %r: %s' % (schema_url, err))
|
||||
|
||||
if location not in self.includes:
|
||||
self.includes[location] = schema
|
||||
elif self.includes[location] != schema:
|
||||
self.includes[schema_url] = schema
|
||||
return schema
|
||||
|
||||
def _import_namespaces(self):
|
||||
"""Processes namespace imports. Return a list of exceptions."""
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
namespace_imports = NamespaceResourcesMap(map(
|
||||
lambda x: (x.get('namespace', '').strip(), x.get('schemaLocation')),
|
||||
lambda x: (x.get('namespace'), x.get('schemaLocation')),
|
||||
iterchildren_xsd_import(self.root)
|
||||
))
|
||||
|
||||
for namespace, locations in namespace_imports.items():
|
||||
if namespace in self.maps.namespaces:
|
||||
# Imports are done on namespace basis not on resource: this is the standard
|
||||
# and also avoids import loops that sometimes are hard to detect.
|
||||
|
||||
# Checks the namespace against the targetNamespace of the schema
|
||||
if namespace is None:
|
||||
namespace = ''
|
||||
if namespace == self.target_namespace:
|
||||
self.parse_error("if the 'namespace' attribute is not present on the import statement "
|
||||
"then the importing schema must has a 'targetNamespace'")
|
||||
continue
|
||||
elif namespace == self.target_namespace:
|
||||
self.parse_error("the attribute 'namespace' must be different from schema's 'targetNamespace'")
|
||||
continue
|
||||
|
||||
# Skip import of already imported namespaces
|
||||
if self.imports.get(namespace) is not None:
|
||||
continue
|
||||
elif namespace in self.maps.namespaces:
|
||||
self.imports[namespace] = self.maps.namespaces[namespace][0]
|
||||
continue
|
||||
|
||||
locations = [url for url in locations if url]
|
||||
if not locations:
|
||||
if not namespace:
|
||||
pass
|
||||
elif not locations:
|
||||
locations = self.get_locations(namespace)
|
||||
elif all(is_remote_url(url) for url in locations):
|
||||
# If all import schema locations are remote URLs and there are local hints
|
||||
|
@ -698,14 +838,26 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
# 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)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
if import_error is None:
|
||||
self.warnings.append("Namespace import failed: no schema location provided.")
|
||||
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)
|
||||
warnings.warn(self.warnings[-1], XMLSchemaImportWarning, stacklevel=3)
|
||||
self.imports[namespace] = None
|
||||
|
||||
def import_schema(self, namespace, location, base_url=None, force=False):
|
||||
"""
|
||||
|
@ -717,33 +869,72 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
:param force: is set to `True` imports the schema also if the namespace is already imported.
|
||||
:return: the imported :class:`XMLSchema` instance.
|
||||
"""
|
||||
if namespace in self.maps.namespaces and not force:
|
||||
return
|
||||
if not force:
|
||||
if self.imports.get(namespace) is not None:
|
||||
return self.imports[namespace]
|
||||
elif namespace in self.maps.namespaces:
|
||||
self.imports[namespace] = self.maps.namespaces[namespace][0]
|
||||
return self.imports[namespace]
|
||||
|
||||
try:
|
||||
schema_url = fetch_resource(location, base_url)
|
||||
except XMLSchemaURLError as err:
|
||||
if namespace:
|
||||
raise XMLSchemaOSError("cannot import namespace %r from %r: %s." % (namespace, location, err))
|
||||
schema_url = fetch_resource(location, base_url)
|
||||
if self.imports.get(namespace) is not None and self.imports[namespace].url == schema_url:
|
||||
return self.imports[namespace]
|
||||
elif namespace in self.maps.namespaces:
|
||||
for schema in self.maps.namespaces[namespace]:
|
||||
if schema_url == schema.url:
|
||||
self.imports[namespace] = schema
|
||||
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
|
||||
)
|
||||
if schema.target_namespace != namespace:
|
||||
raise XMLSchemaValueError('imported schema %r has an unmatched namespace %r' % (location, namespace))
|
||||
self.imports[namespace] = schema
|
||||
return schema
|
||||
|
||||
def resolve_qname(self, qname):
|
||||
"""
|
||||
QName resolution for a schema instance.
|
||||
|
||||
:param qname: a string in xs:QName format.
|
||||
:returns: an expanded QName in the format "{*namespace-URI*}*local-name*".
|
||||
:raises: `XMLSchemaValueError` for an invalid xs:QName or if the namespace prefix is not \
|
||||
declared in the schema instance or if the namespace is not the *targetNamespace* and \
|
||||
the namespace is not imported by the schema.
|
||||
"""
|
||||
qname = qname.strip()
|
||||
if not qname or ' ' in qname or '\t' in qname or '\n' in qname:
|
||||
raise XMLSchemaValueError("{!r} is not a valid value for xs:QName".format(qname))
|
||||
|
||||
if qname[0] == '{':
|
||||
try:
|
||||
namespace, local_name = qname[1:].split('}')
|
||||
except ValueError:
|
||||
raise XMLSchemaValueError("{!r} is not a valid value for xs:QName".format(qname))
|
||||
elif ':' in qname:
|
||||
try:
|
||||
prefix, local_name = qname.split(':')
|
||||
except ValueError:
|
||||
raise XMLSchemaValueError("{!r} is not a valid value for xs:QName".format(qname))
|
||||
else:
|
||||
raise XMLSchemaOSError("cannot import chameleon schema from %r: %s." % (location, err))
|
||||
try:
|
||||
namespace = self.namespaces[prefix]
|
||||
except KeyError:
|
||||
raise XMLSchemaValueError("prefix %r not found in namespace map" % prefix)
|
||||
else:
|
||||
if namespace in self.maps.namespaces:
|
||||
for schema in self.maps.namespaces[namespace]:
|
||||
if schema_url == schema.url:
|
||||
return schema
|
||||
namespace, local_name = self.namespaces.get('', ''), qname
|
||||
|
||||
try:
|
||||
namespace = namespace or self.target_namespace
|
||||
return self.create_schema(
|
||||
schema_url, namespace, self.validation, self.maps, self.converter,
|
||||
self.locations, self.base_url, self.defuse, self.timeout, False
|
||||
if not namespace:
|
||||
return local_name
|
||||
elif self.meta_schema is not None and namespace != self.target_namespace and \
|
||||
namespace not in {XSD_NAMESPACE, XSI_NAMESPACE} and namespace not in self.imports:
|
||||
raise XMLSchemaValueError(
|
||||
"the QName {!r} is mapped to the namespace {!r}, but this namespace has "
|
||||
"not an xs:import statement in the schema.".format(qname, namespace)
|
||||
)
|
||||
except XMLSchemaParseError as err:
|
||||
err.message = 'cannot import namespace %r: %s' % (namespace, err.message)
|
||||
raise err
|
||||
except (XMLSchemaTypeError, OSError, IOError) as err:
|
||||
raise type(err)('cannot import namespace %r: %s' % (namespace, err))
|
||||
return '{%s}%s' % (namespace, local_name)
|
||||
|
||||
def iter_decode(self, source, path=None, validation='lax', process_namespaces=True,
|
||||
namespaces=None, use_defaults=True, decimal_type=None, datetime_types=False,
|
||||
|
@ -793,6 +984,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
namespaces = {}
|
||||
|
||||
converter = self.get_converter(converter, namespaces, **kwargs)
|
||||
id_map = Counter()
|
||||
|
||||
if path is None:
|
||||
xsd_element = self.find(source.root.tag, namespaces=namespaces)
|
||||
|
@ -803,7 +995,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
for obj in xsd_element.iter_decode(
|
||||
source.root, validation, converter, source=source, namespaces=namespaces,
|
||||
use_defaults=use_defaults, decimal_type=decimal_type,
|
||||
datetime_types=datetime_types, **kwargs):
|
||||
datetime_types=datetime_types, id_map=id_map, **kwargs):
|
||||
yield obj
|
||||
else:
|
||||
xsd_element = self.find(path, namespaces=namespaces)
|
||||
|
@ -816,9 +1008,13 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
for obj in xsd_element.iter_decode(
|
||||
elem, validation, converter, source=source, namespaces=namespaces,
|
||||
use_defaults=use_defaults, decimal_type=decimal_type,
|
||||
datetime_types=datetime_types, **kwargs):
|
||||
datetime_types=datetime_types, id_map=id_map, **kwargs):
|
||||
yield obj
|
||||
|
||||
for k, v in id_map.items():
|
||||
if v != 1:
|
||||
self.parse_error("Duplicated xsd:ID value {!r}".format(k), self.root)
|
||||
|
||||
def iter_encode(self, obj, path=None, validation='lax', namespaces=None, converter=None, **kwargs):
|
||||
"""
|
||||
Creates an iterator for encoding a data structure to an ElementTree's Element.
|
||||
|
@ -892,12 +1088,13 @@ class XMLSchema10(XMLSchemaBase):
|
|||
'element_class': XsdElement,
|
||||
'any_element_class': XsdAnyElement,
|
||||
'restriction_class': XsdAtomicRestriction,
|
||||
'union_class': XsdUnion,
|
||||
'simple_type_factory': xsd_simple_type_factory
|
||||
}
|
||||
meta_schema = os.path.join(SCHEMAS_DIR, 'XSD_1.0/XMLSchema.xsd')
|
||||
BASE_SCHEMAS = {
|
||||
XML_NAMESPACE: XML_SCHEMA_FILE,
|
||||
HFP_NAMESPACE: HFP_SCHEMA_FILE,
|
||||
# HFP_NAMESPACE: HFP_SCHEMA_FILE,
|
||||
XSI_NAMESPACE: XSI_SCHEMA_FILE,
|
||||
XLINK_NAMESPACE: XLINK_SCHEMA_FILE,
|
||||
}
|
||||
|
@ -949,13 +1146,14 @@ class XMLSchema11(XMLSchemaBase):
|
|||
'element_class': Xsd11Element,
|
||||
'any_element_class': Xsd11AnyElement,
|
||||
'restriction_class': Xsd11AtomicRestriction,
|
||||
'union_class': Xsd11Union,
|
||||
'simple_type_factory': xsd_simple_type_factory
|
||||
}
|
||||
meta_schema = os.path.join(SCHEMAS_DIR, 'XSD_1.1/XMLSchema.xsd')
|
||||
BASE_SCHEMAS = {
|
||||
XSD_NAMESPACE: os.path.join(SCHEMAS_DIR, 'XSD_1.1/list_builtins.xsd'),
|
||||
XML_NAMESPACE: XML_SCHEMA_FILE,
|
||||
HFP_NAMESPACE: HFP_SCHEMA_FILE,
|
||||
# HFP_NAMESPACE: HFP_SCHEMA_FILE,
|
||||
XSI_NAMESPACE: XSI_SCHEMA_FILE,
|
||||
XLINK_NAMESPACE: XLINK_SCHEMA_FILE,
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version='1.0'?>
|
||||
|
||||
<!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XSD 1.1//EN" "XMLSchema.dtd" [
|
||||
|
||||
<!-- provide ID type information even for parsers which only read the
|
||||
|
@ -51,7 +52,11 @@
|
|||
<!ATTLIST xs:list id ID #IMPLIED>
|
||||
<!ATTLIST xs:union id ID #IMPLIED>
|
||||
]>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xml:lang="EN" targetNamespace="http://www.w3.org/2001/XMLSchema" version="1.0">
|
||||
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
elementFormDefault="qualified" xml:lang="EN"
|
||||
targetNamespace="http://www.w3.org/2001/XMLSchema"
|
||||
version="1.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
Part 1 version: structures.xsd (rec-20120405)
|
||||
|
@ -60,7 +65,7 @@
|
|||
</xs:annotation>
|
||||
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html">
|
||||
<xs:documentation source="../structures/structures.html">
|
||||
The schema corresponding to this document is normative,
|
||||
with respect to the syntactic constraints it expresses in the
|
||||
XML Schema Definition Language. The documentation (within 'documentation' elements)
|
||||
|
@ -78,7 +83,8 @@
|
|||
The simpleType element and all of its members are defined
|
||||
towards the end of this schema document.</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd">
|
||||
<xs:import namespace="http://www.w3.org/XML/1998/namespace"
|
||||
schemaLocation="http://www.w3.org/2001/xml.xsd">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
Get access to the xml: attribute groups for xml:lang
|
||||
|
@ -104,7 +110,7 @@
|
|||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
This type is extended by all types which allow annotation
|
||||
other than <schema> itself
|
||||
other than <schema> itself
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexContent>
|
||||
|
@ -143,7 +149,7 @@
|
|||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
This group is for the
|
||||
elements which can self-redefine (see <redefine> below).</xs:documentation>
|
||||
elements which can self-redefine (see <redefine> below).</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:choice>
|
||||
<xs:element ref="xs:simpleType"/>
|
||||
|
@ -222,7 +228,8 @@
|
|||
</xs:simpleType>
|
||||
<xs:element name="schema" id="schema">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-schema"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-schema"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -231,21 +238,28 @@
|
|||
<xs:group ref="xs:composition" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:sequence minOccurs="0">
|
||||
<xs:element ref="xs:defaultOpenContent"/>
|
||||
<xs:element ref="xs:annotation" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element ref="xs:annotation" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:group ref="xs:schemaTop"/>
|
||||
<xs:element ref="xs:annotation" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element ref="xs:annotation" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="targetNamespace" type="xs:anyURI"/>
|
||||
<xs:attribute name="version" type="xs:token"/>
|
||||
<xs:attribute name="finalDefault" type="xs:fullDerivationSet" default="" use="optional"/>
|
||||
<xs:attribute name="blockDefault" type="xs:blockSet" default="" use="optional"/>
|
||||
<xs:attribute name="attributeFormDefault" type="xs:formChoice" default="unqualified" use="optional"/>
|
||||
<xs:attribute name="elementFormDefault" type="xs:formChoice" default="unqualified" use="optional"/>
|
||||
<xs:attribute name="finalDefault" type="xs:fullDerivationSet"
|
||||
default="" use="optional"/>
|
||||
<xs:attribute name="blockDefault" type="xs:blockSet" default=""
|
||||
use="optional"/>
|
||||
<xs:attribute name="attributeFormDefault" type="xs:formChoice"
|
||||
default="unqualified" use="optional"/>
|
||||
<xs:attribute name="elementFormDefault" type="xs:formChoice"
|
||||
default="unqualified" use="optional"/>
|
||||
<xs:attribute name="defaultAttributes" type="xs:QName"/>
|
||||
<xs:attribute name="xpathDefaultNamespace" type="xs:xpathDefaultNamespace" default="##local" use="optional"/>
|
||||
<xs:attribute name="xpathDefaultNamespace" type="xs:xpathDefaultNamespace"
|
||||
default="##local" use="optional"/>
|
||||
<xs:attribute name="id" type="xs:ID"/>
|
||||
<xs:attribute ref="xml:lang"/>
|
||||
</xs:extension>
|
||||
|
@ -298,7 +312,8 @@
|
|||
<xs:documentation>
|
||||
for all particles</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:attribute name="minOccurs" type="xs:nonNegativeInteger" default="1" use="optional"/>
|
||||
<xs:attribute name="minOccurs" type="xs:nonNegativeInteger" default="1"
|
||||
use="optional"/>
|
||||
<xs:attribute name="maxOccurs" type="xs:allNNI" default="1" use="optional"/>
|
||||
</xs:attributeGroup>
|
||||
<xs:attributeGroup name="defRef">
|
||||
|
@ -394,21 +409,24 @@
|
|||
<xs:element ref="xs:anyAttribute" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
<xs:element name="anyAttribute" id="anyAttribute">
|
||||
<xs:element name="anyAttribute" id="anyAttribute">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-anyAttribute"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-anyAttribute"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
<xs:extension base="xs:wildcard">
|
||||
<xs:attribute name="notQName" type="xs:qnameListA" use="optional"/>
|
||||
<xs:attribute name="notQName" type="xs:qnameListA"
|
||||
use="optional"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:group name="assertions">
|
||||
<xs:sequence>
|
||||
<xs:element name="assert" type="xs:assertion" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="assert" type="xs:assertion"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
<xs:complexType name="assertion">
|
||||
|
@ -427,11 +445,11 @@
|
|||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
This branch is short for
|
||||
<complexContent>
|
||||
<restriction base="xs:anyType">
|
||||
<complexContent>
|
||||
<restriction base="xs:anyType">
|
||||
...
|
||||
</restriction>
|
||||
</complexContent></xs:documentation>
|
||||
</restriction>
|
||||
</complexContent></xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:element ref="xs:openContent" minOccurs="0"/>
|
||||
<xs:group ref="xs:typeDefParticle" minOccurs="0"/>
|
||||
|
@ -457,10 +475,12 @@
|
|||
May be overridden by setting on complexContent child.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="abstract" type="xs:boolean" default="false" use="optional"/>
|
||||
<xs:attribute name="abstract" type="xs:boolean" default="false"
|
||||
use="optional"/>
|
||||
<xs:attribute name="final" type="xs:derivationSet"/>
|
||||
<xs:attribute name="block" type="xs:derivationSet"/>
|
||||
<xs:attribute name="defaultAttributesApply" type="xs:boolean" default="true" use="optional"/>
|
||||
<xs:attribute name="defaultAttributesApply" type="xs:boolean"
|
||||
default="true" use="optional"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
@ -549,7 +569,8 @@
|
|||
</xs:complexType>
|
||||
<xs:element name="complexContent" id="complexContent">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-complexContent"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-complexContent"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -570,7 +591,8 @@
|
|||
</xs:element>
|
||||
<xs:element name="openContent" id="openContent">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-openContent"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-openContent"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -594,7 +616,8 @@
|
|||
</xs:element>
|
||||
<xs:element name="defaultOpenContent" id="defaultOpenContent">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-defaultOpenContent"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-defaultOpenContent"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -602,7 +625,8 @@
|
|||
<xs:sequence>
|
||||
<xs:element name="any" type="xs:wildcard"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="appliesToEmpty" type="xs:boolean" default="false" use="optional"/>
|
||||
<xs:attribute name="appliesToEmpty" type="xs:boolean"
|
||||
default="false" use="optional"/>
|
||||
<xs:attribute name="mode" default="interleave" use="optional">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:NMTOKEN">
|
||||
|
@ -653,7 +677,8 @@
|
|||
</xs:complexType>
|
||||
<xs:element name="simpleContent" id="simpleContent">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-simpleContent"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-simpleContent"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -668,7 +693,8 @@
|
|||
</xs:element>
|
||||
<xs:element name="complexType" type="xs:topLevelComplexType" id="complexType">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-complexType"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-complexType"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:simpleType name="blockSet">
|
||||
|
@ -714,8 +740,10 @@
|
|||
<xs:element name="simpleType" type="xs:localSimpleType"/>
|
||||
<xs:element name="complexType" type="xs:localComplexType"/>
|
||||
</xs:choice>
|
||||
<xs:element name="alternative" type="xs:altType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="alternative" type="xs:altType"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:group ref="xs:identityConstraint" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="xs:defRef"/>
|
||||
<xs:attribute name="type" type="xs:QName"/>
|
||||
|
@ -729,7 +757,8 @@
|
|||
<xs:attribute name="default" type="xs:string"/>
|
||||
<xs:attribute name="fixed" type="xs:string"/>
|
||||
<xs:attribute name="nillable" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="abstract" type="xs:boolean" default="false" use="optional"/>
|
||||
<xs:attribute name="abstract" type="xs:boolean" default="false"
|
||||
use="optional"/>
|
||||
<xs:attribute name="final" type="xs:derivationSet"/>
|
||||
<xs:attribute name="block" type="xs:blockSet"/>
|
||||
<xs:attribute name="form" type="xs:formChoice"/>
|
||||
|
@ -746,8 +775,10 @@
|
|||
<xs:element name="simpleType" type="xs:localSimpleType"/>
|
||||
<xs:element name="complexType" type="xs:localComplexType"/>
|
||||
</xs:choice>
|
||||
<xs:element name="alternative" type="xs:altType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="alternative" type="xs:altType"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:group ref="xs:identityConstraint" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="ref" use="prohibited"/>
|
||||
<xs:attribute name="form" use="prohibited"/>
|
||||
|
@ -768,8 +799,10 @@
|
|||
<xs:element name="simpleType" type="xs:localSimpleType"/>
|
||||
<xs:element name="complexType" type="xs:localComplexType"/>
|
||||
</xs:choice>
|
||||
<xs:element name="alternative" type="xs:altType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="alternative" type="xs:altType"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:group ref="xs:identityConstraint" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="substitutionGroup" use="prohibited"/>
|
||||
<xs:attribute name="final" use="prohibited"/>
|
||||
|
@ -780,7 +813,8 @@
|
|||
</xs:complexType>
|
||||
<xs:element name="element" type="xs:topLevelElement" id="element">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-element"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-element"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:complexType name="altType">
|
||||
|
@ -967,12 +1001,14 @@
|
|||
</xs:element>
|
||||
<xs:element name="choice" type="xs:explicitGroup" id="choice">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-choice"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-choice"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="sequence" type="xs:explicitGroup" id="sequence">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-sequence"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-sequence"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="group" type="xs:namedGroup" id="group">
|
||||
|
@ -981,7 +1017,8 @@
|
|||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="anyAttrGroup">
|
||||
<xs:attribute name="namespace" type="xs:namespaceList" use="optional"/>
|
||||
<xs:attribute name="namespace" type="xs:namespaceList"
|
||||
use="optional"/>
|
||||
<xs:attribute name="notNamespace" use="optional">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:basicNamespaceList">
|
||||
|
@ -1014,7 +1051,8 @@
|
|||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
<xs:extension base="xs:wildcard">
|
||||
<xs:attribute name="notQName" type="xs:qnameList" use="optional"/>
|
||||
<xs:attribute name="notQName" type="xs:qnameList"
|
||||
use="optional"/>
|
||||
<xs:attributeGroup ref="xs:occurs"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
|
@ -1050,7 +1088,7 @@
|
|||
A utility type, not for public use</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
<xs:union memberTypes="xs:specialNamespaceList xs:basicNamespaceList"/>
|
||||
<xs:union memberTypes="xs:specialNamespaceList xs:basicNamespaceList" />
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="basicNamespaceList">
|
||||
<xs:annotation>
|
||||
|
@ -1130,7 +1168,8 @@
|
|||
</xs:simpleType>
|
||||
<xs:element name="attribute" type="xs:topLevelAttribute" id="attribute">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-attribute"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-attribute"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:complexType name="attributeGroup" abstract="true">
|
||||
|
@ -1169,14 +1208,17 @@
|
|||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
<xs:element name="attributeGroup" type="xs:namedAttributeGroup" id="attributeGroup">
|
||||
<xs:element name="attributeGroup" type="xs:namedAttributeGroup"
|
||||
id="attributeGroup">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-attributeGroup"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-attributeGroup"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="include" id="include">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-include"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-include"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1188,7 +1230,8 @@
|
|||
</xs:element>
|
||||
<xs:element name="redefine" id="redefine">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-redefine"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-redefine"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1206,7 +1249,8 @@
|
|||
|
||||
<xs:element name="override" id="override">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-override"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-override"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1223,7 +1267,8 @@
|
|||
</xs:element>
|
||||
<xs:element name="import" id="import">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-import"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-import"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1236,7 +1281,8 @@
|
|||
</xs:element>
|
||||
<xs:element name="selector" id="selector">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-selector"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-selector"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1308,7 +1354,8 @@ use</xs:documentation>
|
|||
</xs:group>
|
||||
<xs:element name="unique" type="xs:keybase" id="unique">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-unique"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-unique"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="key" type="xs:keybase" id="key">
|
||||
|
@ -1318,7 +1365,8 @@ use</xs:documentation>
|
|||
</xs:element>
|
||||
<xs:element name="keyref" id="keyref">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-keyref"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-keyref"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1330,7 +1378,8 @@ use</xs:documentation>
|
|||
</xs:element>
|
||||
<xs:element name="notation" id="notation">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-notation"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-notation"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1353,7 +1402,8 @@ use</xs:documentation>
|
|||
</xs:simpleType>
|
||||
<xs:element name="appinfo" id="appinfo">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-appinfo"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-appinfo"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType mixed="true">
|
||||
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
|
@ -1365,7 +1415,8 @@ use</xs:documentation>
|
|||
</xs:element>
|
||||
<xs:element name="documentation" id="documentation">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-documentation"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-documentation"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType mixed="true">
|
||||
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
|
@ -1378,7 +1429,8 @@ use</xs:documentation>
|
|||
</xs:element>
|
||||
<xs:element name="annotation" id="annotation">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="../structures/structures.html#element-annotation"/>
|
||||
<xs:documentation
|
||||
source="../structures/structures.html#element-annotation"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1396,8 +1448,10 @@ use</xs:documentation>
|
|||
<xs:documentation>
|
||||
notations for use within schema documents</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:notation name="XMLSchemaStructures" public="structures" system="http://www.w3.org/2000/08/XMLSchema.xsd"/>
|
||||
<xs:notation name="XML" public="REC-xml-19980210" system="http://www.w3.org/TR/1998/REC-xml-19980210"/>
|
||||
<xs:notation name="XMLSchemaStructures" public="structures"
|
||||
system="http://www.w3.org/2000/08/XMLSchema.xsd"/>
|
||||
<xs:notation name="XML" public="REC-xml-19980210"
|
||||
system="http://www.w3.org/TR/1998/REC-xml-19980210"/>
|
||||
<xs:complexType name="anyType" mixed="true">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
|
@ -1553,7 +1607,8 @@ use</xs:documentation>
|
|||
</xs:complexType>
|
||||
<xs:element name="simpleType" type="xs:topLevelSimpleType" id="simpleType">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-simpleType"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-simpleType"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="facet" abstract="true">
|
||||
|
@ -1569,16 +1624,19 @@ use</xs:documentation>
|
|||
<xs:group name="simpleRestrictionModel">
|
||||
<xs:sequence>
|
||||
<xs:element name="simpleType" type="xs:localSimpleType" minOccurs="0"/>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:choice minOccurs="0"
|
||||
maxOccurs="unbounded">
|
||||
<xs:element ref="xs:facet"/>
|
||||
<xs:any processContents="lax" namespace="##other"/>
|
||||
<xs:any processContents="lax"
|
||||
namespace="##other"/>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
<xs:element name="restriction" id="restriction">
|
||||
<xs:complexType>
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-restriction">
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-restriction">
|
||||
base attribute and simpleType child are mutually
|
||||
exclusive, but one or other is required
|
||||
</xs:documentation>
|
||||
|
@ -1594,7 +1652,8 @@ use</xs:documentation>
|
|||
<xs:element name="list" id="list">
|
||||
<xs:complexType>
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-list">
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-list">
|
||||
itemType attribute and simpleType child are mutually
|
||||
exclusive, but one or other is required
|
||||
</xs:documentation>
|
||||
|
@ -1602,7 +1661,8 @@ use</xs:documentation>
|
|||
<xs:complexContent>
|
||||
<xs:extension base="xs:annotated">
|
||||
<xs:sequence>
|
||||
<xs:element name="simpleType" type="xs:localSimpleType" minOccurs="0"/>
|
||||
<xs:element name="simpleType" type="xs:localSimpleType"
|
||||
minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="itemType" type="xs:QName" use="optional"/>
|
||||
</xs:extension>
|
||||
|
@ -1612,7 +1672,8 @@ use</xs:documentation>
|
|||
<xs:element name="union" id="union">
|
||||
<xs:complexType>
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-union">
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-union">
|
||||
memberTypes attribute must be non-empty or there must be
|
||||
at least one simpleType child
|
||||
</xs:documentation>
|
||||
|
@ -1620,7 +1681,8 @@ use</xs:documentation>
|
|||
<xs:complexContent>
|
||||
<xs:extension base="xs:annotated">
|
||||
<xs:sequence>
|
||||
<xs:element name="simpleType" type="xs:localSimpleType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="simpleType" type="xs:localSimpleType"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="memberTypes" use="optional">
|
||||
<xs:simpleType>
|
||||
|
@ -1635,7 +1697,8 @@ use</xs:documentation>
|
|||
<xs:complexContent>
|
||||
<xs:extension base="xs:annotated">
|
||||
<xs:attribute name="value" use="required"/>
|
||||
<xs:attribute name="fixed" type="xs:boolean" default="false" use="optional"/>
|
||||
<xs:attribute name="fixed" type="xs:boolean" default="false"
|
||||
use="optional"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
@ -1650,24 +1713,36 @@ use</xs:documentation>
|
|||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
<xs:element name="minExclusive" type="xs:facet" id="minExclusive" substitutionGroup="xs:facet">
|
||||
<xs:element name="minExclusive" type="xs:facet"
|
||||
id="minExclusive"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-minExclusive"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-minExclusive"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="minInclusive" type="xs:facet" id="minInclusive" substitutionGroup="xs:facet">
|
||||
<xs:element name="minInclusive" type="xs:facet"
|
||||
id="minInclusive"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-minInclusive"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-minInclusive"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="maxExclusive" type="xs:facet" id="maxExclusive" substitutionGroup="xs:facet">
|
||||
<xs:element name="maxExclusive" type="xs:facet"
|
||||
id="maxExclusive"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-maxExclusive"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-maxExclusive"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="maxInclusive" type="xs:facet" id="maxInclusive" substitutionGroup="xs:facet">
|
||||
<xs:element name="maxInclusive" type="xs:facet"
|
||||
id="maxInclusive"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-maxInclusive"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-maxInclusive"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:complexType name="numFacet">
|
||||
|
@ -1676,7 +1751,8 @@ use</xs:documentation>
|
|||
<xs:sequence>
|
||||
<xs:element ref="xs:annotation" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="value" type="xs:nonNegativeInteger" use="required"/>
|
||||
<xs:attribute name="value"
|
||||
type="xs:nonNegativeInteger" use="required"/>
|
||||
<xs:anyAttribute namespace="##other" processContents="lax"/>
|
||||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
|
@ -1694,9 +1770,11 @@ use</xs:documentation>
|
|||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="totalDigits" id="totalDigits" substitutionGroup="xs:facet">
|
||||
<xs:element name="totalDigits" id="totalDigits"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-totalDigits"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-totalDigits"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1710,35 +1788,51 @@ use</xs:documentation>
|
|||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="fractionDigits" type="xs:numFacet" id="fractionDigits" substitutionGroup="xs:facet">
|
||||
<xs:element name="fractionDigits" type="xs:numFacet"
|
||||
id="fractionDigits"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-fractionDigits"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-fractionDigits"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="length" type="xs:numFacet" id="length" substitutionGroup="xs:facet">
|
||||
<xs:element name="length" type="xs:numFacet" id="length"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-length"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-length"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="minLength" type="xs:numFacet" id="minLength" substitutionGroup="xs:facet">
|
||||
<xs:element name="minLength" type="xs:numFacet"
|
||||
id="minLength"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-minLength"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-minLength"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="maxLength" type="xs:numFacet" id="maxLength" substitutionGroup="xs:facet">
|
||||
<xs:element name="maxLength" type="xs:numFacet"
|
||||
id="maxLength"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-maxLength"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-maxLength"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="enumeration" type="xs:noFixedFacet" id="enumeration" substitutionGroup="xs:facet">
|
||||
<xs:element name="enumeration" type="xs:noFixedFacet"
|
||||
id="enumeration"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-enumeration"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-enumeration"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="whiteSpace" id="whiteSpace" substitutionGroup="xs:facet">
|
||||
<xs:element name="whiteSpace" id="whiteSpace"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-whiteSpace"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-whiteSpace"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1760,9 +1854,11 @@ use</xs:documentation>
|
|||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="pattern" id="pattern" substitutionGroup="xs:facet">
|
||||
<xs:element name="pattern" id="pattern"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-pattern"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-pattern"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1770,20 +1866,26 @@ use</xs:documentation>
|
|||
<xs:sequence>
|
||||
<xs:element ref="xs:annotation" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="value" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other" processContents="lax"/>
|
||||
<xs:attribute name="value" type="xs:string"
|
||||
use="required"/>
|
||||
<xs:anyAttribute namespace="##other"
|
||||
processContents="lax"/>
|
||||
</xs:restriction>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="assertion" type="xs:assertion" id="assertion" substitutionGroup="xs:facet">
|
||||
<xs:element name="assertion" type="xs:assertion"
|
||||
id="assertion" substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-assertion"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-assertion"/>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="explicitTimezone" id="explicitTimezone" substitutionGroup="xs:facet">
|
||||
<xs:element name="explicitTimezone" id="explicitTimezone"
|
||||
substitutionGroup="xs:facet">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.w3.org/TR/xmlschema11-2/#element-explicitTimezone"/>
|
||||
<xs:documentation
|
||||
source="http://www.w3.org/TR/xmlschema11-2/#element-explicitTimezone"/>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:complexContent>
|
||||
|
@ -1845,4 +1947,4 @@ use</xs:documentation>
|
|||
|
||||
|
||||
|
||||
</xs:schema>
|
||||
</xs:schema>
|
||||
|
|
|
@ -15,19 +15,21 @@ from __future__ import unicode_literals
|
|||
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_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_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
|
||||
)
|
||||
from ..helpers import get_qname, local_name, prefixed_to_qname, get_xsd_derivation_attribute
|
||||
from ..helpers import get_qname, local_name, get_xsd_derivation_attribute
|
||||
|
||||
from .exceptions import XMLSchemaValidationError, XMLSchemaEncodeError, XMLSchemaDecodeError, XMLSchemaParseError
|
||||
from .xsdbase import XsdAnnotation, XsdType, ValidationMixin
|
||||
from .facets import XsdFacet, 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):
|
||||
|
@ -38,29 +40,38 @@ def xsd_simple_type_factory(elem, schema, parent):
|
|||
else:
|
||||
if name == XSD_ANY_SIMPLE_TYPE:
|
||||
return
|
||||
|
||||
annotation = None
|
||||
try:
|
||||
child = elem[0]
|
||||
except IndexError:
|
||||
return schema.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
return schema.maps.types[XSD_ANY_SIMPLE_TYPE]
|
||||
else:
|
||||
if child.tag == XSD_ANNOTATION:
|
||||
try:
|
||||
child = elem[1]
|
||||
annotation = XsdAnnotation(elem[0], schema, child)
|
||||
except IndexError:
|
||||
return schema.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
return schema.maps.types[XSD_ANY_SIMPLE_TYPE]
|
||||
|
||||
if child.tag == XSD_RESTRICTION:
|
||||
result = schema.BUILDERS.restriction_class(child, schema, parent, name=name)
|
||||
elif child.tag == XSD_LIST:
|
||||
result = XsdList(child, schema, parent, name=name)
|
||||
elif child.tag == XSD_UNION:
|
||||
result = XsdUnion(child, schema, parent, name=name)
|
||||
result = schema.BUILDERS.union_class(child, schema, parent, name=name)
|
||||
else:
|
||||
result = schema.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
result = schema.maps.types[XSD_ANY_SIMPLE_TYPE]
|
||||
|
||||
if annotation is not None:
|
||||
result.annotation = annotation
|
||||
|
||||
if 'final' in elem.attrib:
|
||||
try:
|
||||
result._final = get_xsd_derivation_attribute(elem, 'final')
|
||||
except ValueError as err:
|
||||
result.parse_error(err, elem)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
@ -70,14 +81,21 @@ class XsdSimpleType(XsdType, ValidationMixin):
|
|||
instances of xs:anySimpleType.
|
||||
|
||||
<simpleType
|
||||
final = (#all | List of (list | union | restriction))
|
||||
final = (#all | List of (list | union | restriction | extension))
|
||||
id = ID
|
||||
name = NCName
|
||||
{any attributes with non-schema namespace . . .}>
|
||||
Content: (annotation?, (restriction | list | union))
|
||||
</simpleType>
|
||||
"""
|
||||
admitted_tags = {XSD_SIMPLE_TYPE}
|
||||
_special_types = {XSD_ANY_TYPE, XSD_ANY_SIMPLE_TYPE}
|
||||
_admitted_tags = {XSD_SIMPLE_TYPE}
|
||||
|
||||
min_length = None
|
||||
max_length = None
|
||||
white_space = None
|
||||
patterns = None
|
||||
validators = ()
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None, facets=None):
|
||||
super(XsdSimpleType, self).__init__(elem, schema, parent, name)
|
||||
|
@ -85,31 +103,156 @@ class XsdSimpleType(XsdType, ValidationMixin):
|
|||
self.facets = facets or {}
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
super(XsdSimpleType, self).__setattr__(name, value)
|
||||
if name == 'facets':
|
||||
super(XsdSimpleType, self).__setattr__(name, value)
|
||||
try:
|
||||
self.min_length, self.max_length, self.min_value, self.max_value = self.check_facets(value)
|
||||
except XMLSchemaValueError as err:
|
||||
self.parse_error(unicode_type(err))
|
||||
self.min_length = self.max_length = self.min_value = self.max_value = None
|
||||
self.white_space = None
|
||||
self.patterns = None
|
||||
self.validators = []
|
||||
else:
|
||||
self.white_space = getattr(self.get_facet(XSD_WHITE_SPACE), 'value', None)
|
||||
self.patterns = self.get_facet(XSD_PATTERN)
|
||||
self.validators = [
|
||||
v for k, v in value.items()
|
||||
if k not in (XSD_WHITE_SPACE, XSD_PATTERN, XSD_ASSERTION) and callable(v)
|
||||
]
|
||||
if not isinstance(self, XsdAtomicBuiltin):
|
||||
self._parse_facets(value)
|
||||
|
||||
white_space = getattr(self.get_facet(XSD_WHITE_SPACE), 'value', None)
|
||||
if white_space is not None:
|
||||
self.white_space = white_space
|
||||
|
||||
patterns = self.get_facet(XSD_PATTERN)
|
||||
if patterns is not None:
|
||||
self.patterns = patterns
|
||||
|
||||
if value:
|
||||
if None in value:
|
||||
validators = [value[None]] # Use only the validator function!
|
||||
else:
|
||||
validators = [v for k, v in value.items()
|
||||
if k not in {XSD_WHITE_SPACE, XSD_PATTERN, XSD_ASSERTION}]
|
||||
if XSD_ASSERTION in value:
|
||||
assertions = value[XSD_ASSERTION]
|
||||
if isinstance(assertions, list):
|
||||
self.validators.extend(assertions)
|
||||
validators.extend(assertions)
|
||||
else:
|
||||
self.validators.append(assertions)
|
||||
validators.append(assertions)
|
||||
if validators:
|
||||
self.validators = validators
|
||||
|
||||
def _parse_facets(self, facets):
|
||||
if facets and self.base_type is not None:
|
||||
if self.base_type.is_simple():
|
||||
if self.base_type.name == XSD_ANY_SIMPLE_TYPE:
|
||||
self.parse_error("facets not allowed for a direct derivation of xs:anySimpleType")
|
||||
elif self.base_type.has_simple_content():
|
||||
if self.base_type.content_type.name == XSD_ANY_SIMPLE_TYPE:
|
||||
self.parse_error("facets not allowed for a direct content derivation of xs:anySimpleType")
|
||||
|
||||
# Checks the applicability of the facets
|
||||
if any(k not in self.admitted_facets for k in facets if k is not None):
|
||||
reason = "one or more facets are not applicable, admitted set is %r:"
|
||||
self.parse_error(reason % {local_name(e) for e in self.admitted_facets if e})
|
||||
|
||||
# Check group base_type
|
||||
base_type = {t.base_type for t in facets.values() if isinstance(t, XsdFacet)}
|
||||
if len(base_type) > 1:
|
||||
self.parse_error("facet group must have the same base_type: %r" % base_type)
|
||||
base_type = base_type.pop() if base_type else None
|
||||
|
||||
# Checks length based facets
|
||||
length = getattr(facets.get(XSD_LENGTH), 'value', None)
|
||||
min_length = getattr(facets.get(XSD_MIN_LENGTH), 'value', None)
|
||||
max_length = getattr(facets.get(XSD_MAX_LENGTH), 'value', None)
|
||||
if length is not None:
|
||||
if length < 0:
|
||||
self.parse_error("'length' value must be non negative integer")
|
||||
if min_length is not None:
|
||||
if min_length > length:
|
||||
self.parse_error("'minLength' value must be less or equal to 'length'")
|
||||
min_length_facet = base_type.get_facet(XSD_MIN_LENGTH)
|
||||
length_facet = base_type.get_facet(XSD_LENGTH)
|
||||
if min_length_facet is None or \
|
||||
(length_facet is not None and length_facet.base_type == min_length_facet.base_type):
|
||||
self.parse_error("cannot specify both 'length' and 'minLength'")
|
||||
if max_length is not None:
|
||||
if max_length < length:
|
||||
self.parse_error("'maxLength' value must be greater or equal to 'length'")
|
||||
max_length_facet = base_type.get_facet(XSD_MAX_LENGTH)
|
||||
length_facet = base_type.get_facet(XSD_LENGTH)
|
||||
if max_length_facet is None or \
|
||||
(length_facet is not None and length_facet.base_type == max_length_facet.base_type):
|
||||
self.parse_error("cannot specify both 'length' and 'maxLength'")
|
||||
min_length = max_length = length
|
||||
elif min_length is not None or max_length is not None:
|
||||
min_length_facet = base_type.get_facet(XSD_MIN_LENGTH)
|
||||
max_length_facet = base_type.get_facet(XSD_MAX_LENGTH)
|
||||
if min_length is not None:
|
||||
if min_length < 0:
|
||||
self.parse_error("'minLength' value must be non negative integer")
|
||||
if max_length is not None and max_length < min_length:
|
||||
self.parse_error("'maxLength' value is lesser than 'minLength'")
|
||||
if min_length_facet is not None and min_length_facet.value > min_length:
|
||||
self.parse_error("'minLength' has a lesser value than parent")
|
||||
if max_length_facet is not None and min_length > max_length_facet.value:
|
||||
self.parse_error("'minLength' has a greater value than parent 'maxLength'")
|
||||
|
||||
if max_length is not None:
|
||||
if max_length < 0:
|
||||
self.parse_error("'maxLength' value mu st be non negative integer")
|
||||
if min_length_facet is not None and min_length_facet.value > max_length:
|
||||
self.parse_error("'maxLength' has a lesser value than parent 'minLength'")
|
||||
if max_length_facet is not None and max_length > max_length_facet.value:
|
||||
self.parse_error("'maxLength' has a greater value than parent")
|
||||
|
||||
# Checks min/max values
|
||||
min_inclusive = getattr(facets.get(XSD_MIN_INCLUSIVE), 'value', None)
|
||||
min_exclusive = getattr(facets.get(XSD_MIN_EXCLUSIVE), 'value', None)
|
||||
max_inclusive = getattr(facets.get(XSD_MAX_INCLUSIVE), 'value', None)
|
||||
max_exclusive = getattr(facets.get(XSD_MAX_EXCLUSIVE), 'value', None)
|
||||
|
||||
if min_inclusive is not None:
|
||||
if min_exclusive is not None:
|
||||
self.parse_error("cannot specify both 'minInclusive' and 'minExclusive")
|
||||
if max_inclusive is not None and min_inclusive > max_inclusive:
|
||||
self.parse_error("'minInclusive' must be less or equal to 'maxInclusive'")
|
||||
elif max_exclusive is not None and min_inclusive >= max_exclusive:
|
||||
self.parse_error("'minInclusive' must be lesser than 'maxExclusive'")
|
||||
|
||||
elif min_exclusive is not None:
|
||||
if max_inclusive is not None and min_exclusive >= max_inclusive:
|
||||
self.parse_error("'minExclusive' must be lesser than 'maxInclusive'")
|
||||
elif max_exclusive is not None and min_exclusive > max_exclusive:
|
||||
self.parse_error("'minExclusive' must be less or equal to 'maxExclusive'")
|
||||
|
||||
if max_inclusive is not None and max_exclusive is not None:
|
||||
self.parse_error("cannot specify both 'maxInclusive' and 'maxExclusive")
|
||||
|
||||
# Checks fraction digits
|
||||
if XSD_TOTAL_DIGITS in facets:
|
||||
if XSD_FRACTION_DIGITS in facets and facets[XSD_TOTAL_DIGITS].value < facets[XSD_FRACTION_DIGITS].value:
|
||||
self.parse_error("fractionDigits facet value cannot be lesser than the value of totalDigits")
|
||||
total_digits = base_type.get_facet(XSD_TOTAL_DIGITS)
|
||||
if total_digits is not None and total_digits.value < facets[XSD_TOTAL_DIGITS].value:
|
||||
self.parse_error("totalDigits facet value cannot be greater than those on the base type")
|
||||
|
||||
self.min_length = min_length
|
||||
self.max_length = max_length
|
||||
|
||||
@property
|
||||
def min_value(self):
|
||||
min_exclusive_facet = self.get_facet(XSD_MIN_EXCLUSIVE)
|
||||
if min_exclusive_facet is None:
|
||||
return getattr(self.get_facet(XSD_MIN_INCLUSIVE), 'value', None)
|
||||
|
||||
min_inclusive_facet = self.get_facet(XSD_MIN_INCLUSIVE)
|
||||
if min_inclusive_facet is None or min_inclusive_facet.value <= min_exclusive_facet.value:
|
||||
return min_exclusive_facet.value
|
||||
else:
|
||||
super(XsdSimpleType, self).__setattr__(name, value)
|
||||
return min_inclusive_facet.value
|
||||
|
||||
@property
|
||||
def max_value(self):
|
||||
max_exclusive_facet = self.get_facet(XSD_MAX_EXCLUSIVE)
|
||||
if max_exclusive_facet is None:
|
||||
return getattr(self.get_facet(XSD_MAX_INCLUSIVE), 'value', None)
|
||||
|
||||
max_inclusive_facet = self.get_facet(XSD_MAX_INCLUSIVE)
|
||||
if max_inclusive_facet is None or max_inclusive_facet.value >= max_exclusive_facet.value:
|
||||
return max_exclusive_facet.value
|
||||
else:
|
||||
return max_inclusive_facet.value
|
||||
|
||||
@property
|
||||
def admitted_facets(self):
|
||||
|
@ -119,10 +262,6 @@ class XsdSimpleType(XsdType, ValidationMixin):
|
|||
def built(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def final(self):
|
||||
return get_xsd_derivation_attribute(self.elem, 'final', ('list', 'union', 'restriction'))
|
||||
|
||||
@staticmethod
|
||||
def is_simple():
|
||||
return True
|
||||
|
@ -146,104 +285,27 @@ class XsdSimpleType(XsdType, ValidationMixin):
|
|||
def is_element_only(self):
|
||||
return False
|
||||
|
||||
def check_facets(self, facets):
|
||||
"""
|
||||
Verifies the applicability and the mutual incompatibility of a group of facets.
|
||||
Raises a ValueError if the facets group is invalid.
|
||||
|
||||
:param facets: Dictionary with XSD facets.
|
||||
:returns Min and max values, a `None` value means no min/max limit.
|
||||
"""
|
||||
# Checks the applicability of the facets
|
||||
if any(k not in self.admitted_facets for k in facets if k is not None):
|
||||
reason = "one or more facets are not applicable, admitted set is %r:"
|
||||
raise XMLSchemaValueError(reason % {local_name(e) for e in self.admitted_facets if e})
|
||||
|
||||
# Check group base_type
|
||||
base_type = {t.base_type for t in facets.values() if isinstance(t, XsdFacet)}
|
||||
if len(base_type) > 1:
|
||||
raise XMLSchemaValueError("facet group must have the same base_type: %r" % base_type)
|
||||
base_type = base_type.pop() if base_type else None
|
||||
|
||||
# Checks length based facets
|
||||
length = getattr(facets.get(XSD_LENGTH), 'value', None)
|
||||
min_length = getattr(facets.get(XSD_MIN_LENGTH), 'value', None)
|
||||
max_length = getattr(facets.get(XSD_MAX_LENGTH), 'value', None)
|
||||
if length is not None:
|
||||
if length < 0:
|
||||
raise XMLSchemaValueError("'length' value must be non negative integer.")
|
||||
if min_length is not None:
|
||||
if min_length > length:
|
||||
raise XMLSchemaValueError("'minLength' value must be less or equal to 'length'.")
|
||||
min_length_facet = base_type.get_facet(XSD_MIN_LENGTH)
|
||||
length_facet = base_type.get_facet(XSD_LENGTH)
|
||||
if min_length_facet is None or \
|
||||
(length_facet is not None and length_facet.base_type == min_length_facet.base_type):
|
||||
raise XMLSchemaValueError("cannot specify both 'length' and 'minLength'.")
|
||||
if max_length is not None:
|
||||
if max_length < length:
|
||||
raise XMLSchemaValueError("'maxLength' value must be greater or equal to 'length'.")
|
||||
max_length_facet = base_type.get_facet(XSD_MAX_LENGTH)
|
||||
length_facet = base_type.get_facet(XSD_LENGTH)
|
||||
if max_length_facet is None or \
|
||||
(length_facet is not None and length_facet.base_type == max_length_facet.base_type):
|
||||
raise XMLSchemaValueError("cannot specify both 'length' and 'maxLength'.")
|
||||
min_length = max_length = length
|
||||
elif min_length is not None:
|
||||
if min_length < 0:
|
||||
raise XMLSchemaValueError("'minLength' value must be non negative integer.")
|
||||
if max_length is not None and max_length < min_length:
|
||||
raise XMLSchemaValueError("'maxLength' value is lesser than 'minLength'.")
|
||||
min_length_facet = base_type.get_facet(XSD_MIN_LENGTH)
|
||||
if min_length_facet is not None and min_length_facet.value > min_length:
|
||||
raise XMLSchemaValueError("child 'minLength' has a lesser value than parent")
|
||||
elif max_length is not None:
|
||||
if max_length < 0:
|
||||
raise XMLSchemaValueError("'maxLength' value must be non negative integer.")
|
||||
max_length_facet = base_type.get_facet(XSD_MAX_LENGTH)
|
||||
if max_length_facet is not None and max_length > max_length_facet.value:
|
||||
raise XMLSchemaValueError("child 'maxLength' has a greater value than parent.")
|
||||
|
||||
# Checks max/min
|
||||
min_inclusive = getattr(facets.get(XSD_MIN_INCLUSIVE), 'value', None)
|
||||
min_exclusive = getattr(facets.get(XSD_MIN_EXCLUSIVE), 'value', None)
|
||||
max_inclusive = getattr(facets.get(XSD_MAX_INCLUSIVE), 'value', None)
|
||||
max_exclusive = getattr(facets.get(XSD_MAX_EXCLUSIVE), 'value', None)
|
||||
if min_inclusive is not None and min_exclusive is not None:
|
||||
raise XMLSchemaValueError("cannot specify both 'minInclusive' and 'minExclusive.")
|
||||
if max_inclusive is not None and max_exclusive is not None:
|
||||
raise XMLSchemaValueError("cannot specify both 'maxInclusive' and 'maxExclusive.")
|
||||
|
||||
if min_inclusive is not None:
|
||||
if max_inclusive is not None and min_inclusive > max_inclusive:
|
||||
raise XMLSchemaValueError("'minInclusive' must be less or equal to 'maxInclusive'.")
|
||||
elif max_exclusive is not None and min_inclusive >= max_exclusive:
|
||||
raise XMLSchemaValueError("'minInclusive' must be lesser than 'maxExclusive'.")
|
||||
min_value = min_inclusive
|
||||
elif min_exclusive is not None:
|
||||
if max_inclusive is not None and min_exclusive >= max_inclusive:
|
||||
raise XMLSchemaValueError("'minExclusive' must be lesser than 'maxInclusive'.")
|
||||
elif max_exclusive is not None and min_exclusive > max_exclusive:
|
||||
raise XMLSchemaValueError("'minExclusive' must be less or equal to 'maxExclusive'.")
|
||||
min_value = min_exclusive + 1
|
||||
def is_derived(self, other, derivation=None):
|
||||
if self is other:
|
||||
return True
|
||||
elif derivation and self.derivation and derivation != self.derivation:
|
||||
return False
|
||||
elif other.name in self._special_types:
|
||||
return True
|
||||
elif self.base_type is other:
|
||||
return True
|
||||
elif self.base_type is None:
|
||||
if hasattr(other, 'member_types'):
|
||||
return any(self.is_derived(m, derivation) for m in other.member_types)
|
||||
return False
|
||||
elif self.base_type.is_complex():
|
||||
if not self.base_type.has_simple_content():
|
||||
return False
|
||||
return self.base_type.content_type.is_derived(other, derivation)
|
||||
elif hasattr(other, 'member_types'):
|
||||
return any(self.is_derived(m, derivation) for m in other.member_types)
|
||||
else:
|
||||
min_value = None
|
||||
|
||||
if max_inclusive is not None:
|
||||
max_value = max_inclusive
|
||||
elif max_exclusive is not None:
|
||||
max_value = max_exclusive - 1
|
||||
else:
|
||||
max_value = None
|
||||
|
||||
base_min_value = getattr(base_type, 'min_value', None)
|
||||
base_max_value = getattr(base_type, 'max_value', None)
|
||||
if base_min_value is not None and min_value is not None and base_min_value > min_value:
|
||||
raise XMLSchemaValueError("minimum value of base_type is greater.")
|
||||
if base_max_value is not None and max_value is not None and base_max_value < max_value:
|
||||
raise XMLSchemaValueError("maximum value of base_type is lesser.")
|
||||
|
||||
return min_length, max_length, min_value, max_value
|
||||
return self.base_type.is_derived(other, derivation)
|
||||
|
||||
def normalize(self, text):
|
||||
"""
|
||||
|
@ -316,7 +378,8 @@ class XsdAtomic(XsdSimpleType):
|
|||
a base_type attribute that refers to primitive or derived atomic
|
||||
built-in type or another derived simpleType.
|
||||
"""
|
||||
admitted_tags = {XSD_RESTRICTION, XSD_SIMPLE_TYPE}
|
||||
_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
|
||||
|
@ -367,12 +430,14 @@ class XsdAtomic(XsdSimpleType):
|
|||
def primitive_type(self):
|
||||
if self.base_type is None:
|
||||
return self
|
||||
else:
|
||||
try:
|
||||
try:
|
||||
if self.base_type.is_simple():
|
||||
return self.base_type.primitive_type
|
||||
except AttributeError:
|
||||
# The base_type is XsdList or XsdUnion.
|
||||
return self.base_type
|
||||
else:
|
||||
return self.base_type.content_type.primitive_type
|
||||
except AttributeError:
|
||||
# The base_type is XsdList or XsdUnion.
|
||||
return self.base_type
|
||||
|
||||
def get_facet(self, tag):
|
||||
try:
|
||||
|
@ -448,6 +513,10 @@ class XsdAtomicBuiltin(XsdAtomic):
|
|||
yield self.decode_error(validation, obj, self.to_python,
|
||||
reason="value is not an instance of {!r}".format(self.instance_types))
|
||||
|
||||
if 'id_map' in kwargs:
|
||||
if self.name == XSD_ID:
|
||||
kwargs['id_map'][obj] += 1
|
||||
|
||||
if validation == 'skip':
|
||||
try:
|
||||
yield self.to_python(obj)
|
||||
|
@ -540,12 +609,12 @@ class XsdList(XsdSimpleType):
|
|||
Content: (annotation?, simpleType?)
|
||||
</list>
|
||||
"""
|
||||
admitted_tags = {XSD_LIST}
|
||||
_admitted_tags = {XSD_LIST}
|
||||
_white_space_elem = etree_element(XSD_WHITE_SPACE, attrib={'value': 'collapse', 'fixed': 'true'})
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None, facets=None, base_type=None):
|
||||
def __init__(self, elem, schema, parent, name=None):
|
||||
facets = {XSD_WHITE_SPACE: XsdWhiteSpaceFacet(self._white_space_elem, schema, self, self)}
|
||||
super(XsdList, self).__init__(elem, schema, parent, name, facets)
|
||||
if not hasattr(self, 'base_type'):
|
||||
self.base_type = base_type
|
||||
|
||||
def __repr__(self):
|
||||
if self.name is None:
|
||||
|
@ -579,28 +648,36 @@ class XsdList(XsdSimpleType):
|
|||
base_type = xsd_simple_type_factory(child, self.schema, self)
|
||||
except XMLSchemaParseError as err:
|
||||
self.parse_error(err, elem)
|
||||
base_type = self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)
|
||||
base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
|
||||
if 'itemType' in elem.attrib:
|
||||
self.parse_error("ambiguous list type declaration", self)
|
||||
|
||||
elif 'itemType' in elem.attrib:
|
||||
# List tag with itemType attribute that refers to a global type
|
||||
item_qname = prefixed_to_qname(elem.attrib['itemType'], self.namespaces)
|
||||
try:
|
||||
base_type = self.maps.lookup_type(item_qname)
|
||||
except LookupError:
|
||||
self.parse_error("unknown itemType %r" % elem.attrib['itemType'], elem)
|
||||
base_type = self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)
|
||||
else:
|
||||
self.parse_error("missing list type declaration", elem)
|
||||
base_type = self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)
|
||||
# List tag with itemType attribute that refers to a global type
|
||||
try:
|
||||
item_qname = self.schema.resolve_qname(elem.attrib['itemType'])
|
||||
except KeyError:
|
||||
self.parse_error("missing list type declaration", elem)
|
||||
base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
else:
|
||||
try:
|
||||
base_type = self.maps.lookup_type(item_qname)
|
||||
except LookupError:
|
||||
self.parse_error("unknown itemType %r" % elem.attrib['itemType'], elem)
|
||||
base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
|
||||
if base_type.final == '#all' or 'list' in base_type.final:
|
||||
self.parse_error("'final' value of the itemType %r forbids derivation by list" % base_type)
|
||||
|
||||
try:
|
||||
self.base_type = base_type
|
||||
except XMLSchemaValueError as err:
|
||||
self.parse_error(str(err), elem)
|
||||
self.base_type = self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)
|
||||
self.base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
|
||||
@property
|
||||
def admitted_facets(self):
|
||||
|
@ -629,6 +706,18 @@ class XsdList(XsdSimpleType):
|
|||
def is_list():
|
||||
return True
|
||||
|
||||
def is_derived(self, other, derivation=None):
|
||||
if self is other:
|
||||
return True
|
||||
elif derivation and self.derivation and derivation != self.derivation:
|
||||
return False
|
||||
elif other.name in self._special_types:
|
||||
return True
|
||||
elif self.base_type is other:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def iter_components(self, xsd_classes=None):
|
||||
if xsd_classes is None or isinstance(self, xsd_classes):
|
||||
yield self
|
||||
|
@ -697,12 +786,13 @@ class XsdUnion(XsdSimpleType):
|
|||
Content: (annotation?, simpleType*)
|
||||
</union>
|
||||
"""
|
||||
admitted_tags = {XSD_UNION}
|
||||
_admitted_types = XsdSimpleType
|
||||
_admitted_tags = {XSD_UNION}
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None, facets=None, member_types=None):
|
||||
super(XsdUnion, self).__init__(elem, schema, parent, name, facets)
|
||||
if not hasattr(self, 'member_types'):
|
||||
self.member_types = member_types
|
||||
member_types = None
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None):
|
||||
super(XsdUnion, self).__init__(elem, schema, parent, name, facets=None)
|
||||
|
||||
def __repr__(self):
|
||||
if self.name is None:
|
||||
|
@ -719,13 +809,6 @@ class XsdUnion(XsdSimpleType):
|
|||
return
|
||||
raise XMLSchemaValueError("a %r definition required for %r." % (XSD_UNION, self))
|
||||
|
||||
elif name == "member_types":
|
||||
if not value:
|
||||
raise XMLSchemaValueError("%r attribute cannot be empty or None." % name)
|
||||
elif not all(isinstance(mt, (XsdAtomic, XsdList, XsdUnion)) for mt in value):
|
||||
raise XMLSchemaValueError("%r: member types must be all atomic or list types." % self)
|
||||
# FIXME: Union only for XSD 1.1
|
||||
|
||||
elif name == 'white_space':
|
||||
if not (value is None or value == 'collapse'):
|
||||
raise XMLSchemaValueError("Wrong value % for attribute 'white_space'." % value)
|
||||
|
@ -746,29 +829,37 @@ class XsdUnion(XsdSimpleType):
|
|||
|
||||
if 'memberTypes' in elem.attrib:
|
||||
for name in elem.attrib['memberTypes'].split():
|
||||
type_qname = prefixed_to_qname(name, self.namespaces)
|
||||
try:
|
||||
type_qname = self.schema.resolve_qname(name)
|
||||
except ValueError as err:
|
||||
self.parse_error(err)
|
||||
continue
|
||||
|
||||
try:
|
||||
mt = self.maps.lookup_type(type_qname)
|
||||
except LookupError:
|
||||
self.parse_error("unknown member type %r" % type_qname)
|
||||
mt = self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)
|
||||
mt = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
except XMLSchemaParseError as err:
|
||||
self.parse_error(err)
|
||||
mt = self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)
|
||||
mt = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
|
||||
if not isinstance(mt, XsdSimpleType):
|
||||
self.parse_error("a simpleType required", mt)
|
||||
else:
|
||||
member_types.append(mt)
|
||||
if isinstance(mt, tuple):
|
||||
self.parse_error("circular definition found on xs:union type {!r}".format(self.name))
|
||||
continue
|
||||
elif not isinstance(mt, self._admitted_types):
|
||||
self.parse_error("a {!r} required, not {!r}".format(self._admitted_types, mt))
|
||||
continue
|
||||
elif mt.final == '#all' or 'union' in mt.final:
|
||||
self.parse_error("'final' value of the memberTypes %r forbids derivation by union" % member_types)
|
||||
|
||||
if not member_types:
|
||||
self.parse_error("missing union type declarations", elem)
|
||||
member_types.append(mt)
|
||||
|
||||
try:
|
||||
if member_types:
|
||||
self.member_types = member_types
|
||||
except XMLSchemaValueError as err:
|
||||
self.parse_error(str(err), elem)
|
||||
self.member_types = [self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)]
|
||||
else:
|
||||
self.parse_error("missing xs:union type declarations", elem)
|
||||
self.member_types = [self.maps.types[XSD_ANY_ATOMIC_TYPE]]
|
||||
|
||||
@property
|
||||
def admitted_facets(self):
|
||||
|
@ -917,6 +1008,11 @@ class XsdUnion(XsdSimpleType):
|
|||
yield unicode_type(obj)
|
||||
|
||||
|
||||
class Xsd11Union(XsdUnion):
|
||||
|
||||
_admitted_types = XsdAtomic, XsdList, XsdUnion
|
||||
|
||||
|
||||
class XsdAtomicRestriction(XsdAtomic):
|
||||
"""
|
||||
Class for XSD 1.0 atomic simpleType and complexType's simpleContent restrictions.
|
||||
|
@ -931,14 +1027,13 @@ class XsdAtomicRestriction(XsdAtomic):
|
|||
</restriction>
|
||||
"""
|
||||
FACETS_BUILDERS = XSD_10_FACETS_BUILDERS
|
||||
derivation = 'restriction'
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name == 'elem' and value is not None:
|
||||
if self.name != XSD_ANY_ATOMIC_TYPE and value.tag != XSD_RESTRICTION:
|
||||
if not (value.tag == XSD_SIMPLE_TYPE and value.get('name') is not None):
|
||||
raise XMLSchemaValueError(
|
||||
"a %r definition required for %r." % (XSD_RESTRICTION, self)
|
||||
)
|
||||
raise XMLSchemaValueError("an xs:restriction definition required for %r." % self)
|
||||
super(XsdAtomicRestriction, self).__setattr__(name, value)
|
||||
|
||||
def _parse(self):
|
||||
|
@ -949,30 +1044,57 @@ class XsdAtomicRestriction(XsdAtomic):
|
|||
elif elem.tag == XSD_SIMPLE_TYPE and elem.get('name') is not None:
|
||||
elem = self._parse_component(elem) # Global simpleType with internal restriction
|
||||
|
||||
if self.name is not None and self.parent is not None:
|
||||
self.parse_error("'name' attribute in a local simpleType definition", elem)
|
||||
|
||||
base_type = None
|
||||
facets = {}
|
||||
has_attributes = False
|
||||
has_simple_type_child = False
|
||||
|
||||
if 'base' in elem.attrib:
|
||||
base_qname = prefixed_to_qname(elem.attrib['base'], self.namespaces)
|
||||
try:
|
||||
base_type = self.maps.lookup_type(base_qname)
|
||||
except LookupError:
|
||||
self.parse_error("unknown type %r." % elem.attrib['base'])
|
||||
base_type = self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)
|
||||
except XMLSchemaParseError as err:
|
||||
self.parse_error(err)
|
||||
base_type = self.maps.lookup_type(XSD_ANY_ATOMIC_TYPE)
|
||||
base_qname = self.schema.resolve_qname(elem.attrib['base'])
|
||||
except ValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
base_type = self.maps.type[XSD_ANY_ATOMIC_TYPE]
|
||||
else:
|
||||
if base_qname == self.name:
|
||||
if self.redefine is None:
|
||||
self.parse_error("wrong definition with self-reference", elem)
|
||||
base_type = self.maps.type[XSD_ANY_ATOMIC_TYPE]
|
||||
else:
|
||||
base_type = self.base_type
|
||||
else:
|
||||
if self.redefine is not None:
|
||||
self.parse_error("wrong redefinition without self-reference", elem)
|
||||
|
||||
if base_type.is_complex() and base_type.mixed and base_type.is_emptiable():
|
||||
if self._parse_component(elem, strict=False).tag != XSD_SIMPLE_TYPE:
|
||||
# See: "http://www.w3.org/TR/xmlschema-2/#element-restriction"
|
||||
self.parse_error(
|
||||
"when a complexType with simpleContent restricts a complexType "
|
||||
"with mixed and with emptiable content then a simpleType child "
|
||||
"declaration is required.", elem
|
||||
)
|
||||
try:
|
||||
base_type = self.maps.lookup_type(base_qname)
|
||||
except LookupError:
|
||||
self.parse_error("unknown type %r." % elem.attrib['base'])
|
||||
base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
except XMLSchemaParseError as err:
|
||||
self.parse_error(err)
|
||||
base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
else:
|
||||
if isinstance(base_type, tuple):
|
||||
self.parse_error("circularity definition between %r and %r" % (self, base_qname), elem)
|
||||
base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE]
|
||||
|
||||
if base_type.is_simple() and base_type.name == XSD_ANY_SIMPLE_TYPE:
|
||||
self.parse_error("wrong base type {!r}, an atomic type required")
|
||||
elif base_type.is_complex():
|
||||
if base_type.mixed and base_type.is_emptiable():
|
||||
if self._parse_component(elem, strict=False).tag != XSD_SIMPLE_TYPE:
|
||||
# See: "http://www.w3.org/TR/xmlschema-2/#element-restriction"
|
||||
self.parse_error(
|
||||
"when a complexType with simpleContent restricts a complexType "
|
||||
"with mixed and with emptiable content then a simpleType child "
|
||||
"declaration is required.", elem
|
||||
)
|
||||
elif self.parent is None or self.parent.is_simple():
|
||||
self.parse_error("simpleType restriction of %r is not allowed" % base_type, elem)
|
||||
|
||||
for child in self._iterparse_components(elem):
|
||||
if child.tag in {XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_ANY_ATTRIBUTE}:
|
||||
|
@ -983,22 +1105,28 @@ class XsdAtomicRestriction(XsdAtomic):
|
|||
# Case of simpleType declaration inside a restriction
|
||||
if has_simple_type_child:
|
||||
self.parse_error("duplicated simpleType declaration", child)
|
||||
elif base_type is None:
|
||||
|
||||
if base_type is None:
|
||||
try:
|
||||
base_type = xsd_simple_type_factory(child, self.schema, self)
|
||||
except XMLSchemaParseError as err:
|
||||
self.parse_error(err)
|
||||
base_type = self.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)
|
||||
base_type = self.maps.types[XSD_ANY_SIMPLE_TYPE]
|
||||
elif base_type.is_complex():
|
||||
if base_type.admit_simple_restriction():
|
||||
base_type = self.schema.BUILDERS.complex_type_class(
|
||||
elem=elem,
|
||||
schema=self.schema,
|
||||
parent=self,
|
||||
content_type=xsd_simple_type_factory(child, self.schema, self),
|
||||
attributes=base_type.attributes,
|
||||
mixed=base_type.mixed,
|
||||
block=base_type.block,
|
||||
final=base_type.final,
|
||||
)
|
||||
elif 'base' in elem.attrib:
|
||||
self.parse_error("restriction with 'base' attribute and simpleType declaration", child)
|
||||
|
||||
elif base_type.is_complex() and base_type.admit_simple_restriction():
|
||||
base_type = self.schema.BUILDERS.complex_type_class(
|
||||
elem=elem,
|
||||
schema=self.schema,
|
||||
parent=self,
|
||||
content_type=xsd_simple_type_factory(child, self.schema, self),
|
||||
attributes=base_type.attributes,
|
||||
mixed=base_type.mixed
|
||||
)
|
||||
has_simple_type_child = True
|
||||
else:
|
||||
try:
|
||||
|
@ -1022,6 +1150,9 @@ class XsdAtomicRestriction(XsdAtomic):
|
|||
|
||||
if base_type is None:
|
||||
self.parse_error("missing base type in restriction:", self)
|
||||
elif base_type.final == '#all' or 'restriction' in base_type.final:
|
||||
self.parse_error("'final' value of the baseType %r forbids derivation by restriction" % base_type)
|
||||
|
||||
self.base_type = base_type
|
||||
self.facets = facets
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ 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
|
||||
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 ..xpath import ElementPathMixin
|
||||
|
@ -42,16 +42,16 @@ class XsdWildcard(XsdComponent, ValidationMixin):
|
|||
# Parse namespace and processContents
|
||||
namespace = self.elem.get('namespace', '##any')
|
||||
items = namespace.strip().split()
|
||||
if len(items) == 1 and items[0] in ('##any', '##all', '##other', '##local', '##targetNamespace'):
|
||||
if len(items) == 1 and items[0] in ('##any', '##other', '##local', '##targetNamespace'):
|
||||
self.namespace = namespace.strip()
|
||||
elif not all([s not in ('##any', '##other') for s in items]):
|
||||
elif not all(not s.startswith('##') or s in {'##local', '##targetNamespace'} for s in items):
|
||||
self.parse_error("wrong value %r for 'namespace' attribute." % namespace)
|
||||
self.namespace = '##any'
|
||||
else:
|
||||
self.namespace = namespace.strip()
|
||||
|
||||
self.process_contents = self.elem.get('processContents', 'strict')
|
||||
if self.process_contents not in ('lax', 'skip', 'strict'):
|
||||
if self.process_contents not in {'lax', 'skip', 'strict'}:
|
||||
self.parse_error("wrong value %r for 'processContents' attribute." % self.process_contents)
|
||||
|
||||
def _load_namespace(self, namespace):
|
||||
|
@ -77,6 +77,17 @@ class XsdWildcard(XsdComponent, ValidationMixin):
|
|||
def built(self):
|
||||
return True
|
||||
|
||||
def iter_namespaces(self):
|
||||
if self.namespace in ('##any', '##other'):
|
||||
return
|
||||
for ns in self.namespace.split():
|
||||
if ns == '##local':
|
||||
yield ''
|
||||
elif ns == '##targetNamespace':
|
||||
yield self.target_namespace
|
||||
else:
|
||||
yield ns
|
||||
|
||||
def is_matching(self, name, default_namespace=None):
|
||||
if name is None:
|
||||
return False
|
||||
|
@ -104,6 +115,34 @@ class XsdWildcard(XsdComponent, ValidationMixin):
|
|||
else:
|
||||
return namespace in any_namespaces
|
||||
|
||||
def is_restriction(self, other, check_occurs=True):
|
||||
if check_occurs and isinstance(self, ParticleMixin) and not self.has_occurs_restriction(other):
|
||||
return False
|
||||
elif not isinstance(other, type(self)):
|
||||
return False
|
||||
elif other.process_contents == 'strict' and self.process_contents != 'strict':
|
||||
return False
|
||||
elif other.process_contents == 'lax' and self.process_contents == 'skip':
|
||||
return False
|
||||
elif self.namespace == other.namespace:
|
||||
return True
|
||||
elif other.namespace == '##any':
|
||||
return True
|
||||
elif self.namespace == '##any':
|
||||
return False
|
||||
|
||||
other_namespaces = other.namespace.split()
|
||||
for ns in self.namespace.split():
|
||||
if ns in other_namespaces:
|
||||
continue
|
||||
elif ns == self.target_namespace:
|
||||
if '##targetNamespace' in other_namespaces:
|
||||
continue
|
||||
elif not ns.startswith('##') and '##other' in other_namespaces:
|
||||
continue
|
||||
return False
|
||||
return True
|
||||
|
||||
def iter_decode(self, source, validation='lax', *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -125,7 +164,7 @@ class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin):
|
|||
Content: (annotation?)
|
||||
</any>
|
||||
"""
|
||||
admitted_tags = {XSD_ANY}
|
||||
_admitted_tags = {XSD_ANY}
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(namespace=%r, process_contents=%r, occurs=%r)' % (
|
||||
|
@ -158,7 +197,8 @@ class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin):
|
|||
def iterchildren(self, tag=None):
|
||||
return iter(())
|
||||
|
||||
def iter_substitutes(self, tag=None):
|
||||
@staticmethod
|
||||
def iter_substitutes():
|
||||
return iter(())
|
||||
|
||||
def iter_decode(self, elem, validation='lax', converter=None, **kwargs):
|
||||
|
@ -202,10 +242,20 @@ class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin):
|
|||
reason = "element %r not allowed here." % name
|
||||
yield self.validation_error(validation, reason, value, **kwargs)
|
||||
|
||||
def is_restriction(self, other):
|
||||
if not ParticleMixin.is_restriction(self, other):
|
||||
return False
|
||||
return True
|
||||
def overlap(self, other):
|
||||
if not isinstance(other, XsdAnyElement):
|
||||
return other.overlap(self)
|
||||
elif self.namespace == other.namespace:
|
||||
return True
|
||||
elif self.namespace == '##any' or other.namespace == '##any':
|
||||
return True
|
||||
elif self.namespace == '##other':
|
||||
return any(not ns.startswith('##') and ns != self.target_namespace for ns in other.namespace.split())
|
||||
elif other.namespace == '##other':
|
||||
return any(not ns.startswith('##') and ns != other.target_namespace for ns in self.namespace.split())
|
||||
|
||||
any_namespaces = self.namespace.split()
|
||||
return any(ns in any_namespaces for ns in other.namespace.split())
|
||||
|
||||
|
||||
class XsdAnyAttribute(XsdWildcard):
|
||||
|
@ -220,7 +270,33 @@ class XsdAnyAttribute(XsdWildcard):
|
|||
Content: (annotation?)
|
||||
</anyAttribute>
|
||||
"""
|
||||
admitted_tags = {XSD_ANY_ATTRIBUTE}
|
||||
_admitted_tags = {XSD_ANY_ATTRIBUTE}
|
||||
|
||||
def extend_namespace(self, other):
|
||||
if self.namespace == '##any' or self.namespace == other.namespace:
|
||||
return
|
||||
elif other.namespace == '##any':
|
||||
self.namespace = other.namespace
|
||||
return
|
||||
elif other.namespace == '##other':
|
||||
w1, w2 = other, self
|
||||
elif self.namespace == '##other':
|
||||
w1, w2 = self, other
|
||||
elif self.target_namespace == other.target_namespace:
|
||||
self.namespace = ' '.join(set(other.namespace.split() + self.namespace.split()))
|
||||
return
|
||||
else:
|
||||
self.namespace = ' '.join(set(list(other.iter_namespaces()) + self.namespace.split()))
|
||||
return
|
||||
|
||||
namespaces = set(w2.iter_namespaces())
|
||||
if w1.target_namespace in namespaces and '' in namespaces:
|
||||
self.namespace = '##any'
|
||||
elif '' not in namespaces and w1.target_namespace == w2.target_namespace:
|
||||
self.namespace = '##other'
|
||||
else:
|
||||
msg = "not expressible wildcard namespace union: {!r} V {!r}:"
|
||||
raise XMLSchemaValueError(msg.format(other.namespace, self.namespace))
|
||||
|
||||
def match(self, name, default_namespace=None):
|
||||
if self.is_matching(name, default_namespace):
|
||||
|
@ -370,9 +446,26 @@ class XsdOpenContent(XsdComponent):
|
|||
Content: (annotation?), (any?)
|
||||
</openContent>
|
||||
"""
|
||||
_admitted_tags = {XSD_OPEN_CONTENT, XSD_DEFAULT_OPEN_CONTENT}
|
||||
|
||||
def __init__(self, elem, schema, parent):
|
||||
super(XsdOpenContent, self).__init__(elem, schema, parent)
|
||||
self.mode = self.elem.get('mode', 'interleave')
|
||||
if self.mode not in ('none', 'interleave', 'suffix'):
|
||||
self.parse_error("wrong value %r for 'mode' attribute." % self.mode)
|
||||
|
||||
def _parse(self):
|
||||
super(XsdOpenContent, self)._parse()
|
||||
child = self._parse_component(self.elem)
|
||||
if child is None:
|
||||
if self.elem.tag == XSD_DEFAULT_OPEN_CONTENT:
|
||||
self.parse_error("a %r declaration cannot be empty:" % self.elem.tag, self.elem)
|
||||
self.any_element = None
|
||||
elif child.tag == XSD_ANY:
|
||||
self.any_element = Xsd11AnyElement(child, self.schema, self)
|
||||
else:
|
||||
self.any_element = None
|
||||
if self.schema.validation == 'skip':
|
||||
# Also generated by meta-schema validation for 'lax' and 'strict' modes
|
||||
self.parse_error("unexpected tag %r for openContent child:" % child.tag, self.elem)
|
||||
|
||||
|
|
|
@ -16,14 +16,10 @@ 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
|
||||
from ..qnames import XSD_ANNOTATION, XSD_APPINFO, XSD_DOCUMENTATION, XML_LANG, XSD_ANY_TYPE, XSD_ID
|
||||
from ..helpers import get_qname, local_name, qname_to_prefixed, iter_xsd_components, get_xsd_component
|
||||
from ..etree import etree_tostring, is_etree_element
|
||||
|
||||
from .exceptions import (
|
||||
XMLSchemaParseError, XMLSchemaValidationError, XMLSchemaDecodeError,
|
||||
XMLSchemaEncodeError, XMLSchemaChildrenValidationError
|
||||
)
|
||||
from .exceptions import XMLSchemaParseError, XMLSchemaValidationError, XMLSchemaDecodeError, XMLSchemaEncodeError
|
||||
|
||||
XSD_VALIDATION_MODES = {'strict', 'lax', 'skip'}
|
||||
"""
|
||||
|
@ -73,7 +69,7 @@ class XsdValidator(object):
|
|||
@property
|
||||
def validation_attempted(self):
|
||||
"""
|
||||
Property that returns the XSD validator's validation status. It can be 'full', 'partial' or 'none'.
|
||||
Property that returns the *validation status* of the XSD validator. It can be 'full', 'partial' or 'none'.
|
||||
|
||||
| https://www.w3.org/TR/xmlschema-1/#e-validation_attempted
|
||||
| https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validation_attempted
|
||||
|
@ -115,6 +111,14 @@ class XsdValidator(object):
|
|||
errors.extend(comp.errors)
|
||||
return errors
|
||||
|
||||
def copy(self):
|
||||
validator = object.__new__(self.__class__)
|
||||
validator.__dict__.update(self.__dict__)
|
||||
validator.errors = self.errors[:]
|
||||
return validator
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def parse_error(self, error, elem=None):
|
||||
"""
|
||||
Helper method for registering parse errors. Does nothing if validation mode is 'skip'.
|
||||
|
@ -140,7 +144,7 @@ class XsdValidator(object):
|
|||
error.elem = elem
|
||||
error.source = getattr(self, 'source', None)
|
||||
elif isinstance(error, Exception):
|
||||
error = XMLSchemaParseError(self, unicode_type(error), elem)
|
||||
error = XMLSchemaParseError(self, unicode_type(error).strip('\'" '), elem)
|
||||
elif isinstance(error, string_base_type):
|
||||
error = XMLSchemaParseError(self, error, elem)
|
||||
else:
|
||||
|
@ -187,26 +191,25 @@ class XsdComponent(XsdValidator):
|
|||
:param parent: the XSD parent, `None` means that is a global component that has the schema as parent.
|
||||
:param name: name of the component, maybe overwritten by the parse of the `elem` argument.
|
||||
|
||||
:cvar admitted_tags: the set of admitted element tags for component type.
|
||||
:vartype admitted_tags: tuple or set
|
||||
:cvar qualified: for name matching, unqualified matching may be admitted only for elements and attributes..
|
||||
:cvar qualified: for name matching, unqualified matching may be admitted only for elements and attributes.
|
||||
:vartype qualified: bool
|
||||
"""
|
||||
_REGEX_SPACE = re.compile(r'\s')
|
||||
_REGEX_SPACES = re.compile(r'\s+')
|
||||
_admitted_tags = ()
|
||||
|
||||
admitted_tags = ()
|
||||
parent = None
|
||||
name = None
|
||||
qualified = True
|
||||
|
||||
def __init__(self, elem, schema, parent, name=None):
|
||||
def __init__(self, elem, schema, parent=None, name=None):
|
||||
super(XsdComponent, self).__init__(schema.validation)
|
||||
if name == '':
|
||||
raise XMLSchemaValueError("'name' cannot be an empty string!")
|
||||
assert name is None or name[0] == '{' or not schema.target_namespace, \
|
||||
"name=%r argument: can be None or a qualified name of the target namespace." % name
|
||||
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
if name is not None:
|
||||
assert name and (name[0] == '{' or not schema.target_namespace), \
|
||||
"name=%r argument: must be a qualified name of the target namespace." % name
|
||||
self.name = name
|
||||
if parent is not None:
|
||||
self.parent = parent
|
||||
self.schema = schema
|
||||
self.elem = elem
|
||||
|
||||
|
@ -214,15 +217,13 @@ class XsdComponent(XsdValidator):
|
|||
if name == "elem":
|
||||
if not is_etree_element(value):
|
||||
raise XMLSchemaTypeError("%r attribute must be an Etree Element: %r" % (name, value))
|
||||
elif value.tag not in self.admitted_tags:
|
||||
elif value.tag not in self._admitted_tags:
|
||||
raise XMLSchemaValueError(
|
||||
"wrong XSD element %r for %r, must be one of %r." % (
|
||||
local_name(value.tag), self,
|
||||
[local_name(tag) for tag in self.admitted_tags]
|
||||
[local_name(tag) for tag in self._admitted_tags]
|
||||
)
|
||||
)
|
||||
elif hasattr(self, 'elem'):
|
||||
self._elem = self.elem # redefinition cases
|
||||
super(XsdComponent, self).__setattr__(name, value)
|
||||
self._parse()
|
||||
return
|
||||
|
@ -298,6 +299,13 @@ class XsdComponent(XsdValidator):
|
|||
except XMLSchemaValueError as err:
|
||||
self.parse_error(err, elem)
|
||||
|
||||
def _parse_attribute(self, elem, name, values, default=None):
|
||||
value = elem.get(name, default)
|
||||
if value not in values:
|
||||
self.parse_error("wrong value {} for {} attribute.".format(value, name))
|
||||
return default
|
||||
return value
|
||||
|
||||
def _parse_properties(self, *properties):
|
||||
for name in properties:
|
||||
try:
|
||||
|
@ -376,6 +384,16 @@ class XsdComponent(XsdValidator):
|
|||
"""Returns the component if its name is matching the name provided as argument, `None` otherwise."""
|
||||
return self if self.is_matching(name, default_namespace) else None
|
||||
|
||||
def get_global(self):
|
||||
"""Returns the global XSD component that contains the component instance."""
|
||||
if self.parent is None:
|
||||
return self
|
||||
component = self.parent
|
||||
while component is not self:
|
||||
if component.parent is None:
|
||||
return component
|
||||
component = component.parent
|
||||
|
||||
def iter_components(self, xsd_classes=None):
|
||||
"""
|
||||
Creates an iterator for XSD subcomponents.
|
||||
|
@ -436,7 +454,7 @@ class XsdAnnotation(XsdComponent):
|
|||
Content: ({any})*
|
||||
</documentation>
|
||||
"""
|
||||
admitted_tags = {XSD_ANNOTATION}
|
||||
_admitted_tags = {XSD_ANNOTATION}
|
||||
|
||||
@property
|
||||
def built(self):
|
||||
|
@ -460,9 +478,15 @@ class XsdAnnotation(XsdComponent):
|
|||
|
||||
|
||||
class XsdType(XsdComponent):
|
||||
|
||||
abstract = False
|
||||
base_type = None
|
||||
derivation = None
|
||||
redefine = None
|
||||
_final = None
|
||||
|
||||
@property
|
||||
def final(self):
|
||||
return self.schema.final_default if self._final is None else self._final
|
||||
|
||||
@property
|
||||
def built(self):
|
||||
|
@ -509,20 +533,10 @@ class XsdType(XsdComponent):
|
|||
return 'unknown'
|
||||
|
||||
def is_derived(self, other, derivation=None):
|
||||
if other.name == XSD_ANY_TYPE or self.base_type == other:
|
||||
return True if derivation is None else derivation == self.derivation
|
||||
elif self.base_type is not None:
|
||||
return self.base_type.is_derived(other, derivation)
|
||||
else:
|
||||
return False
|
||||
raise NotImplementedError
|
||||
|
||||
def is_subtype(self, qname):
|
||||
if qname == XSD_ANY_TYPE or self.name == qname:
|
||||
return True
|
||||
elif self.base_type is not None:
|
||||
return self.base_type.is_subtype(qname)
|
||||
else:
|
||||
return False
|
||||
def is_key(self):
|
||||
return self.name == XSD_ID or self.is_derived(self.maps.types[XSD_ID])
|
||||
|
||||
|
||||
class ValidationMixin(object):
|
||||
|
@ -761,54 +775,27 @@ class ParticleMixin(object):
|
|||
https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/structures.html#p
|
||||
https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/structures.html#t
|
||||
"""
|
||||
def parse_error(self, *args, **kwargs):
|
||||
# Implemented by XsdValidator
|
||||
raise NotImplementedError
|
||||
min_occurs = 1
|
||||
max_occurs = 1
|
||||
|
||||
def _parse_particle(self, elem):
|
||||
try:
|
||||
self.min_occurs = int(elem.attrib['minOccurs'])
|
||||
if self.min_occurs < 0:
|
||||
raise ValueError()
|
||||
except KeyError:
|
||||
self.min_occurs = 1
|
||||
except (TypeError, ValueError):
|
||||
self.parse_error("minOccurs value must be a non negative integer")
|
||||
self.min_occurs = 1
|
||||
|
||||
if 'maxOccurs' not in elem.attrib:
|
||||
self.max_occurs = 1
|
||||
elif elem.attrib['maxOccurs'] == 'unbounded':
|
||||
self.max_occurs = None
|
||||
else:
|
||||
try:
|
||||
self.max_occurs = int(elem.attrib['maxOccurs'])
|
||||
except ValueError:
|
||||
self.parse_error("maxOccurs value must be a non negative integer or 'unbounded'")
|
||||
self.max_occurs = 1
|
||||
else:
|
||||
if self.min_occurs > self.max_occurs:
|
||||
self.parse_error("maxOccurs must be 'unbounded' or greater than minOccurs:")
|
||||
|
||||
self.occurs = [self.min_occurs, self.max_occurs]
|
||||
@property
|
||||
def occurs(self):
|
||||
return [self.min_occurs, self.max_occurs]
|
||||
|
||||
def is_emptiable(self):
|
||||
return self.min_occurs == 0
|
||||
|
||||
is_optional = is_emptiable
|
||||
def is_empty(self):
|
||||
return self.max_occurs == 0
|
||||
|
||||
def is_single(self):
|
||||
return self.max_occurs == 1
|
||||
|
||||
def is_restriction(self, other):
|
||||
if self.min_occurs < other.min_occurs:
|
||||
return False
|
||||
if other.max_occurs is not None:
|
||||
if self.max_occurs is None:
|
||||
return False
|
||||
elif self.max_occurs > other.max_occurs:
|
||||
return False
|
||||
return True
|
||||
def is_ambiguous(self):
|
||||
return self.min_occurs != self.max_occurs
|
||||
|
||||
def is_univocal(self):
|
||||
return self.min_occurs == self.max_occurs
|
||||
|
||||
def is_missing(self, occurs):
|
||||
return not self.is_emptiable() if occurs == 0 else self.min_occurs > occurs
|
||||
|
@ -816,27 +803,49 @@ class ParticleMixin(object):
|
|||
def is_over(self, occurs):
|
||||
return self.max_occurs is not None and self.max_occurs <= occurs
|
||||
|
||||
def children_validation_error(self, validation, elem, index, particle, occurs=0, expected=None,
|
||||
source=None, namespaces=None, **_kwargs):
|
||||
"""
|
||||
Helper method for generating model validation errors. Incompatible with 'skip' validation mode.
|
||||
Il validation mode is 'lax' returns the error, otherwise raise the error.
|
||||
|
||||
:param validation: the validation mode. Can be 'lax' or 'strict'.
|
||||
:param elem: the instance Element.
|
||||
:param index: the child index.
|
||||
:param particle: the XSD component (subgroup or element) associated to the child.
|
||||
:param occurs: the child tag occurs.
|
||||
:param expected: the expected element tags/object names.
|
||||
:param source: the XML resource related to the validation process.
|
||||
:param namespaces: is an optional mapping from namespace prefix to URI.
|
||||
:param _kwargs: keyword arguments of the validation process that are not used.
|
||||
"""
|
||||
if validation == 'skip':
|
||||
raise XMLSchemaValueError("validation mode 'skip' incompatible with error generation.")
|
||||
|
||||
error = XMLSchemaChildrenValidationError(self, elem, index, particle, occurs, expected, source, namespaces)
|
||||
if validation == 'strict':
|
||||
raise error
|
||||
def has_occurs_restriction(self, other):
|
||||
if self.min_occurs == self.max_occurs == 0:
|
||||
return True
|
||||
elif self.min_occurs < other.min_occurs:
|
||||
return False
|
||||
elif other.max_occurs is None:
|
||||
return True
|
||||
elif self.max_occurs is None:
|
||||
return False
|
||||
else:
|
||||
return error
|
||||
return self.max_occurs <= other.max_occurs
|
||||
|
||||
###
|
||||
# Methods used by XSD components
|
||||
def parse_error(self, *args, **kwargs):
|
||||
"""Helper method overridden by XsdValidator.parse_error() in XSD components."""
|
||||
raise XMLSchemaParseError(*args)
|
||||
|
||||
def _parse_particle(self, elem):
|
||||
if 'minOccurs' in elem.attrib:
|
||||
try:
|
||||
min_occurs = int(elem.attrib['minOccurs'])
|
||||
except (TypeError, ValueError):
|
||||
self.parse_error("minOccurs value is not an integer value")
|
||||
else:
|
||||
if min_occurs < 0:
|
||||
self.parse_error("minOccurs value must be a non negative integer")
|
||||
else:
|
||||
self.min_occurs = min_occurs
|
||||
|
||||
max_occurs = elem.get('maxOccurs')
|
||||
if max_occurs is None:
|
||||
if self.min_occurs > 1:
|
||||
self.parse_error("minOccurs must be lesser or equal than maxOccurs")
|
||||
elif max_occurs == 'unbounded':
|
||||
self.max_occurs = None
|
||||
else:
|
||||
try:
|
||||
max_occurs = int(max_occurs)
|
||||
except ValueError:
|
||||
self.parse_error("maxOccurs value must be a non negative integer or 'unbounded'")
|
||||
else:
|
||||
if self.min_occurs > max_occurs:
|
||||
self.parse_error("maxOccurs must be 'unbounded' or greater than minOccurs")
|
||||
else:
|
||||
self.max_occurs = max_occurs
|
||||
|
|
Loading…
Reference in New Issue