#
# Copyright (c), 2016-2020, SISSA (International School for Advanced Studies).
# All rights reserved.
# This file is distributed under the terms of the MIT License.
# See the file 'LICENSE' in the root directory of the present
# distribution, or http://opensource.org/licenses/MIT.
#
# @author Davide Brunato <brunato@sissa.it>
#
"""
This module contains classes for XML Schema attributes and attribute groups.
"""
from decimal import Decimal
from collections.abc import MutableMapping
from elementpath.datatypes import AbstractDateTime, Duration

from ..compat import ordered_dict_class
from ..exceptions import XMLSchemaAttributeError, XMLSchemaTypeError, XMLSchemaValueError
from ..qnames import XSD_ANY_SIMPLE_TYPE, XSD_SIMPLE_TYPE, XSD_ATTRIBUTE_GROUP, \
    XSD_COMPLEX_TYPE, XSD_RESTRICTION, XSD_EXTENSION, XSD_SEQUENCE, XSD_ALL, \
    XSD_CHOICE, XSD_ATTRIBUTE, XSD_ANY_ATTRIBUTE, XSD_ASSERT, XSD_NOTATION_TYPE, \
    get_namespace, get_qname, is_not_xsd_annotation
from ..helpers import get_xsd_form_attribute
from ..namespaces import XSI_NAMESPACE

from .exceptions import XMLSchemaValidationError
from .xsdbase import XsdComponent, ValidationMixin
from .simple_types import XsdSimpleType
from .wildcards import XsdAnyAttribute


class XsdAttribute(XsdComponent, ValidationMixin):
    """
    Class for XSD 1.0 *attribute* declarations.

    :ivar type: the XSD simpleType of the attribute.

    ..  <attribute
          default = string
          fixed = string
          form = (qualified | unqualified)
          id = ID
          name = NCName
          ref = QName
          type = QName
          use = (optional | prohibited | required) : optional
          {any attributes with non-schema namespace ...}>
          Content: (annotation?, simpleType?)
        </attribute>
    """
    _ADMITTED_TAGS = {XSD_ATTRIBUTE}

    type = None
    qualified = False
    default = None
    fixed = None
    inheritable = False  # For XSD 1.1 attributes, always False for XSD 1.0 attributes.

    def __init__(self, elem, schema, parent):
        super(XsdAttribute, self).__init__(elem, schema, parent)
        if not hasattr(self, 'type'):
            raise XMLSchemaAttributeError("undefined 'type' for %r." % self)

    def __setattr__(self, name, value):
        if name == "type":
            if not isinstance(value, XsdSimpleType):
                raise XMLSchemaTypeError("An XSD attribute's type must be a simpleType.")
        super(XsdAttribute, self).__setattr__(name, value)

    def _parse(self):
        super(XsdAttribute, self)._parse()
        attrib = self.elem.attrib

        self.use = attrib.get('use')
        if self.use is None:
            self.use = 'optional'
        elif self.parent is None:
            self.parse_error("attribute 'use' not allowed in a global attribute.")
        elif self.use not in {'optional', 'prohibited', 'required'}:
            self.parse_error("wrong value %r for 'use' attribute." % self.use)
            self.use = 'optional'

        if 'default' in attrib:
            self.default = attrib['default']

        if 'fixed' in attrib:
            self.fixed = attrib['fixed']

        if self._parse_reference():
            try:
                xsd_attribute = self.maps.lookup_attribute(self.name)
            except LookupError:
                self.parse_error("unknown attribute %r" % self.name)
                self.type = self.any_simple_type
            else:
                self.ref = xsd_attribute
                self.type = xsd_attribute.type
                if xsd_attribute.qualified:
                    self.qualified = True

                if self.default is None and xsd_attribute.default is not None:
                    self.default = xsd_attribute.default

                if xsd_attribute.fixed is not None:
                    if self.fixed is None:
                        self.fixed = xsd_attribute.fixed
                    elif xsd_attribute.fixed != self.fixed:
                        msg = "referenced attribute has a different fixed value %r"
                        self.parse_error(msg % xsd_attribute.fixed)

            for attribute in ('form', 'type'):
                if attribute in self.elem.attrib:
                    self.parse_error("attribute %r is not allowed when "
                                     "attribute reference is used." % attribute)

            child = self._parse_child_component(self.elem)
            if child is not None and child.tag == XSD_SIMPLE_TYPE:
                self.parse_error("not allowed type definition for XSD attribute reference")
            return

        try:
            form = get_xsd_form_attribute(self.elem, 'form')
        except ValueError as err:
            self.parse_error(err)
        else:
            if form is None:
                if self.schema.attribute_form_default == 'qualified':
                    self.qualified = True
            elif self.parent is None:
                self.parse_error("attribute 'form' not allowed in a global attribute.")
            elif form == 'qualified':
                self.qualified = True

        name = attrib.get('name')
        if name is not None:
            if name == 'xmlns':
                self.parse_error("an attribute name must be different from 'xmlns'")

            if self.parent is None or self.qualified:
                if self.target_namespace == XSI_NAMESPACE and \
                        name not in {'nil', 'type', 'schemaLocation', 'noNamespaceSchemaLocation'}:
                    self.parse_error("Cannot add attributes in %r namespace" % XSI_NAMESPACE)
                self.name = get_qname(self.target_namespace, name)
            else:
                self.name = name

        child = self._parse_child_component(self.elem)
        if 'type' in attrib:
            try:
                type_qname = self.schema.resolve_qname(attrib['type'])
            except (KeyError, ValueError, RuntimeError) as err:
                self.parse_error(err)
                xsd_type = self.any_simple_type
            else:
                try:
                    xsd_type = self.maps.lookup_type(type_qname)
                except LookupError as err:
                    self.parse_error(err)
                    xsd_type = self.any_simple_type

                if child is not None and child.tag == XSD_SIMPLE_TYPE:
                    self.parse_error("ambiguous type definition for XSD attribute")
                elif child is not None:
                    self.parse_error("not allowed element in XSD attribute "
                                     "declaration: %r" % child[0])
        elif child is not None:
            # No 'type' attribute in declaration, parse for child local simpleType
            xsd_type = self.schema.BUILDERS.simple_type_factory(child, self.schema, self)
        else:
            # Empty declaration means xsdAnySimpleType
            xsd_type = self.any_simple_type

        try:
            self.type = xsd_type
        except TypeError as err:
            self.parse_error(err)

        # Check value constraints
        if 'default' in attrib:
            if 'fixed' in attrib:
                self.parse_error("'default' and 'fixed' attributes are mutually exclusive")
            if self.use != 'optional':
                self.parse_error("the attribute 'use' must be 'optional' "
                                 "if the attribute 'default' is present")
            if not self.type.is_valid(attrib['default']):
                msg = "'default' value {!r} is not compatible with the type {!r}"
                self.parse_error(msg.format(attrib['default'], self.type))
            elif self.type.is_key() and self.xsd_version == '1.0':
                self.parse_error("'xs:ID' or a type derived from 'xs:ID' cannot has a 'default'")
        elif 'fixed' in attrib:
            if not self.type.is_valid(attrib['fixed']):
                msg = "'fixed' value {!r} is not compatible with the type {!r}"
                self.parse_error(msg.format(attrib['fixed'], self.type))
            elif self.type.is_key() and self.xsd_version == '1.0':
                self.parse_error("'xs:ID' or a type derived from 'xs:ID' cannot has a 'default'")

    @property
    def built(self):
        return True

    @property
    def validation_attempted(self):
        return 'full'

    @property
    def scope(self):
        """The scope of the attribute declaration that can be 'global' or 'local'."""
        return 'global' if self.parent is None else 'local'

    @property
    def value_constraint(self):
        """The fixed or the default value if either is defined, `None` otherwise."""
        return self.fixed if self.fixed is not None else self.default

    @property
    def form(self):
        return get_xsd_form_attribute(self.elem, 'form')

    def is_optional(self):
        return self.use == 'optional'

    def is_required(self):
        return self.use == 'required'

    def is_prohibited(self):
        return self.use == 'prohibited'

    def iter_components(self, xsd_classes=None):
        if xsd_classes is None or isinstance(self, xsd_classes):
            yield self
        if self.ref is None and self.type.parent is not None:
            yield from self.type.iter_components(xsd_classes)

    def data_value(self, text):
        """Returns the decoded data value of the provided text as XPath fn:data()."""
        return self.decode(text, validation='skip')

    def iter_decode(self, text, validation='lax', **kwargs):
        if not text and self.default is not None:
            text = self.default

        if self.type.is_notation():
            if self.type.name == XSD_NOTATION_TYPE:
                msg = "cannot validate against xs:NOTATION directly, " \
                      "only against a subtype with an enumeration facet"
                yield self.validation_error(validation, msg, text, **kwargs)
            elif not self.type.enumeration:
                msg = "missing enumeration facet in xs:NOTATION subtype"
                yield self.validation_error(validation, msg, text, **kwargs)

        if self.fixed is not None:
            if text is None:
                text = self.fixed
            elif text == self.fixed:
                pass
            elif self.type.text_decode(text) != self.type.text_decode(self.fixed):
                msg = "attribute {!r} has a fixed value {!r}".format(self.name, self.fixed)
                yield self.validation_error(validation, msg, text, **kwargs)

        for result in self.type.iter_decode(text, validation, **kwargs):
            if isinstance(result, XMLSchemaValidationError):
                yield result
                continue
            elif isinstance(result, Decimal):
                try:
                    yield kwargs['decimal_type'](result)
                except (KeyError, TypeError):
                    yield result
            elif isinstance(result, (AbstractDateTime, Duration)):
                try:
                    yield result if kwargs['datetime_types'] is True else text
                except KeyError:
                    yield text
            elif isinstance(result, str) and result.startswith('{') and self.type.is_qname():
                yield text
            else:
                yield result
            break

    def iter_encode(self, obj, validation='lax', **kwargs):
        for result in self.type.iter_encode(obj, validation):
            yield result
            if not isinstance(result, XMLSchemaValidationError):
                return


class Xsd11Attribute(XsdAttribute):
    """
    Class for XSD 1.1 *attribute* declarations.

    ..  <attribute
          default = string
          fixed = string
          form = (qualified | unqualified)
          id = ID
          name = NCName
          ref = QName
          targetNamespace = anyURI
          type = QName
          use = (optional | prohibited | required) : optional
          inheritable = boolean
          {any attributes with non-schema namespace . . .}>
          Content: (annotation?, simpleType?)
        </attribute>
    """
    _target_namespace = None

    @property
    def target_namespace(self):
        if self._target_namespace is not None:
            return self._target_namespace
        elif self.ref is not None:
            return self.ref.target_namespace
        else:
            return self.schema.target_namespace

    def _parse(self):
        super(Xsd11Attribute, self)._parse()
        if self.use == 'prohibited' and 'fixed' in self.elem.attrib:
            self.parse_error("attribute 'fixed' with use=prohibited is not allowed in XSD 1.1")
        if self._parse_boolean_attribute('inheritable'):
            self.inheritable = True
        self._parse_target_namespace()


class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin):
    """
    Class for XSD *attributeGroup* definitions.

    .. <attributeGroup
          id = ID
          name = NCName
          ref = QName
          {any attributes with non-schema namespace . . .}>
          Content: (annotation?, ((attribute | attributeGroup)*, anyAttribute?))
        </attributeGroup>
    """
    redefine = None
    _ADMITTED_TAGS = {
        XSD_ATTRIBUTE_GROUP, XSD_COMPLEX_TYPE, XSD_RESTRICTION, XSD_EXTENSION,
        XSD_SEQUENCE, XSD_ALL, XSD_CHOICE, XSD_ATTRIBUTE, XSD_ANY_ATTRIBUTE
    }

    def __init__(self, elem, schema, parent, derivation=None, base_attributes=None):
        self.derivation = derivation
        self._attribute_group = ordered_dict_class()
        self.base_attributes = base_attributes
        XsdComponent.__init__(self, elem, schema, parent)

    def __repr__(self):
        if self.ref is not None:
            return '%s(ref=%r)' % (self.__class__.__name__, self.name)
        elif self.name is not None:
            return '%s(name=%r)' % (self.__class__.__name__, self.name)
        elif self:
            names = [a if a.name is None else a.name for a in self.values()]
            return '%s(%r)' % (self.__class__.__name__, names)
        else:
            return '%s()' % self.__class__.__name__

    # Implementation of abstract methods
    def __getitem__(self, key):
        return self._attribute_group[key]

    def __setitem__(self, key, value):
        if key is None:
            assert isinstance(value, XsdAnyAttribute), 'An XsdAnyAttribute instance is required.'
            self._attribute_group[key] = value
        else:
            assert isinstance(value, XsdAttribute), 'An XsdAttribute instance is required.'
            if key[0] != '{':
                if value.local_name != key:
                    raise XMLSchemaValueError("%r name and key %r mismatch." % (value.name, key))
                if value.target_namespace != self.target_namespace:
                    # Qualify attributes of other namespaces
                    key = value.qualified_name
            elif value.qualified_name != key:
                raise XMLSchemaValueError("%r name and key %r mismatch." % (value.name, key))

            self._attribute_group[key] = value

    def __delitem__(self, key):
        del self._attribute_group[key]

    def __iter__(self):
        if None in self._attribute_group:
            # Put AnyAttribute ('None' key) at the end of iteration
            return iter(sorted(self._attribute_group, key=lambda x: (x is None, x)))
        else:
            return iter(self._attribute_group)

    def __len__(self):
        return len(self._attribute_group)

    # Other methods
    def __setattr__(self, name, value):
        super(XsdAttributeGroup, self).__setattr__(name, value)
        if name == '_attribute_group':
            assert isinstance(value, dict), 'A dictionary object is required.'
            for k, v in value.items():
                if k is None:
                    assert isinstance(value, XsdAnyAttribute), \
                        'An XsdAnyAttribute instance is required.'
                else:
                    assert isinstance(value, XsdAttribute), \
                        'An XsdAttribute instance is required.'

    def _parse(self):
        super(XsdAttributeGroup, self)._parse()
        elem = self.elem
        any_attribute = None
        attribute_group_refs = []

        if elem.tag == XSD_ATTRIBUTE_GROUP:
            if self.parent is not None:
                return  # Skip dummy definitions
            try:
                self.name = get_qname(self.target_namespace, elem.attrib['name'])
            except KeyError:
                self.parse_error("an attribute group declaration requires a 'name' attribute.")
                return
            else:
                if self.schema.default_attributes == self.name and self.xsd_version > '1.0':
                    self.schema.default_attributes = self

        attributes = ordered_dict_class()
        for child in filter(is_not_xsd_annotation, elem):
            if any_attribute is not None:
                if child.tag == XSD_ANY_ATTRIBUTE:
                    self.parse_error("more anyAttribute declarations in the same attribute group")
                elif child.tag != XSD_ASSERT:
                    self.parse_error("another declaration after anyAttribute")

            elif child.tag == XSD_ANY_ATTRIBUTE:
                any_attribute = self.schema.BUILDERS.any_attribute_class(child, self.schema, self)
                if None in attributes:
                    attributes[None] = attributes[None].copy()
                    attributes[None].intersection(any_attribute)
                else:
                    attributes[None] = any_attribute

            elif child.tag == XSD_ATTRIBUTE:
                attribute = self.schema.BUILDERS.attribute_class(child, self.schema, self)
                if attribute.name in attributes:
                    self.parse_error("multiple declaration for attribute "
                                     "{!r}".format(attribute.name))
                elif attribute.use != 'prohibited' or elem.tag != XSD_ATTRIBUTE_GROUP:
                    attributes[attribute.name] = attribute

            elif child.tag == XSD_ATTRIBUTE_GROUP:
                try:
                    ref = child.attrib['ref']
                except KeyError:
                    self.parse_error("the attribute 'ref' is required "
                                     "in a local attributeGroup", elem)
                    continue

                try:
                    attribute_group_qname = self.schema.resolve_qname(ref)
                except (KeyError, ValueError, RuntimeError) as err:
                    self.parse_error(err, elem)
                else:
                    if attribute_group_qname in attribute_group_refs:
                        self.parse_error("duplicated attributeGroup %r" % ref)
                    elif self.redefine is not None:
                        if attribute_group_qname == self.name:
                            if attribute_group_refs:
                                self.parse_error("in a redefinition the reference "
                                                 "to itself must be the first")
                            attribute_group_refs.append(attribute_group_qname)
                            attributes.update(self._attribute_group.items())
                            continue
                        elif not attribute_group_refs:
                            # May be an attributeGroup restriction with a ref to another group
                            if not any(e.tag == XSD_ATTRIBUTE_GROUP and ref == e.get('ref')
                                       for e in self.redefine.elem):
                                self.parse_error("attributeGroup ref=%r is not "
                                                 "in the redefined group" % ref)
                    elif attribute_group_qname == self.name and self.xsd_version == '1.0':
                        self.parse_error("Circular attribute groups not allowed in XSD 1.0")
                    attribute_group_refs.append(attribute_group_qname)

                    try:
                        base_attrs = self.maps.lookup_attribute_group(attribute_group_qname)
                    except LookupError:
                        self.parse_error("unknown attribute group %r" % child.attrib['ref'], elem)
                    else:
                        if not isinstance(base_attrs, tuple):
                            for name, attr in base_attrs.items():
                                if name not in attributes:
                                    attributes[name] = attr
                                elif name is not None:
                                    self.parse_error("multiple declaration for attribute "
                                                     "{!r}".format(name))
                                else:
                                    attributes[None] = attributes[None].copy()
                                    attributes[None].intersection(attr)

                        elif self.xsd_version == '1.0':
                            self.parse_error(
                                "Circular reference found between attribute groups "
                                "{!r} and {!r}".format(self.name, attribute_group_qname)
                            )

            elif self.name is not None:
                self.parse_error("(attribute | attributeGroup) expected, found %r." % child)

        # Check and copy base attributes
        if self.base_attributes is not None:
            wildcard = self.base_attributes.get(None)
            for name, attr in attributes.items():
                if name not in self.base_attributes:
                    if self.derivation != 'restriction':
                        continue
                    elif wildcard is None or not wildcard.is_matching(name, self.default_namespace):
                        self.parse_error("Unexpected attribute %r in restriction" % name)
                    continue

                base_attr = self.base_attributes[name]

                if name is None:
                    if self.derivation == 'extension':
                        try:
                            attr.union(base_attr)
                        except ValueError as err:
                            self.parse_error(err)
                    elif not attr.is_restriction(base_attr):
                        self.parse_error("Attribute wildcard is not a restriction "
                                         "of the base wildcard")
                    continue
                if self.derivation == 'restriction' and attr.type.name != XSD_ANY_SIMPLE_TYPE and \
                        not attr.type.is_derived(base_attr.type, 'restriction'):
                    self.parse_error("Attribute type is not a restriction "
                                     "of the base attribute type")
                if base_attr.use != 'optional' and attr.use == 'optional' or \
                        base_attr.use == 'required' and attr.use != 'required':
                    self.parse_error("Attribute %r: unmatched attribute use in restriction" % name)
                if base_attr.fixed is not None and \
                        attr.type.normalize(attr.fixed) != \
                        base_attr.type.normalize(base_attr.fixed):
                    self.parse_error("Attribute %r: derived attribute "
                                     "has a different fixed value" % name)
                if base_attr.inheritable is not attr.inheritable:
                    msg = "Attribute %r: attribute 'inheritable' value change in restriction"
                    self.parse_error(msg % name)

            if self.redefine is not None:
                pass  # In case of redefinition do not copy base attributes
            else:
                self._attribute_group.update(self.base_attributes.items())

        elif self.redefine is not None and not attribute_group_refs:
            for name, attr in self._attribute_group.items():
                if name is None:
                    continue
                elif name not in attributes:
                    if attr.use == 'required':
                        self.parse_error("Missing required attribute %r in "
                                         "redefinition restriction" % name)
                    continue
                if attr.use != 'optional' and attributes[name].use != attr.use:
                    self.parse_error("Attribute %r: unmatched attribute use in redefinition" % name)
                if attr.fixed is not None and attributes[name].fixed is None:
                    self.parse_error("Attribute %r: redefinition remove fixed constraint" % name)

            pos = 0
            keys = list(self._attribute_group.keys())
            for name in attributes:
                try:
                    next_pos = keys.index(name)
                except ValueError:
                    self.parse_error("Redefinition restriction contains "
                                     "additional attribute %r" % name)
                else:
                    if next_pos < pos:
                        self.parse_error("Wrong attribute order in redefinition restriction")
                        break
                    pos = next_pos
            self.clear()

        self._attribute_group.update(attributes)
        if None in self._attribute_group and None not in attributes \
                and self.derivation == 'restriction':
            wildcard = self._attribute_group[None].copy()
            wildcard.namespace = wildcard.not_namespace = wildcard.not_qname = ()
            self._attribute_group[None] = wildcard

        if self.xsd_version == '1.0':
            has_key = False
            for attr in self._attribute_group.values():
                if attr.name and attr.type.is_key():
                    if has_key:
                        self.parse_error("multiple ID attributes not allowed for XSD 1.0")
                        break
                    has_key = True

        elif self.parent is None and self.schema.default_attributes == self.name:
            self.schema.default_attributes = self

    @property
    def built(self):
        return True

    def parse_error(self, error, elem=None, validation=None):
        if self.parent is None:
            super(XsdAttributeGroup, self).parse_error(error, elem, validation)
        else:
            self.parent.parse_error(error, elem, validation)

    def iter_required(self):
        for k, v in self._attribute_group.items():
            if k is not None and v.use == 'required':
                yield k

    def iter_value_constraints(self, use_defaults=True):
        if use_defaults:
            for k, v in self._attribute_group.items():
                if k is None:
                    continue
                elif v.fixed is not None:
                    yield k, v.fixed
                elif v.default is not None:
                    yield k, v.default
        else:
            for k, v in self._attribute_group.items():
                if k is not None and v.fixed is not None:
                    yield k, v.fixed

    def iter_components(self, xsd_classes=None):
        if xsd_classes is None or isinstance(self, xsd_classes):
            yield self
        if self.ref is None:
            for attr in self.values():
                if attr.parent is not None:
                    yield from attr.iter_components(xsd_classes)

    def iter_decode(self, attrs, validation='lax', **kwargs):
        if not attrs and not self:
            return

        for k in filter(lambda x: x not in attrs, self.iter_required()):
            reason = "missing required attribute: %r" % k
            yield self.validation_error(validation, reason, attrs, **kwargs)

        kwargs['level'] = kwargs.get('level', 0) + 1
        use_defaults = kwargs.get('use_defaults', True)

        additional_attrs = [
            (k, v) for k, v in self.iter_value_constraints(use_defaults) if k not in attrs
        ]
        if additional_attrs:
            attrs = {k: v for k, v in attrs.items()}
            attrs.update(additional_attrs)

        if self.xsd_version == '1.0':
            kwargs['id_list'] = []

        filler = kwargs.get('filler')
        result_list = []
        for name, value in attrs.items():
            try:
                xsd_attribute = self[name]
            except KeyError:
                if get_namespace(name) == XSI_NAMESPACE:
                    try:
                        xsd_attribute = self.maps.lookup_attribute(name)
                    except LookupError:
                        try:
                            xsd_attribute = self[None]  # None key ==> anyAttribute
                            value = (name, value)
                        except KeyError:
                            reason = "%r is not an attribute of the XSI namespace." % name
                            yield self.validation_error(validation, reason, attrs, **kwargs)
                            continue
                else:
                    try:
                        xsd_attribute = self[None]  # None key ==> anyAttribute
                        value = (name, value)
                    except KeyError:
                        reason = "%r attribute not allowed for element." % name
                        yield self.validation_error(validation, reason, attrs, **kwargs)
                        continue
            else:
                if xsd_attribute.use == 'prohibited' and \
                        (None not in self or not self[None].is_matching(name)):
                    reason = "use of attribute %r is prohibited" % name
                    yield self.validation_error(validation, reason, attrs, **kwargs)

            for result in xsd_attribute.iter_decode(value, validation, **kwargs):
                if isinstance(result, XMLSchemaValidationError):
                    yield result
                elif result is None and filler is not None:
                    result_list.append((name, filler(xsd_attribute)))
                    break
                else:
                    result_list.append((name, result))
                    break

        if kwargs.get('fill_missing'):
            if filler is None:
                result_list.extend((k, None) for k in self._attribute_group
                                   if k is not None and k not in attrs)
            else:
                result_list.extend((k, filler(v)) for k, v in self._attribute_group.items()
                                   if k is not None and k not in attrs)

        yield result_list

    def iter_encode(self, attrs, validation='lax', **kwargs):
        if not attrs and not self:
            return

        for k in filter(lambda x: x not in attrs, self.iter_required()):
            reason = "missing required attribute: %r" % k
            yield self.validation_error(validation, reason, attrs, **kwargs)

        use_defaults = kwargs.get('use_defaults', True)
        additional_attrs = [
            (k, v) for k, v in self.iter_value_constraints(use_defaults) if k not in attrs
        ]
        if additional_attrs:
            attrs = {k: v for k, v in attrs.items()}
            attrs.update(additional_attrs)

        result_list = []
        for name, value in attrs.items():
            try:
                xsd_attribute = self[name]
            except KeyError:
                namespace = get_namespace(name) or self.target_namespace
                if namespace == XSI_NAMESPACE:
                    try:
                        xsd_attribute = self.maps.lookup_attribute(name)
                    except LookupError:
                        try:
                            xsd_attribute = self[None]  # None key ==> anyAttribute
                            value = (name, value)
                        except KeyError:
                            reason = "%r is not an attribute of the XSI namespace." % name
                            yield self.validation_error(validation, reason, attrs, **kwargs)
                            continue
                else:
                    try:
                        xsd_attribute = self[None]  # None key ==> anyAttribute
                        value = (name, value)
                    except KeyError:
                        reason = "%r attribute not allowed for element." % name
                        yield self.validation_error(validation, reason, attrs, **kwargs)
                        continue

            for result in xsd_attribute.iter_encode(value, validation, **kwargs):
                if isinstance(result, XMLSchemaValidationError):
                    yield result
                else:
                    if result is not None:
                        result_list.append((name, result))
                    break

        yield result_list
