Implement element substitution and xsi:type block in instances

This commit is contained in:
Davide Brunato 2019-10-12 21:19:01 +02:00
parent 22fdcc9a5a
commit de7e2343bd
4 changed files with 50 additions and 44 deletions

View File

@ -52,10 +52,10 @@ class XsdComplexType(XsdType, ValidationMixin):
mixed = False
assertions = ()
open_content = None
_block = None
_ADMITTED_TAGS = {XSD_COMPLEX_TYPE, XSD_RESTRICTION}
_CONTENT_TAIL_TAGS = {XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_ANY_ATTRIBUTE}
_block = None
@staticmethod
def normalize(text):
@ -149,19 +149,10 @@ class XsdComplexType(XsdType, ValidationMixin):
return
self.base_type = base_type = self._parse_base_type(derivation_elem)
block = base_type.block
if self._block is None and block:
self._block = block
if derivation_elem.tag == XSD_RESTRICTION:
self._parse_simple_content_restriction(derivation_elem, base_type)
if base_type.blocked or 'restriction' in block and base_type != self:
self.blocked = True
else:
self._parse_simple_content_extension(derivation_elem, base_type)
if base_type.blocked or 'extension' in block and base_type != self:
self.blocked = True
if content_elem is not elem[-1]:
k = 2 if content_elem is not elem[0] else 1
@ -184,24 +175,15 @@ class XsdComplexType(XsdType, ValidationMixin):
return
base_type = self._parse_base_type(derivation_elem, complex_content=True)
if base_type is not self:
self.base_type = base_type
elif self.redefine:
self.base_type = self.redefine
block = base_type.block
if self._block is None and block:
self._block = block
if derivation_elem.tag == XSD_RESTRICTION:
self._parse_complex_content_restriction(derivation_elem, base_type)
if base_type.blocked or 'restriction' in block and base_type != self:
self.blocked = True
else:
self._parse_complex_content_extension(derivation_elem, base_type)
if base_type.blocked or 'extension' in block and base_type != self:
self.blocked = True
if content_elem is not elem[-1]:
k = 2 if content_elem is not elem[0] else 1
@ -450,6 +432,10 @@ class XsdComplexType(XsdType, ValidationMixin):
self._parse_content_tail(elem, derivation='extension', base_attributes=base_type.attributes)
@property
def block(self):
return self.schema.block_default if self._block is None else self._block
@property
def built(self):
return self.content_type.parent is not None or self.content_type.built
@ -458,10 +444,6 @@ class XsdComplexType(XsdType, ValidationMixin):
def validation_attempted(self):
return 'full' if self.built else self.content_type.validation_attempted
@property
def block(self):
return self.schema.block_default if self._block is None else self._block
@staticmethod
def is_simple():
return False
@ -514,14 +496,15 @@ class XsdComplexType(XsdType, ValidationMixin):
self.base_type.is_valid(source, use_defaults, namespaces)
def is_derived(self, other, derivation=None):
if derivation and derivation == self.derivation:
derivation = None # derivation mode checked
if self is other:
return True
elif derivation and self.derivation and derivation != self.derivation and other.is_complex():
return False
return derivation is None
elif other.name == XSD_ANY_TYPE:
return True
elif self.base_type is other:
return True
return derivation is None or self.base_type.derivation == derivation
elif hasattr(other, 'member_types'):
return any(self.is_derived(m, derivation) for m in other.member_types)
elif self.base_type is None:

View File

@ -358,11 +358,19 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
@property
def final(self):
return self._final or self.schema.final_default if self.ref is None else self.ref.final
if self.ref is not None:
return self.ref.final
elif self._final is not None:
return self._final
return self.schema.final_default
@property
def block(self):
return self._block or self.schema.block_default if self.ref is None else self.ref.block
if self.ref is not None:
return self.ref.block
elif self._block is not None:
return self._block
return self.schema.block_default
@property
def nillable(self):
@ -479,8 +487,8 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
except (KeyError, TypeError) as err:
yield self.validation_error(validation, err, elem, **kwargs)
if xsd_type.is_blocked(self.block):
yield self.validation_error(validation, "usage of %r is blocked" % xsd_type, elem, **kwargs)
if xsd_type.is_blocked(self):
yield self.validation_error(validation, "usage of %r is blocked" % xsd_type, elem, **kwargs)
# Decode attributes
attribute_group = self.get_attributes(xsd_type)

View File

@ -481,6 +481,13 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
return other_max_occurs >= max_occurs * self.max_occurs
def check_dynamic_context(self, elem, xsd_element, model_element, converter):
if model_element is not xsd_element:
if 'substitution' in model_element.block \
or xsd_element.type.is_blocked(model_element):
raise XMLSchemaValidationError(
model_element, "substitution of %r is blocked" % model_element
)
alternatives = ()
if isinstance(xsd_element, XsdAnyElement):
if xsd_element.process_contents == 'skip':
@ -707,8 +714,10 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
content = model.iter_unordered_content(element_data.content)
elif converter.losslessly:
content = element_data.content
else:
elif isinstance(element_data.content, list):
content = model.iter_collapsed_content(element_data.content)
else:
content = []
for index, (name, value) in enumerate(content):
if isinstance(name, int):
@ -775,7 +784,7 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
else:
children[-1].tail = children[-1].tail.strip() + (padding[:-indent] or '\n')
if validation != 'skip' and errors:
if validation != 'skip' and (errors or not content):
attrib = {k: unicode_type(v) for k, v in element_data.attributes.items()}
if validation == 'lax' and converter.etree_element_class is not etree_element:
child_tags = [converter.etree_element(e.tag, attrib=e.attrib) for e in children]
@ -783,6 +792,10 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
else:
elem = converter.etree_element(element_data.tag, text, children, attrib)
if not content:
reason = "wrong content type {!r}".format(type(element_data.content))
yield self.validation_error(validation, reason, elem, **kwargs)
for index, particle, occurs, expected in errors:
yield self.children_validation_error(validation, elem, index, particle, occurs, expected, **kwargs)

View File

@ -569,8 +569,7 @@ class XsdType(XsdComponent):
"""Common base class for XSD types."""
abstract = False
blocked = False
block = ''
block = None
base_type = None
derivation = None
redefine = None
@ -664,17 +663,20 @@ class XsdType(XsdComponent):
def is_derived(self, other, derivation=None):
raise NotImplementedError
def is_blocked(self, block=''):
if self.blocked:
return True
elif not block:
def is_blocked(self, xsd_element):
"""
Returns `True` if the base type derivation is blocked, `False` otherwise.
"""
xsd_type = xsd_element.type
if self is xsd_type:
return False
elif self.derivation and self.derivation in block:
return True
elif self.base_type is None:
block = ('%s %s' % (xsd_element.block, xsd_type.block)).strip()
if not block:
return False
else:
return self.base_type.is_blocked(block)
block = {x for x in block.split() if x in ('extension', 'restriction')}
return any(self.is_derived(xsd_type, derivation) for derivation in block)
def is_dynamic_consistent(self, other):
return self.is_derived(other) or hasattr(other, 'member_types') and \