# Source code for xsdata.formats.dataclass.models.elements

    import itertools
    import operator
    import sys
    from typing import (
        Any,
        Callable,
        Dict,
        Iterator,
        List,
        Mapping,
        Optional,
        Sequence,
        Set,
        Tuple,
        Type,
    )

    from xsdata.formats.converter import converter
    from xsdata.models.enums import NamespaceType
    from xsdata.utils import collections
    from xsdata.utils.namespaces import local_name, target_uri

    NoneType = type(None)

    class XmlType:
        """Xml node types."""

        TEXT = sys.intern("Text")
        ELEMENT = sys.intern("Element")
        ELEMENTS = sys.intern("Elements")
        WILDCARD = sys.intern("Wildcard")
        ATTRIBUTE = sys.intern("Attribute")
        ATTRIBUTES = sys.intern("Attributes")
        IGNORE = sys.intern("Ignore")

    class MetaMixin:
        """Use this mixin for unit tests only!!!"""

        __slots__: Tuple[str, ...] = ()

        def __eq__(self, other: Any) -> bool:
            return tuple(self) == tuple(other)

        def __iter__(self) -> Iterator:
            for name in self.__slots__:
                yield getattr(self, name)

        def __repr__(self) -> str:
            params = (f"{name}={getattr(self, name)!r}" for name in self.__slots__)
            return f"{self.__class__.__qualname__}({', '.join(params)})"

    class XmlVar(MetaMixin):
        """
        Class field binding metadata.

        :param index: Field ordering
        :param name: Field name
        :param qname: Qualified name
        :param types: List of all the supported data types
        :param init: Include field in the constructor
        :param mixed: Field supports mixed content type values
        :param tokens: Field is derived from xs:list
        :param format: Value format information
        :param derived: Wrap parsed values with a generic type
        :param any_type: Field supports dynamic value types
        :param required: Field is mandatory
        :param nillable: Field supports nillable content
        :param sequence: Render values in sequential mode
        :param list_element: Field is a list of elements
        :param default: Field default value or factory
        :param xml_Type: Field xml type
        :param namespaces: List of the supported namespaces
        :param elements: Mapping of qname-repeatable elements
        :param wildcards: List of repeatable wildcards
        :param wrapper: A name for the wrapper. Applies for list types only.
        """

        __slots__ = (
            "index",
            "name",
            "qname",
            "types",
            "clazz",
            "init",
            "mixed",
            "factory",
            "tokens_factory",
            "format",
            "derived",
            "any_type",
            "process_contents",
            "required",
            "nillable",
            "sequence",
            "default",
            "namespaces",
            "elements",
            "wildcards",
            "wrapper",
            # Calculated
            "tokens",
            "list_element",
            "is_text",
            "is_element",
            "is_elements",
            "is_wildcard",
            "is_attribute",
            "is_attributes",
            "namespace_matches",
            "is_clazz_union",
            "local_name",
        )

        def __init__(
            self,
            index: int,
            name: str,
            qname: str,
            types: Sequence[Type],
            clazz: Optional[Type],
            init: bool,
            mixed: bool,
            factory: Optional[Callable],
            tokens_factory: Optional[Callable],
            format: Optional[str],
            derived: bool,
            any_type: bool,
            process_contents: str,
            required: bool,
            nillable: bool,
            sequence: Optional[int],
            default: Any,
            xml_type: str,
            namespaces: Sequence[str],
            elements: Mapping[str, "XmlVar"],
            wildcards: Sequence["XmlVar"],
            wrapper: Optional[str] = None,
            **kwargs: Any,
        ):
            self.index = index
            self.name = name
            self.qname = qname
            self.types = types
            self.clazz = clazz
            self.init = init
            self.mixed = mixed
            self.tokens = tokens_factory is not None
            self.format = format
            self.derived = derived
            self.any_type = any_type
            self.process_contents = process_contents
            self.required = required
            self.nillable = nillable
            self.sequence = sequence
            self.list_element = factory in (list, tuple)
            self.default = default
            self.namespaces = namespaces
            self.elements = elements
            self.wildcards = wildcards
            self.wrapper = wrapper

            self.factory = factory
            self.tokens_factory = tokens_factory

            self.namespace_matches: Optional[Dict[str, bool]] = None

            self.is_clazz_union = self.clazz and len(types) > 1
            self.local_name = local_name(qname)

            self.is_text = False
            self.is_element = False
            self.is_elements = False
            self.is_wildcard = False
            self.is_attribute = False
            self.is_attributes = False

            if xml_type == XmlType.ELEMENTS:
                self.is_elements = True
            elif xml_type == XmlType.ELEMENT or self.clazz:
                self.is_element = True
            elif xml_type == XmlType.ATTRIBUTE:
                self.is_attribute = True
            elif xml_type == XmlType.ATTRIBUTES:
                self.is_attributes = True
            elif xml_type == XmlType.WILDCARD:
                self.is_wildcard = True
            else:
                self.is_text = True

        @property
        def element_types(self) -> Set[Type]:
            return {tp for element in self.elements.values() for tp in element.types}

        def find_choice(self, qname: str) -> Optional["XmlVar"]:
            """Match and return a choice field by its qualified name."""
            match = self.elements.get(qname)
            return match or find_by_namespace(self.wildcards, qname)

        def find_value_choice(self, value: Any, is_class: bool) -> Optional["XmlVar"]:
            """
            Match and return a choice field that matches the given value.

            Cases:
                - value is none or empty tokens list: look for a nillable choice
                - value is a dataclass: look for exact type or a subclass
                - value is primitive: test value against the converter
            """
            is_tokens = collections.is_array(value)
            if value is None or (not value and is_tokens):
                return self.find_nillable_choice(is_tokens)

            if is_class:
                return self.find_clazz_choice(type(value))

            return self.find_primitive_choice(value, is_tokens)

        def find_nillable_choice(self, is_tokens: bool) -> Optional["XmlVar"]:
            return collections.first(
                element
                for element in self.elements.values()
                if element.nillable and is_tokens == element.tokens
            )

        def find_clazz_choice(self, tp: Type) -> Optional["XmlVar"]:
            derived = None
            for element in self.elements.values():
                if element.clazz:
                    if tp in element.types:
                        return element

                    if derived is None and any(issubclass(tp, t) for t in element.types):
                        derived = element

            return derived

        def find_primitive_choice(self, value: Any, is_tokens: bool) -> Optional["XmlVar"]:
            tp = type(value) if not is_tokens else type(value[0])
            for element in self.elements.values():
                if (element.any_type or element.clazz) or element.tokens != is_tokens:
                    continue

                if tp in element.types:
                    return element

                if is_tokens and all(converter.test(val, element.types) for val in value):
                    return element

                if converter.test(value, element.types):
                    return element

            return None

        def is_optional(self, value: Any) -> bool:
            """Return whether this var instance is not required and the given value
            matches the default one."""
            if self.required:
                return False

            if callable(self.default):
                return self.default() == value
            return self.default == value

        def match_namespace(self, qname: str) -> bool:
            """Match the given qname to the wildcard allowed namespaces."""
            if self.namespace_matches is None:
                self.namespace_matches = {}

            matches = self.namespace_matches.get(qname)
            if matches is None:
                matches = self._match_namespace(qname)
                self.namespace_matches[qname] = matches

            return matches

        def _match_namespace(self, qname: str) -> bool:
            uri = target_uri(qname)
            if not self.namespaces and uri is None:
                return True

            for check in self.namespaces:
                if (
                    (not check and uri is None)
                    or check == uri
                    or check == NamespaceType.ANY_NS
                    or (check and check[0] == "!" and check[1:] != uri)
                ):
                    return True

            return False

    get_index = operator.attrgetter("index")

    class XmlMeta(MetaMixin):
        """
        Class binding metadata.

        :param clazz: The dataclass type
        :param qname: The namespace qualified name.
        :param target_qname: The target namespace qualified name.
        :param nillable: Specifies whether an explicit empty value can be
            assigned.
        :param mixed_content: Has a wildcard with mixed flag enabled
        :param text: Text var
        :param choices: List of compound vars
        :param elements: Mapping of qname-element vars
        :param wildcards: List of wildcard vars
        :param attributes: Mapping of qname-attribute vars
        :param any_attributes: List of wildcard attributes vars
        """

        __slots__ = (
            "clazz",
            "qname",
            "target_qname",
            "nillable",
            "text",
            "choices",
            "elements",
            "wildcards",
            "attributes",
            "any_attributes",
            "wrappers",
            # Calculated
            "namespace",
            "mixed_content",
        )

        def __init__(
            self,
            clazz: Type,
            qname: str,
            target_qname: Optional[str],
            nillable: bool,
            text: Optional[XmlVar],
            choices: Sequence[XmlVar],
            elements: Mapping[str, Sequence[XmlVar]],
            wildcards: Sequence[XmlVar],
            attributes: Mapping[str, XmlVar],
            any_attributes: Sequence[XmlVar],
            wrappers: Mapping[str, Sequence[XmlVar]],
            **kwargs: Any,
        ):
            self.clazz = clazz
            self.qname = qname
            self.namespace = target_uri(qname)
            self.target_qname = target_qname
            self.nillable = nillable
            self.text = text
            self.choices = choices
            self.elements = elements
            self.wildcards = wildcards
            self.attributes = attributes
            self.any_attributes = any_attributes
            self.mixed_content = any(wildcard.mixed for wildcard in self.wildcards)
            self.wrappers = wrappers

        @property
        def element_types(self) -> Set[Type]:
            return {
                tp
                for elements in self.elements.values()
                for element in elements
                for tp in element.types
            }

        def get_element_vars(self) -> List[XmlVar]:
            result = list(
                itertools.chain(self.wildcards, self.choices, *self.elements.values())
            )
            if self.text:
                result.append(self.text)

            return sorted(result, key=get_index)

        def get_attribute_vars(self) -> List[XmlVar]:
            result = itertools.chain(self.any_attributes, self.attributes.values())
            return sorted(result, key=get_index)

        def get_all_vars(self) -> List[XmlVar]:
            result = list(
                itertools.chain(
                    self.wildcards,
                    self.choices,
                    self.any_attributes,
                    self.attributes.values(),
                    *self.elements.values(),
                )
            )
            if self.text:
                result.append(self.text)

            return sorted(result, key=get_index)

        def find_attribute(self, qname: str) -> Optional[XmlVar]:
            return self.attributes.get(qname)

        def find_any_attributes(self, qname: str) -> Optional[XmlVar]:
            return find_by_namespace(self.any_attributes, qname)

        def find_wildcard(self, qname: str) -> Optional[XmlVar]:
            """Match the given qualified name to a wildcard and optionally to one
            of its choice elements."""
            wildcard = find_by_namespace(self.wildcards, qname)

            if wildcard and wildcard.elements:
                choice = wildcard.find_choice(qname)
                if choice:
                    return choice

            return wildcard

        def find_any_wildcard(self) -> Optional[XmlVar]:
            if self.wildcards:
                return self.wildcards[0]

            return None

        def find_children(self, qname: str) -> Iterator[XmlVar]:
            elements = self.elements.get(qname)
            if elements:
                yield from elements

            for choice in self.choices:
                match = choice.find_choice(qname)
                if match:
                    yield match

            chd = self.find_wildcard(qname)
            if chd:
                yield chd

    def find_by_namespace(xml_vars: Sequence[XmlVar], qname: str) -> Optional[XmlVar]:
        for xml_var in xml_vars:
            if xml_var.match_namespace(qname):
                return xml_var

        return None