XSD_ASSERT = _XSD_TEMPLATE % 'assert'                            # complex type assertions C = TypeVar('C')
D = TypeVar('D')
E = TypeVar('E')
T = TypeVar('T') class XMLSchemaProtocol(protocols.XMLSchemaProtocol, Protocol):
    attributes: Dict[str, Any] class ElementProtocol(protocols.ElementProtocol, Protocol):
    schema: XMLSchemaProtocol
    attributes: Dict[str, Any] class ElementPathMixin(Sequence[E]):
    """
    Mixin abstract class for enabling ElementTree and XPath 2.0 API on XSD components.

    :cvar text: the Element text, for compatibility with the ElementTree API.
    :cvar tail: the Element tail, for compatibility with the ElementTree API.
    """
    text: Optional[str] = None
    tail: Optional[str] = None
    name: Optional[str] = None
    attributes: Any = {}
    namespaces: Any = {}
    xpath_default_namespace = ''

    @abstractmethod
    def __iter__(self) -> Iterator[E]:
        raise NotImplementedError

    @overload
    def __getitem__(self, i: int) -> E: ...

    @overload
    def __getitem__(self, s: slice) -> Sequence[E]: ...

    def __getitem__(self, i: Union[int, slice]) -> Union[E, Sequence[E]]:
        try:
            return [e for e in self][i]
        except IndexError:
            raise IndexError('child index out of range')

    def __reversed__(self) -> Iterator[E]:
        return reversed([e for e in self])

    def __len__(self) -> int:
        return len([e for e in self])

    @property
    def tag(self) -> str:
        """Alias of the *name* attribute. For compatibility with the ElementTree API."""
        return self.name or ''

    @property
    def attrib(self) -> Any:
        """Returns the Element attributes. For compatibility with the ElementTree API."""
        return self.attributes

    def get(self, key: str, default: Any = None) -> Any:
        """Gets an Element attribute. For compatibility with the ElementTree API."""
        return self.attributes.get(key, default)

    @property
    def xpath_proxy(self) -> XMLSchemaProxy:
        """Returns an XPath proxy instance bound with the schema."""
        raise NotImplementedError

    def _get_xpath_namespaces(self, namespaces: Optional[NamespacesType] = None) \
            -> Dict[str, str]:
        """
        Returns a dictionary with namespaces for XPath selection.

        :param namespaces: an optional map from namespace prefix to namespace URI. \
        If this argument is not provided the schema's namespaces are used.
        """
        if namespaces is None:
            namespaces = {k: v for k, v in self.namespaces.items() if k}
            namespaces[''] = self.xpath_default_namespace
        elif '' not in namespaces:
            namespaces[''] = self.xpath_default_namespace

        xpath_namespaces: Dict[str, str] = XPath2Parser.DEFAULT_NAMESPACES.copy()
        xpath_namespaces.update(namespaces)
        return xpath_namespaces

    def is_matching(self, name: Optional[str], default_namespace: Optional[str] = None) -> bool:
        if not name or name[0] == '{' or not default_namespace:
            return self.name == name
        else:
            return self.name == '{%s}%s' % (default_namespace, name)

    def find(self, path: str, namespaces: Optional[NamespacesType] = None) -> Optional[E]:
        """
        Finds the first XSD subelement matching the path.

        :param path: an XPath expression that considers the XSD component as the root element.
        :param namespaces: an optional mapping from namespace prefix to namespace URI.
        :return: the first matching XSD subelement or ``None`` if there is no match.
        """
        path = _REGEX_TAG_POSITION.sub('', path.strip())  # Strips tags positions from path
        namespaces = self._get_xpath_namespaces(namespaces)
        parser = XPath2Parser(namespaces, strict=False)
        context = XMLSchemaContext(self)  # type: ignore[arg-type]

        return cast(Optional[E], next(parser.parse(path).select_results(context), None))

    def findall(self, path: str, namespaces: Optional[NamespacesType] = None) -> List[E]:
        """
        Finds all XSD subelements matching the path.

        :param path: an XPath expression that considers the XSD component as the root element.
        :param namespaces: an optional mapping from namespace prefix to full name.
        :return: a list containing all matching XSD subelements in document order, an empty \
        list is returned if there is no match.
        """
        path = _REGEX_TAG_POSITION.sub('', path.strip())  # Strips tags positions from path
        namespaces = self._get_xpath_namespaces(namespaces)
        parser = XPath2Parser(namespaces, strict=False)
        context = XMLSchemaContext(self)  # type: ignore[arg-type]

        return cast(List[E], parser.parse(path).get_results(context))

    def iterfind(self, path: str, namespaces: Optional[NamespacesType] = None) -> Iterator[E]:
        """
        Creates and iterator for all XSD subelements matching the path.

        :param path: an XPath expression that considers the XSD component as the root element.
        :param namespaces: is an optional mapping from namespace prefix to full name.
        :return: an iterable yielding all matching XSD subelements in document order.
        """
        path = _REGEX_TAG_POSITION.sub('', path.strip())  # Strips tags positions from path
        namespaces = self._get_xpath_namespaces(namespaces)
        parser = XPath2Parser(namespaces, strict=False)
        context = XMLSchemaContext(self)  # type: ignore[arg-type]

        return cast(Iterator[E], parser.parse(path).select_results(context))

    def iter(self, tag: Optional[str] = None) -> Iterator[E]:
        """
        Creates an iterator for the XSD element and its subelements. If tag is not `None` or '*',
        only XSD elements whose matches tag are returned from the iterator. Local elements are
        expanded without repetitions. Element references are not expanded because the global
        elements are not descendants of other elements.
        """
        def safe_iter(elem: Any) -> Iterator[E]:
            if tag is None or elem.is_matching(tag):
                yield elem
            for child in elem:
                if child.parent is None:
                    yield from safe_iter(child)
                elif getattr(child, 'ref', None) is not None:
                    if tag is None or child.is_matching(tag):
                        yield child
                elif child not in local_elements:
                    local_elements.add(child)
                    yield from safe_iter(child)

        if tag == '*':
            tag = None
        local_elements: Set[E] = set()
        return safe_iter(self)

    def iterchildren(self, tag: Optional[str] = None) -> Iterator[E]:
        """
        Creates an iterator for the child elements of the XSD component. If *tag* is not `None`
        or '*', only XSD elements whose name matches tag are returned from the iterator.
        """
        if tag == '*':
            tag = None
        for child in self:
            if tag is None or child.is_matching(tag):
                yield child class XMLSchemaProxy(AbstractSchemaProxy):
    """XPath schema proxy for the *xmlschema* library."""
    _schema: SchemaType  # type: ignore[assignment]

    def __init__(self, schema: Optional[XMLSchemaProtocol] = None,
                 base_element: Optional[ElementProtocol] = None) -> None:

        if schema is None:
            from xmlschema import XMLSchema10
            schema = cast(XMLSchemaProtocol, getattr(XMLSchema10, 'meta_schema', None))

        super(XMLSchemaProxy, self).__init__(schema, base_element)

        if base_element is not None:
            try:
                if base_element.schema is not schema:
                    raise XMLSchemaValueError("%r is not an element of %r" % (base_element, schema))
            except AttributeError:
                raise XMLSchemaTypeError("%r is not an XsdElement" % base_element)

    def bind_parser(self, parser: XPath2Parser) -> None:
        parser.schema = self
        parser.symbol_table = dict(parser.__class__.symbol_table)

        with self._schema.lock:
            if self._schema.xpath_tokens is None:
                self._schema.xpath_tokens = {
                    xsd_type.name: parser.schema_constructor(xsd_type.name)
                    for xsd_type in self.iter_atomic_types() if xsd_type.name
                }

        parser.symbol_table.update(self._schema.xpath_tokens)

    def get_context(self) -> XMLSchemaContext:
        return XMLSchemaContext(
            root=self._schema,  # type: ignore[arg-type]
            namespaces=dict(self._schema.namespaces),
            item=self._base_element
        )

    def is_instance(self, obj: Any, type_qname: str) -> bool:
        # FIXME: use elementpath.datatypes for checking atomic datatypes
        xsd_type = self._schema.maps.types[type_qname]
        if isinstance(xsd_type, tuple):
            from .validators import XMLSchemaNotBuiltError
            raise XMLSchemaNotBuiltError(xsd_type[1], f"XSD type {type_qname} is not built")

        try:
            xsd_type.encode(obj)
        except ValueError:
            return False
        else:
            return True

    def cast_as(self, obj: Any, type_qname: str) -> Any:
        xsd_type = self._schema.maps.types[type_qname]
        if isinstance(xsd_type, tuple):
            from .validators import XMLSchemaNotBuiltError
            raise XMLSchemaNotBuiltError(xsd_type[1], f"XSD type {type_qname} is not built")
        return xsd_type.decode(obj)

    def iter_atomic_types(self) -> Iterator[XsdTypeProtocol]:
        for xsd_type in self._schema.maps.types.values():
            if not isinstance(xsd_type, tuple) and \
                    xsd_type.target_namespace != XSD_NAMESPACE and \
                    hasattr(xsd_type, 'primitive_type'):
                yield cast(XsdTypeProtocol, xsd_type)

    def get_primitive_type(self, xsd_type: XsdTypeProtocol) -> XsdTypeProtocol:
        primitive_type = cast(BaseXsdType, xsd_type).root_type
        return cast(XsdTypeProtocol, primitive_type) class XMLSchemaNotBuiltError(XMLSchemaValidatorError, RuntimeError):
    """
    Raised when there is an improper usage attempt of a not built XSD validator.

    :param validator: the XSD validator.
    :param message: the error message.
    """
    def __init__(self, validator: 'XsdValidator', message: str) -> None:
        super(XMLSchemaNotBuiltError, self).__init__(
            validator=validator,
            message=message,
            elem=getattr(validator, 'elem', None),
            source=getattr(validator, 'source', None),
            namespaces=getattr(validator, 'namespaces', None)
        ) class XMLSchemaValidationError(XMLSchemaValidatorError, ValueError):
    """
    Raised when the XML data is not validated with the XSD component or schema.
    It's used by decoding and encoding methods. Encoding validation errors do
    not include XML data element and source, so the error is limited to a message
    containing object representation and a reason.

    :param validator: the XSD validator.
    :param obj: the not validated XML data.
    :param reason: the detailed reason of failed validation.
    :param source: the XML resource that contains the error.
    :param namespaces: is an optional mapping from namespace prefix to URI.
    """
    def __init__(self,
                 validator: ValidatorType,
                 obj: Any,
                 reason: Optional[str] = None,
                 source: Optional['XMLResource'] = None,
                 namespaces: Optional[NamespacesType] = None) -> None:
        if not isinstance(obj, str):
            _obj = obj
        else:
            _obj = obj.encode('ascii', 'xmlcharrefreplace').decode('utf-8')

        super(XMLSchemaValidationError, self).__init__(
            validator=validator,
            message="failed validating {!r} with {!r}".format(_obj, validator),
            elem=obj if is_etree_element(obj) else None,
            source=source,
            namespaces=namespaces,
        )
        self.obj = obj
        self.reason = reason

    def __repr__(self) -> str:
        return '%s(reason=%r)' % (self.__class__.__name__, self.reason)

    def __str__(self) -> str:
        msg = ['%s:\n' % self.message]

        if self.reason is not None:
            msg.append('Reason: %s\n' % self.reason)

        if hasattr(self.validator, 'tostring'):
            chunk = self.validator.tostring('  ', 20)  # type: ignore[union-attr]
            msg.append("Schema:\n\n%s\n" % chunk)

        if self.elem is not None and is_etree_element(self.elem):
            try:
                elem_as_string = cast(str, etree_tostring(self.elem, self.namespaces, '  ', 20))
            except (ValueError, TypeError):        # pragma: no cover
                elem_as_string = repr(self.elem)   # pragma: no cover

            if hasattr(self.elem, 'sourceline'):
                line = getattr(self.elem, 'sourceline')
                msg.append("Instance (line %r):\n\n%s\n" % (line, elem_as_string))
            else:
                msg.append("Instance:\n\n%s\n" % elem_as_string)

        if self.path is not None:
            msg.append("Path: %s\n" % self.path)

        if len(msg) == 1:
            return msg[0][:-2]

        return '\n'.join(msg) class XsdComponent(XsdValidator):
    """
    Class for XSD components. See: https://www.w3.org/TR/xmlschema-ref/

    :param elem: ElementTree's node containing the definition.
    :param schema: the XMLSchema object that owns the definition.
    :param parent: the XSD parent, `None` means that is a global component that \
    has the schema as parent.
    :param name: name of the component, maybe overwritten by the parse of the `elem` argument.

    :cvar qualified: for name matching, unqualified matching may be admitted only \
    for elements and attributes.
    :vartype qualified: bool
    """
    _REGEX_SPACE = re.compile(r'\s')
    _REGEX_SPACES = re.compile(r'\s+')
    _ADMITTED_TAGS: Union[Set[str], Tuple[str, ...], Tuple[()]] = ()

    elem: etree_element
    parent = None
    name = None
    ref: Optional['XsdComponent'] = None
    qualified = True
    redefine = None
    _annotation = None
    _target_namespace: Optional[str]

    def __init__(self, elem: etree_element,
                 schema: SchemaType,
                 parent: Optional['XsdComponent'] = None,
                 name: Optional[str] = None) -> None:

        super(XsdComponent, self).__init__(schema.validation)
        if name:
            self.name = name
        if parent is not None:
            self.parent = parent
        self.schema = schema
        self.maps: XsdGlobals = schema.maps
        self.elem = elem

    def __setattr__(self, name: str, value: Any) -> None:
        if name == 'elem':
            if value.tag not in self._ADMITTED_TAGS:
                msg = "wrong XSD element {!r} for {!r}, must be one of {!r}"
                raise XMLSchemaValueError(
                    msg.format(value.tag, self.__class__, self._ADMITTED_TAGS)
                )
            super(XsdComponent, self).__setattr__(name, value)
            if self.errors:
                self.errors.clear()
            self._parse()
        else:
            super(XsdComponent, self).__setattr__(name, value)

    @property
    def xsd_version(self) -> str:
        return self.schema.XSD_VERSION

    def is_global(self) -> bool:
        """Returns `True` if the instance is a global component, `False` if it's local."""
        return self.parent is None

    def is_override(self) -> bool:
        """Returns `True` if the instance is an override of a global component."""
        if self.parent is not None:
            return False
        return any(self.elem in x for x in self.schema.root if x.tag == XSD_OVERRIDE)

    @property
    def schema_elem(self) -> ElementType:
        """The reference element of the schema for the component instance."""
        return self.elem

    @property
    def source(self) -> XMLResource:
        """Property that references to schema source."""
        return self.schema.source

    @property
    def target_namespace(self) -> str:
        """Property that references to schema's targetNamespace."""
        return self.schema.target_namespace if self.ref is None else self.ref.target_namespace

    @property
    def default_namespace(self) -> Optional[str]:
        """Property that references to schema's default namespaces."""
        return self.schema.namespaces.get('')

    @property
    def namespaces(self) -> NamespacesType:
        """Property that references to schema's namespace mapping."""
        return self.schema.namespaces

    @property
    def any_type(self) -> 'XsdComplexType':
        """Property that references to the xs:anyType instance of the global maps."""
        return cast('XsdComplexType', self.maps.types[XSD_ANY_TYPE])

    @property
    def any_simple_type(self) -> 'XsdSimpleType':
        """Property that references to the xs:anySimpleType instance of the global maps."""
        return cast('XsdSimpleType', self.maps.types[XSD_ANY_SIMPLE_TYPE])

    @property
    def any_atomic_type(self) -> 'XsdSimpleType':
        """Property that references to the xs:anyAtomicType instance of the global maps."""
        return cast('XsdSimpleType', self.maps.types[XSD_ANY_ATOMIC_TYPE])

    @property
    def annotation(self) -> Optional['XsdAnnotation']:
        if '_annotation' not in self.__dict__:
            for child in self.elem:
                if child.tag == XSD_ANNOTATION:
                    self._annotation = XsdAnnotation(child, self.schema, self)
                    break
                elif not callable(child.tag):
                    self._annotation = None
                    break
            else:
                self._annotation = None

        return self._annotation

    def __repr__(self) -> str:
        if self.ref is not None:
            return '%s(ref=%r)' % (self.__class__.__name__, self.prefixed_name)
        else:
            return '%s(name=%r)' % (self.__class__.__name__, self.prefixed_name)

    def _parse(self) -> None:
        return

    def _parse_reference(self) -> Optional[bool]:
        """
        Helper method for referable components. Returns `True` if a valid reference QName
        is found without any error, otherwise returns `None`. Sets an id-related name for
        the component ('nameless_<id of the instance>') if both the attributes 'ref' and
        'name' are missing.
        """
        ref = self.elem.get('ref')
        if ref is None:
            if 'name' in self.elem.attrib:
                return None
            elif self.parent is None:
                self.parse_error("missing attribute 'name' in a global %r" % type(self))
            else:
                self.parse_error(
                    "missing both attributes 'name' and 'ref' in local %r" % type(self)
                )
        elif 'name' in self.elem.attrib:
            self.parse_error("attributes 'name' and 'ref' are mutually exclusive")
        elif self.parent is None:
            self.parse_error("attribute 'ref' not allowed in a global %r" % type(self))
        else:
            try:
                self.name = self.schema.resolve_qname(ref)
            except (KeyError, ValueError, RuntimeError) as err:
                self.parse_error(err)
            else:
                if self._parse_child_component(self.elem, strict=False) is not None:
                    self.parse_error("a reference component cannot have "
                                     "child definitions/declarations")
                return True

        return None

    def _parse_child_component(self, elem: ElementType, strict: bool = True) \
            -> Optional[ElementType]:
        child = None
        for e in elem:
            if e.tag == XSD_ANNOTATION or callable(e.tag):
                continue
            elif not strict:
                return e
            elif child is not None:
                msg = "too many XSD components, unexpected {!r} found at position {}"
                self.parse_error(msg.format(child, elem[:].index(e)), elem)
                break
            else:
                child = e
        return child

    def _parse_target_namespace(self) -> None:
        """
        XSD 1.1 targetNamespace attribute in elements and attributes declarations.
        """
        if 'targetNamespace' not in self.elem.attrib:
            return

        self._target_namespace = self.elem.attrib['targetNamespace'].strip()
        if 'name' not in self.elem.attrib:
            self.parse_error("attribute 'name' must be present when "
                             "'targetNamespace' attribute is provided")
        if 'form' in self.elem.attrib:
            self.parse_error("attribute 'form' must be absent when "
                             "'targetNamespace' attribute is provided")
        if self._target_namespace != self.schema.target_namespace:
            if self.parent is None:
                self.parse_error("a global %s must have the same namespace as "
                                 "its parent schema" % self.__class__.__name__)

            xsd_type = self.get_parent_type()
            if xsd_type is None or xsd_type.parent is not None:
                pass
            elif xsd_type.derivation != 'restriction' or \
                    getattr(xsd_type.base_type, 'name', None) == XSD_ANY_TYPE:
                self.parse_error("a declaration contained in a global complexType "
                                 "must have the same namespace as its parent schema")

        if self.name is None:
            pass  # pragma: no cover
        elif not self._target_namespace:
            self.name = local_name(self.name)
        else:
            self.name = '{%s}%s' % (self._target_namespace, local_name(self.name))

    @property
    def local_name(self) -> Optional[str]:
        """The local part of the name of the component, or `None` if the name is `None`."""
        return None if self.name is None else local_name(self.name)

    @property
    def qualified_name(self) -> Optional[str]:
        """The name of the component in extended format, or `None` if the name is `None`."""
        return None if self.name is None else get_qname(self.target_namespace, self.name)

    @property
    def prefixed_name(self) -> Optional[str]:
        """The name of the component in prefixed format, or `None` if the name is `None`."""
        return None if self.name is None else get_prefixed_qname(self.name, self.namespaces)

    @property
    def id(self) -> Optional[str]:
        """The ``'id'`` attribute of the component tag, ``None`` if missing."""
        return self.elem.get('id')

    @property
    def validation_attempted(self) -> str:
        return 'full' if self.built else 'partial'

    def build(self) -> None:
        """
        Builds components that are not fully parsed at initialization, like model groups
        or internal local elements in model groups. Otherwise does nothing.
        """

    @property
    def built(self) -> bool:
        raise NotImplementedError()

    def is_matching(self, name: Optional[str], default_namespace: Optional[str] = None,
                    **kwargs: Any) -> bool:
        """
        Returns `True` if the component name is matching the name provided as argument,
        `False` otherwise. For XSD elements the matching is extended to substitutes.

        :param name: a local or fully-qualified name.
        :param default_namespace: used if it's not None and not empty for completing \
        the name argument in case it's a local name.
        :param kwargs: additional options that can be used by certain components.
        """
        if not name:
            return self.name == name
        elif name[0] == '{':
            return self.qualified_name == name
        elif not default_namespace:
            return self.name == name or not self.qualified and self.local_name == name
        else:
            qname = '{%s}%s' % (default_namespace, name)
            return self.qualified_name == qname or not self.qualified and self.local_name == name

    def match(self, name: Optional[str], default_namespace: Optional[str] = None,
              **kwargs: Any) -> Optional['XsdComponent']:
        """
        Returns the component if its name is matching the name provided as argument,
        `None` otherwise.
        """
        return self if self.is_matching(name, default_namespace, **kwargs) else None

    def get_matching_item(self, mapping: MutableMapping[str, Any],
                          ns_prefix: str = 'xmlns',
                          match_local_name: bool = False) -> Optional[Any]:
        """
        If a key is matching component name, returns its value, otherwise returns `None`.
        """
        if self.name is None:
            return None
        elif not self.target_namespace:
            return mapping.get(self.name)
        elif self.qualified_name in mapping:
            return mapping[cast(str, self.qualified_name)]
        elif self.prefixed_name in mapping:
            return mapping[cast(str, self.prefixed_name)]

        # Try a match with other prefixes
        target_namespace = self.target_namespace
        suffix = ':%s' % self.local_name

        for k in filter(lambda x: x.endswith(suffix), mapping):
            prefix = k.split(':')[0]
            if self.namespaces.get(prefix) == target_namespace:
                return mapping[k]

            # Match namespace declaration within value
            ns_declaration = '{}:{}'.format(ns_prefix, prefix)
            try:
                if mapping[k][ns_declaration] == target_namespace:
                    return mapping[k]
            except (KeyError, TypeError):
                pass
        else:
            if match_local_name:
                return mapping.get(self.local_name)  # type: ignore[arg-type]
            return None

    def get_global(self) -> 'XsdComponent':
        """Returns the global XSD component that contains the component instance."""
        if self.parent is None:
            return self
        component = self.parent
        while component is not self:  # pragma: no cover
            if component.parent is None:
                return component
            component = component.parent
        else:
            raise XMLSchemaValueError(f"parent circularity from {self}")  # pragma: no cover

    def get_parent_type(self) -> Optional['XsdType']:
        """
        Returns the nearest XSD type that contains the component instance,
        or `None` if the component doesn't have an XSD type parent.
        """
        component = self.parent
        while component is not self and component is not None:
            if isinstance(component, XsdType):
                return component
            component = component.parent
        return None

    def iter_components(self, xsd_classes: ComponentClassType = None) \
            -> Iterator['XsdComponent']:
        """
        Creates an iterator for XSD subcomponents.

        :param xsd_classes: provide a class or a tuple of classes to iterates over only a \
        specific classes of components.
        """
        if xsd_classes is None or isinstance(self, xsd_classes):
            yield self

    def iter_ancestors(self, xsd_classes: ComponentClassType = None)\
            -> Iterator['XsdComponent']:
        """
        Creates an iterator for XSD ancestor components, schema excluded. Stops when the component
        is global or if the ancestor is not an instance of the specified class/classes.

        :param xsd_classes: provide a class or a tuple of classes to iterates over only a \
        specific classes of components.
        """
        ancestor = self
        while True:
            if ancestor.parent is None:
                break
            ancestor = ancestor.parent
            if xsd_classes is not None and not isinstance(ancestor, xsd_classes):
                break
            yield ancestor

    def tostring(self, indent: str = '', max_lines: Optional[int] = None,
                 spaces_for_tab: int = 4) -> Union[str, bytes]:
        """Serializes the XML elements that declare or define the component to a string."""
        return etree_tostring(self.schema_elem, self.namespaces, indent, max_lines, spaces_for_tab) class XsdGroup(XsdComponent, MutableSequence[ModelParticleType],
               ParticleMixin, ValidationMixin[ElementType, GroupDecodeType]):
    """
    Class for XSD 1.0 *model group* definitions.

    ..  <group
          id = ID
          maxOccurs = (nonNegativeInteger | unbounded) : 1
          minOccurs = nonNegativeInteger : 1
          name = NCName
          ref = QName
          {any attributes with non-schema namespace . . .}>
          Content: (annotation?, (all | choice | sequence)?)
        </group>

    ..  <all
          id = ID
          maxOccurs = 1 : 1
          minOccurs = (0 | 1) : 1
          {any attributes with non-schema namespace . . .}>
          Content: (annotation?, element*)
        </all>

    ..  <choice
          id = ID
          maxOccurs = (nonNegativeInteger | unbounded)  : 1
          minOccurs = nonNegativeInteger : 1
          {any attributes with non-schema namespace . . .}>
          Content: (annotation?, (element | group | choice | sequence | any)*)
        </choice>

    ..  <sequence
          id = ID
          maxOccurs = (nonNegativeInteger | unbounded)  : 1
          minOccurs = nonNegativeInteger : 1
          {any attributes with non-schema namespace . . .}>
          Content: (annotation?, (element | group | choice | sequence | any)*)
        </sequence>
    """
    parent: Optional[Union['XsdComplexType', 'XsdGroup']]
    model: str
    mixed: bool = False
    ref: Optional['XsdGroup']
    restriction: Optional['XsdGroup'] = None

    # For XSD 1.1 openContent processing
    interleave: Optional[Xsd11AnyElement] = None  # if openContent with mode='interleave'
    suffix: Optional[Xsd11AnyElement] = None  # if openContent with mode='suffix'/'interleave'

    _ADMITTED_TAGS = {XSD_GROUP, XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}

    def __init__(self, elem: ElementType,
                 schema: SchemaType,
                 parent: Optional[Union['XsdComplexType', 'XsdGroup']] = None) -> None:

        self._group: List[ModelParticleType] = []
        if parent is not None and parent.mixed:
            self.mixed = parent.mixed
        super(XsdGroup, self).__init__(elem, schema, parent)

    def __repr__(self) -> str:
        if self.name is None:
            return '%s(model=%r, occurs=%r)' % (
                self.__class__.__name__, self.model, list(self.occurs)
            )
        elif self.ref is None:
            return '%s(name=%r, model=%r, occurs=%r)' % (
                self.__class__.__name__, self.prefixed_name, self.model, list(self.occurs)
            )
        else:
            return '%s(ref=%r, model=%r, occurs=%r)' % (
                self.__class__.__name__, self.prefixed_name, self.model, list(self.occurs)
            )

    @overload
    def __getitem__(self, i: int) -> ModelParticleType: ...

    @overload
    def __getitem__(self, s: slice) -> MutableSequence[ModelParticleType]: ...

    def __getitem__(self, i: Union[int, slice]) \
            -> Union[ModelParticleType, MutableSequence[ModelParticleType]]:
        return self._group[i]

    def __setitem__(self, i: Union[int, slice], o: Any) -> None:
        self._group[i] = o

    def __delitem__(self, i: Union[int, slice]) -> None:
        del self._group[i]

    def __len__(self) -> int:
        return len(self._group)

    def insert(self, i: int, item: ModelParticleType) -> None:
        self._group.insert(i, item)

    def clear(self) -> None:
        del self._group[:]

    def is_emptiable(self) -> bool:
        if self.model == 'choice':
            return self.min_occurs == 0 or not self or any(item.is_emptiable() for item in self)
        else:
            return self.min_occurs == 0 or not self or all(item.is_emptiable() for item in self)

    def is_single(self) -> bool:
        if self.max_occurs != 1 or not self:
            return False
        elif len(self) > 1 or not isinstance(self[0], XsdGroup):
            return True
        else:
            return self[0].is_single()

    def is_pointless(self, parent: 'XsdGroup') -> bool:
        """
        Returns `True` if the group may be eliminated without affecting the model,
        `False` otherwise. A group is pointless if one of those conditions is verified:

         - the group is empty
         - minOccurs == maxOccurs == 1 and the group has one child
         - minOccurs == maxOccurs == 1 and the group and its parent have a sequence model
         - minOccurs == maxOccurs == 1 and the group and its parent have a choice model

        Ref: https://www.w3.org/TR/2004/REC-xmlschema-1-20041028/#coss-particle

        :param parent: effective parent of the model group.
        """
        if not self:
            return True
        elif self.min_occurs != 1 or self.max_occurs != 1:
            return False
        elif len(self) == 1:
            return True
        elif self.model == 'sequence' and parent.model != 'sequence':
            return False
        elif self.model == 'choice' and parent.model != 'choice':
            return False
        else:
            return True

    @property
    def effective_min_occurs(self) -> int:
        if not self.min_occurs or not self:
            return 0
        elif self.model == 'choice':
            if any(not e.effective_min_occurs for e in self.iter_model()):
                return 0
        else:
            if all(not e.effective_min_occurs for e in self.iter_model()):
                return 0
        return self.min_occurs

    @property
    def effective_max_occurs(self) -> Optional[int]:
        if self.max_occurs == 0 or not self:
            return 0

        effective_items: List[Any]
        value: int

        effective_items = [e for e in self.iter_model() if e.effective_max_occurs != 0]
        if not effective_items:
            return 0
        elif self.max_occurs is None:
            return None
        elif self.model == 'choice':
            try:
                value = max(e.effective_max_occurs for e in effective_items)
            except TypeError:
                return None
            else:
                return self.max_occurs * value

        not_emptiable_items = [e for e in effective_items if e.effective_min_occurs]
        if not not_emptiable_items:
            try:
                value = max(e.effective_max_occurs for e in effective_items)
            except TypeError:
                return None
            else:
                return self.max_occurs * value

        elif len(not_emptiable_items) > 1:
            return self.max_occurs

        value = not_emptiable_items[0].effective_max_occurs
        return None if value is None else self.max_occurs * value

    def has_occurs_restriction(
            self, other: Union[ModelParticleType, ParticleMixin, 'OccursCalculator']) -> bool:

        if not self:
            return True
        elif isinstance(other, XsdGroup):
            return super(XsdGroup, self).has_occurs_restriction(other)

        # Group particle compared to element particle
        if self.max_occurs is None or any(e.max_occurs is None for e in self):
            if other.max_occurs is not None:
                return False
            elif self.model == 'choice':
                return self.min_occurs * min(e.min_occurs for e in self) >= other.min_occurs
            else:
                return self.min_occurs * sum(e.min_occurs for e in self) >= other.min_occurs

        elif self.model == 'choice':
            if self.min_occurs * min(e.min_occurs for e in self) < other.min_occurs:
                return False
            elif other.max_occurs is None:
                return True
            else:
                value: int
                try:
                    value = max(e.max_occurs for e in self)  # type: ignore[type-var, assignment]
                except TypeError:
                    return False
                else:
                    return self.max_occurs * value <= other.max_occurs

        else:
            if self.min_occurs * sum(e.min_occurs for e in self) < other.min_occurs:
                return False
            elif other.max_occurs is None:
                return True
            else:
                try:
                    value = sum(e.max_occurs for e in self)  # type: ignore[misc]
                except TypeError:
                    return False
                else:
                    return self.max_occurs * value <= other.max_occurs

    def iter_model(self) -> Iterator[ModelParticleType]:
        """
        A generator function iterating elements and groups of a model group.
        Skips pointless groups, iterating deeper through them.
        Raises `XMLSchemaModelDepthError` if the *depth* of the model is over
        `limits.MAX_MODEL_DEPTH` value.
        """
        iterators: List[Iterator[ModelParticleType]] = []
        particles = iter(self)

        while True:
            try:
                item = next(particles)
            except StopIteration:
                try:
                    particles = iterators.pop()
                except IndexError:
                    return
            else:
                if isinstance(item, XsdGroup) and item.is_pointless(parent=self):
                    iterators.append(particles)
                    particles = iter(item)
                    if len(iterators) > limits.MAX_MODEL_DEPTH:
                        raise XMLSchemaModelDepthError(self)
                else:
                    yield item

    def iter_elements(self) -> Iterator[SchemaElementType]:
        """
        A generator function iterating model's elements. Raises `XMLSchemaModelDepthError`
        if the overall depth of the model groups is over `limits.MAX_MODEL_DEPTH`.
        """
        if self.max_occurs == 0:
            return

        iterators: List[Iterator[ModelParticleType]] = []
        particles = iter(self)

        while True:
            try:
                item = next(particles)
            except StopIteration:
                try:
                    particles = iterators.pop()
                except IndexError:
                    return
            else:
                if isinstance(item, XsdGroup):
                    iterators.append(particles)
                    particles = iter(item)
                    if len(iterators) > limits.MAX_MODEL_DEPTH:
                        raise XMLSchemaModelDepthError(self)
                else:
                    yield item

    def get_subgroups(self, item: ModelParticleType) -> List['XsdGroup']:
        """
        Returns a list of the groups that represent the path to the enclosed particle.
        Raises an `XMLSchemaModelError` if *item* is not a particle of the model group.
        """
        subgroups: List[Tuple[XsdGroup, Iterator[ModelParticleType]]] = []
        group, children = self, iter(self)

        while True:
            try:
                child = next(children)
            except StopIteration:
                try:
                    group, children = subgroups.pop()
                except IndexError:
                    msg = '{!r} is not a particle of the model group'
                    raise XMLSchemaModelError(self, msg.format(item)) from None
                else:
                    continue

            if child is item:
                _subgroups = [x[0] for x in subgroups]
                _subgroups.append(group)
                return _subgroups
            elif isinstance(child, XsdGroup):
                if len(subgroups) > limits.MAX_MODEL_DEPTH:
                    raise XMLSchemaModelDepthError(self)
                subgroups.append((group, children))
                group, children = child, iter(child)

    def overall_min_occurs(self, item: ModelParticleType) -> int:
        """Returns the overall min occurs of a particle in the model."""
        min_occurs = item.min_occurs

        for group in self.get_subgroups(item):
            if group.model == 'choice' and len(group) > 1:
                return 0
            min_occurs *= group.min_occurs

        return min_occurs

    def overall_max_occurs(self, item: ModelParticleType) -> Optional[int]:
        """Returns the overall max occurs of a particle in the model."""
        max_occurs = item.max_occurs

        for group in self.get_subgroups(item):
            if max_occurs == 0:
                return 0
            elif max_occurs is None:
                continue
            elif group.max_occurs is None:
                max_occurs = None
            else:
                max_occurs *= group.max_occurs

        return max_occurs

    def copy(self) -> 'XsdGroup':
        group: XsdGroup = object.__new__(self.__class__)
        group.__dict__.update(self.__dict__)
        group.errors = self.errors[:]
        group._group = self._group[:]
        return group

    __copy__ = copy

    def _parse(self) -> None:
        self.clear()
        self._parse_particle(self.elem)

        if self.elem.tag != XSD_GROUP:
            # Local group (sequence|all|choice)
            if 'name' in self.elem.attrib:
                self.parse_error("attribute 'name' not allowed for a local group")
            self._parse_content_model(self.elem)

        elif self._parse_reference():
            assert self.name is not None
            try:
                xsd_group = self.maps.lookup_group(self.name)
            except KeyError:
                self.parse_error("missing group %r" % self.prefixed_name)
                xsd_group = self.schema.create_any_content_group(parent=self)

            if isinstance(xsd_group, XsdGroup):
                self.model = xsd_group.model
                if self.model == 'all':
                    if self.max_occurs != 1:
                        self.parse_error("maxOccurs must be 1 for 'all' model groups")
                    if self.min_occurs not in (0, 1):
                        self.parse_error("minOccurs must be (0 | 1) for 'all' model groups")
                    if self.xsd_version == '1.0' and isinstance(self.parent, XsdGroup):
                        self.parse_error("in XSD 1.0 the 'all' model group cannot be nested")
                self._group.append(xsd_group)
                self.ref = xsd_group
            else:
                # Disallowed circular definition, substitute with any content group.
                self.parse_error("Circular definitions detected for group %r:" % self.name,
                                 xsd_group[0])
                self.model = 'sequence'
                self.mixed = True
                self._group.append(self.schema.xsd_any_class(ANY_ELEMENT, self.schema, self))

        else:
            attrib = self.elem.attrib
            try:
                self.name = get_qname(self.target_namespace, attrib['name'])
            except KeyError:
                pass
            else:
                if self.parent is not None:
                    self.parse_error("attribute 'name' not allowed for a local group")
                else:
                    if 'minOccurs' in attrib:
                        self.parse_error("attribute 'minOccurs' not allowed for a global group")
                    if 'maxOccurs' in attrib:
                        self.parse_error("attribute 'maxOccurs' not allowed for a global group")

                content_model = self._parse_child_component(self.elem, strict=True)
                if content_model is not None:
                    if self.parent is None:
                        if 'minOccurs' in content_model.attrib:
                            self.parse_error("attribute 'minOccurs' not allowed for the model "
                                             "of a global group", content_model)
                        if 'maxOccurs' in content_model.attrib:
                            self.parse_error("attribute 'maxOccurs' not allowed for the model "
                                             "of a global group", content_model)

                    if content_model.tag in {XSD_SEQUENCE, XSD_ALL, XSD_CHOICE}:
                        self._parse_content_model(content_model)
                    else:
                        self.parse_error('unexpected tag %r' % content_model.tag, content_model)

    def _parse_content_model(self, content_model: ElementType) -> None:
        self.model = local_name(content_model.tag)
        if self.model == 'all':
            if self.max_occurs != 1:
                self.parse_error("maxOccurs must be 1 for 'all' model groups")
            if self.min_occurs not in (0, 1):
                self.parse_error("minOccurs must be (0 | 1) for 'all' model groups")

        child: ElementType
        for child in content_model:
            if child.tag == XSD_ANNOTATION or callable(child.tag):
                continue
            elif child.tag == XSD_ELEMENT:
                # Builds inner elements later, for avoid circularity.
                self.append(self.schema.xsd_element_class(child, self.schema, self, False))
            elif content_model.tag == XSD_ALL:
                self.parse_error("'all' model can contains only elements.")
            elif child.tag == XSD_ANY:
                self._group.append(XsdAnyElement(child, self.schema, self))
            elif child.tag in (XSD_SEQUENCE, XSD_CHOICE):
                self._group.append(XsdGroup(child, self.schema, self))
            elif child.tag == XSD_GROUP:
                try:
                    ref = self.schema.resolve_qname(child.attrib['ref'])
                except (KeyError, ValueError, RuntimeError) as err:
                    if 'ref' not in child.attrib:
                        self.parse_error("missing attribute 'ref' in local group", child)
                    else:
                        self.parse_error(err, child)
                    continue

                if ref != self.name:
                    xsd_group = XsdGroup(child, self.schema, self)
                    if xsd_group.model == 'all':
                        self.parse_error("'all' model can appears only at 1st level "
                                         "of a model group")
                    else:
                        self._group.append(xsd_group)
                elif self.redefine is None:
                    self.parse_error("Circular definition detected for group %r:" % self.name)
                else:
                    if child.get('minOccurs', '1') != '1' or child.get('maxOccurs', '1') != '1':
                        self.parse_error("Redefined group reference cannot have "
                                         "minOccurs/maxOccurs other than 1:")
                    self._group.append(self.redefine)

    def build(self) -> None:
        for item in self._group:
            if isinstance(item, XsdElement):
                item.build()

        if self.redefine is not None:
            for group in self.redefine.iter_components(XsdGroup):
                group.build()

    @property
    def built(self) -> bool:
        for item in self:
            if isinstance(item, XsdElement):
                if not item.built:
                    return False
            elif isinstance(item, XsdAnyElement):
                continue
            elif item.parent is None:
                continue
            elif item.parent is not self.parent and \
                    isinstance(item.parent, XsdType) and item.parent.parent is None:
                continue
            elif not item.ref and not item.built:
                return False

        return True if self.model else False

    @property
    def validation_attempted(self) -> str:
        if self.built:
            return 'full'
        elif any(item.validation_attempted == 'partial' for item in self):
            return 'partial'
        else:
            return 'none'

    @property
    def schema_elem(self) -> ElementType:
        return self.parent.elem if self.parent is not None else self.elem

    def iter_components(self, xsd_classes: Optional[ComponentClassType] = None) \
            -> Iterator[XsdComponent]:
        if xsd_classes is None or isinstance(self, xsd_classes):
            yield self
        for item in self:
            if item.parent is None:
                continue
            elif item.parent is not self.parent and isinstance(item.parent, XsdType) \
                    and item.parent.parent is None:
                continue
            yield from item.iter_components(xsd_classes)

        if self.redefine is not None and self.redefine not in self:
            yield from self.redefine.iter_components(xsd_classes)

    def admits_restriction(self, model: str) -> bool:
        if self.model == model:
            return True
        elif self.model == 'all':
            return model == 'sequence'
        elif self.model == 'choice':
            return model == 'sequence' or len(self.ref or self) <= 1
        else:
            return model == 'choice' or len(self.ref or self) <= 1

    def is_empty(self) -> bool:
        return not self.mixed and (not self._group or self.max_occurs == 0)

    def is_restriction(self, other: ModelParticleType, check_occurs: bool = True) -> bool:
        if not self._group:
            return True
        elif not isinstance(other, ParticleMixin):
            raise XMLSchemaValueError("the argument 'other' must be an XSD particle")
        elif not isinstance(other, XsdGroup):
            return self.is_element_restriction(other)
        elif not other:
            return False
        elif len(other) == other.min_occurs == other.max_occurs == 1:
            if len(self) > 1:
                return self.is_restriction(other[0], check_occurs)
            elif self.ref is None and isinstance(self[0], XsdGroup) \
                    and self[0].is_pointless(parent=self):
                return self[0].is_restriction(other[0], check_occurs)

        # Compare model with model
        if self.model != other.model and self.model != 'sequence' and \
                (len(self) > 1 or self.ref is not None and len(self[0]) > 1):
            return False
        elif self.model == other.model or other.model == 'sequence':
            return self.is_sequence_restriction(other)
        elif other.model == 'all':
            return self.is_all_restriction(other)
        else:  # other.model == 'choice':
            return self.is_choice_restriction(other)

    def is_element_restriction(self, other: ModelParticleType) -> bool:
        if self.xsd_version == '1.0' and isinstance(other, XsdElement) and \
                not other.ref and other.name not in self.schema.substitution_groups:
            return False
        elif not self.has_occurs_restriction(other):
            return False
        elif self.model == 'choice':
            if other.name in self.maps.substitution_groups and \
                    all(isinstance(e, XsdElement) and e.substitution_group == other.name
                        for e in self):
                return True
            return any(e.is_restriction(other, False) for e in self)
        else:
            min_occurs = 0
            max_occurs: Optional[int] = 0
            for item in self.iter_model():
                if isinstance(item, XsdGroup):
                    return False
                elif item.min_occurs == 0 or item.is_restriction(other, False):
                    min_occurs += item.min_occurs
                    if max_occurs is not None:
                        if item.max_occurs is None:
                            max_occurs = None
                        else:
                            max_occurs += item.max_occurs
                    continue
                return False

            if min_occurs < other.min_occurs:
                return False
            elif max_occurs is None:
                return other.max_occurs is None
            elif other.max_occurs is None:
                return True
            else:
                return max_occurs <= other.max_occurs

    def is_sequence_restriction(self, other: 'XsdGroup') -> bool:
        if not self.has_occurs_restriction(other):
            return False

        check_occurs = other.max_occurs != 0

        # Same model: declarations must simply preserve order
        other_iterator = iter(other.iter_model())
        for item in self.iter_model():
            while True:
                try:
                    other_item = next(other_iterator)
                except StopIteration:
                    return False
                if other_item is item or item.is_restriction(other_item, check_occurs):
                    break
                elif other.model == 'choice':
                    if item.max_occurs != 0:
                        continue
                    elif not other_item.is_matching(item.name, self.default_namespace):
                        continue
                    elif all(e.max_occurs == 0 for e in self.iter_model()):
                        return False
                    else:
                        break
                elif not other_item.is_emptiable():
                    return False

        if other.model == 'choice':
            return True

        while True:
            try:
                other_item = next(other_iterator)
            except StopIteration:
                return True
            else:
                if not other_item.is_emptiable():
                    return False

    def is_all_restriction(self, other: 'XsdGroup') -> bool:
        if not self.has_occurs_restriction(other):
            return False

        check_occurs = other.max_occurs != 0
        if self.ref is None:
            restriction_items = [x for x in self]
        else:
            restriction_items = [x for x in self[0]]

        for other_item in other.iter_model():
            for item in restriction_items:
                if other_item is item or item.is_restriction(other_item, check_occurs):
                    break
            else:
                if not other_item.is_emptiable():
                    return False
                continue
            restriction_items.remove(item)

        return not bool(restriction_items)

    def is_choice_restriction(self, other: 'XsdGroup') -> bool:
        if self.ref is None:
            if self.parent is None and other.parent is not None:
                return False  # not allowed restriction in XSD 1.0
            restriction_items = [x for x in self]
        elif other.parent is None:
            restriction_items = [x for x in self[0]]
        else:
            return False  # not allowed restriction in XSD 1.0

        check_occurs = other.max_occurs != 0
        max_occurs: Optional[int] = 0
        other_max_occurs: Optional[int] = 0

        for other_item in other.iter_model():
            for item in restriction_items:
                if other_item is item or item.is_restriction(other_item, check_occurs):
                    if max_occurs is not None:
                        if item.max_occurs is None:
                            max_occurs = None
                        else:
                            max_occurs += item.max_occurs

                    if other_max_occurs is not None:
                        if other_item.max_occurs is None:
                            other_max_occurs = None
                        else:
                            other_max_occurs = max(other_max_occurs, other_item.max_occurs)
                    break
            else:
                continue
            restriction_items.remove(item)

        if restriction_items:
            return False
        elif other_max_occurs is None:
            if other.max_occurs != 0:
                return True
            other_max_occurs = 0
        elif other.max_occurs is None:
            if other_max_occurs != 0:
                return True
            other_max_occurs = 0
        else:
            other_max_occurs *= other.max_occurs

        if max_occurs is None:
            return self.max_occurs == 0
        elif self.max_occurs is None:
            return max_occurs == 0
        else:
            return other_max_occurs >= max_occurs * self.max_occurs

    def check_model(self) -> None:
        """
        Checks if the model group is deterministic. Element Declarations Consistent and
        Unique Particle Attribution constraints are checked.
        :raises: an `XMLSchemaModelError` at first violated constraint.
        """
        def safe_iter_path() -> Iterator[SchemaElementType]:
            iterators: List[Iterator[ModelParticleType]] = []
            particles = iter(self)

            while True:
                try:
                    item = next(particles)
                except StopIteration:
                    try:
                        current_path.pop()
                        particles = iterators.pop()
                    except IndexError:
                        return
                else:
                    if isinstance(item, XsdGroup):
                        current_path.append(item)
                        iterators.append(particles)
                        particles = iter(item)
                        if len(iterators) > limits.MAX_MODEL_DEPTH:
                            raise XMLSchemaModelDepthError(self)
                    else:
                        yield item

        paths: Any = {}
        current_path: List[ModelParticleType] = [self]
        try:
            any_element = self.parent.open_content.any_element  # type: ignore[union-attr]
        except AttributeError:
            any_element = None

        for e in safe_iter_path():

            previous_path: List[ModelParticleType]
            for pe, previous_path in paths.values():
                # EDC check
                if not e.is_consistent(pe) or any_element and not any_element.is_consistent(pe):
                    msg = "Element Declarations Consistent violation between %r and %r: " \
                          "match the same name but with different types" % (e, pe)
                    raise XMLSchemaModelError(self, msg)

                # UPA check
                if pe is e or not pe.is_overlap(e):
                    continue
                elif pe.parent is e.parent:
                    if pe.parent.model in {'all', 'choice'}:
                        if isinstance(pe, Xsd11AnyElement) and not isinstance(e, XsdAnyElement):
                            pe.add_precedence(e, self)
                        elif isinstance(e, Xsd11AnyElement) and not isinstance(pe, XsdAnyElement):
                            e.add_precedence(pe, self)
                        else:
                            msg = "{!r} and {!r} overlap and are in the same {!r} group"
                            raise XMLSchemaModelError(self, msg.format(pe, e, pe.parent.model))
                    elif pe.min_occurs == pe.max_occurs:
                        continue

                if distinguishable_paths(previous_path + [pe], current_path + [e]):
                    continue
                elif isinstance(pe, Xsd11AnyElement) and not isinstance(e, XsdAnyElement):
                    pe.add_precedence(e, self)
                elif isinstance(e, Xsd11AnyElement) and not isinstance(pe, XsdAnyElement):
                    e.add_precedence(pe, self)
                else:
                    msg = "Unique Particle Attribution violation between {!r} and {!r}"
                    raise XMLSchemaModelError(self, msg.format(pe, e))

            paths[e.name] = e, current_path[:]

    def check_dynamic_context(self, elem: ElementType,
                              xsd_element: SchemaElementType,
                              model_element: SchemaElementType,
                              namespaces: NamespacesType) -> None:

        if model_element is not xsd_element and isinstance(model_element, XsdElement):
            if 'substitution' in model_element.block \
                    or xsd_element.type and xsd_element.type.is_blocked(model_element):
                raise XMLSchemaValidationError(
                    model_element, elem, "substitution of %r is blocked" % model_element
                )

        alternatives: Union[Tuple[()], List[XsdAlternative]] = []
        if isinstance(xsd_element, XsdAnyElement):
            if xsd_element.process_contents == 'skip':
                return

            try:
                xsd_element = self.maps.lookup_element(elem.tag)
            except LookupError:
                try:
                    type_name = elem.attrib[XSI_TYPE].strip()
                except KeyError:
                    return
                else:
                    xsd_type = self.maps.get_instance_type(
                        type_name, self.any_type, namespaces
                    )
            else:
                alternatives = xsd_element.alternatives
                try:
                    type_name = elem.attrib[XSI_TYPE].strip()
                except KeyError:
                    xsd_type = xsd_element.type
                else:
                    xsd_type = self.maps.get_instance_type(
                        type_name, xsd_element.type, namespaces
                    )

        else:
            if XSI_TYPE not in elem.attrib:
                xsd_type = xsd_element.type
            else:
                alternatives = xsd_element.alternatives
                try:
                    type_name = elem.attrib[XSI_TYPE].strip()
                except KeyError:
                    xsd_type = xsd_element.type
                else:
                    xsd_type = self.maps.get_instance_type(
                        type_name, xsd_element.type, namespaces
                    )

            if model_element is not xsd_element and \
                    isinstance(model_element, XsdElement) and model_element.block:
                for derivation in model_element.block.split():
                    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, elem, reason % (xsd_element, derivation)
                        )

            if XSI_TYPE not in elem.attrib:
                return

        # If it's a restriction the context is the base_type's group
        group = self.restriction if self.restriction is not None else self

        # Dynamic EDC check of matched element
        for e in group.iter_elements():
            if not isinstance(e, XsdElement):
                continue
            elif e.name == elem.tag:
                other = e
            else:
                for other in e.iter_substitutes():
                    if other.name == elem.tag:
                        break
                else:
                    continue

            if len(other.alternatives) != len(alternatives) or \
                    not xsd_type.is_dynamic_consistent(other.type):
                reason = "%r that matches %r is not consistent with local declaration %r"
                raise XMLSchemaValidationError(self, reason % (elem, xsd_element, other))

            if not all(any(a == x for x in alternatives) for a in other.alternatives) or \
                    not all(any(a == x for x in other.alternatives) for a in alternatives):
                msg = "Maybe a not equivalent type table between elements %r and %r."
                warnings.warn(msg % (self, xsd_element), XMLSchemaTypeTableWarning, stacklevel=3)

    def match_element(self, name: str, default_namespace: Optional[str] = None) \
            -> Optional[SchemaElementType]:
        """
        Try a model-less match of a child element. Returns the
        matched element, or `None` if there is no match.
        """
        for xsd_element in self.iter_elements():
            if xsd_element.is_matching(name, default_namespace, group=self):
                return xsd_element
        return None

    def iter_decode(self, obj: ElementType, validation: str = 'lax', **kwargs: Any) \
            -> IterDecodeType[GroupDecodeType]:
        """
        Creates an iterator for decoding an Element content.

        :param obj: an Element.
        :param validation: the validation mode, can be 'lax', 'strict' or 'skip'.
        :param kwargs: keyword arguments for the decoding process.
        :return: yields a list of 3-tuples (key, decoded data, decoder), \
        eventually preceded by a sequence of validation or decoding errors.
        """
        result_list: GroupDecodeType = []
        cdata_index = 1  # keys for CDATA sections are positive integers

        if not self._group and self.model == 'choice' and self.min_occurs:
            reason = "an empty 'choice' group with minOccurs > 0 cannot validate any content"
            yield self.validation_error(validation, reason, obj, **kwargs)
            yield result_list
            return

        if not self.mixed:
            # Check element CDATA
            if obj.text and obj.text.strip() or \
                    any(child.tail and child.tail.strip() for child in obj):
                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"
                    yield self.validation_error(validation, reason, obj, **kwargs)
                    cdata_index = 0  # Do not decode CDATA

        if cdata_index and obj.text is not None:
            text = str(obj.text.strip())
            if text:
                result_list.append((cdata_index, text, None))
                cdata_index += 1

        level = kwargs['level'] = kwargs.pop('level', 0) + 1
        over_max_depth = 'max_depth' in kwargs and kwargs['max_depth'] <= level
        if level > limits.MAX_XML_DEPTH:
            reason = "XML data depth exceeded (MAX_XML_DEPTH=%r)" % limits.MAX_XML_DEPTH
            self.validation_error('strict', reason, obj, **kwargs)

        try:
            namespaces = kwargs['namespaces']
        except KeyError:
            namespaces = default_namespace = None
        else:
            try:
                default_namespace = namespaces.get('')
            except AttributeError:
                default_namespace = None

        errors: List[Tuple[int, ModelParticleType, int, Optional[List[SchemaElementType]]]]
        xsd_element: Optional[SchemaElementType]
        expected: Optional[List[SchemaElementType]]

        model = ModelVisitor(self)
        errors = []
        broken_model = False

        for index, child in enumerate(obj):
            if callable(child.tag):
                continue  # child is a <class 'lxml.etree._Comment'>

            while model.element is not None:
                xsd_element = model.element.match(
                    child.tag, default_namespace, group=self, occurs=model.occurs
                )
                if xsd_element is None:
                    if self.interleave is not None and self.interleave.is_matching(
                            child.tag, default_namespace, self, model.occurs):
                        xsd_element = self.interleave
                        break

                    for particle, occurs, expected in model.advance(False):
                        errors.append((index, particle, occurs, expected))
                        model.clear()
                        broken_model = True  # the model is broken, continues with raw decoding.
                        xsd_element = self.match_element(child.tag, default_namespace)
                        break
                    else:
                        continue
                    break

                try:
                    self.check_dynamic_context(child, xsd_element, model.element, namespaces)
                except XMLSchemaValidationError as err:
                    yield self.validation_error(validation, err, obj, **kwargs)

                for particle, occurs, expected in model.advance(True):
                    errors.append((index, particle, occurs, expected))
                break
            else:
                if self.suffix is not None and \
                        self.suffix.is_matching(child.tag, default_namespace, self):
                    xsd_element = self.suffix
                else:
                    xsd_element = self.match_element(child.tag, default_namespace)
                    if xsd_element is None:
                        errors.append((index, self, 0, None))
                        broken_model = True
                    elif not broken_model:
                        errors.append((index, xsd_element, 0, []))
                        broken_model = True

            if xsd_element is None:
                if kwargs.get('keep_unknown') and 'converter' in kwargs:
                    for result in self.any_type.iter_decode(child, validation, **kwargs):
                        result_list.append((child.tag, result, None))
                continue
            elif 'converter' not in kwargs:
                # Validation-only mode: do not append results
                for result in xsd_element.iter_decode(child, validation, **kwargs):
                    if isinstance(result, XMLSchemaValidationError):
                        yield result
                continue
            elif over_max_depth:
                if 'depth_filler' in kwargs:
                    func = kwargs['depth_filler']
                    result_list.append((child.tag, func(xsd_element), xsd_element))
                continue

            for result in xsd_element.iter_decode(child, validation, **kwargs):
                if isinstance(result, XMLSchemaValidationError):
                    yield result
                else:
                    result_list.append((child.tag, result, xsd_element))

            if cdata_index and child.tail is not None:
                tail = str(child.tail.strip())
                if tail:
                    if result_list and isinstance(result_list[-1][0], int):
                        tail = result_list[-1][1] + ' ' + tail
                        result_list[-1] = result_list[-1][0], tail, None
                    else:
                        result_list.append((cdata_index, tail, None))
                        cdata_index += 1

        if model.element is not None:
            index = len(obj)
            for particle, occurs, expected in model.stop():
                errors.append((index, particle, occurs, expected))

        if errors:
            source = kwargs.get('source')
            for index, particle, occurs, expected in errors:
                error = XMLSchemaChildrenValidationError(
                    self, obj, index, particle, occurs, expected, source, namespaces
                )
                if validation == 'strict':
                    raise error
                yield error

        yield result_list

    def iter_encode(self, obj: ElementData, validation: str = 'lax', **kwargs: Any) \
            -> IterEncodeType[GroupEncodeType]:
        """
        Creates an iterator for encoding data to a list containing Element data.

        :param obj: an ElementData instance.
        :param validation: the validation mode: can be 'lax', 'strict' or 'skip'.
        :param kwargs: keyword arguments for the encoding process.
        :return: yields a couple with the text of the Element and a list of child \
        elements, eventually preceded by a sequence of validation errors.
        """
        level = kwargs['level'] = kwargs.get('level', 0) + 1
        errors = []
        text = raw_xml_encode(obj.text)
        children: List[ElementType] = []
        try:
            indent = kwargs['indent']
        except KeyError:
            indent = 4

        padding = '\n' + ' ' * indent * level

        try:
            converter = kwargs['converter']
        except KeyError:
            converter = kwargs['converter'] = self.schema.get_converter(**kwargs)

        default_namespace = converter.get('')
        model = ModelVisitor(self)
        index = cdata_index = 0
        wrong_content_type = False
        over_max_depth = 'max_depth' in kwargs and kwargs['max_depth'] <= level

        content: Iterable[Any]
        if obj.content is None:
            content = []
        elif isinstance(obj.content, MutableMapping) or kwargs.get('unordered'):
            content = ModelVisitor(self).iter_unordered_content(
                obj.content, default_namespace
            )
        elif not isinstance(obj.content, MutableSequence):
            wrong_content_type = True
            content = []
        elif converter.losslessly:
            content = obj.content
        else:
            content = ModelVisitor(self).iter_collapsed_content(
                obj.content, default_namespace
            )

        for index, (name, value) in enumerate(content):
            if isinstance(name, int):
                if not children:
                    text = padding + value if text is None else text + value + padding
                elif children[-1].tail is None:
                    children[-1].tail = padding + value
                else:
                    children[-1].tail += value + padding
                cdata_index += 1
                continue

            xsd_element: Optional[SchemaElementType]
            if self.interleave and self.interleave.is_matching(name, default_namespace, group=self):
                xsd_element = self.interleave
                value = get_qname(default_namespace, name), value
            else:
                while model.element is not None:
                    xsd_element = model.element.match(
                        name, default_namespace, group=self, occurs=model.occurs
                    )
                    if xsd_element is None:
                        for particle, occurs, expected in model.advance():
                            errors.append((index - cdata_index, particle, occurs, expected))
                        continue
                    elif isinstance(xsd_element, XsdAnyElement):
                        value = get_qname(default_namespace, name), value

                    for particle, occurs, expected in model.advance(True):
                        errors.append((index - cdata_index, particle, occurs, expected))
                    break
                else:
                    if self.suffix and self.suffix.is_matching(name, default_namespace, group=self):
                        xsd_element = self.suffix
                        value = get_qname(default_namespace, name), value
                    else:
                        errors.append((index - cdata_index, self, 0, []))
                        xsd_element = self.match_element(name, default_namespace)
                        if isinstance(xsd_element, XsdAnyElement):
                            value = get_qname(default_namespace, name), value
                        elif xsd_element is None:
                            if name.startswith('{') or ':' not in name:
                                reason = '{!r} does not match any declared element ' \
                                         'of the model group.'.format(name)
                            else:
                                reason = '{} has an unknown prefix {!r}'.format(
                                    name, name.split(':')[0]
                                )
                            yield self.validation_error(validation, reason, value, **kwargs)
                            continue

            if over_max_depth:
                continue

            for result in xsd_element.iter_encode(value, validation, **kwargs):
                if isinstance(result, XMLSchemaValidationError):
                    yield result
                else:
                    children.append(result)

        if model.element is not None:
            for particle, occurs, expected in model.stop():
                errors.append((index - cdata_index + 1, particle, occurs, expected))

        if children:
            if children[-1].tail is None:
                children[-1].tail = padding[:-indent] or '\n'
            else:
                children[-1].tail = children[-1].tail.strip() + (padding[:-indent] or '\n')

        cdata_not_allowed = not self.mixed and text and text.strip() and self and \
            (len(self) > 1 or not isinstance(self[0], XsdAnyElement))

        if errors or cdata_not_allowed or wrong_content_type:
            attrib = {k: raw_xml_encode(v) for k, v in obj.attributes.items()}
            elem = converter.etree_element(obj.tag, text, children, attrib)

            if wrong_content_type:
                reason = "wrong content type {!r}".format(type(obj.content))
                yield self.validation_error(validation, reason, elem, **kwargs)

            if cdata_not_allowed:
                reason = "character data between child elements not allowed"
                yield self.validation_error(validation, reason, elem, **kwargs)

            for index, particle, occurs, expected in errors:
                error = XMLSchemaChildrenValidationError(
                    validator=self,
                    elem=elem,
                    index=index,
                    particle=particle,
                    occurs=occurs,
                    expected=expected,
                    namespaces=converter.namespaces,
                )
                if validation == 'strict':
                    raise error

                error.elem = None  # replace with the element of the encoded tree
                yield error

        yield text, children
import threading
from typing import TYPE_CHECKING, cast, Any, Dict, Iterator, Optional, Union
from elementpath import XPath2Parser, XPathContext, XPathToken, ElementPathError
from ..names import XSD_ASSERT
from ..aliases import ElementType, SchemaType, SchemaElementType, NamespacesType
from ..xpath import XMLSchemaProtocol, ElementProtocol, ElementPathMixin, XMLSchemaProxy
from .exceptions import XMLSchemaNotBuiltError, XMLSchemaValidationError
from .xsdbase import XsdComponent
from .groups import XsdGroup
    from ..resources import XMLResource
    from .attributes import XsdAttributeGroup
    from .complex_types import XsdComplexType
    from .elements import XsdElement
    from .wildcards import XsdAnyElement
#
# Copyright (c), 2016-2020, 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 <brunato@sissa.it>
#




if TYPE_CHECKING:


class XsdAssert(XsdComponent, ElementPathMixin[Union['XsdAssert', SchemaElementType]]):
    """
    Class for XSD *assert* constraint definitions.

    ..  <assert
          id = ID
          test = an XPath expression
          xpathDefaultNamespace = (anyURI | (##defaultNamespace | ##targetNamespace | ##local))
          {any attributes with non-schema namespace . . .}>
          Content: (annotation?)
        </assert>
    """
    parent: 'XsdComplexType'
    _ADMITTED_TAGS = {XSD_ASSERT}
    token: Optional[XPathToken] = None
    parser: Optional[XPath2Parser] = None
    path = 'true()'

    def __init__(self, elem: ElementType,
                 schema: SchemaType,
                 parent: 'XsdComplexType',
                 base_type: 'XsdComplexType') -> None:

        self._xpath_lock = threading.Lock()
        self.base_type = base_type
        super(XsdAssert, self).__init__(elem, schema, parent)

    def __repr__(self) -> str:
        if len(self.path) < 40:
            return '%s(test=%r)' % (self.__class__.__name__, self.path)
        else:
            return '%s(test=%r)' % (self.__class__.__name__, self.path[:37] + '...')

    def __getstate__(self) -> Dict[str, Any]:
        state = self.__dict__.copy()
        state.pop('_xpath_lock', None)
        return state

    def __setstate__(self, state: Any) -> None:
        self.__dict__.update(state)
        self._xpath_lock = threading.Lock()

    def _parse(self) -> None:
        if self.base_type.is_simple():
            self.parse_error("base_type=%r is not a complexType definition" % self.base_type)
        else:
            try:
                self.path = self.elem.attrib['test'].strip()
            except KeyError as err:
                self.parse_error(err)

        if 'xpathDefaultNamespace' in self.elem.attrib:
            self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
        else:
            self.xpath_default_namespace = self.schema.xpath_default_namespace

    @property
    def built(self) -> bool:
        return self.parser is not None and self.token is not None

    def build(self) -> None:
        # Assert requires a schema bound parser because select
        # is on XML elements and with XSD type decoded values
        self.parser = XPath2Parser(
            namespaces=self.namespaces,
            variable_types={'value': self.base_type.sequence_type},
            strict=False,
            default_namespace=self.xpath_default_namespace,
            schema=self.xpath_proxy,
        )

        try:
            self.token = self.parser.parse(self.path)
        except ElementPathError as err:
            self.parse_error(err)
            self.token = self.parser.parse('true()')
        finally:
            if self.parser.variable_types:
                self.parser.variable_types.clear()

    def __call__(self, elem: ElementType,
                 value: Any = None,

                 namespaces: Optional[NamespacesType] = None,