2017-12-14 13:38:23 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
2019-01-20 16:53:23 +01:00
|
|
|
# Copyright (c), 2016-2019, SISSA (International School for Advanced Studies).
|
2017-12-14 13:38:23 +01:00
|
|
|
# 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>
|
|
|
|
#
|
|
|
|
"""
|
2019-05-31 09:29:41 +02:00
|
|
|
This module contains functions and classes for namespaces XSD declarations/definitions.
|
2017-12-14 13:38:23 +01:00
|
|
|
"""
|
2018-09-15 22:18:16 +02:00
|
|
|
from __future__ import unicode_literals
|
2019-04-11 07:09:19 +02:00
|
|
|
import warnings
|
2019-03-26 07:32:44 +01:00
|
|
|
from collections import Counter
|
2018-10-08 23:47:18 +02:00
|
|
|
|
2019-09-11 18:43:13 +02:00
|
|
|
from ..compat import string_base_type
|
2019-04-11 07:09:19 +02:00
|
|
|
from ..exceptions import XMLSchemaKeyError, XMLSchemaTypeError, XMLSchemaValueError, XMLSchemaWarning
|
2019-10-07 15:31:18 +02:00
|
|
|
from ..namespaces import XSD_NAMESPACE, NamespaceResourcesMap
|
|
|
|
from ..qnames import XSD_REDEFINE, XSD_OVERRIDE, XSD_NOTATION, XSD_ANY_TYPE, \
|
|
|
|
XSD_SIMPLE_TYPE, XSD_COMPLEX_TYPE, XSD_GROUP, XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, \
|
|
|
|
XSD_ELEMENT, XSI_TYPE, get_qname, local_name, qname_to_extended
|
2018-10-08 23:47:18 +02:00
|
|
|
|
2019-07-30 14:19:52 +02:00
|
|
|
from . import XMLSchemaNotBuiltError, XMLSchemaModelError, XMLSchemaModelDepthError, \
|
|
|
|
XsdValidator, XsdComponent, XsdAttribute, XsdSimpleType, XsdComplexType, XsdElement, \
|
|
|
|
XsdAttributeGroup, XsdGroup, XsdNotation, Xsd11Element, XsdKeyref, XsdAssert
|
2018-08-08 19:11:01 +02:00
|
|
|
from .builtins import xsd_builtin_types_factory
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2017-12-28 08:14:42 +01:00
|
|
|
|
2017-12-14 13:38:23 +01:00
|
|
|
#
|
|
|
|
# Defines the load functions for XML Schema structures
|
2019-08-10 23:58:00 +02:00
|
|
|
def create_load_function(tag):
|
|
|
|
|
2017-12-14 13:38:23 +01:00
|
|
|
def load_xsd_globals(xsd_globals, schemas):
|
|
|
|
redefinitions = []
|
|
|
|
for schema in schemas:
|
|
|
|
target_namespace = schema.target_namespace
|
2019-08-10 23:58:00 +02:00
|
|
|
|
|
|
|
for elem in filter(lambda x: x.tag in (XSD_REDEFINE, XSD_OVERRIDE), schema.root):
|
2019-05-04 14:40:03 +02:00
|
|
|
location = elem.get('schemaLocation')
|
|
|
|
if location is None:
|
|
|
|
continue
|
2019-08-10 23:58:00 +02:00
|
|
|
for child in filter(lambda x: x.tag == tag and 'name' in x.attrib, elem):
|
2018-10-04 15:55:55 +02:00
|
|
|
qname = get_qname(target_namespace, child.attrib['name'])
|
2019-08-10 23:58:00 +02:00
|
|
|
redefinitions.append((qname, elem, child, schema, schema.includes[location]))
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2019-08-10 23:58:00 +02:00
|
|
|
for elem in filter(lambda x: x.tag == tag and 'name' in x.attrib, schema.root):
|
2018-10-04 15:55:55 +02:00
|
|
|
qname = get_qname(target_namespace, elem.attrib['name'])
|
2019-08-10 23:58:00 +02:00
|
|
|
if qname not in xsd_globals:
|
2017-12-14 13:38:23 +01:00
|
|
|
xsd_globals[qname] = (elem, schema)
|
2019-08-10 23:58:00 +02:00
|
|
|
else:
|
2019-08-17 09:36:24 +02:00
|
|
|
try:
|
|
|
|
other_schema = xsd_globals[qname][1]
|
|
|
|
except (TypeError, IndexError):
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
# It's ignored or replaced in case of an override
|
|
|
|
if other_schema.override is schema:
|
|
|
|
continue
|
|
|
|
elif schema.override is other_schema:
|
|
|
|
xsd_globals[qname] = (elem, schema)
|
|
|
|
continue
|
|
|
|
|
2019-08-10 23:58:00 +02:00
|
|
|
msg = "global {} with name={!r} is already defined"
|
|
|
|
schema.parse_error(msg.format(local_name(tag), qname))
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2019-05-04 14:40:03 +02:00
|
|
|
tags = Counter([x[0] for x in redefinitions])
|
2019-08-10 23:58:00 +02:00
|
|
|
for qname, elem, child, schema, redefined_schema in redefinitions:
|
2019-05-04 14:40:03 +02:00
|
|
|
|
|
|
|
# Checks multiple redefinitions
|
2019-03-26 07:32:44 +01:00
|
|
|
if tags[qname] > 1:
|
2019-05-04 14:40:03 +02:00
|
|
|
tags[qname] = 1
|
|
|
|
|
2019-08-10 23:58:00 +02:00
|
|
|
redefined_schemas = [x[-1] for x in redefinitions if x[0] == qname]
|
2019-05-04 14:40:03 +02:00
|
|
|
if any(redefined_schemas.count(x) > 1 for x in redefined_schemas):
|
2019-08-10 23:58:00 +02:00
|
|
|
msg = "multiple redefinition for {} {!r}"
|
|
|
|
schema.parse_error(msg.format(local_name(child.tag), qname), child)
|
2019-05-04 14:40:03 +02:00
|
|
|
else:
|
2019-08-10 23:58:00 +02:00
|
|
|
redefined_schemas = {x[-1]: x[-2] for x in redefinitions if x[0] == qname}
|
2019-05-04 14:40:03 +02:00
|
|
|
for rs, s in redefined_schemas.items():
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
s = redefined_schemas[s]
|
|
|
|
except KeyError:
|
|
|
|
break
|
|
|
|
|
|
|
|
if s is rs:
|
2019-08-10 23:58:00 +02:00
|
|
|
msg = "circular redefinition for {} {!r}"
|
|
|
|
schema.parse_error(msg.format(local_name(child.tag), qname), child)
|
2019-05-04 14:40:03 +02:00
|
|
|
break
|
|
|
|
|
2019-08-10 23:58:00 +02:00
|
|
|
if elem.tag == XSD_OVERRIDE:
|
|
|
|
xsd_globals[qname] = (child, schema)
|
|
|
|
else:
|
|
|
|
# Append to a list if it's a redefine
|
|
|
|
try:
|
|
|
|
xsd_globals[qname].append((child, schema))
|
|
|
|
except KeyError:
|
|
|
|
schema.parse_error("not a redefinition!", child)
|
|
|
|
except AttributeError:
|
|
|
|
xsd_globals[qname] = [xsd_globals[qname], (child, schema)]
|
2017-12-14 13:38:23 +01:00
|
|
|
|
|
|
|
return load_xsd_globals
|
|
|
|
|
|
|
|
|
2019-08-10 23:58:00 +02:00
|
|
|
load_xsd_simple_types = create_load_function(XSD_SIMPLE_TYPE)
|
|
|
|
load_xsd_attributes = create_load_function(XSD_ATTRIBUTE)
|
|
|
|
load_xsd_attribute_groups = create_load_function(XSD_ATTRIBUTE_GROUP)
|
|
|
|
load_xsd_complex_types = create_load_function(XSD_COMPLEX_TYPE)
|
|
|
|
load_xsd_elements = create_load_function(XSD_ELEMENT)
|
|
|
|
load_xsd_groups = create_load_function(XSD_GROUP)
|
|
|
|
load_xsd_notations = create_load_function(XSD_NOTATION)
|
2017-12-14 13:38:23 +01:00
|
|
|
|
|
|
|
|
2018-06-15 18:39:52 +02:00
|
|
|
def create_lookup_function(xsd_classes):
|
|
|
|
if isinstance(xsd_classes, tuple):
|
|
|
|
types_desc = ' or '.join([c.__name__ for c in xsd_classes])
|
|
|
|
else:
|
|
|
|
types_desc = xsd_classes.__name__
|
|
|
|
|
2019-09-11 18:43:13 +02:00
|
|
|
def lookup(qname, global_map, tag_map):
|
2018-06-15 18:39:52 +02:00
|
|
|
try:
|
|
|
|
obj = global_map[qname]
|
|
|
|
except KeyError:
|
2019-03-11 22:00:32 +01:00
|
|
|
if '{' in qname:
|
2019-10-03 19:08:14 +02:00
|
|
|
raise XMLSchemaKeyError("missing an %s component for %r!" % (types_desc, qname))
|
|
|
|
raise XMLSchemaKeyError("missing an %s component for %r! As the name has no namespace "
|
2019-03-11 22:00:32 +01:00
|
|
|
"maybe a missing default namespace declaration." % (types_desc, qname))
|
2018-06-15 18:39:52 +02:00
|
|
|
else:
|
|
|
|
if isinstance(obj, xsd_classes):
|
|
|
|
return obj
|
|
|
|
|
|
|
|
elif isinstance(obj, tuple):
|
|
|
|
# Not built XSD global component without redefinitions
|
|
|
|
try:
|
|
|
|
elem, schema = obj
|
|
|
|
except ValueError:
|
|
|
|
return obj[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))
|
|
|
|
|
|
|
|
global_map[qname] = obj, # Encapsulate into a single-item tuple to catch circular builds
|
2018-08-08 15:47:24 +02:00
|
|
|
global_map[qname] = factory_or_class(elem, schema, parent=None)
|
2018-06-15 18:39:52 +02:00
|
|
|
return global_map[qname]
|
|
|
|
|
|
|
|
elif isinstance(obj, list):
|
2019-04-07 11:50:22 +02:00
|
|
|
# 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))
|
|
|
|
|
|
|
|
global_map[qname] = obj[0], # To catch circular builds
|
|
|
|
global_map[qname] = component = factory_or_class(elem, schema, parent=None)
|
2018-06-15 18:39:52 +02:00
|
|
|
|
2019-01-30 15:22:04 +01:00
|
|
|
# Apply redefinitions (changing elem involve a re-parsing of the component)
|
2018-06-15 18:39:52 +02:00
|
|
|
for elem, schema in obj[1:]:
|
2019-04-07 11:50:22 +02:00
|
|
|
component.redefine = component.copy()
|
2019-05-28 23:11:24 +02:00
|
|
|
component.redefine.parent = component
|
2019-04-07 11:50:22 +02:00
|
|
|
component.schema = schema
|
|
|
|
component.elem = elem
|
|
|
|
|
2018-06-15 18:39:52 +02:00
|
|
|
return global_map[qname]
|
|
|
|
|
|
|
|
else:
|
|
|
|
raise XMLSchemaTypeError(
|
|
|
|
"wrong instance %s for XSD global %r, a %s required." % (obj, qname, types_desc)
|
|
|
|
)
|
|
|
|
|
|
|
|
return lookup
|
|
|
|
|
|
|
|
|
|
|
|
lookup_notation = create_lookup_function(XsdNotation)
|
|
|
|
lookup_type = create_lookup_function((XsdSimpleType, XsdComplexType))
|
|
|
|
lookup_attribute = create_lookup_function(XsdAttribute)
|
|
|
|
lookup_attribute_group = create_lookup_function(XsdAttributeGroup)
|
|
|
|
lookup_group = create_lookup_function(XsdGroup)
|
|
|
|
lookup_element = create_lookup_function(XsdElement)
|
|
|
|
|
|
|
|
|
2018-08-06 07:11:37 +02:00
|
|
|
class XsdGlobals(XsdValidator):
|
2017-12-14 13:38:23 +01:00
|
|
|
"""
|
|
|
|
Mediator class for related XML schema instances. It stores the global
|
|
|
|
declarations defined in the registered schemas. Register a schema to
|
|
|
|
add it's declarations to the global maps.
|
|
|
|
|
2019-03-09 10:09:03 +01:00
|
|
|
:param validator: the origin schema class/instance used for creating the global maps.
|
2018-08-08 07:12:58 +02:00
|
|
|
:param validation: the XSD validation mode to use, can be 'strict', 'lax' or 'skip'.
|
2017-12-14 13:38:23 +01:00
|
|
|
"""
|
2018-08-08 07:12:58 +02:00
|
|
|
def __init__(self, validator, validation='strict'):
|
|
|
|
super(XsdGlobals, self).__init__(validation)
|
2019-03-09 10:09:03 +01:00
|
|
|
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))
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2019-03-09 10:09:03 +01:00
|
|
|
self.validator = validator
|
2018-07-04 13:18:35 +02:00
|
|
|
self.namespaces = NamespaceResourcesMap() # Registered schemas by namespace URI
|
2017-12-14 13:38:23 +01:00
|
|
|
|
|
|
|
self.types = {} # Global types (both complex and simple)
|
|
|
|
self.attributes = {} # Global attributes
|
|
|
|
self.attribute_groups = {} # Attribute groups
|
|
|
|
self.groups = {} # Model groups
|
|
|
|
self.notations = {} # Notations
|
|
|
|
self.elements = {} # Global elements
|
|
|
|
self.substitution_groups = {} # Substitution groups
|
2019-08-24 23:39:20 +02:00
|
|
|
self.identities = {} # Identity constraints (uniqueness, keys, keyref)
|
2017-12-14 13:38:23 +01:00
|
|
|
|
|
|
|
self.global_maps = (self.notations, self.types, self.attributes,
|
|
|
|
self.attribute_groups, self.groups, self.elements)
|
|
|
|
|
2019-01-29 18:51:03 +01:00
|
|
|
def __repr__(self):
|
|
|
|
return '%s(validator=%r, validation=%r)' % (self.__class__.__name__, self.validator, self.validation)
|
|
|
|
|
|
|
|
def copy(self, validator=None, validation=None):
|
2017-12-14 13:38:23 +01:00
|
|
|
"""Makes a copy of the object."""
|
2019-01-29 18:51:03 +01:00
|
|
|
obj = XsdGlobals(self.validator if validator is None else validator, validation or self.validation)
|
2017-12-14 13:38:23 +01:00
|
|
|
obj.namespaces.update(self.namespaces)
|
|
|
|
obj.types.update(self.types)
|
|
|
|
obj.attributes.update(self.attributes)
|
|
|
|
obj.attribute_groups.update(self.attribute_groups)
|
|
|
|
obj.groups.update(self.groups)
|
|
|
|
obj.notations.update(self.notations)
|
|
|
|
obj.elements.update(self.elements)
|
|
|
|
obj.substitution_groups.update(self.substitution_groups)
|
2019-08-24 23:39:20 +02:00
|
|
|
obj.identities.update(self.identities)
|
2017-12-14 13:38:23 +01:00
|
|
|
return obj
|
|
|
|
|
|
|
|
__copy__ = copy
|
|
|
|
|
2018-06-15 18:39:52 +02:00
|
|
|
def lookup_notation(self, qname):
|
2019-09-11 18:43:13 +02:00
|
|
|
return lookup_notation(qname, self.notations, self.validator.BUILDERS_MAP)
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2018-06-15 18:39:52 +02:00
|
|
|
def lookup_type(self, qname):
|
2019-09-11 18:43:13 +02:00
|
|
|
return lookup_type(qname, self.types, self.validator.BUILDERS_MAP)
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2018-06-15 18:39:52 +02:00
|
|
|
def lookup_attribute(self, qname):
|
2019-09-11 18:43:13 +02:00
|
|
|
return lookup_attribute(qname, self.attributes, self.validator.BUILDERS_MAP)
|
2018-04-26 13:08:56 +02:00
|
|
|
|
2018-06-15 18:39:52 +02:00
|
|
|
def lookup_attribute_group(self, qname):
|
2019-09-11 18:43:13 +02:00
|
|
|
return lookup_attribute_group(qname, self.attribute_groups, self.validator.BUILDERS_MAP)
|
2018-04-26 13:08:56 +02:00
|
|
|
|
2018-06-15 18:39:52 +02:00
|
|
|
def lookup_group(self, qname):
|
2019-09-11 18:43:13 +02:00
|
|
|
return lookup_group(qname, self.groups, self.validator.BUILDERS_MAP)
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2018-06-15 18:39:52 +02:00
|
|
|
def lookup_element(self, qname):
|
2019-09-11 18:43:13 +02:00
|
|
|
return lookup_element(qname, self.elements, self.validator.BUILDERS_MAP)
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2019-03-11 10:49:19 +01:00
|
|
|
def lookup(self, tag, qname):
|
2019-08-19 13:52:30 +02:00
|
|
|
"""
|
|
|
|
General lookup method for XSD global components.
|
|
|
|
|
|
|
|
:param tag: the expanded QName of the XSD the global declaration/definition \
|
|
|
|
(eg. '{http://www.w3.org/2001/XMLSchema}element'), that is used to select \
|
|
|
|
the global map for lookup.
|
|
|
|
:param qname: the expanded QName of the component to be looked-up.
|
|
|
|
:returns: an XSD global component.
|
|
|
|
:raises: an XMLSchemaValueError if the *tag* argument is not appropriate for a global \
|
|
|
|
component, an XMLSchemaKeyError if the *qname* argument is not found in the global map.
|
|
|
|
"""
|
2019-03-11 10:49:19 +01:00
|
|
|
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))
|
|
|
|
|
2019-10-03 19:08:14 +02:00
|
|
|
def get_instance_type(self, type_name, base_type, namespaces):
|
|
|
|
"""
|
|
|
|
Returns the instance XSI type from global maps, validating it with the reference base type.
|
|
|
|
|
|
|
|
:param type_name: the XSI type attribute value, a QName in prefixed format.
|
|
|
|
:param base_type: the XSD from which the instance type has to be derived.
|
|
|
|
:param namespaces: a map from prefixes to namespaces.
|
|
|
|
"""
|
|
|
|
if base_type.is_complex() and XSI_TYPE in base_type.attributes:
|
|
|
|
base_type.attributes[XSI_TYPE].validate(type_name)
|
|
|
|
|
|
|
|
extended_name = qname_to_extended(type_name, namespaces)
|
|
|
|
xsi_type = lookup_type(extended_name, self.types, self.validator.BUILDERS_MAP)
|
|
|
|
if not xsi_type.is_derived(base_type):
|
|
|
|
raise XMLSchemaTypeError("%r is not a derived type of %r" % (xsi_type, self))
|
|
|
|
return xsi_type
|
|
|
|
|
2017-12-14 13:38:23 +01:00
|
|
|
@property
|
|
|
|
def built(self):
|
2019-07-30 14:19:52 +02:00
|
|
|
return all(schema.built for schema in self.iter_schemas())
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2019-08-05 17:57:34 +02:00
|
|
|
@property
|
|
|
|
def unbuilt(self):
|
|
|
|
"""Property that returns a list with unbuilt components."""
|
|
|
|
return [c for s in self.iter_schemas() for c in s.iter_components()
|
|
|
|
if c is not s and not c.built]
|
|
|
|
|
2017-12-14 13:38:23 +01:00
|
|
|
@property
|
|
|
|
def validation_attempted(self):
|
|
|
|
if self.built:
|
|
|
|
return 'full'
|
2019-07-30 14:19:52 +02:00
|
|
|
elif any(schema.validation_attempted == 'partial' for schema in self.iter_schemas()):
|
2017-12-14 13:38:23 +01:00
|
|
|
return 'partial'
|
|
|
|
else:
|
|
|
|
return 'none'
|
|
|
|
|
|
|
|
@property
|
|
|
|
def validity(self):
|
|
|
|
if not self.namespaces:
|
|
|
|
return False
|
2018-02-15 21:11:03 +01:00
|
|
|
if all(schema.validity == 'valid' for schema in self.iter_schemas()):
|
|
|
|
return 'valid'
|
|
|
|
elif any(schema.validity == 'invalid' for schema in self.iter_schemas()):
|
|
|
|
return 'invalid'
|
|
|
|
else:
|
|
|
|
return 'notKnown'
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2018-04-03 21:42:59 +02:00
|
|
|
@property
|
2019-07-30 21:06:54 +02:00
|
|
|
def xsd_version(self):
|
|
|
|
return self.validator.XSD_VERSION
|
2018-04-03 21:42:59 +02:00
|
|
|
|
2019-09-11 18:43:13 +02:00
|
|
|
@property
|
|
|
|
def builders_map(self):
|
|
|
|
return self.validator.BUILDERS_MAP
|
|
|
|
|
2019-03-06 10:08:21 +01:00
|
|
|
@property
|
|
|
|
def all_errors(self):
|
|
|
|
errors = []
|
|
|
|
for schema in self.iter_schemas():
|
2019-03-11 22:00:32 +01:00
|
|
|
errors.extend(schema.all_errors)
|
2019-03-06 10:08:21 +01:00
|
|
|
return errors
|
|
|
|
|
2019-08-24 23:39:20 +02:00
|
|
|
@property
|
|
|
|
def constraints(self):
|
|
|
|
"""
|
|
|
|
Old reference to identity constraints, for backward compatibility. Will be removed in v1.1.0.
|
|
|
|
"""
|
|
|
|
return self.identities
|
|
|
|
|
2017-12-14 13:38:23 +01:00
|
|
|
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 obj in xsd_global.iter_components(xsd_classes):
|
|
|
|
yield obj
|
|
|
|
|
2018-08-08 07:12:58 +02:00
|
|
|
def iter_schemas(self):
|
|
|
|
"""Creates an iterator for the schemas registered in the instance."""
|
|
|
|
for ns_schemas in self.namespaces.values():
|
|
|
|
for schema in ns_schemas:
|
|
|
|
yield schema
|
|
|
|
|
2017-12-28 08:14:42 +01:00
|
|
|
def iter_globals(self):
|
2018-02-15 21:11:03 +01:00
|
|
|
"""
|
|
|
|
Creates an iterator for XSD global definitions/declarations.
|
|
|
|
"""
|
2017-12-28 08:14:42 +01:00
|
|
|
for global_map in self.global_maps:
|
|
|
|
for obj in global_map.values():
|
|
|
|
yield obj
|
|
|
|
|
2017-12-14 13:38:23 +01:00
|
|
|
def register(self, schema):
|
|
|
|
"""
|
|
|
|
Registers an XMLSchema instance.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
ns_schemas = self.namespaces[schema.target_namespace]
|
|
|
|
except KeyError:
|
|
|
|
self.namespaces[schema.target_namespace] = [schema]
|
|
|
|
else:
|
|
|
|
if schema in ns_schemas:
|
|
|
|
return
|
2019-07-30 14:19:52 +02:00
|
|
|
elif not any(schema.url == obj.url and schema.__class__ == obj.__class__ for obj in ns_schemas):
|
2017-12-14 13:38:23 +01:00
|
|
|
ns_schemas.append(schema)
|
|
|
|
|
2018-07-04 13:18:35 +02:00
|
|
|
def clear(self, remove_schemas=False, only_unbuilt=False):
|
2017-12-14 13:38:23 +01:00
|
|
|
"""
|
2018-07-04 13:18:35 +02:00
|
|
|
Clears the instance maps and schemas.
|
|
|
|
|
|
|
|
:param remove_schemas: removes also the schema instances.
|
|
|
|
:param only_unbuilt: removes only not built objects/schemas.
|
2017-12-14 13:38:23 +01:00
|
|
|
"""
|
2018-07-04 13:18:35 +02:00
|
|
|
if only_unbuilt:
|
|
|
|
not_built_schemas = {schema for schema in self.iter_schemas() if not schema.built}
|
|
|
|
if not not_built_schemas:
|
|
|
|
return
|
|
|
|
|
|
|
|
for global_map in self.global_maps:
|
|
|
|
for k in list(global_map.keys()):
|
|
|
|
obj = global_map[k]
|
|
|
|
if not isinstance(obj, XsdComponent) or obj.schema in not_built_schemas:
|
|
|
|
del global_map[k]
|
|
|
|
if k in self.substitution_groups:
|
|
|
|
del self.substitution_groups[k]
|
2019-08-24 23:39:20 +02:00
|
|
|
if k in self.identities:
|
|
|
|
del self.identities[k]
|
2018-07-04 13:18:35 +02:00
|
|
|
|
|
|
|
if remove_schemas:
|
|
|
|
namespaces = NamespaceResourcesMap()
|
|
|
|
for uri, value in self.namespaces.items():
|
|
|
|
for schema in value:
|
|
|
|
if schema not in not_built_schemas:
|
|
|
|
namespaces[uri] = schema
|
|
|
|
self.namespaces = namespaces
|
|
|
|
|
|
|
|
else:
|
|
|
|
for global_map in self.global_maps:
|
|
|
|
global_map.clear()
|
|
|
|
self.substitution_groups.clear()
|
2019-08-24 23:39:20 +02:00
|
|
|
self.identities.clear()
|
2017-12-14 13:38:23 +01:00
|
|
|
|
2018-07-04 13:18:35 +02:00
|
|
|
if remove_schemas:
|
|
|
|
self.namespaces.clear()
|
2017-12-14 13:38:23 +01:00
|
|
|
|
|
|
|
def build(self):
|
|
|
|
"""
|
2019-01-30 15:22:04 +01:00
|
|
|
Build the maps of XSD global definitions/declarations. The global maps are
|
|
|
|
updated adding and building the globals of not built registered schemas.
|
2017-12-14 13:38:23 +01:00
|
|
|
"""
|
|
|
|
try:
|
2018-03-08 15:22:47 +01:00
|
|
|
meta_schema = self.namespaces[XSD_NAMESPACE][0]
|
2017-12-14 13:38:23 +01:00
|
|
|
except KeyError:
|
2019-03-11 10:49:19 +01:00
|
|
|
# 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)
|
2019-08-24 23:39:20 +02:00
|
|
|
self.identities.update(meta_schema.maps.identities)
|
2017-12-14 13:38:23 +01:00
|
|
|
|
|
|
|
not_built_schemas = [schema for schema in self.iter_schemas() if not schema.built]
|
2018-06-29 18:13:45 +02:00
|
|
|
for schema in not_built_schemas:
|
|
|
|
schema._root_elements = None
|
2017-12-14 13:38:23 +01:00
|
|
|
|
|
|
|
# Load and build global declarations
|
|
|
|
load_xsd_simple_types(self.types, not_built_schemas)
|
2019-08-01 07:03:03 +02:00
|
|
|
load_xsd_complex_types(self.types, not_built_schemas)
|
|
|
|
load_xsd_notations(self.notations, not_built_schemas)
|
2017-12-14 13:38:23 +01:00
|
|
|
load_xsd_attributes(self.attributes, not_built_schemas)
|
|
|
|
load_xsd_attribute_groups(self.attribute_groups, not_built_schemas)
|
|
|
|
load_xsd_elements(self.elements, not_built_schemas)
|
|
|
|
load_xsd_groups(self.groups, not_built_schemas)
|
|
|
|
|
|
|
|
if not meta_schema.built:
|
2018-08-08 19:11:01 +02:00
|
|
|
xsd_builtin_types_factory(meta_schema, self.types)
|
2017-12-14 13:38:23 +01:00
|
|
|
|
|
|
|
for qname in self.notations:
|
|
|
|
self.lookup_notation(qname)
|
|
|
|
for qname in self.attributes:
|
|
|
|
self.lookup_attribute(qname)
|
2019-09-11 18:43:13 +02:00
|
|
|
|
2017-12-14 13:38:23 +01:00
|
|
|
for qname in self.attribute_groups:
|
|
|
|
self.lookup_attribute_group(qname)
|
2019-09-11 18:43:13 +02:00
|
|
|
for schema in filter(
|
|
|
|
lambda x: isinstance(x.default_attributes, string_base_type),
|
|
|
|
not_built_schemas):
|
|
|
|
try:
|
|
|
|
schema.default_attributes = schema.maps.attribute_groups[schema.default_attributes]
|
|
|
|
except KeyError:
|
|
|
|
schema.default_attributes = None
|
|
|
|
msg = "defaultAttributes={!r} doesn't match an attribute group of {!r}"
|
|
|
|
schema.parse_error(
|
|
|
|
error=msg.format(schema.root.get('defaultAttributes'), schema),
|
|
|
|
elem=schema.root,
|
|
|
|
validation=schema.validation
|
|
|
|
)
|
|
|
|
|
2017-12-14 13:38:23 +01:00
|
|
|
for qname in self.types:
|
|
|
|
self.lookup_type(qname)
|
|
|
|
for qname in self.elements:
|
|
|
|
self.lookup_element(qname)
|
|
|
|
for qname in self.groups:
|
|
|
|
self.lookup_group(qname)
|
|
|
|
|
|
|
|
# Builds element declarations inside model groups.
|
2018-04-06 23:42:50 +02:00
|
|
|
for schema in not_built_schemas:
|
|
|
|
for group in schema.iter_components(XsdGroup):
|
2019-04-07 11:50:22 +02:00
|
|
|
group.build()
|
2018-04-06 23:42:50 +02:00
|
|
|
|
2019-09-20 11:49:01 +02:00
|
|
|
# Builds xs:keyref's key references
|
|
|
|
for constraint in filter(lambda x: isinstance(x, XsdKeyref), self.identities.values()):
|
|
|
|
constraint.parse_refer()
|
|
|
|
|
2019-07-30 14:19:52 +02:00
|
|
|
# Build XSD 1.1 identity references and assertions
|
2019-09-11 18:43:13 +02:00
|
|
|
if self.xsd_version != '1.0':
|
2019-07-30 14:19:52 +02:00
|
|
|
for schema in filter(lambda x: x.meta_schema is not None, not_built_schemas):
|
|
|
|
for e in schema.iter_components(Xsd11Element):
|
2019-08-24 23:39:20 +02:00
|
|
|
for constraint in filter(lambda x: x.ref is not None, e.identities.values()):
|
2019-07-30 14:19:52 +02:00
|
|
|
try:
|
2019-08-24 23:39:20 +02:00
|
|
|
ref = self.identities[constraint.name]
|
2019-07-30 14:19:52 +02:00
|
|
|
except KeyError:
|
|
|
|
schema.parse_error("Unknown %r constraint %r" % (type(constraint), constraint.name))
|
2019-08-05 17:57:34 +02:00
|
|
|
else:
|
|
|
|
constraint.selector = ref.selector
|
|
|
|
constraint.fields = ref.fields
|
2019-08-23 08:48:02 +02:00
|
|
|
if not isinstance(ref, constraint.__class__):
|
|
|
|
constraint.parse_error("attribute 'ref' points to a different kind constraint")
|
|
|
|
elif isinstance(constraint, XsdKeyref):
|
2019-08-05 17:57:34 +02:00
|
|
|
constraint.refer = ref.refer
|
|
|
|
constraint.ref = ref
|
2019-07-30 14:19:52 +02:00
|
|
|
|
|
|
|
for assertion in schema.iter_components(XsdAssert):
|
|
|
|
assertion.parse_xpath_test()
|
|
|
|
|
|
|
|
self.check(filter(lambda x: x.meta_schema is not None, not_built_schemas), self.validation)
|
|
|
|
|
|
|
|
def check(self, schemas=None, validation='strict'):
|
|
|
|
"""
|
|
|
|
Checks the global maps. For default checks all schemas and raises an exception at first error.
|
|
|
|
|
|
|
|
:param schemas: optional argument with the set of the schemas to check.
|
|
|
|
:param validation: overrides the default validation mode of the validator.
|
|
|
|
:raise: XMLSchemaParseError
|
|
|
|
"""
|
2019-08-20 22:10:12 +02:00
|
|
|
schemas = set(schemas if schemas is not None else self.iter_schemas())
|
2019-03-21 07:14:58 +01:00
|
|
|
|
2019-07-30 14:19:52 +02:00
|
|
|
# Checks substitution groups circularities
|
|
|
|
for qname in self.substitution_groups:
|
|
|
|
xsd_element = self.elements[qname]
|
2019-08-20 22:10:12 +02:00
|
|
|
for e in xsd_element.iter_substitutes():
|
|
|
|
if e is xsd_element:
|
|
|
|
msg = "circularity found for substitution group with head element %r"
|
|
|
|
e.parse_error(msg.format(e), validation=validation)
|
2019-09-11 18:43:13 +02:00
|
|
|
elif e.abstract and e.name not in self.substitution_groups and self.xsd_version > '1.0':
|
2019-08-20 22:10:12 +02:00
|
|
|
self.parse_error("in XSD 1.1 an abstract element cannot be member of a substitution group")
|
|
|
|
|
2019-07-30 14:19:52 +02:00
|
|
|
if validation == 'strict' and not self.built:
|
2019-08-05 17:57:34 +02:00
|
|
|
raise XMLSchemaNotBuiltError(self, "global map has unbuilt components: %r" % self.unbuilt)
|
2019-03-28 14:08:36 +01:00
|
|
|
|
2019-07-30 14:19:52 +02:00
|
|
|
# Check redefined global groups restrictions
|
|
|
|
for group in filter(lambda x: x.schema in schemas 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):
|
|
|
|
msg = "The redefined group is an illegal restriction of the original group."
|
|
|
|
group.parse_error(msg, validation=validation)
|
|
|
|
|
|
|
|
# Check complex content types models restrictions
|
|
|
|
for xsd_global in filter(lambda x: x.schema in schemas, self.iter_globals()):
|
|
|
|
for xsd_type in xsd_global.iter_components(XsdComplexType):
|
|
|
|
if not isinstance(xsd_type.content_type, XsdGroup):
|
|
|
|
continue
|
|
|
|
|
|
|
|
if xsd_type.derivation == 'restriction':
|
|
|
|
base_type = xsd_type.base_type
|
|
|
|
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):
|
|
|
|
msg = "The derived group is an illegal restriction of the base type group."
|
|
|
|
xsd_type.parse_error(msg, validation=validation)
|
|
|
|
|
2019-08-24 23:39:20 +02:00
|
|
|
if base_type.is_complex() and not base_type.open_content and \
|
|
|
|
xsd_type.open_content and xsd_type.open_content.mode != 'none':
|
|
|
|
group = xsd_type.schema.create_any_content_group(
|
|
|
|
parent=xsd_type,
|
|
|
|
any_element=xsd_type.open_content.any_element
|
|
|
|
)
|
|
|
|
if not group.is_restriction(base_type.content_type):
|
|
|
|
self.parse_error("restriction has an open content but base type has not")
|
|
|
|
|
2019-07-30 14:19:52 +02:00
|
|
|
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.format(xsd_type))
|
|
|
|
warnings.warn(msg, XMLSchemaWarning, stacklevel=4)
|
|
|
|
except XMLSchemaModelError as err:
|
|
|
|
if validation == 'strict':
|
|
|
|
raise
|
|
|
|
xsd_type.errors.append(err)
|