Fix defaultOpenContent and defaultAttributes parsing

This commit is contained in:
Davide Brunato 2019-11-18 06:40:16 +01:00
parent a60532a3ab
commit 92de835afa
7 changed files with 92 additions and 52 deletions

View File

@ -13,8 +13,9 @@ from __future__ import unicode_literals
from ..exceptions import XMLSchemaValueError
from ..qnames import XSD_ANNOTATION, XSD_GROUP, XSD_ATTRIBUTE_GROUP, XSD_SEQUENCE, \
XSD_ALL, XSD_CHOICE, XSD_ANY_ATTRIBUTE, XSD_ATTRIBUTE, XSD_COMPLEX_CONTENT, \
XSD_RESTRICTION, XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_ANY_TYPE, XSD_SIMPLE_CONTENT, \
XSD_ANY_SIMPLE_TYPE, XSD_OPEN_CONTENT, XSD_ASSERT, get_qname, local_name
XSD_RESTRICTION, XSD_COMPLEX_TYPE, XSD_EXTENSION, XSD_ANY_TYPE, XSD_OVERRIDE, \
XSD_SIMPLE_CONTENT, XSD_ANY_SIMPLE_TYPE, XSD_OPEN_CONTENT, XSD_ASSERT, \
get_qname, local_name
from ..helpers import get_xsd_derivation_attribute
from .exceptions import XMLSchemaValidationError, XMLSchemaDecodeError
@ -52,6 +53,8 @@ class XsdComplexType(XsdType, ValidationMixin):
mixed = False
assertions = ()
open_content = None
content_type = None
default_open_content = None
_block = None
_ADMITTED_TAGS = {XSD_COMPLEX_TYPE, XSD_RESTRICTION}
@ -138,6 +141,10 @@ class XsdComplexType(XsdType, ValidationMixin):
elif content_elem.tag in {XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
self.content_type = self.schema.BUILDERS.group_class(content_elem, self.schema, self)
default_open_content = self.default_open_content
if default_open_content and \
(self.mixed or self.content_type or default_open_content.applies_to_empty):
self.open_content = default_open_content
self._parse_content_tail(elem)
elif content_elem.tag == XSD_SIMPLE_CONTENT:
@ -179,6 +186,7 @@ class XsdComplexType(XsdType, ValidationMixin):
self.base_type = base_type
elif self.redefine:
self.base_type = self.redefine
self.open_content = None
if derivation_elem.tag == XSD_RESTRICTION:
self._parse_complex_content_restriction(derivation_elem, base_type)
@ -344,9 +352,11 @@ class XsdComplexType(XsdType, ValidationMixin):
"derived an empty content from base type that has not empty content.", elem
)
if not self.open_content and self.schema.default_open_content:
if content_type or self.schema.default_open_content.applies_to_empty:
self.open_content = self.schema.default_open_content
if not self.open_content:
default_open_content = self.default_open_content
if default_open_content and \
(self.mixed or content_type or default_open_content.applies_to_empty):
self.open_content = default_open_content
if self.open_content and content_type and \
not self.open_content.is_restriction(base_type.open_content):
@ -453,6 +463,8 @@ class XsdComplexType(XsdType, ValidationMixin):
def is_empty(self):
if self.name == XSD_ANY_TYPE:
return False
elif self.open_content and self.open_content.mode != 'none':
return False
return self.content_type.is_empty()
def is_emptiable(self):
@ -571,6 +583,10 @@ class XsdComplexType(XsdType, ValidationMixin):
:return: yields a 3-tuple (simple content, complex content, attributes) containing \
the decoded parts, eventually preceded by a sequence of validation or decoding errors.
"""
if self.is_empty() and elem.text:
reason = "character data between child elements not allowed because the type's content is empty"
yield self.validation_error(validation, reason, elem, **kwargs)
# XSD 1.1 assertions
for assertion in self.assertions:
for error in assertion(elem, **kwargs):
@ -665,6 +681,32 @@ class Xsd11ComplexType(XsdComplexType):
_CONTENT_TAIL_TAGS = {XSD_ATTRIBUTE_GROUP, XSD_ATTRIBUTE, XSD_ANY_ATTRIBUTE, XSD_ASSERT}
@property
def default_attributes(self):
if self.redefine is not None:
return self.schema.default_attributes
for child in filter(lambda x: x.tag == XSD_OVERRIDE, self.schema.root):
if self.elem in child:
schema = self.schema.includes[child.attrib['schemaLocation']]
if schema.override is self.schema:
return schema.default_attributes
else:
return self.schema.default_attributes
@property
def default_open_content(self):
if self.parent is not None:
return self.schema.default_open_content
for child in filter(lambda x: x.tag == XSD_OVERRIDE, self.schema.root):
if self.elem in child:
schema = self.schema.includes[child.attrib['schemaLocation']]
if schema.override is self.schema:
return schema.default_open_content
else:
return self.schema.default_open_content
def _parse(self):
super(Xsd11ComplexType, self)._parse()
@ -677,19 +719,12 @@ class Xsd11ComplexType(XsdComplexType):
# Add open content to complex content type
if isinstance(self.content_type, XsdGroup):
open_content = self.open_content
if open_content is not None:
pass
elif self.schema.default_open_content is not None:
if self.content_type or self.schema.default_open_content.applies_to_empty:
open_content = self.schema.default_open_content
if open_content is None:
pass
elif open_content.mode == 'interleave':
self.content_type.interleave = self.content_type.suffix = open_content.any_element
elif open_content.mode == 'suffix':
self.content_type.suffix = open_content.any_element
if self.open_content is None:
assert self.content_type.interleave is None and self.content_type.suffix is None
elif self.open_content.mode == 'interleave':
self.content_type.interleave = self.content_type.suffix = self.open_content.any_element
elif self.open_content.mode == 'suffix':
self.content_type.suffix = self.open_content.any_element
# Add inheritable attributes
if hasattr(self.base_type, 'attributes'):
@ -707,19 +742,12 @@ class Xsd11ComplexType(XsdComplexType):
self.default_attributes_apply = True
# Add default attributes
if self.redefine is None:
default_attributes = self.schema.default_attributes
else:
default_attributes = self.redefine.schema.default_attributes
if default_attributes is None:
pass
elif self.default_attributes_apply and not self.is_override():
if self.redefine is None and any(k in self.attributes for k in default_attributes):
self.parse_error("at least a default attribute is already declared in the complex type")
self.attributes.update(
(k, v) for k, v in default_attributes.items() if k not in self.attributes
)
if self.default_attributes_apply:
default_attributes = self.default_attributes
if default_attributes is not None:
if self.redefine is None and any(k in self.attributes for k in default_attributes):
self.parse_error("at least a default attribute is already declared in the complex type")
self.attributes.update((k, v) for k, v in default_attributes.items())
def _parse_complex_content_extension(self, elem, base_type):
# Complex content extension with simple base is forbidden XSD 1.1.
@ -744,19 +772,6 @@ class Xsd11ComplexType(XsdComplexType):
else:
group_elem = None
if not self.open_content:
if self.schema.default_open_content:
self.open_content = self.schema.default_open_content
elif getattr(base_type, 'open_content', None):
self.open_content = base_type.open_content
try:
if self.open_content and not base_type.open_content.is_restriction(self.open_content):
msg = "{!r} is not an extension of the base type {!r}"
self.parse_error(msg.format(self.open_content, base_type.open_content))
except AttributeError:
pass
if not base_type.content_type:
if not base_type.mixed:
# Empty element-only model extension: don't create a nested sequence group.
@ -831,6 +846,21 @@ class Xsd11ComplexType(XsdComplexType):
else:
self.content_type = self.schema.create_empty_content_group(self)
if not self.open_content:
default_open_content = self.default_open_content
if default_open_content and \
(self.mixed or self.content_type or default_open_content.applies_to_empty):
self.open_content = default_open_content
elif base_type.open_content:
self.open_content = base_type.open_content
if base_type.open_content and self.open_content is not base_type.open_content:
if self.open_content.mode == 'none':
self.open_content = base_type.open_content
elif not base_type.open_content.is_restriction(self.open_content):
msg = "{!r} is not an extension of the base type {!r}"
self.parse_error(msg.format(self.open_content, base_type.open_content))
self._parse_content_tail(elem, derivation='extension', base_attributes=base_type.attributes)
def _parse_content_tail(self, elem, **kwargs):

View File

@ -531,6 +531,10 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
yield converter.element_decode(element_data, self, level)
return
if xsd_type.is_empty() and elem.text:
reason = "character data is not allowed because the type's content is empty"
yield self.validation_error(validation, reason, elem, **kwargs)
if not xsd_type.has_simple_content():
for assertion in xsd_type.assertions:
for error in assertion(elem, **kwargs):

View File

@ -526,7 +526,8 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
if model_element is not xsd_element and model_element.block:
for derivation in model_element.block.split():
if xsd_type.is_derived(model_element.type, derivation):
if xsd_type is not model_element.type and \
xsd_type.is_derived(model_element.type, derivation):
reason = "usage of %r with type %s is blocked by head element"
raise XMLSchemaValidationError(self, reason % (xsd_element, derivation))
@ -578,7 +579,7 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
if len(self) == 1 and isinstance(self[0], XsdAnyElement):
pass # [XsdAnyElement()] equals to an empty complexType declaration
else:
reason = "character data between child elements not allowed!"
reason = "character data between child elements not allowed"
yield self.validation_error(validation, reason, elem, **kwargs)
cdata_index = 0 # Do not decode CDATA

View File

@ -201,7 +201,7 @@ class XsdIdentity(XsdComponent):
yield XMLSchemaValidationError(self, e, "{!r} is not an element".format(xsd_element))
xsd_fields = self.get_fields(xsd_element)
if all(fld is None for fld in xsd_fields):
if not xsd_fields or all(fld is None for fld in xsd_fields):
continue
try:

View File

@ -334,6 +334,10 @@ class XsdSimpleType(XsdType, ValidationMixin):
else:
return self.base_type.is_derived(other, derivation)
def is_dynamic_consistent(self, other):
return other is self.any_type or other is self.any_simple_type or self.is_derived(other) or \
hasattr(other, 'member_types') and any(self.is_derived(mt) for mt in other.member_types)
def normalize(self, text):
"""
Normalize and restrict value-space with pre-lexical and lexical facets.
@ -867,7 +871,8 @@ class XsdUnion(XsdSimpleType):
return all(mt.is_list() for mt in self.member_types)
def is_dynamic_consistent(self, other):
return other.is_derived(self) or hasattr(other, 'member_types') and \
return other is self.any_type or other is self.any_simple_type or \
other.is_derived(self) or hasattr(other, 'member_types') and \
any(mt1.is_derived(mt2) for mt1 in other.member_types for mt2 in self.member_types)
def iter_components(self, xsd_classes=None):

View File

@ -782,8 +782,8 @@ class XsdOpenContent(XsdComponent):
return True
def is_restriction(self, other):
if self.mode == 'none' or other is None or other.mode == 'none':
return True
if other is None or other.mode == 'none':
return self.mode == 'none'
elif self.mode == 'interleave' and other.mode == 'suffix':
return False
else:

View File

@ -701,8 +701,8 @@ class XsdType(XsdComponent):
return any(self.is_derived(xsd_type, derivation) for derivation in block)
def is_dynamic_consistent(self, other):
return self.is_derived(other) or hasattr(other, 'member_types') and \
any(self.is_derived(mt) for mt in other.member_types)
return other is self.any_type or self.is_derived(other) or \
hasattr(other, 'member_types') and any(self.is_derived(mt) for mt in other.member_types)
def is_key(self):
return self.name == XSD_ID or self.is_derived(self.maps.types[XSD_ID])