1#
2# Copyright (c), 2016-2020, SISSA (International School for Advanced Studies).
3# All rights reserved.
4# This file is distributed under the terms of the MIT License.
5# See the file 'LICENSE' in the root directory of the present
6# distribution, or http://opensource.org/licenses/MIT.
7#
8# @author Davide Brunato <brunato@sissa.it>
9#
10"""
11This module contains base functions and classes XML Schema components.
12"""
13import re
14from typing import TYPE_CHECKING, cast, Any, Dict, Generic, List, Iterator, Optional, \
15    Set, Tuple, TypeVar, Union, MutableMapping
16
17import elementpath
18
19from ..exceptions import XMLSchemaValueError, XMLSchemaTypeError
20from ..names import XSD_ANNOTATION, XSD_APPINFO, XSD_DOCUMENTATION, XML_LANG, \
21    XSD_ANY_TYPE, XSD_ANY_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE, XSD_ID, XSD_QNAME, \
22    XSD_OVERRIDE, XSD_NOTATION_TYPE, XSD_DECIMAL
23from ..etree import is_etree_element, etree_tostring, etree_element
24from ..aliases import ElementType, NamespacesType, SchemaType, BaseXsdType, \
25    ComponentClassType, ExtraValidatorType, DecodeType, IterDecodeType, \
26    EncodeType, IterEncodeType
27from ..helpers import get_qname, local_name, get_prefixed_qname
28from ..resources import XMLResource
29from .exceptions import XMLSchemaParseError, XMLSchemaValidationError
30
31if TYPE_CHECKING:
32    from .simple_types import XsdSimpleType
33    from .complex_types import XsdComplexType
34    from .elements import XsdElement
35    from .groups import XsdGroup
36    from .global_maps import XsdGlobals
37
38XSD_TYPE_DERIVATIONS = {'extension', 'restriction'}
39XSD_ELEMENT_DERIVATIONS = {'extension', 'restriction', 'substitution'}
40
41XSD_VALIDATION_MODES = {'strict', 'lax', 'skip'}
42"""
43XML Schema validation modes
44Ref.: https://www.w3.org/TR/xmlschema11-1/#key-va
45"""
46
47
48def check_validation_mode(validation: str) -> None:
49    if validation not in XSD_VALIDATION_MODES:
50        raise XMLSchemaValueError("validation mode can be 'strict', "
51                                  "'lax' or 'skip': %r" % validation)
52
53
54class XsdValidator:
55    """
56    Common base class for XML Schema validator, that represents a PSVI (Post Schema Validation
57    Infoset) information item. A concrete XSD validator have to report its validity collecting
58    building errors and implementing the properties.
59
60    :param validation: defines the XSD validation mode to use for build the validator, \
61    its value can be 'strict', 'lax' or 'skip'. Strict mode is the default.
62    :type validation: str
63
64    :ivar validation: XSD validation mode.
65    :vartype validation: str
66    :ivar errors: XSD validator building errors.
67    :vartype errors: list
68    """
69    elem: Optional[etree_element] = None
70    namespaces: Any = None
71    errors: List[XMLSchemaParseError]
72
73    def __init__(self, validation: str = 'strict') -> None:
74        self.validation = validation
75        self.errors = []
76
77    @property
78    def built(self) -> bool:
79        """
80        Property that is ``True`` if XSD validator has been fully parsed and built,
81        ``False`` otherwise. For schemas the property is checked on all global
82        components. For XSD components check only the building of local subcomponents.
83        """
84        raise NotImplementedError()
85
86    @property
87    def validation_attempted(self) -> str:
88        """
89        Property that returns the *validation status* of the XSD validator.
90        It can be 'full', 'partial' or 'none'.
91
92        | https://www.w3.org/TR/xmlschema-1/#e-validation_attempted
93        | https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validation_attempted
94        """
95        raise NotImplementedError()
96
97    @property
98    def validity(self) -> str:
99        """
100        Property that returns the XSD validator's validity.
101        It can be ‘valid’, ‘invalid’ or ‘notKnown’.
102
103        | https://www.w3.org/TR/xmlschema-1/#e-validity
104        | https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validity
105        """
106        if self.validation == 'skip':
107            return 'notKnown'
108        elif self.errors or any(comp.errors for comp in self.iter_components()):
109            return 'invalid'
110        elif self.built:
111            return 'valid'
112        else:
113            return 'notKnown'
114
115    def iter_components(self, xsd_classes: ComponentClassType = None) \
116            -> Iterator[Union['XsdComponent', SchemaType, 'XsdGlobals']]:
117        """
118        Creates an iterator for traversing all XSD components of the validator.
119
120        :param xsd_classes: returns only a specific class/classes of components, \
121        otherwise returns all components.
122        """
123        raise NotImplementedError()
124
125    @property
126    def all_errors(self) -> List[XMLSchemaParseError]:
127        """
128        A list with all the building errors of the XSD validator and its components.
129        """
130        errors = []
131        for comp in self.iter_components():
132            if comp.errors:
133                errors.extend(comp.errors)
134        return errors
135
136    def copy(self) -> 'XsdValidator':
137        validator: 'XsdValidator' = object.__new__(self.__class__)
138        validator.__dict__.update(self.__dict__)
139        validator.errors = self.errors[:]  # shallow copy duplicates errors list
140        return validator
141
142    __copy__ = copy
143
144    def parse_error(self, error: Union[str, Exception],
145                    elem: Optional[ElementType] = None,
146                    validation: Optional[str] = None) -> None:
147        """
148        Helper method for registering parse errors. Does nothing if validation mode is 'skip'.
149        Il validation mode is 'lax' collects the error, otherwise raise the error.
150
151        :param error: can be a parse error or an error message.
152        :param elem: the Element instance related to the error, for default uses the 'elem' \
153        attribute of the validator, if it's present.
154        :param validation: overrides the default validation mode of the validator.
155        """
156        if validation is not None:
157            check_validation_mode(validation)
158        else:
159            validation = self.validation
160
161        if validation == 'skip':
162            return
163        elif elem is None:
164            elem = self.elem
165        elif not is_etree_element(elem):
166            msg = "the argument 'elem' must be an Element instance, not {!r}."
167            raise XMLSchemaTypeError(msg.format(elem))
168
169        if isinstance(error, XMLSchemaParseError):
170            error.validator = self
171            error.namespaces = getattr(self, 'namespaces', None)
172            error.elem = elem
173            error.source = getattr(self, 'source', None)
174        elif isinstance(error, Exception):
175            message = str(error).strip()
176            if message[0] in '\'"' and message[0] == message[-1]:
177                message = message.strip('\'"')
178            error = XMLSchemaParseError(self, message, elem)
179        elif isinstance(error, str):
180            error = XMLSchemaParseError(self, error, elem)
181        else:
182            msg = "'error' argument must be an exception or a string, not {!r}."
183            raise XMLSchemaTypeError(msg.format(error))
184
185        if validation == 'lax':
186            self.errors.append(error)
187        else:
188            raise error
189
190    def validation_error(self, validation: str,
191                         error: Union[str, Exception],
192                         obj: Any = None,
193                         source: Optional[XMLResource] = None,
194                         namespaces: Optional[NamespacesType] = None,
195                         **_kwargs: Any) -> XMLSchemaValidationError:
196        """
197        Helper method for generating and updating validation errors. If validation
198        mode is 'lax' or 'skip' returns the error, otherwise raises the error.
199
200        :param validation: an error-compatible validation mode: can be 'lax' or 'strict'.
201        :param error: an error instance or the detailed reason of failed validation.
202        :param obj: the instance related to the error.
203        :param source: the XML resource related to the validation process.
204        :param namespaces: is an optional mapping from namespace prefix to URI.
205        :param _kwargs: keyword arguments of the validation process that are not used.
206        """
207        check_validation_mode(validation)
208        if isinstance(error, XMLSchemaValidationError):
209            if error.namespaces is None and namespaces is not None:
210                error.namespaces = namespaces
211            if error.source is None and source is not None:
212                error.source = source
213            if error.obj is None and obj is not None:
214                error.obj = obj
215            if error.elem is None and is_etree_element(obj):
216                error.elem = obj
217        elif isinstance(error, Exception):
218            error = XMLSchemaValidationError(self, obj, str(error), source, namespaces)
219        else:
220            error = XMLSchemaValidationError(self, obj, error, source, namespaces)
221
222        if validation == 'strict' and error.elem is not None:
223            raise error
224        return error
225
226    def _parse_xpath_default_namespace(self, elem: ElementType) -> str:
227        """
228        Parse XSD 1.1 xpathDefaultNamespace attribute for schema, alternative, assert, assertion
229        and selector declarations, checking if the value is conforming to the specification. In
230        case the attribute is missing or for wrong attribute values defaults to ''.
231        """
232        try:
233            value = elem.attrib['xpathDefaultNamespace']
234        except KeyError:
235            return ''
236
237        value = value.strip()
238        if value == '##local':
239            return ''
240        elif value == '##defaultNamespace':
241            default_namespace = getattr(self, 'default_namespace', None)
242            return default_namespace if isinstance(default_namespace, str) else ''
243        elif value == '##targetNamespace':
244            target_namespace = getattr(self, 'target_namespace', '')
245            return target_namespace if isinstance(target_namespace, str) else ''
246        elif len(value.split()) == 1:
247            return value
248        else:
249            admitted_values = ('##defaultNamespace', '##targetNamespace', '##local')
250            msg = "wrong value %r for 'xpathDefaultNamespace' attribute, can be (anyURI | %s)."
251            self.parse_error(msg % (value, ' | '.join(admitted_values)), elem)
252            return ''
253
254
255class XsdComponent(XsdValidator):
256    """
257    Class for XSD components. See: https://www.w3.org/TR/xmlschema-ref/
258
259    :param elem: ElementTree's node containing the definition.
260    :param schema: the XMLSchema object that owns the definition.
261    :param parent: the XSD parent, `None` means that is a global component that \
262    has the schema as parent.
263    :param name: name of the component, maybe overwritten by the parse of the `elem` argument.
264
265    :cvar qualified: for name matching, unqualified matching may be admitted only \
266    for elements and attributes.
267    :vartype qualified: bool
268    """
269    _REGEX_SPACE = re.compile(r'\s')
270    _REGEX_SPACES = re.compile(r'\s+')
271    _ADMITTED_TAGS: Union[Set[str], Tuple[str, ...], Tuple[()]] = ()
272
273    elem: etree_element
274    parent = None
275    name = None
276    ref: Optional['XsdComponent'] = None
277    qualified = True
278    redefine = None
279    _annotation = None
280    _target_namespace: Optional[str]
281
282    def __init__(self, elem: etree_element,
283                 schema: SchemaType,
284                 parent: Optional['XsdComponent'] = None,
285                 name: Optional[str] = None) -> None:
286
287        super(XsdComponent, self).__init__(schema.validation)
288        if name:
289            self.name = name
290        if parent is not None:
291            self.parent = parent
292        self.schema = schema
293        self.maps: XsdGlobals = schema.maps
294        self.elem = elem
295
296    def __setattr__(self, name: str, value: Any) -> None:
297        if name == 'elem':
298            if value.tag not in self._ADMITTED_TAGS:
299                msg = "wrong XSD element {!r} for {!r}, must be one of {!r}"
300                raise XMLSchemaValueError(
301                    msg.format(value.tag, self.__class__, self._ADMITTED_TAGS)
302                )
303            super(XsdComponent, self).__setattr__(name, value)
304            if self.errors:
305                self.errors.clear()
306            self._parse()
307        else:
308            super(XsdComponent, self).__setattr__(name, value)
309
310    @property
311    def xsd_version(self) -> str:
312        return self.schema.XSD_VERSION
313
314    def is_global(self) -> bool:
315        """Returns `True` if the instance is a global component, `False` if it's local."""
316        return self.parent is None
317
318    def is_override(self) -> bool:
319        """Returns `True` if the instance is an override of a global component."""
320        if self.parent is not None:
321            return False
322        return any(self.elem in x for x in self.schema.root if x.tag == XSD_OVERRIDE)
323
324    @property
325    def schema_elem(self) -> ElementType:
326        """The reference element of the schema for the component instance."""
327        return self.elem
328
329    @property
330    def source(self) -> XMLResource:
331        """Property that references to schema source."""
332        return self.schema.source
333
334    @property
335    def target_namespace(self) -> str:
336        """Property that references to schema's targetNamespace."""
337        return self.schema.target_namespace if self.ref is None else self.ref.target_namespace
338
339    @property
340    def default_namespace(self) -> Optional[str]:
341        """Property that references to schema's default namespaces."""
342        return self.schema.namespaces.get('')
343
344    @property
345    def namespaces(self) -> NamespacesType:
346        """Property that references to schema's namespace mapping."""
347        return self.schema.namespaces
348
349    @property
350    def any_type(self) -> 'XsdComplexType':
351        """Property that references to the xs:anyType instance of the global maps."""
352        return cast('XsdComplexType', self.maps.types[XSD_ANY_TYPE])
353
354    @property
355    def any_simple_type(self) -> 'XsdSimpleType':
356        """Property that references to the xs:anySimpleType instance of the global maps."""
357        return cast('XsdSimpleType', self.maps.types[XSD_ANY_SIMPLE_TYPE])
358
359    @property
360    def any_atomic_type(self) -> 'XsdSimpleType':
361        """Property that references to the xs:anyAtomicType instance of the global maps."""
362        return cast('XsdSimpleType', self.maps.types[XSD_ANY_ATOMIC_TYPE])
363
364    @property
365    def annotation(self) -> Optional['XsdAnnotation']:
366        if '_annotation' not in self.__dict__:
367            for child in self.elem:
368                if child.tag == XSD_ANNOTATION:
369                    self._annotation = XsdAnnotation(child, self.schema, self)
370                    break
371                elif not callable(child.tag):
372                    self._annotation = None
373                    break
374            else:
375                self._annotation = None
376
377        return self._annotation
378
379    def __repr__(self) -> str:
380        if self.ref is not None:
381            return '%s(ref=%r)' % (self.__class__.__name__, self.prefixed_name)
382        else:
383            return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name)
384
385    def _parse(self) -> None:
386        return
387
388    def _parse_reference(self) -> Optional[bool]:
389        """
390        Helper method for referable components. Returns `True` if a valid reference QName
391        is found without any error, otherwise returns `None`. Sets an id-related name for
392        the component ('nameless_<id of the instance>') if both the attributes 'ref' and
393        'name' are missing.
394        """
395        ref = self.elem.get('ref')
396        if ref is None:
397            if 'name' in self.elem.attrib:
398                return None
399            elif self.parent is None:
400                self.parse_error("missing attribute 'name' in a global %r" % type(self))
401            else:
402                self.parse_error(
403                    "missing both attributes 'name' and 'ref' in local %r" % type(self)
404                )
405        elif 'name' in self.elem.attrib:
406            self.parse_error("attributes 'name' and 'ref' are mutually exclusive")
407        elif self.parent is None:
408            self.parse_error("attribute 'ref' not allowed in a global %r" % type(self))
409        else:
410            try:
411                self.name = self.schema.resolve_qname(ref)
412            except (KeyError, ValueError, RuntimeError) as err:
413                self.parse_error(err)
414            else:
415                if self._parse_child_component(self.elem, strict=False) is not None:
416                    self.parse_error("a reference component cannot have "
417                                     "child definitions/declarations")
418                return True
419
420        return None
421
422    def _parse_child_component(self, elem: ElementType, strict: bool = True) \
423            -> Optional[ElementType]:
424        child = None
425        for e in elem:
426            if e.tag == XSD_ANNOTATION or callable(e.tag):
427                continue
428            elif not strict:
429                return e
430            elif child is not None:
431                msg = "too many XSD components, unexpected {!r} found at position {}"
432                self.parse_error(msg.format(child, elem[:].index(e)), elem)
433                break
434            else:
435                child = e
436        return child
437
438    def _parse_target_namespace(self) -> None:
439        """
440        XSD 1.1 targetNamespace attribute in elements and attributes declarations.
441        """
442        if 'targetNamespace' not in self.elem.attrib:
443            return
444
445        self._target_namespace = self.elem.attrib['targetNamespace'].strip()
446        if 'name' not in self.elem.attrib:
447            self.parse_error("attribute 'name' must be present when "
448                             "'targetNamespace' attribute is provided")
449        if 'form' in self.elem.attrib:
450            self.parse_error("attribute 'form' must be absent when "
451                             "'targetNamespace' attribute is provided")
452        if self._target_namespace != self.schema.target_namespace:
453            if self.parent is None:
454                self.parse_error("a global %s must have the same namespace as "
455                                 "its parent schema" % self.__class__.__name__)
456
457            xsd_type = self.get_parent_type()
458            if xsd_type is None or xsd_type.parent is not None:
459                pass
460            elif xsd_type.derivation != 'restriction' or \
461                    getattr(xsd_type.base_type, 'name', None) == XSD_ANY_TYPE:
462                self.parse_error("a declaration contained in a global complexType "
463                                 "must have the same namespace as its parent schema")
464
465        if self.name is None:
466            pass
467        elif not self._target_namespace:
468            self.name = local_name(self.name)
469        else:
470            self.name = '{%s}%s' % (self._target_namespace, local_name(self.name))
471
472    @property
473    def local_name(self) -> Optional[str]:
474        """The local part of the name of the component, or `None` if the name is `None`."""
475        return None if self.name is None else local_name(self.name)
476
477    @property
478    def qualified_name(self) -> Optional[str]:
479        """The name of the component in extended format, or `None` if the name is `None`."""
480        return None if self.name is None else get_qname(self.target_namespace, self.name)
481
482    @property
483    def prefixed_name(self) -> Optional[str]:
484        """The name of the component in prefixed format, or `None` if the name is `None`."""
485        return None if self.name is None else get_prefixed_qname(self.name, self.namespaces)
486
487    @property
488    def id(self) -> Optional[str]:
489        """The ``'id'`` attribute of the component tag, ``None`` if missing."""
490        return self.elem.get('id')
491
492    @property
493    def validation_attempted(self) -> str:
494        return 'full' if self.built else 'partial'
495
496    def build(self) -> None:
497        """
498        Builds components that are not fully parsed at initialization, like model groups
499        or internal local elements in model groups. Otherwise does nothing.
500        """
501
502    @property
503    def built(self) -> bool:
504        raise NotImplementedError()
505
506    def is_matching(self, name: Optional[str], default_namespace: Optional[str] = None,
507                    **kwargs: Any) -> bool:
508        """
509        Returns `True` if the component name is matching the name provided as argument,
510        `False` otherwise. For XSD elements the matching is extended to substitutes.
511
512        :param name: a local or fully-qualified name.
513        :param default_namespace: used if it's not None and not empty for completing \
514        the name argument in case it's a local name.
515        :param kwargs: additional options that can be used by certain components.
516        """
517        if not name:
518            return self.name == name
519        elif name[0] == '{':
520            return self.qualified_name == name
521        elif not default_namespace:
522            return self.name == name or not self.qualified and self.local_name == name
523        else:
524            qname = '{%s}%s' % (default_namespace, name)
525            return self.qualified_name == qname or not self.qualified and self.local_name == name
526
527    def match(self, name: Optional[str], default_namespace: Optional[str] = None,
528              **kwargs: Any) -> Optional['XsdComponent']:
529        """
530        Returns the component if its name is matching the name provided as argument,
531        `None` otherwise.
532        """
533        return self if self.is_matching(name, default_namespace, **kwargs) else None
534
535    def get_matching_item(self, mapping: MutableMapping[str, Any],
536                          ns_prefix: str = 'xmlns',
537                          match_local_name: bool = False) -> Optional[Any]:
538        """
539        If a key is matching component name, returns its value, otherwise returns `None`.
540        """
541        if self.name is None:
542            return None
543        elif not self.target_namespace:
544            return mapping.get(self.name)
545        elif self.qualified_name in mapping:
546            return mapping[cast(str, self.qualified_name)]
547        elif self.prefixed_name in mapping:
548            return mapping[cast(str, self.prefixed_name)]
549
550        # Try a match with other prefixes
551        target_namespace = self.target_namespace
552        suffix = ':%s' % self.local_name
553
554        for k in filter(lambda x: x.endswith(suffix), mapping):
555            prefix = k.split(':')[0]
556            if self.namespaces.get(prefix) == target_namespace:
557                return mapping[k]
558
559            # Match namespace declaration within value
560            ns_declaration = '{}:{}'.format(ns_prefix, prefix)
561            try:
562                if mapping[k][ns_declaration] == target_namespace:
563                    return mapping[k]
564            except (KeyError, TypeError):
565                pass
566        else:
567            if match_local_name:
568                return mapping.get(self.local_name)  # type: ignore[arg-type]
569            return None
570
571    def get_global(self) -> 'XsdComponent':
572        """Returns the global XSD component that contains the component instance."""
573        if self.parent is None:
574            return self
575        component = self.parent
576        while component is not self:  # pragma: no cover
577            if component.parent is None:
578                return component
579            component = component.parent
580        else:
581            return self
582
583    def get_parent_type(self) -> Optional['XsdType']:
584        """
585        Returns the nearest XSD type that contains the component instance,
586        or `None` if the component doesn't have an XSD type parent.
587        """
588        component = self.parent
589        while component is not self and component is not None:
590            if isinstance(component, XsdType):
591                return component
592            component = component.parent
593        return None
594
595    def iter_components(self, xsd_classes: ComponentClassType = None) \
596            -> Iterator['XsdComponent']:
597        """
598        Creates an iterator for XSD subcomponents.
599
600        :param xsd_classes: provide a class or a tuple of classes to iterates over only a \
601        specific classes of components.
602        """
603        if xsd_classes is None or isinstance(self, xsd_classes):
604            yield self
605
606    def iter_ancestors(self, xsd_classes: ComponentClassType = None)\
607            -> Iterator['XsdComponent']:
608        """
609        Creates an iterator for XSD ancestor components, schema excluded. Stops when the component
610        is global or if the ancestor is not an instance of the specified class/classes.
611
612        :param xsd_classes: provide a class or a tuple of classes to iterates over only a \
613        specific classes of components.
614        """
615        ancestor = self
616        while True:
617            if ancestor.parent is None:
618                break
619            ancestor = ancestor.parent
620            if xsd_classes is not None and not isinstance(ancestor, xsd_classes):
621                break
622            yield ancestor
623
624    def tostring(self, indent: str = '', max_lines: Optional[int] = None,
625                 spaces_for_tab: int = 4) -> Union[str, bytes]:
626        """Serializes the XML elements that declare or define the component to a string."""
627        return etree_tostring(self.schema_elem, self.namespaces, indent, max_lines, spaces_for_tab)
628
629
630class XsdAnnotation(XsdComponent):
631    """
632    Class for XSD *annotation* definitions.
633
634    :ivar appinfo: a list containing the xs:appinfo children.
635    :ivar documentation: a list containing the xs:documentation children.
636
637    ..  <annotation
638          id = ID
639          {any attributes with non-schema namespace . . .}>
640          Content: (appinfo | documentation)*
641        </annotation>
642
643    ..  <appinfo
644          source = anyURI
645          {any attributes with non-schema namespace . . .}>
646          Content: ({any})*
647        </appinfo>
648
649    ..  <documentation
650          source = anyURI
651          xml:lang = language
652          {any attributes with non-schema namespace . . .}>
653          Content: ({any})*
654        </documentation>
655    """
656    _ADMITTED_TAGS = {XSD_ANNOTATION}
657
658    annotation = None
659
660    def __repr__(self) -> str:
661        return '%s(%r)' % (self.__class__.__name__, str(self)[:40])
662
663    def __str__(self) -> str:
664        return '\n'.join(elementpath.select(self.elem, '*/fn:string()'))
665
666    @property
667    def built(self) -> bool:
668        return True
669
670    def _parse(self) -> None:
671        self.appinfo = []
672        self.documentation = []
673        for child in self.elem:
674            if child.tag == XSD_APPINFO:
675                for key in child.attrib:
676                    if key != 'source':
677                        self.parse_error("wrong attribute %r for appinfo declaration." % key)
678                self.appinfo.append(child)
679            elif child.tag == XSD_DOCUMENTATION:
680                for key in child.attrib:
681                    if key not in ['source', XML_LANG]:
682                        self.parse_error("wrong attribute %r for documentation declaration." % key)
683                self.documentation.append(child)
684
685
686class XsdType(XsdComponent):
687    """Common base class for XSD types."""
688
689    abstract = False
690    base_type: Optional[BaseXsdType] = None
691    derivation: Optional[str] = None
692    _final: Optional[str] = None
693
694    @property
695    def final(self) -> str:
696        return self.schema.final_default if self._final is None else self._final
697
698    @property
699    def built(self) -> bool:
700        raise NotImplementedError()
701
702    @property
703    def content_type_label(self) -> str:
704        """The content type classification."""
705        raise NotImplementedError()
706
707    @property
708    def sequence_type(self) -> str:
709        """The XPath sequence type associated with the content."""
710        raise NotImplementedError()
711
712    @property
713    def root_type(self) -> BaseXsdType:
714        """
715        The root type of the type definition hierarchy. For an atomic type
716        is the primitive type. For a list is the primitive type of the item.
717        For a union is the base union type. For a complex type is xs:anyType.
718        """
719        if getattr(self, 'attributes', None):
720            return cast('XsdComplexType', self.maps.types[XSD_ANY_TYPE])
721        elif self.base_type is None:
722            if self.is_simple():
723                return cast('XsdSimpleType', self)
724            return cast('XsdComplexType', self.maps.types[XSD_ANY_TYPE])
725
726        primitive_type: BaseXsdType
727        try:
728            if self.base_type.is_simple():
729                primitive_type = self.base_type.primitive_type  # type: ignore[union-attr]
730            else:
731                primitive_type = self.base_type.content.primitive_type  # type: ignore[union-attr]
732        except AttributeError:
733            # The type has complex or XsdList content
734            return self.base_type.root_type
735        else:
736            return primitive_type
737
738    @property
739    def simple_type(self) -> Optional['XsdSimpleType']:
740        """
741        Property that is the instance itself for a simpleType. For a
742        complexType is the instance's *content* if this is a simpleType
743        or `None` if the instance's *content* is a model group.
744        """
745        return None
746
747    @property
748    def model_group(self) -> Optional['XsdGroup']:
749        """
750        Property that is `None` for a simpleType. For a complexType is
751        the instance's *content* if this is a model group or `None` if
752        the instance's *content* is a simpleType.
753        """
754        return None
755
756    @staticmethod
757    def is_simple() -> bool:
758        """Returns `True` if the instance is a simpleType, `False` otherwise."""
759        raise NotImplementedError()
760
761    @staticmethod
762    def is_complex() -> bool:
763        """Returns `True` if the instance is a complexType, `False` otherwise."""
764
765    def is_atomic(self) -> bool:
766        """Returns `True` if the instance is an atomic simpleType, `False` otherwise."""
767        return False
768
769    def is_list(self) -> bool:
770        """Returns `True` if the instance is a list simpleType, `False` otherwise."""
771        return False
772
773    def is_union(self) -> bool:
774        """Returns `True` if the instance is a union simpleType, `False` otherwise."""
775        return False
776
777    def is_datetime(self) -> bool:
778        """
779        Returns `True` if the instance is a datetime/duration XSD builtin-type, `False` otherwise.
780        """
781        return False
782
783    def is_empty(self) -> bool:
784        """Returns `True` if the instance has an empty content, `False` otherwise."""
785        raise NotImplementedError()
786
787    def is_emptiable(self) -> bool:
788        """Returns `True` if the instance has an emptiable value or content, `False` otherwise."""
789        raise NotImplementedError()
790
791    def has_simple_content(self) -> bool:
792        """
793        Returns `True` if the instance has a simple content, `False` otherwise.
794        """
795        raise NotImplementedError()
796
797    def has_complex_content(self) -> bool:
798        """
799        Returns `True` if the instance is a complexType with mixed or element-only
800        content, `False` otherwise.
801        """
802        raise NotImplementedError()
803
804    def has_mixed_content(self) -> bool:
805        """
806        Returns `True` if the instance is a complexType with mixed content, `False` otherwise.
807        """
808        raise NotImplementedError()
809
810    def is_element_only(self) -> bool:
811        """
812        Returns `True` if the instance is a complexType with element-only content,
813        `False` otherwise.
814        """
815        raise NotImplementedError()
816
817    def is_derived(self, other: Union[BaseXsdType, Tuple[ElementType, SchemaType]],
818                   derivation: Optional[str] = None) -> bool:
819        """
820        Returns `True` if the instance is derived from *other*, `False` otherwise.
821        The optional argument derivation can be a string containing the words
822        'extension' or 'restriction' or both.
823        """
824        raise NotImplementedError()
825
826    def is_extension(self) -> bool:
827        return self.derivation == 'extension'
828
829    def is_restriction(self) -> bool:
830        return self.derivation == 'restriction'
831
832    def is_blocked(self, xsd_element: 'XsdElement') -> bool:
833        """
834        Returns `True` if the base type derivation is blocked, `False` otherwise.
835        """
836        xsd_type = xsd_element.type
837        if self is xsd_type:
838            return False
839
840        block = ('%s %s' % (xsd_element.block, xsd_type.block)).strip()
841        if not block:
842            return False
843
844        _block = {x for x in block.split() if x in ('extension', 'restriction')}
845        return any(self.is_derived(xsd_type, derivation) for derivation in _block)
846
847    def is_dynamic_consistent(self, other: Any) -> bool:
848        return other.name == XSD_ANY_TYPE or self.is_derived(other) or \
849            hasattr(other, 'member_types') and \
850            any(self.is_derived(mt) for mt in other.member_types)  # pragma: no cover
851
852    def is_key(self) -> bool:
853        return self.name == XSD_ID or self.is_derived(self.maps.types[XSD_ID])
854
855    def is_qname(self) -> bool:
856        return self.name == XSD_QNAME or self.is_derived(self.maps.types[XSD_QNAME])
857
858    def is_notation(self) -> bool:
859        return self.name == XSD_NOTATION_TYPE or self.is_derived(self.maps.types[XSD_NOTATION_TYPE])
860
861    def is_decimal(self) -> bool:
862        return self.name == XSD_DECIMAL or self.is_derived(self.maps.types[XSD_DECIMAL])
863
864    def text_decode(self, text: str) -> Any:
865        raise NotImplementedError()
866
867
868ST = TypeVar('ST')
869DT = TypeVar('DT')
870
871
872class ValidationMixin(Generic[ST, DT]):
873    """
874    Mixin for implementing XML data validators/decoders on XSD components.
875    A derived class must implement the methods `iter_decode` and `iter_encode`.
876    """
877    def validate(self, obj: ST,
878                 use_defaults: bool = True,
879                 namespaces: Optional[NamespacesType] = None,
880                 max_depth: Optional[int] = None,
881                 extra_validator: Optional[ExtraValidatorType] = None) -> None:
882        """
883        Validates XML data against the XSD schema/component instance.
884
885        :param obj: the XML data. Can be a string for an attribute or a simple type \
886        validators, or an ElementTree's Element otherwise.
887        :param use_defaults: indicates whether to use default values for filling missing data.
888        :param namespaces: is an optional mapping from namespace prefix to URI.
889        :param max_depth: maximum level of validation, for default there is no limit.
890        :param extra_validator: an optional function for performing non-standard \
891        validations on XML data. The provided function is called for each traversed \
892        element, with the XML element as 1st argument and the corresponding XSD \
893        element as 2nd argument. It can be also a generator function and has to \
894        raise/yield :exc:`XMLSchemaValidationError` exceptions.
895        :raises: :exc:`XMLSchemaValidationError` if the XML data instance is invalid.
896        """
897        for error in self.iter_errors(obj, use_defaults, namespaces,
898                                      max_depth, extra_validator):
899            raise error
900
901    def is_valid(self, obj: ST,
902                 use_defaults: bool = True,
903                 namespaces: Optional[NamespacesType] = None,
904                 max_depth: Optional[int] = None,
905                 extra_validator: Optional[ExtraValidatorType] = None) -> bool:
906        """
907        Like :meth:`validate` except that does not raise an exception but returns
908        ``True`` if the XML data instance is valid, ``False`` if it is invalid.
909        """
910        error = next(self.iter_errors(obj, use_defaults, namespaces,
911                                      max_depth, extra_validator), None)
912        return error is None
913
914    def iter_errors(self, obj: ST,
915                    use_defaults: bool = True,
916                    namespaces: Optional[NamespacesType] = None,
917                    max_depth: Optional[int] = None,
918                    extra_validator: Optional[ExtraValidatorType] = None) \
919            -> Iterator[XMLSchemaValidationError]:
920        """
921        Creates an iterator for the errors generated by the validation of an XML data against
922        the XSD schema/component instance. Accepts the same arguments of :meth:`validate`.
923        """
924        kwargs: Dict[str, Any] = {
925            'use_defaults': use_defaults,
926            'namespaces': namespaces,
927        }
928        if max_depth is not None:
929            kwargs['max_depth'] = max_depth
930        if extra_validator is not None:
931            kwargs['extra_validator'] = extra_validator
932
933        for result in self.iter_decode(obj, **kwargs):
934            if isinstance(result, XMLSchemaValidationError):
935                yield result
936            else:
937                del result
938
939    def decode(self, obj: ST, validation: str = 'strict', **kwargs: Any) -> DecodeType[DT]:
940        """
941        Decodes XML data.
942
943        :param obj: the XML data. Can be a string for an attribute or for simple type \
944        components or a dictionary for an attribute group or an ElementTree's \
945        Element for other components.
946        :param validation: the validation mode. Can be 'lax', 'strict' or 'skip.
947        :param kwargs: optional keyword arguments for the method :func:`iter_decode`.
948        :return: a dictionary like object if the XSD component is an element, a \
949        group or a complex type; a list if the XSD component is an attribute group; \
950        a simple data type object otherwise. If *validation* argument is 'lax' a 2-items \
951        tuple is returned, where the first item is the decoded object and the second item \
952        is a list containing the errors.
953        :raises: :exc:`XMLSchemaValidationError` if the object is not decodable by \
954        the XSD component, or also if it's invalid when ``validation='strict'`` is provided.
955        """
956        check_validation_mode(validation)
957
958        result: Union[DT, XMLSchemaValidationError]
959        errors: List[XMLSchemaValidationError] = []
960        for result in self.iter_decode(obj, validation, **kwargs):  # pragma: no cover
961            if not isinstance(result, XMLSchemaValidationError):
962                return (result, errors) if validation == 'lax' else result
963            elif validation == 'strict':
964                raise result
965            else:
966                errors.append(result)
967
968        return (None, errors) if validation == 'lax' else None
969
970    def encode(self, obj: Any, validation: str = 'strict', **kwargs: Any) -> EncodeType[Any]:
971        """
972        Encodes data to XML.
973
974        :param obj: the data to be encoded to XML.
975        :param validation: the validation mode. Can be 'lax', 'strict' or 'skip.
976        :param kwargs: optional keyword arguments for the method :func:`iter_encode`.
977        :return: An element tree's Element if the original data is a structured data or \
978        a string if it's simple type datum. If *validation* argument is 'lax' a 2-items \
979        tuple is returned, where the first item is the encoded object and the second item \
980        is a list containing the errors.
981        :raises: :exc:`XMLSchemaValidationError` if the object is not encodable by the XSD \
982        component, or also if it's invalid when ``validation='strict'`` is provided.
983        """
984        check_validation_mode(validation)
985        result, errors = None, []
986        for result in self.iter_encode(obj, validation=validation, **kwargs):  # pragma: no cover
987            if not isinstance(result, XMLSchemaValidationError):
988                break
989            elif validation == 'strict':
990                raise result
991            else:
992                errors.append(result)
993
994        return (result, errors) if validation == 'lax' else result
995
996    def iter_decode(self, obj: ST, validation: str = 'lax', **kwargs: Any) \
997            -> IterDecodeType[DT]:
998        """
999        Creates an iterator for decoding an XML source to a Python object.
1000
1001        :param obj: the XML data.
1002        :param validation: the validation mode. Can be 'lax', 'strict' or 'skip.
1003        :param kwargs: keyword arguments for the decoder API.
1004        :return: Yields a decoded object, eventually preceded by a sequence of \
1005        validation or decoding errors.
1006        """
1007        raise NotImplementedError()
1008
1009    def iter_encode(self, obj: Any, validation: str = 'lax', **kwargs: Any) \
1010            -> IterEncodeType[Any]:
1011        """
1012        Creates an iterator for encoding data to an Element tree.
1013
1014        :param obj: The data that has to be encoded.
1015        :param validation: The validation mode. Can be 'lax', 'strict' or 'skip'.
1016        :param kwargs: keyword arguments for the encoder API.
1017        :return: Yields an Element, eventually preceded by a sequence of validation \
1018        or encoding errors.
1019        """
1020        raise NotImplementedError()
1021