From 92de835afa9bbd354ead7ad1e4b06051eef085c6 Mon Sep 17 00:00:00 2001 From: Davide Brunato Date: Mon, 18 Nov 2019 06:40:16 +0100 Subject: [PATCH] Fix defaultOpenContent and defaultAttributes parsing --- xmlschema/validators/complex_types.py | 118 ++++++++++++++++---------- xmlschema/validators/elements.py | 4 + xmlschema/validators/groups.py | 5 +- xmlschema/validators/identities.py | 2 +- xmlschema/validators/simple_types.py | 7 +- xmlschema/validators/wildcards.py | 4 +- xmlschema/validators/xsdbase.py | 4 +- 7 files changed, 92 insertions(+), 52 deletions(-) diff --git a/xmlschema/validators/complex_types.py b/xmlschema/validators/complex_types.py index 4010445..edfe0b1 100644 --- a/xmlschema/validators/complex_types.py +++ b/xmlschema/validators/complex_types.py @@ -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): diff --git a/xmlschema/validators/elements.py b/xmlschema/validators/elements.py index 4b2d3c7..636c537 100644 --- a/xmlschema/validators/elements.py +++ b/xmlschema/validators/elements.py @@ -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): diff --git a/xmlschema/validators/groups.py b/xmlschema/validators/groups.py index f3fa2ce..23ccbad 100644 --- a/xmlschema/validators/groups.py +++ b/xmlschema/validators/groups.py @@ -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 diff --git a/xmlschema/validators/identities.py b/xmlschema/validators/identities.py index 1e51d95..65b4fd3 100644 --- a/xmlschema/validators/identities.py +++ b/xmlschema/validators/identities.py @@ -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: diff --git a/xmlschema/validators/simple_types.py b/xmlschema/validators/simple_types.py index 182015a..e6e5a3d 100644 --- a/xmlschema/validators/simple_types.py +++ b/xmlschema/validators/simple_types.py @@ -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): diff --git a/xmlschema/validators/wildcards.py b/xmlschema/validators/wildcards.py index fe2e448..82c2071 100644 --- a/xmlschema/validators/wildcards.py +++ b/xmlschema/validators/wildcards.py @@ -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: diff --git a/xmlschema/validators/xsdbase.py b/xmlschema/validators/xsdbase.py index aab0b89..a1af296 100644 --- a/xmlschema/validators/xsdbase.py +++ b/xmlschema/validators/xsdbase.py @@ -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])