########################################################################
# File name: model.py
# This file is part of: aioxmpp
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
"""
:mod:`aioxmpp.xso.model` --- Declarative-style XSO definition
#############################################################

See :mod:`aioxmpp.xso` for documentation.
"""
import abc
import collections
import copy
import enum
import logging
import sys
import xml.sax.handler

import lxml.sax

import sortedcollections

import multidict  # get it from PyPI

from enum import Enum

from aioxmpp.utils import etree, namespaces

from . import query as xso_query
from . import types as xso_types
from . import tag_to_str, normalize_tag
from .. import structs


logger = logging.getLogger(__name__)


class UnknownChildPolicy(Enum):
    """
    Describe the event which shall take place whenever a child element is
    encountered for which no descriptor can be found to parse it.

    .. attribute:: FAIL

       Raise a :class:`ValueError`

    .. attribute:: DROP

       Drop and ignore the element and all of its children

    """

    FAIL = 0
    DROP = 1


class UnknownAttrPolicy(Enum):
    """
    Describe the event which shall take place whenever a XML attribute is
    encountered for which no descriptor can be found to parse it.

    .. attribute:: FAIL

       Raise a :class:`ValueError`

    .. attribute:: DROP

       Drop and ignore the attribute

    """
    FAIL = 0
    DROP = 1


class UnknownTextPolicy(Enum):
    """
    Describe the event which shall take place whenever XML character data is
    encountered on an object which does not support it.

    .. attribute:: FAIL

       Raise a :class:`ValueError`

    .. attribute:: DROP

       Drop and ignore the text

    """
    FAIL = 0
    DROP = 1


class ValidateMode(Enum):
    """
    Control which ways to set a value in a descriptor are passed through a
    validator.

    .. attribute:: FROM_RECV

       Values which are obtained from XML source are validated.

    .. attribute:: FROM_CODE

       Values which are set through attribute access are validated.

    .. attribute:: ALWAYS

       All values, whether set by attribute or obtained from XML source, are
       validated.

    """

    FROM_RECV = 1
    FROM_CODE = 2
    ALWAYS = 3

    @property
    def from_recv(self):
        return self.value & 1

    @property
    def from_code(self):
        return self.value & 2


class UnknownTopLevelTag(ValueError):
    """
    Subclass of :class:`ValueError`. `ev_args` must be the arguments of the
    ``"start"`` event and are stored as the :attr:`ev_args` attribute for
    inspection.

    .. attribute:: ev_args

       The `ev_args` passed to the constructor.

    """

    def __init__(self, msg, ev_args):
        super().__init__(msg + ": {}".format((ev_args[0], ev_args[1])))
        self.ev_args = ev_args


class XSOList(list):
    """
    A :class:`list` subclass; it provides the complete :class:`list` interface
    with the addition of the following methods:

    .. automethod:: filter

    .. automethod:: filtered

    In the future, methods to add indices to :class:`XSOList` instances may be
    added; right now, there is no need for the huge complexity which would
    arise from keeping the indices up-to-date with changes in the elements
    attributes.
    """

    def _filter_type(self, chained_results, type_):
        return (obj for obj in chained_results if isinstance(obj, type_))

    def _filter_lang(self, chained_results, lang):
        # first, filter on availability of the "lang" attribute
        result = [item
                  for item in chained_results
                  if hasattr(item, "lang") and item.lang is not None]

        # get a sorted list of all languages in the current result set
        languages = sorted(
            {item.lang for item in result}
        )

        if not languages:
            # no languages -> no results
            result = iter([])
        else:
            # lookup a matching language
            if isinstance(lang, structs.LanguageRange):
                lang = [lang]
            else:
                lang = list(lang)
            match = structs.lookup_language(languages, lang)
            # no language? fallback is using the first one
            if match is None:
                match = languages[0]
            result = (item for item in result if item.lang == match)

        return result

    def _filter_attrs(self, chained_results, attrs):
        result = chained_results
        for key, value in attrs.items():
            result = (item for item in result
                      if hasattr(item, key) and getattr(item, key) == value)
        return result

    def filter(self, *, type_=None, lang=None, attrs={}):
        """
        Return an iterable which produces a sequence of the elements inside
        this :class:`XSOList`, filtered by the criteria given as arguments. The
        function starts with a working sequence consisting of the whole list.

        If `type_` is not :data:`None`, elements which are not an instance of
        the given type are excluded from the working sequence.

        If `lang` is not :data:`None`, it must be either a
        :class:`~.structs.LanguageRange` or an iterable of language ranges. The
        set of languages present among the working sequence is determined and
        used for a call to
        :class:`~.structs.lookup_language`. If the lookup returns a language,
        all elements whose :attr:`lang` is different from that value are
        excluded from the working sequence.

        .. note::

           If an iterable of language ranges is given, it is evaluated into a
           list. This may be of concern if a huge iterable is about to be used
           for language ranges, but it is an requirement of the
           :class:`~.structs.lookup_language` function which is used under the
           hood.

        .. note::

           Filtering by language assumes that the elements have a
           :class:`~aioxmpp.xso.LangAttr` descriptor named ``lang``.

        If `attrs` is not empty, the filter iterates over each `key`-`value`
        pair. For each iteration, all elements which do not have an attribute
        of the name in `key` or where that attribute has a value not equal to
        `value` are excluded from the working sequence.

        In general, the iterable returned from :meth:`filter` can only be used
        once. It is dynamic in the sense that changes to elements which are in
        the list *behind* the last element returned from the iterator will
        still be picked up when the iterator is resumed.
        """
        result = self
        if type_ is not None:
            result = self._filter_type(result, type_)
        if lang is not None:
            result = self._filter_lang(result, lang)
        if attrs:
            result = self._filter_attrs(result, attrs)
        return result

    def filtered(self, *, type_=None, lang=None, attrs={}):
        """
        This method is a convencience wrapper around :meth:`filter` which
        evaluates the result into a list and returns that list.
        """
        return list(self.filter(type_=type_, lang=lang, attrs=attrs))


class PropBaseMeta(type):
    def __instancecheck__(self, instance):
        if (isinstance(instance, xso_query.BoundDescriptor) and
                super().__instancecheck__(instance.xq_descriptor)):
            return True
        return super().__instancecheck__(instance)


class _PropBase(metaclass=PropBaseMeta):
    class NO_DEFAULT:
        def __repr__(self):
            return "<no default>"

        def __bool__(self):
            raise TypeError("cannot convert {!r} to bool".format(self))

    NO_DEFAULT = NO_DEFAULT()

    class __INCOMPLETE:
        def __repr__(self):
            return "<incomplete value>"

        def __bool__(self):
            raise TypeError("cannot convert {!r} to bool".format(self))

    __INCOMPLETE = __INCOMPLETE()

    def __init__(self, default=NO_DEFAULT,
                 *,
                 validator=None,
                 validate=ValidateMode.FROM_RECV):
        super().__init__()
        self.default = default
        self.validate = validate
        self.validator = validator

    def _set(self, instance, value):
        instance._xso_contents[self] = value

    def __set__(self, instance, value):
        if     (self.default != value and
                self.validate.from_code and
                self.validator and
                not self.validator.validate(value)):
            raise ValueError("invalid value")
        self._set(instance, value)

    def _set_from_code(self, instance, value):
        self.__set__(instance, value)

    def _set_from_recv(self, instance, value):
        if     (self.default != value and
                self.validate.from_recv and
                self.validator and
                not self.validator.validate(value)):
            raise ValueError("invalid value")
        self._set(instance, value)

    def __get__(self, instance, type_):
        if instance is None:
            return xso_query.BoundDescriptor(
                type_,
                self,
                xso_query.GetDescriptor,
            )
        try:
            value = instance._xso_contents[self]
        except KeyError:
            if self.default is self.NO_DEFAULT:
                raise AttributeError(
                    "attribute is unset ({} on instance of {})".format(
                        self, type_)
                ) from None
            return self.default
        if value is self.__INCOMPLETE:
            raise AttributeError(
                "attribute value is incomplete"
            )
        return value

    def mark_incomplete(self, instance):
        instance._xso_contents[self] = self.__INCOMPLETE

    def validate_contents(self, instance):
        try:
            self.__get__(instance, type(instance))
        except AttributeError as exc:
            raise ValueError(str(exc)) from None

    def to_node(self, instance, parent):
        handler = lxml.sax.ElementTreeContentHandler(
            makeelement=parent.makeelement)
        handler.startDocument()
        handler.startElementNS((None, "_"), None, {})
        self.to_sax(instance, handler)

        parent.extend(handler.etree.getroot())


class _TypedPropBase(_PropBase):
    def __init__(self, *,
                 type_=xso_types.String(),
                 erroneous_as_absent=False,
                 **kwargs):
        super().__init__(**kwargs)
        self.type_ = type_
        self.erroneous_as_absent = erroneous_as_absent

    def __set__(self, instance, value):
        if self.default is self.NO_DEFAULT or value != self.default:
            value = self.type_.coerce(value)
        super().__set__(instance, value)


class Text(_TypedPropBase):
    """
    When assigned to a class’ attribute, it collects all character data of the
    XML element.

    Note that this destroys the relative ordering of child elements and
    character data pieces. This is known and a WONTFIX, as it is not required
    in XMPP to keep that relative order: Elements either have character data
    *or* other elements as children.

    The `type_`, `validator`, `validate`, `default` and `erroneous_as_absent`
    arguments behave like in :class:`Attr`.

    .. automethod:: from_value

    .. automethod:: to_sax

    """

    def from_value(self, instance, value):
        """
        Convert the given value using the set `type_` and store it into
        `instance`’ attribute.
        """
        try:
            parsed = self.type_.parse(value)
        except (TypeError, ValueError):
            if self.erroneous_as_absent:
                return False
            raise
        self._set_from_recv(instance, parsed)
        return True

    def to_sax(self, instance, dest):
        """
        Assign the formatted value stored at `instance`’ attribute to the text
        of `el`.

        If the `value` is :data:`None`, no text is generated.
        """
        value = self.__get__(instance, type(instance))
        if value is None:
            return
        dest.characters(self.type_.format(value))


class _ChildPropBase(_PropBase):
    """
    This is a base class for descriptors related to child :class:`XSO`
    instances.

    It provides a few implementation parts shared between :class:`Child`,
    :class:`ChildList` and :class:`ChildMap`.
    """

    def __init__(self, classes, default=None):
        super().__init__(default)
        self._classes = set()
        self._tag_map = {}

        for cls in classes:
            self._register(cls)

    def _process(self, instance, ev_args, ctx):
        cls = self._tag_map[ev_args[0], ev_args[1]]
        return (yield from cls.parse_events(ev_args, ctx))

    def get_tag_map(self):
        """
        Return a dictionary mapping the tags of the supported classes to the
        classes themselves. Can be used to obtain a set of supported tags.
        """
        return self._tag_map

    def _register(self, cls):
        if cls.TAG in self._tag_map:
            raise ValueError("ambiguous children: {} and {} share the same "
                             "TAG".format(
                                 self._tag_map[cls.TAG],
                                 cls))
        self._tag_map[cls.TAG] = cls
        self._classes.add(cls)


class Child(_ChildPropBase):
    """
    When assigned to a class’ attribute, it collects any child which matches
    any :attr:`XSO.TAG` of the given `classes`.

    :param strict: Enable strict type checking on assigned values.
    :type strict: :class:`bool`

    The tags among the `classes` must be unique, otherwise :class:`ValueError`
    is raised on construction.

    Instead of the `default` argument like supplied by :class:`Attr`,
    :class:`Child` only supports `required`: if `required` is a false value
    (the default), a missing child is tolerated and :data:`None` is valid value
    for the described attribute. Otherwise, a missing matching child is an
    error and the attribute cannot be set to :data:`None`.

    If `strict` is true, only instances of the exact classes registered with
    the descriptor can be assigned to it. Subclasses of the registered classes
    also need to be registered explicitly to be allowed as types for values.

    .. automethod:: get_tag_map

    .. automethod:: from_events

    .. automethod:: to_sax
    """

    def __init__(self, classes, required=False, strict=False):
        super().__init__(
            classes,
            default=_PropBase.NO_DEFAULT if required else None
        )
        self.__strict = strict

    @property
    def required(self):
        return self.default is _PropBase.NO_DEFAULT

    @property
    def strict(self):
        return self.__strict

    def __set__(self, instance, value):
        if value is None and self.required:
            raise ValueError("cannot set required member to None")
        if (self.__strict and
                value is not None and
                type(value) not in self._classes):
            raise TypeError("{!r} object is not a valid value".format(
                type(value)
            ))
        super().__set__(instance, value)

    def __delete__(self, instance):
        if self.required:
            raise AttributeError("cannot delete required member")
        try:
            del instance._xso_contents[self]
        except KeyError:
            pass

    def from_events(self, instance, ev_args, ctx):
        """
        Detect the object to instanciate from the arguments `ev_args` of the
        ``"start"`` event. The new object is stored at the corresponding
        descriptor attribute on `instance`.

        This method is suspendable.
        """
        obj = yield from self._process(instance, ev_args, ctx)
        self.__set__(instance, obj)
        return obj

    def validate_contents(self, instance):
        try:
            obj = self.__get__(instance, type(instance))
        except AttributeError:
            raise ValueError("missing required member")
        if obj is not None:
            obj.validate()

    def to_sax(self, instance, dest):
        """
        Take the object associated with this descriptor on `instance` and
        serialize it as child into the given :class:`lxml.etree.Element`
        `parent`.

        If the object is :data:`None`, no content is generated.
        """
        obj = self.__get__(instance, type(instance))
        if obj is None:
            return
        obj.unparse_to_sax(dest)


class ChildList(_ChildPropBase):
    """
    The :class:`ChildList` works like :class:`Child`, with two key differences:

    * multiple children which are matched by this descriptor get collected into
      an :class:`~aioxmpp.xso.model.XSOList`.
    * the default is fixed at an empty list.
    * `required` is not supported

    .. automethod:: from_events

    .. automethod:: to_sax
    """

    def __init__(self, classes):
        super().__init__(classes)

    def __get__(self, instance, type_):
        if instance is None:
            return xso_query.BoundDescriptor(
                type_,
                self,
                xso_query.GetSequenceDescriptor,
            )

        return instance._xso_contents.setdefault(self, XSOList())

    def _set(self, instance, value):
        if not isinstance(value, list):
            raise TypeError("expected list, but found {}".format(type(value)))
        return super()._set(instance, value)

    def from_events(self, instance, ev_args, ctx):
        """
        Like :meth:`.Child.from_events`, but instead of replacing the attribute
        value, the new object is appended to the list.
        """

        obj = yield from self._process(instance, ev_args, ctx)
        self.__get__(instance, type(instance)).append(obj)
        return obj

    def validate_contents(self, instance):
        for child in self.__get__(instance, type(instance)):
            child.validate()

    def to_sax(self, instance, dest):
        """
        Like :meth:`.Child.to_node`, but instead of serializing a single
        object, all objects in the list are serialized.
        """

        for obj in self.__get__(instance, type(instance)):
            obj.unparse_to_sax(dest)


class Collector(_PropBase):
    """
    When assigned to a class’ attribute, it collects all children which are not
    known to any other descriptor into an XML tree. The root node has the tag
    of the XSO class it pertains to.

    The default is fixed to the emtpy root node.

    .. versionchanged:: 0.10

       Before the subtrees were collected in a list. This was changed to an
       ElementTree to allow using XPath over all collected elements. Most code
       should not be affected by this, since the interface is very similar.
       Assignment is now forbidden. Use ``[:] = `` instead.

    .. automethod:: from_events

    .. automethod:: to_sax
    """

    def __init__(self):
        super().__init__(default=[])

    def __get__(self, instance, type_):
        if instance is None:
            return xso_query.BoundDescriptor(
                type_,
                self,
                xso_query.GetSequenceDescriptor,
            )

        try:
            return instance._xso_contents[self]
        except KeyError:
            res = etree.Element(tag_to_str(instance.TAG))
            instance._xso_contents[self] = res
            return res

    def _set(self, instance, value):
        raise AttributeError("Collector attribute cannot be assigned to")

    def from_events(self, instance, ev_args, ctx):
        """
        Collect the events and convert them to a single XML subtree, which then
        gets appended to the list at `instance`. `ev_args` must be the
        arguments of the ``"start"`` event of the new child.

        This method is suspendable.
        """

        # goal: collect all elements starting with the element for which we got
        # the start-ev_args in a lxml.etree.Element.

        def make_from_args(ev_args, parent):
            el = etree.SubElement(parent,
                                  tag_to_str((ev_args[0], ev_args[1])))
            for key, value in ev_args[2].items():
                el.set(tag_to_str(key), value)
            return el

        root_el = make_from_args(ev_args,
                                 self.__get__(instance, type(instance)))
        # create an element stack
        stack = [root_el]
        while stack:
            # we get send all sax-ish events until we return. we return when
            # the stack is empty, i.e. when our top element ended.
            ev_type, *ev_args = yield
            if ev_type == "start":
                # new element started, create and push to stack
                stack.append(make_from_args(ev_args, stack[-1]))
            elif ev_type == "text":
                # text for current element
                curr = stack[-1]
                if curr.text is not None:
                    curr.text += ev_args[0]
                else:
                    curr.text = ev_args[0]
            elif ev_type == "end":
                # element ended, remove from stack (it is already appended to
                # the current element)
                stack.pop()
            else:
                # not in coverage -- this is more like an assertion
                raise ValueError(ev_type)

    def to_sax(self, instance, dest):
        for node in self.__get__(instance, type(instance)):
            lxml.sax.saxify(node, _CollectorContentHandlerFilter(dest))


class Attr(Text):
    """
    Descriptor which represents an XML attribute.

    When assigned to a class’ attribute, it binds that attribute to the XML
    attribute with the given `tag`. `tag` must be a valid input to
    :func:`normalize_tag`.

    The following arguments occur at several of the descriptor classes, and are
    all available at :class:`Attr`.

    :param type_: A character data type to interpret the XML character data.
    :type type_: :class:`~.xso.AbstractCDataType`
    :param validator: An object which has a :meth:`validate` method. That
        method receives a value which was either assigned to the property
        (depending on the `validate` argument) or parsed from XML (after it
        passed through `type_`).
    :param validate: A value from the :class:`ValidateMode` enum, which defines
        which values have to pass through the validator. At some points it
        makes sense to only validate outgoing values, but be liberal with
        incoming values. This defaults to :attr:`ValidateMode.FROM_RECV`.
    :param default: The value which the attribute has if no value has been
        assigned. This must be given to allow the attribute to be missing. It
        defaults to a special value. If the attribute has not been assigned to
        and `default` has not been set, accessing the attribute for reading
        raises :class:`AttributeError`. An attribute with `default` value
        is not emitted in the output.
    :param missing: A callable which takes a :class:`Context` instance. It is
        called whenever the attribute is missing (independent from the fact
        whether it is required or not). The callable shall return a
        not-:data:`None` value for the attribute to use. If the value is
        :data:`None`, the usual handling of missing attributes takes place.
    :param erroneous_as_absent: Treat an erroneous value (= the `type_` raises
        :class:`ValueError` or :class:`TypeError` while parsing) as if the
        attribute was not present. Note that this is almost never the right
        thing to do in XMPP.

    .. seealso::

       :class:`LangAttr`, which is a subclass of :class:`Attr` specialized for
       describing ``xml:lang`` attributes.

    .. note::

       The `default` argument does not need to comply with either `type_` or
       `validator`. This can be used to convey meaning with the absence of the
       attribute. Note that assigning the default value is not possible if it
       does not comply with `type_` or `validator` and the ``del`` operator
       must be used instead.

    .. automethod:: from_value

    .. automethod:: handle_missing

    .. automethod:: to_dict

    """

    def __init__(self, tag, *,
                 type_=xso_types.String(),
                 missing=None,
                 **kwargs):
        super().__init__(type_=type_, **kwargs)
        self.tag = normalize_tag(tag)
        self.missing = missing

    def __set__(self, instance, value):
        super().__set__(instance, value)

    def __delete__(self, instance):
        try:
            del instance._xso_contents[self]
        except KeyError:
            pass

    def handle_missing(self, instance, ctx):
        """
        Handle a missing attribute on `instance`. This is called whenever no
        value for the attribute is found during parsing. The call to
        :meth:`missing` is independent of the value of `required`.

        If the `missing` callback is not :data:`None`, it is called with the
        `instance` and the `ctx` as arguments. If the returned value is not
        :data:`None`, it is used as the value of the attribute (validation
        takes place as if the value had been set from the code, not as if the
        value had been received from XML) and the handler returns.

        If the `missing` callback is :data:`None` or returns :data:`None`, the
        handling continues as normal: if `required` is true, a
        :class:`ValueError` is raised.
        """
        if self.missing is not None:
            value = self.missing(instance, ctx)
            if value is not None:
                self._set_from_code(instance, value)
                return

        if self.default is _PropBase.NO_DEFAULT:
            raise ValueError("missing attribute {} on {}".format(
                tag_to_str(self.tag),
                tag_to_str(instance.TAG),
            ))

        # no need to set explicitly, it will be handled by _PropBase.__get__

    def validate_contents(self, instance):
        try:
            self.__get__(instance, type(instance))
        except AttributeError:
            raise ValueError("non-None value required for {}".format(
                tag_to_str(self.tag)
            )) from None

    def to_dict(self, instance, d):
        """
        Override the implementation from :class:`Text` by storing the formatted
        value in the XML attribute instead of the character data.

        If the value is :data:`None`, no element is generated.
        """

        value = self.__get__(instance, type(instance))
        if value == self.default:
            return

        d[self.tag] = self.type_.format(value)


class LangAttr(Attr):
    """
    An attribute representing the ``xml:lang`` attribute, including inheritance
    semantics.

    This is a subclass of :class:`Attr` which takes care of inheriting the
    ``xml:lang`` value of the parent. The `tag` is set to the
    ``(namespaces.xml, "lang")`` value to match ``xml:lang`` attributes.
    `type_` is a :class:`xso.LanguageTag` instance and `missing` is set to
    :func:`lang_attr`.

    Note that :class:`LangAttr` overrides `default` to be :data:`None` by
    default.
    """

    def __init__(self, default=None, **kwargs):
        super().__init__(
            (namespaces.xml, "lang"),
            default=default,
            type_=xso_types.LanguageTag(),
            missing=lang_attr
        )


class ChildText(_TypedPropBase):
    """
    Represents the character data of a child element.

    When assigned to a class’ attribute, it binds that attribute to the XML
    character data of a child element with the given `tag`. `tag` must be a
    valid input to :func:`normalize_tag`.

    :param child_policy: The policy to apply when children are found in the
        child element whose text this descriptor represents.
    :type child_policy: :class:`UnknownChildPolicy`
    :param attr_policy: The policy to apply when attributes are found at the
        child element whose text this descriptor represents.
    :type attr_policy: :class:`UnknownAttrPolicy`

    The `type_`, `validate`, `validator`, `default` and `erroneous_as_absent`
    arguments behave like in :class:`Attr`.

    `declare_prefix` works as for :class:`ChildTag`.

    `child_policy` and `attr_policy` describe how the parser behaves when an
    unknown child or attribute (respectively) is encountered on the child
    element whose text this descriptor represents. See
    :class:`UnknownChildPolicy` and :class:`UnknownAttrPolicy` for the possible
    behaviours.

    .. automethod:: get_tag_map

    .. automethod:: from_events

    .. automethod:: to_sax

    """

    def __init__(self, tag,
                 *,
                 child_policy=UnknownChildPolicy.FAIL,
                 attr_policy=UnknownAttrPolicy.FAIL,
                 declare_prefix=False,
                 **kwargs):
        super().__init__(**kwargs)
        self.tag = normalize_tag(tag)
        self.child_policy = child_policy
        self.attr_policy = attr_policy
        self.declare_prefix = declare_prefix

    def get_tag_map(self):
        """
        Return an iterable yielding :attr:`tag`.

        This is for compatiblity with the :class:`Child` interface.
        """
        return {self.tag}

    def from_events(self, instance, ev_args, ctx):
        """
        Starting with the element to which the start event information in
        `ev_args` belongs, parse text data. If any children are encountered,
        :attr:`child_policy` is enforced (see
        :class:`UnknownChildPolicy`). Likewise, if the start event contains
        attributes, :attr:`attr_policy` is enforced
        (c.f. :class:`UnknownAttrPolicy`).

        The extracted text is passed through :attr:`type_` and
        :attr:`validator` and if it passes, stored in the attribute on the
        `instance` with which the property is associated.

        This method is suspendable.
        """

        # goal: take all text inside the child element and collect it as
        # attribute value

        attrs = ev_args[2]
        if attrs and self.attr_policy == UnknownAttrPolicy.FAIL:
            raise ValueError("unexpected attribute (at text only node)")
        parts = []
        while True:
            ev_type, *ev_args = yield
            if ev_type == "text":
                # collect ALL TEH TEXT!
                parts.append(ev_args[0])
            elif ev_type == "start":
                # ok, a child inside the child was found, we look at our policy
                # to see what to do
                yield from enforce_unknown_child_policy(
                    self.child_policy,
                    ev_args)
            elif ev_type == "end":
                # end of our element, return
                break

        joined = "".join(parts)
        try:
            parsed = self.type_.parse(joined)
        except (ValueError, TypeError):
            if self.erroneous_as_absent:
                return
            raise
        self._set_from_recv(instance, parsed)

    def to_sax(self, instance, dest):
        """
        Create a child node at `parent` with the tag :attr:`tag`. Set the text
        contents to the value of the attribute which this descriptor represents
        at `instance`.

        If the value is :data:`None`, no element is generated.
        """

        value = self.__get__(instance, type(instance))
        if value == self.default:
            return

        if self.declare_prefix is not False and self.tag[0]:
            dest.startPrefixMapping(self.declare_prefix, self.tag[0])
        dest.startElementNS(self.tag, None, {})
        try:
            dest.characters(self.type_.format(value))
        finally:
            dest.endElementNS(self.tag, None)
            if self.declare_prefix is not False and self.tag[0]:
                dest.endPrefixMapping(self.declare_prefix)


class ChildMap(_ChildPropBase):
    """
    Represents a subset of the children of an XML element, as map.

    The :class:`ChildMap` class works like :class:`ChildList`, but instead of
    storing the child objects in a list, they are stored in a map which
    contains an :class:`~aioxmpp.xso.model.XSOList` of objects for each tag.

    `key` may be callable. If it is given, it is used while parsing to
    determine the dictionary key under which a newly parsed XSO will be
    put. For that, the `key` callable is called with the newly parsed XSO as
    the only argument and is expected to return the key.

    .. automethod:: from_events

    .. automethod:: to_sax

    The following utility function is useful when filling data into descriptors
    using this class:

    .. automethod:: fill_into_dict

    """

    def __init__(self, classes, *, key=None):
        super().__init__(classes)
        self.key = key or (lambda obj: obj.TAG)

    def __get__(self, instance, type_):
        if instance is None:
            return xso_query.BoundDescriptor(
                type_,
                self,
                xso_query.GetMappingDescriptor,
            )

        return instance._xso_contents.setdefault(
            self,
            collections.defaultdict(XSOList)
        )

    def __set__(self, instance, value):
        raise AttributeError("ChildMap attribute cannot be assigned to")

    def _set(self, instance, value):
        if not isinstance(value, dict):
            raise TypeError("expected dict, but found {}".format(type(value)))
        return super()._set(instance, value)

    def fill_into_dict(self, items, dest):
        """
        Take an iterable of `items` and group it into the given `dest` dict,
        using the :attr:`key` function.

        The `dest` dict must either already contain the keys which are
        generated by the :attr:`key` function for the items in `items`, or must
        default them suitably. The values of the affected keys must be
        sequences or objects with an :meth:`append` method which does what you
        want it to do.
        """
        for item in items:
            dest[self.key(item)].append(item)

    def from_events(self, instance, ev_args, ctx):
        """
        Like :meth:`.ChildList.from_events`, but the object is appended to the
        list associated with its tag in the dict.
        """

        tag = ev_args[0], ev_args[1]
        cls = self._tag_map[tag]
        obj = yield from cls.parse_events(ev_args, ctx)
        mapping = self.__get__(instance, type(instance))
        mapping[self.key(obj)].append(obj)

    def validate_contents(self, instance):
        mapping = self.__get__(instance, type(instance))
        for objects in mapping.values():
            for obj in objects:
                obj.validate()

    def to_sax(self, instance, dest):
        """
        Serialize all objects in the dict associated with the descriptor at
        `instance` to the given `parent`.

        The order of elements within a tag is preserved; the order of the tags
        relative to each other is undefined.
        """

        for items in self.__get__(instance, type(instance)).values():
            for obj in items:
                obj.unparse_to_sax(dest)


class ChildLangMap(ChildMap):
    """
    Represents a subset of the children of an XML element, as map.

    The :class:`ChildLangMap` class is a specialized version of the
    :class:`ChildMap`, which uses a `key` function to group the children by
    their XML language tag.

    It is expected that the language tag is available as ``lang`` attribute on
    the objects stored in this map.
    """

    @staticmethod
    def _lang_key(obj):
        return obj.lang

    def __init__(self, classes, **kwargs):
        super().__init__(classes, key=self._lang_key, **kwargs)


class ChildTag(_PropBase):
    """
    Represents a subset of the children of an XML element, as single value.

    When assigned to a class’ attribute, this descriptor represents the
    presence or absence of a single child with a tag from a given set of valid
    tags.

    `tags` must be an iterable of valid arguments to
    :func:`normalize_tag` or an :class:`enum.Enum` whose values are
    valid arguments to :func:`normalize_tag`. If :func:`normalize_tag`
    returns a false value (such as :data:`None`) as `namespace_uri`,
    it is replaced with `default_ns` (defaulting to :data:`None`,
    which makes this sentence a no-op). This allows a benefit to
    readability if you have many tags which share the same namespace.
    This is, however, not allowed for tags given as enumeration.

    `text_policy`, `child_policy` and `attr_policy` describe the behaviour if
    the child element unexpectedly has text, children or attributes,
    respectively. The default for each is to fail with a :class:`ValueError`.

    If `allow_none` is :data:`True`, assignment of :data:`None` to the
    attribute to which this descriptor belongs is allowed and represents the
    absence of the child element.

    If `declare_prefix` is not :data:`False` (note that :data:`None` is a
    valid, non-:data:`False` value in this context!), the namespace is
    explicitly declared using the given prefix when serializing to SAX.

    .. automethod:: from_events

    .. automethod:: to_sax

    """

    class ConvertEnum:

        def __init__(self, enum_=None):
            self._enum = enum_

        def parse(self, v):
            return self._enum(normalize_tag(v))

        def format(self, v):
            return tag_to_str(v.value)

    class ConvertTag:

        def parse(self, v):
            return normalize_tag(v)

        def format(self, v):
            return tag_to_str(v)

    def __init__(self, tags, *,
                 default_ns=None,
                 text_policy=UnknownTextPolicy.FAIL,
                 child_policy=UnknownChildPolicy.FAIL,
                 attr_policy=UnknownAttrPolicy.FAIL,
                 allow_none=False,
                 declare_prefix=False):

        def normalize_tags(tags):
            return {
                (ns or default_ns, localname)
                for ns, localname in map(normalize_tag, tags)
            }

        if isinstance(tags, type(enum.Enum)):
            self._converter = self.ConvertEnum(tags)
            values = list(tags)
            tags = normalize_tags([tag.value for tag in tags])
        else:
            self._converter = self.ConvertTag()
            tags = normalize_tags(tags)
            values = tags

        super().__init__(
            default=None if allow_none else _PropBase.NO_DEFAULT,
            validator=xso_types.RestrictToSet(values),
            validate=ValidateMode.ALWAYS)

        self.child_map = tags
        self.text_policy = text_policy
        self.attr_policy = attr_policy
        self.child_policy = child_policy
        self.declare_prefix = declare_prefix

    @property
    def allow_none(self):
        return self.default is not _PropBase.NO_DEFAULT

    def get_tag_map(self):
        return self.child_map

    def from_events(self, instance, ev_args, ctx):
        attrs = ev_args[2]
        if attrs and self.attr_policy == UnknownAttrPolicy.FAIL:
            raise ValueError("unexpected attributes")
        tag = ev_args[0], ev_args[1]
        while True:
            ev_type, *ev_args = yield
            if ev_type == "text":
                if self.text_policy == UnknownTextPolicy.FAIL:
                    raise ValueError("unexpected text")
            elif ev_type == "start":
                yield from enforce_unknown_child_policy(
                    self.child_policy,
                    ev_args)
            elif ev_type == "end":
                break
        self._set_from_recv(instance, self._converter.parse(tag))

    def to_sax(self, instance, dest):
        value = self.__get__(instance, type(instance))
        if value is None:
            return

        value = normalize_tag(self._converter.format(value))

        if self.declare_prefix is not False and value[0]:
            dest.startPrefixMapping(self.declare_prefix, value[0])
        dest.startElementNS(value, None, {})
        dest.endElementNS(value, None)
        if self.declare_prefix is not False and value[0]:
            dest.endPrefixMapping(self.declare_prefix)


class ChildFlag(_PropBase):
    """
    Represents the presence of a specific child element, as a boolean flag.

    When used as a :class:`XSO` descriptor, it represents the presence or
    absence of a single child with the given `tag`. The presence or absence is
    represented by the values :data:`True` and :data:`False` respectively.

    `tag` must be a valid tag.

    The default value for attributes using this descriptor is :data:`False`.

    `text_policy`, `child_policy` and `attr_policy` describe the behaviour if
    the child element unexpectedly has text, children or attributes,
    respectively. The default for each is to fail with a :class:`ValueError`.

    If `declare_prefix` is not :data:`False` (note that :data:`None` is a
    valid, non-:data:`False` value in this context!), the namespace is
    explicitly declared using the given prefix when serializing to SAX.
    """

    def __init__(self, tag,
                 text_policy=UnknownTextPolicy.FAIL,
                 child_policy=UnknownChildPolicy.FAIL,
                 attr_policy=UnknownAttrPolicy.FAIL,
                 declare_prefix=False):
        super().__init__(
            default=False,
        )
        self.tag = normalize_tag(tag)
        self.text_policy = text_policy
        self.attr_policy = attr_policy
        self.child_policy = child_policy
        self.declare_prefix = declare_prefix

    def get_tag_map(self):
        return {self.tag}

    def from_events(self, instance, ev_args, ctx):
        attrs = ev_args[2]
        if attrs and self.attr_policy == UnknownAttrPolicy.FAIL:
            raise ValueError("unexpected attributes")
        while True:
            ev_type, *ev_args = yield
            if ev_type == "text":
                if self.text_policy == UnknownTextPolicy.FAIL:
                    raise ValueError("unexpected text")
            elif ev_type == "start":
                yield from enforce_unknown_child_policy(
                    self.child_policy,
                    ev_args)
            elif ev_type == "end":
                break
        self._set_from_recv(instance, True)

    def to_sax(self, instance, dest):
        value = self.__get__(instance, type(instance))
        if not value:
            return

        if self.declare_prefix is not False and self.tag[0]:
            dest.startPrefixMapping(self.declare_prefix, self.tag[0])
        dest.startElementNS(self.tag, None, {})
        dest.endElementNS(self.tag, None)
        if self.declare_prefix is not False and self.tag[0]:
            dest.endPrefixMapping(self.declare_prefix)


class ChildValueList(_ChildPropBase):
    """
    Represents a subset of the child elements as values. The value
    representation is governed by the `type_` argument.

    :param type_: Type describing the subtree to convert to python values.
    :type type_: :class:`~.xso.AbstractElementType`
    :param container_type: Type of the container to use.
    :type container_type: Subclass of :class:`~collections.abc.MutableSequence`
        or :class:`~collections.abc.MutableSet`

    This descriptor parses the XSO classes advertised by the `type_` (via
    :meth:`~.AbstractElementType.get_xso_types`) and exposes the unpacked
    values in a container.

    The optional `container_type` argument must, if given, be a callable which
    returns a mutable container supporting either :meth:`add` or :meth:`append`
    of the values used with the `type_` and iteration. It will be used instead
    of :class:`list` to create the values for the descriptor.

    .. versionadded:: 0.5
    """

    def __init__(self, type_, *, container_type=list):
        super().__init__(type_.get_xso_types())
        self.type_ = type_
        self.container_type = container_type
        try:
            self._add = container_type.append
        except AttributeError:
            self._add = container_type.add

    def __get__(self, instance, type_):
        if instance is None:
            return xso_query.BoundDescriptor(
                type_,
                self,
                xso_query.GetSequenceDescriptor,
                expr_kwargs={"sequence_factory": self.container_type},
            )

        try:
            return instance._xso_contents[self]
        except KeyError:
            result = self.container_type()
            instance._xso_contents[self] = result
            return result

    def __set__(self, instance, value):
        raise AttributeError("child value list not writable")

    def from_events(self, instance, ev_args, ctx):
        obj = yield from self._process(instance, ev_args, ctx)
        value = self.type_.unpack(obj)
        self._add(self.__get__(instance, type(instance)), value)

    def to_sax(self, instance, dest):
        for value in self.__get__(instance, type(instance)):
            self.type_.pack(value).unparse_to_sax(dest)


class ChildValueMap(_ChildPropBase):
    """
    A mapping of keys to values, generated by child tags.

    :param type_: Type describing the subtree to convert to pairs of key and
        value.
    :type type_: :class:`~.xso.AbstractElementType`
    :param mapping_type: Type of the mapping to use.
    :type mapping_type: Subclass of :class:`~collections.abc.MutableMapping`

    This works very similar to :class:`ChildValueList`, but instead of a
    mutable sequence, the value of the descriptor is a mutable mapping.

    The `type_` must return key-value pairs from
    :meth:`.xso.AbstractElementType.unpack` and must accept such key-value
    pairs in :meth:`.xso.AbstractElementType.pack`.

    The optional `mapping_type` argument must, if given, be a callable which
    returns a :class:`collections.abc.MutableMapping` supporting the keys and
    values used by the `type_`. It will be used instead of :class:`dict` to
    create the values for the descriptor. A possible use-case is using
    :class:`.structs.LanguageMap` together with :class:`~.xso.TextChildMap`.

    .. seealso::

       :class:`ChildTextMap` for a specialised version to deal with
       :class:`AbstractTextChild` subclasses.

    .. versionadded:: 0.5
    """

    def __init__(self, type_, *, mapping_type=dict):
        super().__init__(type_.get_xso_types())
        self.type_ = type_
        self.mapping_type = mapping_type

    def __get__(self, instance, type_):
        if instance is None:
            return xso_query.BoundDescriptor(
                type_,
                self,
                xso_query.GetMappingDescriptor,
                expr_kwargs={"mapping_factory": self.mapping_type}
            )

        try:
            return instance._xso_contents[self]
        except KeyError:
            result = self.mapping_type()
            instance._xso_contents[self] = result
            return result

    def __set__(self, instance, value):
        raise AttributeError("child value map not writable")

    def to_sax(self, instance, dest):
        for item in self.__get__(instance, type(instance)).items():
            self.type_.pack(item).unparse_to_sax(dest)

    def from_events(self, instance, ev_args, ctx):
        obj = yield from self._process(instance, ev_args, ctx)
        key, value = self.type_.unpack(obj)
        self.__get__(instance, type(instance))[key] = value


class ChildValueMultiMap(_ChildPropBase):
    """
    A mapping of keys to lists of values, representing child tags.

    :param type_: Type describing the subtree to convert to pairs of key and
        value.
    :type type_: :class:`~.xso.AbstractElementType`

    This is very similar to :class:`ChildValueMap`, but it uses a
    :class:`multidict.MultiDict` as storage. Interface-compatible classes can
    be substituted by passing them to `mapping_type`. Candidate for that are
    :class:`multidict.CIMultiDict`.

    .. versionadded:: 0.6
    """

    def __init__(self, type_, *, mapping_type=multidict.MultiDict):
        super().__init__(type_.get_xso_types())
        self.type_ = type_
        self.mapping_type = mapping_type

    def __get__(self, instance, type_):
        if instance is None:
            return xso_query.BoundDescriptor(
                type_,
                self,
                xso_query.GetMappingDescriptor,
                expr_kwargs={"mapping_factory": self.mapping_type},
            )

        try:
            return instance._xso_contents[self]
        except KeyError:
            result = self.mapping_type()
            instance._xso_contents[self] = result
            return result

    def __set__(self, instance, value):
        raise AttributeError("child value multi map not writable")

    def to_sax(self, instance, dest):
        for key, value in self.__get__(instance, type(instance)).items():
            self.type_.pack((key, value)).unparse_to_sax(dest)

    def from_events(self, instance, ev_args, ctx):
        obj = yield from self._process(instance, ev_args, ctx)
        key, value = self.type_.unpack(obj)
        self.__get__(instance, type(instance)).add(key, value)


class ChildTextMap(ChildValueMap):
    """
    A specialised version of :class:`ChildValueMap` which uses
    :class:`TextChildMap` together with :class:`.structs.LanguageMap` to
    convert the :class:`AbstractTextChild` subclass `xso_type` to and from
    a language-text mapping.

    For an example, see :class:`.Message`.
    """

    def __init__(self, xso_type):
        super().__init__(
            xso_types.TextChildMap(xso_type),
            mapping_type=structs.LanguageMap
        )


def _mark_attributes_incomplete(attrs, obj):
    for attr in attrs:
        attr.mark_incomplete(obj)


class XMLStreamClass(xso_query.Class, abc.ABCMeta):
    """
    This metaclass is used to implement the fancy features of :class:`.XSO`
    classes and instances. Its documentation details on some of the
    restrictions and features of XML Stream Classes.

    .. note::

       There should be no need to use this metaclass directly when implementing
       your own XSO classes. Instead, derive from :class:`~.xso.XSO`.

    The following restrictions apply when a class uses the
    :class:`XMLStreamClass` metaclass:

    1. At no point in the inheritance tree there must exist more than one
       distinct :class:`~.xso.Text` descriptor. It is possible to inherit two
       identical text descriptors from several base classes though.

    2. The above applies equivalently for :class:`~.xso.Collector`
       descriptors.

    3. At no point in the inheritance tree there must exist more than one
       :class:`~.xso.Attr` descriptor which handles a given attribute tag. Like
       with :class:`~.xso.Text`, it is allowed that the same
       :class:`~.xso.Attr` descriptor is inherited through multiple paths from
       parent classes.

    4. The above applies likewise for element tags and :class:`~.xso.Child` (or
       similar) descriptors.

    Objects of this metaclass (i.e. classes) have some useful attributes. The
    following attributes are gathered from the namespace of the class, by
    collecting the different XSO-related descriptors:

    .. attribute:: TEXT_PROPERTY

       The :class:`~.xso.Text` descriptor object associated with this
       class. This is :data:`None` if no attribute using that descriptor is
       declared on the class.

    .. attribute:: COLLECTOR_PROPERTY

       The :class:`~.xso.Collector` descriptor object associated with this
       class. This is :data:`None` if no attribute using that descriptor is
       declared on the class.

    .. attribute:: ATTR_MAP

       A dictionary mapping attribute tags to the :class:`~.xso.Attr`
       descriptor objects for these attributes.

    .. attribute:: CHILD_MAP

       A dictionary mapping element tags to the :class:`~.xso.Child` (or
       similar) descriptor objects which accept these child elements.

    .. attribute:: CHILD_PROPS

       A set of all :class:`~.xso.Child` (or :class:`~.xso.ChildList`)
       descriptor objects of this class.

    .. attribute:: DECLARE_NS

       A dictionary which defines the namespace mappings which shall be
       declared when serializing this element. It must map namespace prefixes
       (such as :data:`None` or ``"foo"``) to namespace URIs.

       For maximum compatibility with legacy XMPP implementations (I’m looking
       at you, ejabberd!), :attr:`DECLARE_NS` is set by this metaclass unless
       it is provided explicitly when declaring the class:

       * If no :attr:`TAG` is set, :attr:`DECLARE_NS` is also not set. The
         attribute does not exist on the class in that case, unless it is
         inherited from a base class.

       * If :attr:`TAG` is set and at least one base class has a
         :attr:`DECLARE_NS`, :attr:`DECLARE_NS` is not auto generated, so that
         inheritance can take place.

       * If :attr:`TAG` is set and has a namespace (and no base class has a
         :attr:`DECLARE_NS`), :attr:`DECLARE_NS` is set to
         ``{ None: namespace }``, where ``namespace`` is the namespace of the
         :attr:`TAG`.

       * If :attr:`TAG` is set and does not have a namespace,
         :attr:`DECLARE_NS` is set to the empty dict. This should not occur
         outside testing, and support for tags without namespace might be
         removed in future versions.

       .. warning::

          It is discouraged to use namespace prefixes of the format
          ``"ns{:d}".format(n)``, for any given number `n`. These prefixes are
          reserved for ad-hoc namespace declarations, and attempting to use
          them may have unwanted side-effects.

       .. versionchanged:: 0.4

          The automatic generation of the :attr:`DECLARE_NS` attribute was
          added in 0.4.

    .. attribute:: __slots__

       The metaclass automatically sets this attribute to the empty tuple,
       unless a different value is set in the class or `protect` is passed as
       false to the metaclass.

       Thus, to disable the automatic setting of :attr:`__slots__`, inherit for
       example like this::

         class MyXSO(xso.XSO, protect=False):
             pass

       The rationale for this is that attributes on XSO instances are magic.
       Having a typo in an attribute may fail non-obviously, if it causes an
       entirely different semantic to be invoked at the peer (for example the
       :attr:`.Message.type_` attribute).

       Setting :attr:`__slots__` to empty by default prevents assigning any
       attribute not bound to an descriptor.

       .. seealso::

          :ref:`slots`
             The official Python documentation describes the semantics of the
             :attr:`__slots__` attribute in more detail.

       :class:`~.xso.XSO` automatically sets a sensible :attr:`__slots__`
       (including ``__weakref__``, but not ``__dict__``).

       .. versionadded:: 0.6

       .. note::

          If you need to stay compatible with versions before 0.6 *and* have
          arbitrary attributes writable, the correct way of doing things is to
          explicitly set :attr:`__slots__` to ``("__dict__",)`` in your class.
          You cannot use `protect` because it is not known in pre-0.6 versions.

    .. note::

       :class:`~.xso.XSO` defines defaults for more attributes which also
       must be present on objects which are used as XSOs.

    When inheriting from :class:`XMLStreamClass` objects, the properties are
    merged sensibly.

    Rebinding attributes of :class:`XMLStreamClass` instances (i.e. classes
    using this metaclass) is somewhat restricted. The following rules cannot be
    broken, attempting to do so will result in :class:`TypeError` being raised
    when setting the attribute:

    1. Existing descriptors for XSO purposes (such as :class:`.xso.Attr`)
       cannot be removed (either by assigning a new value to the name they are
       bound to or deleting the name).

    2. New descriptors can only be added if they do not violate the rules
       stated at the beginning of the :class:`XMLStreamClass` documentation.

    3. New descriptors can only be added if no subclasses exist (see
       :meth:`.xso.XSO.register_child` for reasons why).

    """

    def __new__(mcls, name, bases, namespace, protect=True):
        text_property = None
        child_map = {}
        child_props = sortedcollections.OrderedSet()
        attr_map = {}
        collector_property = None

        for base in reversed(bases):
            if not isinstance(base, XMLStreamClass):
                continue

            if base.TEXT_PROPERTY is not None:
                if     (text_property is not None and
                        base.TEXT_PROPERTY.xq_descriptor is not text_property):
                    raise TypeError("multiple text properties in inheritance")
                text_property = base.TEXT_PROPERTY.xq_descriptor

            for key, prop in base.CHILD_MAP.items():
                try:
                    existing = child_map[key]
                except KeyError:
                    child_map[key] = prop
                else:
                    if existing is not prop:
                        raise TypeError("ambiguous Child properties inherited")

            child_props |= base.CHILD_PROPS

            for key, prop in base.ATTR_MAP.items():
                try:
                    existing = attr_map[key]
                except KeyError:
                    attr_map[key] = prop
                else:
                    if existing is not prop:
                        raise TypeError("ambiguous Attr properties inherited")

            if base.COLLECTOR_PROPERTY is not None:
                if     (collector_property is not None and
                        base.COLLECTOR_PROPERTY.xq_descriptor is not collector_property):
                    raise TypeError("multiple collector properties in "
                                    "inheritance")
                collector_property = base.COLLECTOR_PROPERTY.xq_descriptor

        for attrname, obj in namespace.items():
            if isinstance(obj, Attr):
                if obj.tag in attr_map:
                    raise TypeError("ambiguous Attr properties")
                attr_map[obj.tag] = obj
            elif isinstance(obj, Text):
                if text_property is not None:
                    raise TypeError("multiple Text properties on XSO class")
                text_property = obj
            elif isinstance(obj, (_ChildPropBase, ChildText, ChildTag,
                                  ChildFlag)):
                for key in obj.get_tag_map():
                    if key in child_map:
                        raise TypeError("ambiguous Child properties: {} and {}"
                                        " both use the same tag".format(
                                            child_map[key],
                                            obj))
                    child_map[key] = obj
                child_props.add(obj)
            elif isinstance(obj, Collector):
                if collector_property is not None:
                    raise TypeError("multiple Collector properties on XSO "
                                    "class")
                collector_property = obj

        namespace["TEXT_PROPERTY"] = text_property
        namespace["CHILD_MAP"] = child_map
        namespace["CHILD_PROPS"] = child_props
        namespace["ATTR_MAP"] = attr_map
        namespace["COLLECTOR_PROPERTY"] = collector_property

        try:
            tag = namespace["TAG"]
        except KeyError:
            tag = None
        else:
            try:
                namespace["TAG"] = tag = normalize_tag(tag)
            except ValueError:
                raise TypeError("TAG attribute has incorrect format")

        if     (tag is not None and
                "DECLARE_NS" not in namespace and
                not any(hasattr(base, "DECLARE_NS") for base in bases)):
            if tag[0] is None:
                namespace["DECLARE_NS"] = {}
            else:
                namespace["DECLARE_NS"] = {
                    None: tag[0]
                }

        if protect:
            namespace.setdefault("__slots__", ())

        return super().__new__(mcls, name, bases, namespace)

    def __init__(cls, name, bases, namespace, protect=True):
        super().__init__(name, bases, namespace)

    def __setattr__(cls, name, value):
        try:
            existing = getattr(cls, name).xq_descriptor
        except AttributeError:
            pass
        else:
            if isinstance(existing, _PropBase):
                raise AttributeError("cannot rebind XSO descriptors")

        if isinstance(value, _PropBase) and cls.__subclasses__():
            raise TypeError("adding descriptors is forbidden on classes with"
                            " subclasses (subclasses: {})".format(
                                ", ".join(map(str, cls.__subclasses__()))
                            ))

        if isinstance(value, Attr):
            if value.tag in cls.ATTR_MAP:
                raise TypeError("ambiguous Attr properties")
            cls.ATTR_MAP[value.tag] = value

        elif isinstance(value, Text):
            if cls.TEXT_PROPERTY is not None:
                raise TypeError("multiple Text properties on XSO class")
            super().__setattr__("TEXT_PROPERTY", value)

        elif isinstance(value, (_ChildPropBase, ChildText, ChildTag,
                                ChildFlag)):
            updates = {}
            for key in value.get_tag_map():
                if key in cls.CHILD_MAP:
                    raise TypeError("ambiguous Child properties: {} and {} "
                                    "both use the same tag".format(
                                        cls.CHILD_MAP[key],
                                        value))
                updates[key] = value
            cls.CHILD_MAP.update(updates)
            cls.CHILD_PROPS.add(value)

        elif isinstance(value, Collector):
            if cls.COLLECTOR_PROPERTY is not None:
                raise TypeError("multiple Collector properties on XSO class")
            super().__setattr__("COLLECTOR_PROPERTY", value)

        super().__setattr__(name, value)

    def __delattr__(cls, name):
        try:
            existing = getattr(cls, name).xq_descriptor
        except AttributeError:
            pass
        else:
            if isinstance(existing, _PropBase):
                raise AttributeError("cannot unbind XSO descriptors")

        super().__delattr__(name)

    def __prepare__(name, bases, **kwargs):
        return collections.OrderedDict()

    def parse_events(cls, ev_args, parent_ctx):
        """
        Create an instance of this class, using the events sent into this
        function. `ev_args` must be the event arguments of the ``"start"``
        event.

        .. seealso::

           You probably should not call this method directly, but instead use
           :class:`XSOParser` with a :class:`SAXDriver`.

        .. note::

           While this method creates an instance of the class, ``__init__`` is
           not called. See the documentation of :meth:`.xso.XSO` for details.

        This method is suspendable.
        """
        with parent_ctx as ctx:
            obj = cls.__new__(cls)
            attrs = ev_args[2]
            attr_map = cls.ATTR_MAP.copy()
            for key, value in attrs.items():
                try:
                    prop = attr_map.pop(key)
                except KeyError:
                    if cls.UNKNOWN_ATTR_POLICY == UnknownAttrPolicy.DROP:
                        continue
                    else:
                        raise ValueError(
                            "unexpected attribute {!r} on {}".format(
                                key,
                                tag_to_str((ev_args[0], ev_args[1]))
                            )) from None
                try:
                    if not prop.from_value(obj, value):
                        # assignment failed due to recoverable error, treat as
                        # absent
                        attr_map[key] = prop
                except:
                    prop.mark_incomplete(obj)
                    _mark_attributes_incomplete(attr_map.values(), obj)
                    logger.debug("while parsing XSO", exc_info=True)
                    # true means suppress
                    if not obj.xso_error_handler(
                            prop,
                            value,
                            sys.exc_info()):
                        raise

            for key, prop in attr_map.items():
                try:
                    prop.handle_missing(obj, ctx)
                except:
                    logger.debug("while parsing XSO", exc_info=True)
                    # true means suppress
                    if not obj.xso_error_handler(
                            prop,
                            None,
                            sys.exc_info()):
                        raise

            try:
                prop = cls.ATTR_MAP[namespaces.xml, "lang"]
            except KeyError:
                pass
            else:
                lang = prop.__get__(obj, cls)
                if lang is not None:
                    ctx.lang = lang

            collected_text = []
            while True:
                ev_type, *ev_args = yield
                if ev_type == "end":
                    break
                elif ev_type == "text":
                    if not cls.TEXT_PROPERTY:
                        if ev_args[0].strip():
                            # true means suppress
                            if not obj.xso_error_handler(
                                    None,
                                    ev_args[0],
                                    None):
                                raise ValueError("unexpected text")
                    else:
                        collected_text.append(ev_args[0])
                elif ev_type == "start":
                    try:
                        handler = cls.CHILD_MAP[ev_args[0], ev_args[1]]
                    except KeyError:
                        if cls.COLLECTOR_PROPERTY:
                            handler = cls.COLLECTOR_PROPERTY.xq_descriptor
                        else:
                            yield from enforce_unknown_child_policy(
                                cls.UNKNOWN_CHILD_POLICY,
                                ev_args,
                                obj.xso_error_handler)
                            continue
                    try:
                        yield from guard(
                            handler.from_events(obj, ev_args, ctx),
                            ev_args
                        )
                    except:
                        logger.debug("while parsing XSO", exc_info=True)
                        # true means suppress
                        if not obj.xso_error_handler(
                                handler,
                                ev_args,
                                sys.exc_info()):
                            raise

            if collected_text:
                collected_text = "".join(collected_text)
                try:
                    cls.TEXT_PROPERTY.xq_descriptor.from_value(
                        obj,
                        collected_text
                    )
                except:
                    logger.debug("while parsing XSO", exc_info=True)
                    # true means suppress
                    if not obj.xso_error_handler(
                            cls.TEXT_PROPERTY.xq_descriptor,
                            collected_text,
                            sys.exc_info()):
                        raise

        obj.validate()

        obj.xso_after_load()

        return obj

    def register_child(cls, prop, child_cls):
        """
        Register a new :class:`XMLStreamClass` instance `child_cls` for a given
        :class:`Child` descriptor `prop`.

        .. warning::

           This method cannot be used after a class has been derived from this
           class. This is for consistency: the method modifies the bookkeeping
           attributes of the class. There would be two ways to deal with the
           situation:

           1. Updating all the attributes at all the subclasses and re-evaluate
              the constraints of inheritance. This is simply not implemented,
              although it would be the preferred way.

           2. Only update the bookkeeping attributes on *this* class, hiding
              the change from any existing subclasses. New subclasses would
              pick the change up, however, which is inconsistent. This is the
              way which was previously documented here and is not supported
              anymore.

           Obviously, (2) is bad, which is why it is not supported anymore. (1)
           might be supported at some point in the future.

           Attempting to use :meth:`register_child` on a class which already
           has subclasses results in a :class:`TypeError`.

        Note that *first* using :meth:`register_child` and only *then* deriving
        clasess is a valid use: it will still lead to a consistent inheritance
        hierarchy and is a convenient way to break reference cycles (e.g. if an
        XSO may be its own child).
        """
        if cls.__subclasses__():
            raise TypeError(
                "register_child is forbidden on classes with subclasses"
                " (subclasses: {})".format(
                    ", ".join(map(str, cls.__subclasses__()))
                ))

        if child_cls.TAG in cls.CHILD_MAP:
            raise ValueError("ambiguous Child")

        prop.xq_descriptor._register(child_cls)
        cls.CHILD_MAP[child_cls.TAG] = prop.xq_descriptor


# I know it makes only partially sense to have a separate metasubclass for
# this, but I like how :meth:`parse_events` is *not* accessible from
# instances.
class CapturingXMLStreamClass(XMLStreamClass):
    """
    This is a subclass of :meth:`XMLStreamClass`. It overrides the
    :meth:`parse_events` to capture the incoming events, including the initial
    event.

    .. see::

       :class:`CapturingXSO`

    .. automethod:: parse_events
    """

    def parse_events(cls, ev_args, parent_ctx):
        """
        Capture the events sent to :meth:`.XSO.parse_events`,
        including the initial `ev_args` to a list and call
        :meth:`_set_captured_events` on the result of
        :meth:`.XSO.parse_events`.

        Like the method it overrides, :meth:`parse_events` is suspendable.
        """

        dest = [("start", )+tuple(ev_args)]
        result = yield from capture_events(
            super().parse_events(ev_args, parent_ctx),
            dest
        )
        result._set_captured_events(dest)

        return result


class XSO(metaclass=XMLStreamClass):
    """
    XSO is short for **X**\ ML **S**\ tream **O**\ bject and means an object
    which represents a subtree of an XML stream. These objects can also be
    created and validated on-the-fly from SAX-like events using
    :class:`XSOParser`.

    The constructor does not require any arguments and forwards them directly
    the next class in the resolution order. Note that during deserialization,
    ``__init__`` is not called. It is assumed that all data is loaded from the
    XML stream and thus no initialization is required.

    This is beneficial to applications, as it allows them to define mandatory
    arguments for ``__init__``. This would not be possible if ``__init__`` was
    called during deserialization. A way to execute code after successful
    deserialization is provided through :meth:`xso_after_load`.

    :class:`XSO` objects support copying. Like with deserialisation,
    ``__init__`` is not called during copy. The default implementation only
    copies the XSO descriptors’ values (with deepcopy, they are copied
    deeply). If you have more attributes to copy, you need to override
    ``__copy__`` and ``__deepcopy__`` methods.

    .. versionchanged:: 0.4

       Copy and deepcopy support has been added. Previously, copy copied not
       enough data, while deepcopy copied too much data (including descriptor
       objects).

    To declare an XSO, inherit from :class:`XSO` and provide
    the following attributes on your class:

    * A ``TAG`` attribute, which is a tuple ``(namespace_uri, localname)``
      representing the tag of the XML element you want to match.
    * An arbitrary number of :class:`Text`, :class:`Collector`, :class:`Child`,
      :class:`ChildList` and :class:`Attr`-based attributes.

    .. seealso::

       :class:`.xso.model.XMLStreamClass`
          is the metaclass of :class:`XSO`. The documentation of the metaclass
          holds valuable information with respect to modifying :class:`XSO`
          *classes* and subclassing.

    .. note::

       Attributes whose name starts with ``xso_`` or ``_xso_`` are reserved for
       use by the :mod:`aioxmpp.xso` implementation. Do not use these in your
       code if you can possibly avoid it.

    :class:`XSO` subclasses automatically declare a
    :attr:`~.xso.model.XMLStreamClass.__slots__` attribute which does not
    include the ``__dict__`` value. This effectively prevents any attributes
    not declared on the class as descriptors from being written. The rationale
    is detailed on in the linked documentation. To prevent this from happening
    in your subclass, inherit with `protect` set to false::

      class MyXSO(xso.XSO, protect=False):
          pass

    .. versionadded:: 0.6

       The handling of the :attr:`~.xso.model.XMLStreamClass.__slots__`
       attribute was added.

    To further influence the parsing behaviour of a class, two attributes are
    provided which give policies for unexpected elements in the XML tree:

    .. attribute:: UNKNOWN_CHILD_POLICY = UnknownChildPolicy.DROP

       A value from the :class:`UnknownChildPolicy` enum which defines the
       behaviour if a child is encountered for which no matching attribute is
       found.

       Note that this policy has no effect if a :class:`Collector` descriptor
       is present, as it takes all children for which no other descriptor
       exists, thus all children are known.

    .. attribute:: UNKNOWN_ATTR_POLICY = UnknownAttrPolicy.DROP

       A value from the :class:`UnknownAttrPolicy` enum which defines the
       behaviour if an attribute is encountered for which no matching
       descriptor is found.

    Example::

        class Body(aioxmpp.xso.XSO):
            TAG = ("jabber:client", "body")

            text = aioxmpp.xso.Text()

        class Message(aioxmpp.xso.XSO):
            TAG = ("jabber:client", "message")
            UNKNOWN_CHILD_POLICY = aioxmpp.xso.UnknownChildPolicy.DROP

            type_ = aioxmpp.xso.Attr(tag="type")
            from_ = aioxmpp.xso.Attr(tag="from")
            to = aioxmpp.xso.Attr(tag="to")
            id_ = aioxmpp.xso.Attr(tag="id")

            body = aioxmpp.xso.Child([Body])

    Beyond the validation of the individual descriptor values, it is possible
    to implement more complex validation steps by overriding the
    :meth:`validate` method:

    .. automethod:: validate

    The following methods are available on instances of :class:`XSO`:

    .. automethod:: unparse_to_sax

    The following **class methods** are provided by the metaclass:

    .. automethod:: parse_events(ev_args)

    .. automethod:: register_child(prop, child_cls)

    To customize behaviour of deserialization, these methods are provided which
    can be re-implemented by subclasses:

    .. automethod:: xso_after_load

    .. automethod:: xso_error_handler

    """
    UNKNOWN_CHILD_POLICY = UnknownChildPolicy.DROP
    UNKNOWN_ATTR_POLICY = UnknownAttrPolicy.DROP

    __slots__ = ("_xso_contents", "__weakref__")

    def __new__(cls, *args, **kwargs):
        # XXX: is it always correct to omit the arguments here?
        # the semantics of the __new__ arguments are odd to say the least
        result = super().__new__(cls)
        result._xso_contents = dict()
        return result

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def __copy__(self):
        result = type(self).__new__(type(self))
        result._xso_contents.update(self._xso_contents)
        return result

    def __deepcopy__(self, memo):
        result = type(self).__new__(type(self))
        result._xso_contents = {
            k: copy.deepcopy(v, memo)
            for k, v in self._xso_contents.items()
        }
        return result

    def validate(self):
        """
        Validate the objects structure beyond the values of individual fields
        (which have their own validators).

        This first calls :meth:`_PropBase.validate_contents` recursively on the
        values of all child descriptors. These may raise (or re-raise) errors
        which occur during validation of the child elements.

        To implement your own validation logic in a subclass of :class:`XSO`,
        override this method and call it via :func:`super` before doing your
        own validation.

        Validate is called by the parsing stack after an object has been fully
        deserialized from the SAX event stream. If the deserialization fails
        due to invalid values of descriptors or due to validation failures in
        child objects, this method is obviously not called.
        """

        for descriptor in self.CHILD_PROPS:
            descriptor.validate_contents(self)

    def xso_after_load(self):
        """
        After an object has been successfully deserialized, this method is
        called. Note that ``__init__`` is never called on objects during
        deserialization.
        """

    def xso_error_handler(self, descriptor, ev_args, exc_info):
        """
        This method is called whenever an error occurs while parsing.

        If an exception is raised by the parsing function of a descriptor
        attribute, such as :class:`Attr`, the `descriptor` is passed as first
        argument, the `exc_info` tuple as third argument and the arguments
        which led to the descriptor being invoked as second argument.

        If an unknown child is encountered and the :attr:`UNKNOWN_CHILD_POLICY`
        is set to :attr:`UnknownChildPolicy.FAIL`, `descriptor` and `exc_info`
        are passed as :data:`None` and `ev_args` are the arguments to the
        ``"start"`` event of the child (i.e. a triple
        ``(namespace_uri, localname, attributes)``).

        If the error handler wishes to suppress the exception, it must return a
        true value. Otherwise, the exception is propagated (or a new exception
        is raised, if the error was not caused by an exception). The error
        handler may also raise its own exception.

        .. warning::

           Suppressing exceptions can cause invalid input to reside in the
           object or the object in general being in a state which violates the
           schema.

           For example, suppressing exceptions about missing attributes will
           cause the attribute to remain uninitialized (i.e. left at its
           :attr:`default` value).

        Even if the error handler suppresses an exception caused by a broken
        child, that child will not be added to the object.
        """

    def unparse_to_sax(self, dest):
        # XXX: if anyone has an idea on how to optimize this, this is a hotspot
        # when serialising XML
        # things which do not suffice or even change anything:
        # 1. pull things in local variables
        # 2. get rid of the try/finally, even without any replacement
        cls = type(self)
        attrib = {}
        for prop in cls.ATTR_MAP.values():
            prop.to_dict(self, attrib)
        if cls.DECLARE_NS:
            for prefix, uri in cls.DECLARE_NS.items():
                dest.startPrefixMapping(prefix, uri)
        dest.startElementNS(self.TAG, None, attrib)
        try:
            if cls.TEXT_PROPERTY:
                cls.TEXT_PROPERTY.to_sax(self, dest)
            for prop in cls.CHILD_PROPS:
                prop.to_sax(self, dest)
            if cls.COLLECTOR_PROPERTY:
                cls.COLLECTOR_PROPERTY.to_sax(self, dest)
        finally:
            dest.endElementNS(self.TAG, None)
            if cls.DECLARE_NS:
                for prefix, uri in cls.DECLARE_NS.items():
                    dest.endPrefixMapping(prefix)

    def unparse_to_node(self, parent):
        handler = lxml.sax.ElementTreeContentHandler(
            makeelement=parent.makeelement)
        handler.startDocument()
        handler.startElementNS((None, "root"), None)
        self.unparse_to_sax(handler)
        handler.endElementNS((None, "root"), None)
        handler.endDocument()

        parent.extend(handler.etree.getroot())


class CapturingXSO(XSO, metaclass=CapturingXMLStreamClass):
    """
    The following **class methods** is provided by the metaclass (which is not
    publicly available, but a subclass of :class:`~.XMLStreamClass`):

    .. automethod:: parse_events

    The :meth:`_set_captured_events` method can be overriden by subclasses to
    make use of the captured events:

    .. automethod:: _set_captured_events

    An example use case for this class is :class:`.disco.InfoQuery`, combined
    with :mod:`aioxmpp.entitycaps`. We want to be able to store hashes and the
    generating XML data for use with future versions, including XML data which
    cannot be parsed by an XSO in the current process (for example, due to an
    unknown namespace or a plugin which is available but not loaded). With the
    captured events, it is possible to re-create XML semantically equivalent to
    the XML originally received.

    .. versionadded:: 0.5
    """

    @abc.abstractmethod
    def _set_captured_events(self, events):
        """
        This method is called by :meth:`parse_events` after parsing the
        object. `events` is the list of event tuples which this object was
        deserialised from.

        Subclasses must override this method.
        """


class SAXDriver(xml.sax.handler.ContentHandler):
    """
    This is a :class:`xml.sax.handler.ContentHandler` subclass which *only*
    supports namespace-conforming SAX event sources.

    `dest_generator_factory` must be a function which returns a new suspendable
    method supporting the interface of :class:`XSOParser`. The SAX events are
    converted to an internal event format and sent to the suspendable function
    in order.

    `on_emit` may be a callable. Whenever a suspendable function returned by
    `dest_generator_factory` returns, with the return value as sole argument.

    When you are done with a :class:`SAXDriver`, you should call :meth:`close`
    to clean up internal parser state.

    .. automethod:: close
    """

    def __init__(self, dest_generator_factory, on_emit=None):
        self._on_emit = on_emit
        self._dest_factory = dest_generator_factory
        self._dest = None

    def _emit(self, value):
        if self._on_emit:
            self._on_emit(value)

    def _send(self, value):
        if self._dest is None:
            self._dest = self._dest_factory()
            self._dest.send(None)
        try:
            self._dest.send(value)
        except StopIteration as err:
            self._emit(err.value)
            self._dest = None
        except:
            self._dest = None
            raise

    def startElementNS(self, name, qname, attributes):
        uri, localname = name
        self._send(("start", uri, localname, dict(attributes)))

    def characters(self, data):
        self._send(("text", data))

    def endElementNS(self, name, qname):
        self._send(("end",))

    def close(self):
        """
        Clean up all internal state.
        """
        if self._dest is not None:
            self._dest.close()
            self._dest = None


class Context:
    def __init__(self):
        super().__init__()
        self.lang = None

    def __enter__(self):
        new_ctx = Context()
        new_ctx.__dict__ = self.__dict__.copy()
        return new_ctx

    def __exit__(self, *args):
        pass


class XSOParser:
    """
    A generic XSO parser which supports a dynamic set of XSOs to
    parse. :class:`XSOParser` objects are callable and they are suspendable
    methods (i.e. calling a :class:`XSOParser` returns a generator which parses
    stanzas from sax-ish events. Use with :class:`SAXDriver`).

    Example use::

        # let Message be a XSO class, like in the XSO example
        result = None
        def catch_result(value):
            nonlocal result
            result = value

        parser = aioxmpp.xso.XSOParser()
        parser.add_class(Message, catch_result)
        sd = aioxmpp.xso.SAXDriver(parser)
        lxml.sax.saxify(lmxl.etree.fromstring(
            "<message id='foo' from='bar' type='chat' />"
        ))


    The following methods can be used to dynamically add and remove top-level
    :class:`XSO` classes.

    .. automethod:: add_class

    .. automethod:: remove_class

    .. automethod:: get_tag_map

    """

    def __init__(self):
        self._class_map = {}
        self._tag_map = {}
        self._ctx = Context()

    @property
    def lang(self):
        return self._ctx.lang

    @lang.setter
    def lang(self, value):
        self._ctx.lang = value

    def add_class(self, cls, callback):
        """
        Add a class `cls` for parsing as root level element. When an object of
        `cls` type has been completely parsed, `callback` is called with the
        object as argument.
        """
        if cls.TAG in self._tag_map:
            raise ValueError(
                "duplicate tag: {!r} is already handled by {}".format(
                    cls.TAG,
                    self._tag_map[cls.TAG]))
        self._class_map[cls] = callback
        self._tag_map[cls.TAG] = (cls, callback)

    def get_tag_map(self):
        """
        Return the internal mapping which maps tags to tuples of ``(cls,
        callback)``.

        .. warning::

           The results of modifying this dict are undefined. Make a copy if you
           need to modify the result of this function.

        """
        return self._tag_map

    def get_class_map(self):
        """
        Return the internal mapping which maps classes to the associated
        callbacks.

        .. warning::

           The results of modifying this dict are undefined. Make a copy if you
           need to modify the result of this function.

        """
        return self._class_map

    def remove_class(self, cls):
        """
        Remove a XSO class `cls` from parsing. This method raises
        :class:`KeyError` with the classes :attr:`TAG` attribute as argument if
        removing fails because the class is not registered.
        """
        del self._tag_map[cls.TAG]
        del self._class_map[cls]

    def __call__(self):
        while True:
            ev_type, *ev_args = yield
            if ev_type == "text" and not ev_args[0].strip():
                continue

            tag = ev_args[0], ev_args[1]
            try:
                cls, cb = self._tag_map[tag]
            except KeyError:
                raise UnknownTopLevelTag(
                    "unhandled top-level element",
                    ev_args)
            cb((yield from cls.parse_events(ev_args, self._ctx)))


def drop_handler(ev_args):
    depth = 1
    while depth:
        ev = yield
        if ev[0] == "start":
            depth += 1
        elif ev[0] == "end":
            depth -= 1


def enforce_unknown_child_policy(policy, ev_args, error_handler=None):
    if policy == UnknownChildPolicy.DROP:
        yield from drop_handler(ev_args)
    else:
        if error_handler is not None:
            if error_handler(None, ev_args, None):
                yield from drop_handler(ev_args)
                return
        raise ValueError("unexpected child")


def guard(dest, ev_args):
    depth = 1
    try:
        next(dest)
        while True:
            ev = yield
            if ev[0] == "start":
                depth += 1
            elif ev[0] == "end":
                depth -= 1
            try:
                dest.send(ev)
            except StopIteration as exc:
                return exc.value
    finally:
        while depth > 0:
            ev_type, *_ = yield
            if ev_type == "end":
                depth -= 1
            elif ev_type == "start":
                depth += 1


def lang_attr(instance, ctx):
    """
    A `missing` handler for :class:`Attr` descriptors. If any parent object has
    a ``xml:lang`` attribute set, its value is used.

    Pass as `missing` argument to :class:`Attr` constructors to use this
    behaviour for a given attribute.
    """
    return ctx.lang


def capture_events(receiver, dest):
    """
    Capture all events sent to `receiver` in the sequence `dest`. This is a
    generator, and it is best used with ``yield from``. The observable effect
    of using this generator with ``yield from`` is identical to the effect of
    using `receiver` with ``yield from`` directly (including the return value),
    but in addition, the values which are *sent* to the receiver are captured
    in `dest`.

    If `receiver` raises an exception or the generator is closed prematurely
    using its :meth:`close`, `dest` is cleared.

    This is used to implement :class:`CapturingXSO`. See the documentation
    there for use cases.

    .. versionadded:: 0.5
    """
    # the following code is a copy of the formal definition of `yield from`
    # in PEP 380, with modifications to capture the value sent during yield
    _i = iter(receiver)
    try:
        _y = next(_i)
    except StopIteration as _e:
        return _e.value

    try:
        while True:
            try:
                _s = yield _y
            except GeneratorExit as _e:
                try:
                    _m = _i.close
                except AttributeError:
                    pass
                else:
                    _m()
                    raise _e
            except BaseException as _e:
                _x = sys.exc_info()
                try:
                    _m = _i.throw
                except AttributeError:
                    raise _e
                else:
                    try:
                        _y = _m(*_x)
                    except StopIteration as _e:
                        _r = _e.value
                        break
            else:
                dest.append(_s)
                try:
                    if _s is None:
                        _y = next(_i)
                    else:
                        _y = _i.send(_s)
                except StopIteration as _e:
                    _r = _e.value
                    break
    except:
        dest.clear()
        raise
    return _r


def events_to_sax(events, dest):
    """
    Convert an iterable `events` of XSO events to SAX events by calling the
    matching SAX methods on `dest`
    """
    name_stack = []

    for ev_type, *ev_args in events:
        if ev_type == "start":
            name = (ev_args[0], ev_args[1])
            dest.startElementNS(name, None, ev_args[2])
            name_stack.append(name)
        elif ev_type == "end":
            name = name_stack.pop()
            dest.endElementNS(name, None)
        elif ev_type == "text":
            dest.characters(ev_args[0])


class _CollectorContentHandlerFilter(xml.sax.handler.ContentHandler):
    def __init__(self, receiver):
        super().__init__()
        self.__receiver = receiver

    def setDocumentLocator(self, locator):
        self.__receiver.setDocumentLocator(locator)

    def startElement(self, name, attrs):
        self.__receiver.startElement(name, attrs)

    def endElement(self, name):
        self.__receiver.endElement(name)

    def startElementNS(self, name, qname, attrs):
        self.__receiver.startElementNS(name, qname, attrs)

    def endElementNS(self, name, qname):
        self.__receiver.endElementNS(name, qname)

    def characters(self, content):
        self.__receiver.characters(content)

    def ignorableWhitespace(self, content):
        self.__receiver.ignorableWhitespace(content)

    def processingInstruction(self, target, data):
        self.__receiver.processingInstruction(target, data)

    def skippedEntity(self, name):
        self.__receiver.skippedEntity(name)


class XSOEnumMixin:
    """
    Mix-in to create enumerations of XSOs.

    .. versionadded:: 0.10

    The enumeration member values must be pairs of ``namespace``, ``localpart``
    strings. Each enumeration member is equipped with an :attr:`xso_class`
    attribute at definition time.

    .. automethod:: to_xso

    .. autoattribute:: enum_member

    .. attribute:: xso_class

        A :class:`aioxmpp.xso.XSO` *subclass* which has the enumeration members
        value as :attr:`~.XSO.TAG`. So the subclass matches elements which have
        the qualified tag in the enumeration member value.

        The class does not have any XSO descriptors assigned. They can be added
        after class definition.

        .. attribute:: enum_member

            The enumeration member to which the :attr:`xso_class` belongs.

            This allows to use XSOs and enumeration members more
            interchangeably; see :attr:`enum_member` for details.

        .. method:: to_xso

            Return the XSO itself.

            This allows to use XSOs and enumeration members more
            interchangeably; see :meth:`to_xso` for details.

    Example usage::

        class TagEnum(aioxmpp.xso.XSOEnumMixin, enum.Enum):
            X = ("uri:foo", "x")
            Y = ("uri:foo", "y")

        TagEnum.X.xso_class.enabled = aioxmpp.xso.Attr(
            "enabled",
            type_=aioxmpp.xso.Bool()
        )

    The :class:`TagEnum` members then have a :attr:`xso_class` attribute which
    is a *subclass* of :class:`~aioxmpp.xso.XSO` (**not** an instance of a
    subclass of :class:`~aioxmpp.xso.XSO`).

    The :attr:`xso_class` for :attr:`TagEnum.X` also supports the ``enabled``
    attribute (due to it being monkey-patched onto it), while the
    :attr:`xso_class` for :attr:`TagEnum.Y` does not. Thus, monkey-patching
    can be used to customize the individual XSO classes of the members.

    To use such an enum on a descriptor, the following syntax can be used::

        class Element(aioxmpp.xso.XSO):
            TAG = ("uri:foo", "parent")

            child = aioxmpp.xso.Child([
                member.xso_class
                for member in TagEnum
            ])
    """

    def __init__(self, namespace, localname):
        super().__init__()
        self.xso_class = self._create_class()

    def _create_name(self):
        return "".join(map(str.title, self.name.split("_")))

    def _create_class(self):
        def to_xso(self):
            return self

        return XMLStreamClass(
            self._create_name(),
            (XSO,),
            {
                "TAG": self.value,
                "__qualname__": "{}.{}.xso_class".format(
                    type(self).__qualname__,
                    self.name,
                ),
                "enum_member": self,
                "to_xso": to_xso,
            },
        )

    @property
    def enum_member(self):
        """
        The object (enum member) itself.

        This property exists to make it easier to use the XSO objects and the
        enumeration members interchangeably. The XSO objects also have the
        :attr:`enum_member` property to obtain the enumeration member to which
        they belong. Code which is only interested in the enumeration member
        can thus access the :attr:`enum_member` attribute to "coerce" both
        (enumeration members and instances of their XSO classes) into
        enumeration members.
        """
        return self

    def to_xso(self):
        """
        A new instance of the :attr:`xso_class`.

        This method exists to make it easier to use the XSO objects and the
        enumeration members interchangeably. The XSO objects also have the
        :meth:`to_xso` method which just returns the XSO unmodified.

        Code which needs an XSO, but does not care about the data, can thus use
        the :meth:`to_xso` method to "coerce" either (enumeration members and
        instances of their XSO classes) into XSOs.
        """
        return self.xso_class()
