# -*- 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 # """ This module contains classes for XML Schema simple data types. """ 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_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_IDREF, XSD_FRACTION_DIGITS, XSD_TOTAL_DIGITS, XSD_EXPLICIT_TIMEZONE, \ XSD_ERROR, XSD_ASSERT, get_qname, local_name from ..helpers import get_xsd_derivation_attribute from .exceptions import XMLSchemaValidationError, XMLSchemaEncodeError, \ XMLSchemaDecodeError, XMLSchemaParseError from .xsdbase import XsdAnnotation, XsdType, ValidationMixin 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): """ Factory function for XSD simple types. Parses the xs:simpleType element and its child component, that can be a restriction, a list or an union. Annotations are linked to simple type instance, omitting the inner annotation if both are given. """ annotation = None try: child = elem[0] except IndexError: return schema.maps.types[XSD_ANY_SIMPLE_TYPE] else: if child.tag == XSD_ANNOTATION: annotation = XsdAnnotation(elem[0], schema, child) try: child = elem[1] except IndexError: schema.parse_error("(restriction | list | union) expected", elem) return schema.maps.types[XSD_ANY_SIMPLE_TYPE] if child.tag == XSD_RESTRICTION: xsd_type = schema.BUILDERS.restriction_class(child, schema, parent) elif child.tag == XSD_LIST: xsd_type = XsdList(child, schema, parent) elif child.tag == XSD_UNION: xsd_type = schema.BUILDERS.union_class(child, schema, parent) else: schema.parse_error("(restriction | list | union) expected", elem) return schema.maps.types[XSD_ANY_SIMPLE_TYPE] if annotation is not None: xsd_type.annotation = annotation try: xsd_type.name = get_qname(schema.target_namespace, elem.attrib['name']) except KeyError: if parent is None: schema.parse_error("missing attribute 'name' in a global simpleType", elem) xsd_type.name = 'nameless_%s' % str(id(xsd_type)) else: if parent is not None: schema.parse_error("attribute 'name' not allowed for a local simpleType", elem) xsd_type.name = None if 'final' in elem.attrib: try: xsd_type._final = get_xsd_derivation_attribute(elem, 'final') except ValueError as err: xsd_type.parse_error(err, elem) return xsd_type class XsdSimpleType(XsdType, ValidationMixin): """ Base class for simpleTypes definitions. Generally used only for instances of xs:anySimpleType. .. Content: (annotation?, (restriction | list | union)) """ _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) if not hasattr(self, 'facets'): self.facets = facets or {} def __setattr__(self, name, value): super(XsdSimpleType, self).__setattr__(name, value) if name == 'facets': 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): validators.extend(assertions) else: 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 facet") 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 " "the value of the same facet in the base type") # Checks XSD 1.1 facets if XSD_EXPLICIT_TIMEZONE in facets: explicit_tz_facet = base_type.get_facet(XSD_EXPLICIT_TIMEZONE) if explicit_tz_facet and explicit_tz_facet.value in ('prohibited', 'required') \ and facets[XSD_EXPLICIT_TIMEZONE].value != explicit_tz_facet.value: self.parse_error("the explicitTimezone facet value cannot be changed if the base " "type has the same facet with value %r" % explicit_tz_facet.value) 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: 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): return XSD_10_FACETS if self.xsd_version == '1.0' else XSD_11_FACETS @property def built(self): return True @staticmethod def is_simple(): return True @staticmethod def is_complex(): return False @staticmethod def is_list(): return False def is_empty(self): return self.max_length == 0 def is_emptiable(self): return self.min_length is None or self.min_length == 0 def has_simple_content(self): return True def has_mixed_content(self): return False def is_element_only(self): return False 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: return self.base_type.is_derived(other, derivation) def normalize(self, text): """ Normalize and restrict value-space with pre-lexical and lexical facets. The normalized string is returned. Returns the argument if it isn't a string. :param text: text string encoded value. :return: normalized string. """ if isinstance(text, bytes): text = text.decode('utf-8') elif not isinstance(text, string_base_type): raise XMLSchemaValueError('argument is not a string: %r' % text) if self.white_space == 'replace': return self._REGEX_SPACE.sub(' ', text) elif self.white_space == 'collapse': return self._REGEX_SPACES.sub(' ', text).strip() else: return text def text_decode(self, text): return self.decode(text, validation='skip') def iter_decode(self, obj, validation='lax', **kwargs): if isinstance(obj, (string_base_type, bytes)): obj = self.normalize(obj) if validation != 'skip' and obj is not None: if self.patterns is not None: for error in self.patterns(obj): yield error for validator in self.validators: for error in validator(obj): yield error yield obj def iter_encode(self, obj, validation='lax', **kwargs): if isinstance(obj, (string_base_type, bytes)): obj = self.normalize(obj) elif validation != 'skip': yield self.encode_error(validation, obj, unicode_type) if validation != 'skip' and obj is not None: if self.patterns is not None: for error in self.patterns(obj): yield error for validator in self.validators: for error in validator(obj): yield error yield obj def get_facet(self, tag): return self.facets.get(tag) # # simpleType's derived classes: class XsdAtomic(XsdSimpleType): """ Class for atomic simpleType definitions. An atomic definition has a base_type attribute that refers to primitive or derived atomic built-in type or another derived simpleType. """ to_python = str _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): if base_type is None: self.primitive_type = self else: self.base_type = base_type super(XsdAtomic, self).__init__(elem, schema, parent, name, facets) def __repr__(self): if self.name is None: return '%s(primitive_type=%r)' % (self.__class__.__name__, self.primitive_type.local_name) else: return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name) def __setattr__(self, name, value): super(XsdAtomic, self).__setattr__(name, value) if name == 'base_type': assert isinstance(value, XsdType) if not hasattr(self, 'white_space'): try: self.white_space = self.base_type.white_space except AttributeError: pass try: if value.is_simple(): self.primitive_type = self.base_type.primitive_type else: self.primitive_type = self.base_type.content_type.primitive_type except AttributeError: self.primitive_type = value @property def admitted_facets(self): if self.primitive_type.is_complex(): return XSD_10_FACETS if self.xsd_version == '1.0' else XSD_11_FACETS return self.primitive_type.admitted_facets def get_facet(self, tag): try: return self.facets[tag] except KeyError: try: return self.base_type.get_facet(tag) except AttributeError: return @staticmethod def is_atomic(): return True class XsdAtomicBuiltin(XsdAtomic): """ Class for defining XML Schema built-in simpleType atomic datatypes. An instance contains a Python's type transformation and a list of validator functions. The 'base_type' is not used for validation, but only for reference to the XML Schema restriction hierarchy. Type conversion methods: - to_python(value): Decoding from XML - from_python(value): Encoding to XML """ def __init__(self, elem, schema, name, python_type, base_type=None, admitted_facets=None, facets=None, to_python=None, from_python=None): """ :param name: the XSD type's qualified name. :param python_type: the correspondent Python's type. If a tuple or list of types \ is provided uses the first and consider the others as compatible types. :param base_type: the reference base type, None if it's a primitive type. :param admitted_facets: admitted facets tags for type (required for primitive types). :param facets: optional facets validators. :param to_python: optional decode function. :param from_python: optional encode function. """ if isinstance(python_type, (tuple, list)): self.instance_types, python_type = python_type, python_type[0] else: self.instance_types = python_type if not callable(python_type): raise XMLSchemaTypeError("%r object is not callable" % python_type.__class__) if base_type is None and not admitted_facets and name != XSD_ERROR: raise XMLSchemaValueError("argument 'admitted_facets' must be a not empty set of a primitive type") self._admitted_facets = admitted_facets super(XsdAtomicBuiltin, self).__init__(elem, schema, None, name, facets, base_type) self.python_type = python_type self.to_python = to_python or python_type self.from_python = from_python or unicode_type def __repr__(self): return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name) def _parse(self): pass @property def admitted_facets(self): return self._admitted_facets or self.primitive_type.admitted_facets def is_datetime(self): return self.to_python.__name__ == 'fromstring' def iter_decode(self, obj, validation='lax', **kwargs): if isinstance(obj, (string_base_type, bytes)): obj = self.normalize(obj) elif validation != 'skip' and obj is not None and not isinstance(obj, self.instance_types): yield self.decode_error(validation, obj, self.to_python, reason="value is not an instance of {!r}".format(self.instance_types)) if self.name == XSD_ID: try: id_map = kwargs['id_map'] except KeyError: pass else: try: id_map[obj] += 1 except TypeError: id_map[obj] = 1 if id_map[obj] > 1 and '_skip_id' not in kwargs: yield self.validation_error(validation, "Duplicated xsd:ID value {!r}".format(obj)) elif self.name == XSD_IDREF: try: id_map = kwargs['id_map'] except KeyError: pass else: if obj not in id_map: id_map[obj] = kwargs.get('node', 0) if validation == 'skip': try: yield self.to_python(obj) except (ValueError, DecimalException): yield unicode_type(obj) return if self.patterns is not None: for error in self.patterns(obj): yield error try: result = self.to_python(obj) except (ValueError, DecimalException) as err: yield self.decode_error(validation, obj, self.to_python, reason=str(err)) yield None return except TypeError: # xs:error type (eg. an XSD 1.1 type alternative used to catch invalid values) yield self.validation_error(validation, "Invalid value {!r}".format(obj)) yield None return for validator in self.validators: for error in validator(result): yield error yield result def iter_encode(self, obj, validation='lax', **kwargs): if isinstance(obj, (string_base_type, bytes)): obj = self.normalize(obj) if validation == 'skip': try: yield self.from_python(obj) except ValueError: yield unicode_type(obj) return elif isinstance(obj, bool): types_ = self.instance_types if types_ is not bool or (isinstance(types_, tuple) and bool in types_): reason = "boolean value {!r} requires a {!r} decoder.".format(obj, bool) yield self.encode_error(validation, obj, self.from_python, reason) obj = self.python_type(obj) elif not isinstance(obj, self.instance_types): reason = "{!r} is not an instance of {!r}.".format(obj, self.instance_types) yield self.encode_error(validation, obj, self.from_python, reason) try: value = self.python_type(obj) if value != obj: raise ValueError() else: obj = value except ValueError: yield self.encode_error(validation, obj, self.from_python) yield None return except TypeError: yield self.validation_error(validation, "Invalid value {!r}".format(obj)) yield None return for validator in self.validators: for error in validator(obj): yield error try: text = self.from_python(obj) except ValueError: yield self.encode_error(validation, obj, self.from_python) yield None else: if self.patterns is not None: for error in self.patterns(text): yield error yield text class XsdList(XsdSimpleType): """ Class for 'list' definitions. A list definition has an item_type attribute that refers to an atomic or union simpleType definition. .. Content: (annotation?, simpleType?) """ _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 = {XSD_WHITE_SPACE: XsdWhiteSpaceFacet(self._white_space_elem, schema, self, self)} super(XsdList, self).__init__(elem, schema, parent, name, facets) def __repr__(self): if self.name is None: return '%s(item_type=%r)' % (self.__class__.__name__, self.base_type) else: return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name) def __setattr__(self, name, value): if name == 'elem' and value is not None and value.tag != XSD_LIST: if value.tag == XSD_SIMPLE_TYPE: for child in value: if child.tag == XSD_LIST: super(XsdList, self).__setattr__(name, child) return raise XMLSchemaValueError("a %r definition required for %r." % (XSD_LIST, self)) elif name == 'base_type': if not value.is_atomic(): raise XMLSchemaValueError("%r: a list must be based on atomic data types." % self) elif name == 'white_space' and value is None: value = 'collapse' super(XsdList, self).__setattr__(name, value) def _parse(self): super(XsdList, self)._parse() elem = self.elem child = self._parse_child_component(elem) if child is not None: # Case of a local simpleType declaration inside the list tag try: base_type = xsd_simple_type_factory(child, self.schema, self) except XMLSchemaParseError as err: self.parse_error(err, elem) base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE] if 'itemType' in elem.attrib: self.parse_error("ambiguous list type declaration", self) else: # List tag with itemType attribute that refers to a global type try: item_qname = self.schema.resolve_qname(elem.attrib['itemType']) except (KeyError, ValueError, RuntimeError) as err: if 'itemType' not in elem.attrib: self.parse_error("missing list type declaration") else: self.parse_error(err) base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE] else: try: base_type = self.maps.lookup_type(item_qname) except KeyError: self.parse_error("unknown itemType %r" % elem.attrib['itemType'], elem) base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE] else: if isinstance(base_type, tuple): self.parse_error("circular definition found for type {!r}".format(item_qname)) 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) if base_type is self.any_atomic_type: self.parse_error("Cannot use xs:anyAtomicType as base type of a user-defined type") try: self.base_type = base_type except XMLSchemaValueError as err: self.parse_error(str(err), elem) self.base_type = self.maps.types[XSD_ANY_ATOMIC_TYPE] @property def admitted_facets(self): return XSD_10_LIST_FACETS if self.xsd_version == '1.0' else XSD_11_LIST_FACETS @property def item_type(self): return self.base_type @staticmethod def is_atomic(): return False @staticmethod 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 if self.base_type.parent is not None: for obj in self.base_type.iter_components(xsd_classes): yield obj def iter_decode(self, obj, validation='lax', **kwargs): if isinstance(obj, (string_base_type, bytes)): obj = self.normalize(obj) items = [] for chunk in obj.split(): for result in self.base_type.iter_decode(chunk, validation, **kwargs): if isinstance(result, XMLSchemaValidationError): yield result else: items.append(result) yield items def iter_encode(self, obj, validation='lax', **kwargs): if not hasattr(obj, '__iter__') or isinstance(obj, (str, unicode_type, bytes)): obj = [obj] encoded_items = [] for item in obj: for result in self.base_type.iter_encode(item, validation, **kwargs): if isinstance(result, XMLSchemaValidationError): yield result else: encoded_items.append(result) yield ' '.join(item for item in encoded_items if item is not None) class XsdUnion(XsdSimpleType): """ Class for 'union' definitions. A union definition has a member_types attribute that refers to a 'simpleType' definition. .. Content: (annotation?, simpleType*) """ _ADMITTED_TYPES = XsdSimpleType _ADMITTED_TAGS = {XSD_UNION} 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: return '%s(member_types=%r)' % (self.__class__.__name__, self.member_types) else: return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name) def __setattr__(self, name, value): if name == 'elem' and value is not None and value.tag != XSD_UNION: if value.tag == XSD_SIMPLE_TYPE: for child in value: if child.tag == XSD_UNION: super(XsdUnion, self).__setattr__(name, child) return raise XMLSchemaValueError("a %r definition required for %r." % (XSD_UNION, self)) elif name == 'white_space': if not (value is None or value == 'collapse'): raise XMLSchemaValueError("Wrong value % for attribute 'white_space'." % value) value = 'collapse' super(XsdUnion, self).__setattr__(name, value) def _parse(self): super(XsdUnion, self)._parse() elem = self.elem member_types = [] for child in filter(lambda x: x.tag != XSD_ANNOTATION, elem): mt = xsd_simple_type_factory(child, self.schema, self) if isinstance(mt, XMLSchemaParseError): self.parse_error(mt) else: member_types.append(mt) if 'memberTypes' in elem.attrib: for name in elem.attrib['memberTypes'].split(): try: type_qname = self.schema.resolve_qname(name) except (KeyError, ValueError, RuntimeError) as err: self.parse_error(err) continue try: mt = self.maps.lookup_type(type_qname) except KeyError: self.parse_error("unknown member type %r" % type_qname) mt = self.maps.types[XSD_ANY_ATOMIC_TYPE] except XMLSchemaParseError as err: self.parse_error(err) mt = self.maps.types[XSD_ANY_ATOMIC_TYPE] 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) member_types.append(mt) if not member_types: self.parse_error("missing xs:union type declarations", elem) self.member_types = [self.maps.types[XSD_ANY_ATOMIC_TYPE]] elif any(mt is self.any_atomic_type for mt in member_types): self.parse_error("Cannot use xs:anyAtomicType as base type of a user-defined type") else: self.member_types = member_types @property def admitted_facets(self): return XSD_10_UNION_FACETS if self.xsd_version == '1.0' else XSD_11_UNION_FACETS def is_atomic(self): return all(mt.is_atomic() for mt in self.member_types) def is_list(self): 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 \ any(mt1.is_derived(mt2) for mt1 in other.member_types for mt2 in self.member_types) def iter_components(self, xsd_classes=None): if xsd_classes is None or isinstance(self, xsd_classes): yield self for mt in filter(lambda x: x.parent is not None, self.member_types): for obj in mt.iter_components(xsd_classes): yield obj def iter_decode(self, obj, validation='lax', patterns=None, **kwargs): # Try decoding the whole text for member_type in self.member_types: for result in member_type.iter_decode(obj, validation='lax', **kwargs): if not isinstance(result, XMLSchemaValidationError): if validation != 'skip' and patterns: obj = member_type.normalize(obj) for error in patterns(obj): yield error yield result return break if validation != 'skip' and ' ' not in obj.strip(): reason = "invalid value %r." % obj yield self.decode_error(validation, obj, self.member_types, reason) items = [] not_decodable = [] for chunk in obj.split(): for member_type in self.member_types: for result in member_type.iter_decode(chunk, validation='lax', **kwargs): if isinstance(result, XMLSchemaValidationError): break else: items.append(result) else: break else: if validation != 'skip': not_decodable.append(chunk) else: items.append(unicode_type(chunk)) if validation != 'skip': if not_decodable: reason = "no type suitable for decoding the values %r." % not_decodable yield self.decode_error(validation, obj, self.member_types, reason) yield items if len(items) > 1 else items[0] if items else None def iter_encode(self, obj, validation='lax', **kwargs): for member_type in self.member_types: for result in member_type.iter_encode(obj, validation='lax', **kwargs): if result is not None and not isinstance(result, XMLSchemaValidationError): yield result return elif validation == 'strict': # In 'strict' mode avoid lax encoding by similar types (eg. float encoded by int) break if hasattr(obj, '__iter__') and not isinstance(obj, (str, unicode_type, bytes)): for member_type in self.member_types: results = [] for item in obj: for result in member_type.iter_encode(item, validation='lax', **kwargs): if result is not None and not isinstance(result, XMLSchemaValidationError): results.append(result) break elif validation == 'strict': break if len(results) == len(obj): yield results break if validation != 'skip': reason = "no type suitable for encoding the object." yield self.encode_error(validation, obj, self.member_types, reason) yield None else: 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. .. Content: (annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | enumeration | whiteSpace | pattern)*)) """ FACETS_BUILDERS = XSD_10_FACETS_BUILDERS derivation = 'restriction' _CONTENT_TAIL_TAGS = {XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_ANY_ATTRIBUTE} 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("an xs:restriction definition required for %r." % self) super(XsdAtomicRestriction, self).__setattr__(name, value) def _parse(self): super(XsdAtomicRestriction, self)._parse() elem = self.elem if elem.get('name') == XSD_ANY_ATOMIC_TYPE: return # skip special type xs:anyAtomicType elif elem.tag == XSD_SIMPLE_TYPE and elem.get('name') is not None: elem = self._parse_child_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: try: base_qname = self.schema.resolve_qname(elem.attrib['base']) except (KeyError, ValueError, RuntimeError) 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) try: base_type = self.maps.lookup_type(base_qname) except KeyError: 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(): child = self._parse_child_component(elem, strict=False) if child is None: self.parse_error("an xs:simpleType definition expected") elif child.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 filter(lambda x: x.tag != XSD_ANNOTATION, elem): if child.tag in self._CONTENT_TAIL_TAGS: has_attributes = True # only if it's a complexType restriction elif has_attributes: self.parse_error("unexpected tag after attribute declarations", child) elif child.tag == XSD_SIMPLE_TYPE: # Case of simpleType declaration inside a restriction if has_simple_type_child: self.parse_error("duplicated simpleType declaration", child) 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.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) has_simple_type_child = True else: try: facet_class = self.FACETS_BUILDERS[child.tag] except KeyError: self.parse_error("unexpected tag %r in restriction:" % child.tag) continue if child.tag not in facets: facets[child.tag] = facet_class(child, self.schema, self, base_type) elif child.tag not in MULTIPLE_FACETS: self.parse_error("multiple %r constraint facet" % local_name(child.tag)) elif child.tag != XSD_ASSERTION: facets[child.tag].append(child) else: assertion = facet_class(child, self.schema, self, base_type) try: facets[child.tag].append(assertion) except AttributeError: facets[child.tag] = [facets[child.tag], assertion] 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) if base_type is self.any_atomic_type: self.parse_error("Cannot use xs:anyAtomicType as base type of a user-defined type") self.base_type = base_type self.facets = facets def iter_components(self, xsd_classes=None): if xsd_classes is None or isinstance(self, xsd_classes): yield self if self.base_type.parent is not None: for obj in self.base_type.iter_components(xsd_classes): yield obj def iter_decode(self, obj, validation='lax', **kwargs): if isinstance(obj, (string_base_type, bytes)): obj = self.normalize(obj) if self.base_type.is_simple(): base_type = self.base_type elif self.base_type.has_simple_content(): base_type = self.base_type.content_type elif self.base_type.mixed: yield obj return else: raise XMLSchemaValueError("wrong base type %r: a simpleType or a complexType with " "simple or mixed content required." % self.base_type) if validation != 'skip' and self.patterns: if not isinstance(self.primitive_type, XsdUnion): for error in self.patterns(obj): yield error elif 'patterns' not in kwargs: kwargs['patterns'] = self.patterns for result in base_type.iter_decode(obj, validation, **kwargs): if isinstance(result, XMLSchemaValidationError): yield result if isinstance(result, XMLSchemaDecodeError): yield unicode_type(obj) if validation == 'skip' else None else: if validation != 'skip' and result is not None: for validator in self.validators: for error in validator(result): yield error yield result return def iter_encode(self, obj, validation='lax', **kwargs): if self.is_list(): if not hasattr(obj, '__iter__') or isinstance(obj, (str, unicode_type, bytes)): obj = [] if obj is None or obj == '' else [obj] base_type = self.base_type else: if isinstance(obj, (string_base_type, bytes)): obj = self.normalize(obj) if self.base_type.is_simple(): base_type = self.base_type elif self.base_type.has_simple_content(): base_type = self.base_type.content_type elif self.base_type.mixed: yield unicode_type(obj) return else: raise XMLSchemaValueError("wrong base type %r: a simpleType or a complexType with " "simple or mixed content required." % self.base_type) for result in base_type.iter_encode(obj, validation): if isinstance(result, XMLSchemaValidationError): yield result if isinstance(result, XMLSchemaEncodeError): yield unicode_type(obj) if validation == 'skip' else None return else: if validation != 'skip' and self.validators and obj is not None: if isinstance(obj, (string_base_type, bytes)): if self.primitive_type.is_datetime(): obj = self.primitive_type.to_python(obj) for validator in self.validators: for error in validator(obj): yield error yield result return def is_list(self): return self.primitive_type.is_list() class Xsd11AtomicRestriction(XsdAtomicRestriction): """ Class for XSD 1.1 atomic simpleType and complexType's simpleContent restrictions. .. Content: (annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | enumeration | whiteSpace | pattern | assertion | explicitTimezone | {any with namespace: ##other})*)) """ FACETS_BUILDERS = XSD_11_FACETS_BUILDERS _CONTENT_TAIL_TAGS = {XSD_ATTRIBUTE, XSD_ATTRIBUTE_GROUP, XSD_ANY_ATTRIBUTE, XSD_ASSERT}