# -*- coding: utf-8 -*-
# Copyright 2009-2013, Peter A. Bigot
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain a
# copy of the License at:
#
#            http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Helper classes that maintain the content model of XMLSchema in the binding
classes.

L{AttributeUse} and L{ElementDeclaration} record information associated with a binding
class, for example the types of values, the original XML QName or NCName, and
the Python field in which the values are stored.  They also provide the
low-level interface to set and get the corresponding values in a binding
instance.

L{Wildcard} holds content-related information used in the content model.
"""

import logging
import xml.dom

import pyxb
import pyxb.namespace
import pyxb.utils.fac
from pyxb.binding import basis
import pyxb.utils.utility
from pyxb.utils import six

_log = logging.getLogger(__name__)

class AttributeUse (pyxb.cscRoot):
    """A helper class that encapsulates everything we need to know
    about the way an attribute is used within a binding class.

    Attributes are stored internally as pairs C{(provided, value)}, where
    C{provided} is a boolean indicating whether a value for the attribute was
    provided externally, and C{value} is an instance of the attribute
    datatype.  The C{provided} flag is used to determine whether an XML
    attribute should be added to a created DOM node when generating the XML
    corresponding to a binding instance.
    """

    __name = None
    """ExpandedName of the attribute"""

    __id = None
    """Identifier used for this attribute within the owning class"""

    __key = None
    """Private Python attribute used in instances to hold the attribute value"""

    __dataType = None
    """The L{pyxb.binding.basis.simpleTypeDefinition} for values of the attribute"""

    __unicodeDefault = None
    """The default attribute value as a unicode string, or C{None}"""

    __defaultValue = None
    """The default value as an instance of L{__dataType}, or C{None}"""

    __fixed = False
    """C{True} if the attribute value cannot be changed"""

    __required = False
    """C{True} if the attribute must appear in every instance of the type"""

    __prohibited = False
    """C{True} if the attribute must not appear in any instance of the type"""

    def __init__ (self, name, id, key, data_type, unicode_default=None, fixed=False, required=False, prohibited=False):
        """Create an AttributeUse instance.

        @param name: The name by which the attribute is referenced in the XML
        @type name: L{pyxb.namespace.ExpandedName}

        @param id: The Python identifier for the attribute within the
        containing L{pyxb.basis.binding.complexTypeDefinition}.  This is a
        public identifier, derived from the local part of the attribute name
        and modified to be unique, and is usually used as the name of the
        attribute's inspector method.
        @type id: C{str}

        @param key: The string used to store the attribute
        value in the dictionary of the containing
        L{pyxb.basis.binding.complexTypeDefinition}.  This is mangled so
        that it is unique among and is treated as a Python private member.
        @type key: C{str}

        @param data_type: The class reference to the subclass of
        L{pyxb.binding.basis.simpleTypeDefinition} of which the attribute
        values must be instances.
        @type data_type: C{type}

        @keyword unicode_default: The default value of the attribute as
        specified in the schema, or None if there is no default attribute
        value.  The default value (of the keyword) is C{None}.
        @type unicode_default: C{unicode}

        @keyword fixed: If C{True}, indicates that the attribute, if present,
        must have the value that was given via C{unicode_default}.  The
        default value is C{False}.
        @type fixed: C{bool}

        @keyword required: If C{True}, indicates that the attribute must appear
        in the DOM node used to create an instance of the corresponding
        L{pyxb.binding.basis.complexTypeDefinition}.  The default value is
        C{False}.  No more that one of L{required} and L{prohibited} should be
        assigned C{True}.
        @type required: C{bool}

        @keyword prohibited: If C{True}, indicates that the attribute must
        B{not} appear in the DOM node used to create an instance of the
        corresponding L{pyxb.binding.basis.complexTypeDefinition}.  The
        default value is C{False}.  No more that one of L{required} and
        L{prohibited} should be assigned C{True}.
        @type prohibited: C{bool}

        @raise pyxb.SimpleTypeValueError: the L{unicode_default} cannot be used
        to initialize an instance of L{data_type}
        """

        self.__name = name
        self.__id = id
        self.__key = key
        self.__dataType = data_type
        self.__unicodeDefault = unicode_default
        if self.__unicodeDefault is not None:
            self.__defaultValue = self.__dataType.Factory(self.__unicodeDefault, _from_xml=True)
        self.__fixed = fixed
        self.__required = required
        self.__prohibited = prohibited
        super(AttributeUse, self).__init__()

    def name (self):
        """The expanded name of the element.

        @rtype: L{pyxb.namespace.ExpandedName}
        """
        return self.__name

    def defaultValue (self):
        """The default value of the attribute."""
        return self.__defaultValue

    def fixed (self):
        """C{True} iff the value of the attribute cannot be changed."""
        return self.__fixed

    def required (self):
        """C{True} iff the attribute must be assigned a value."""
        return self.__required

    def prohibited (self):
        """C{True} iff the attribute must not be assigned a value."""
        return self.__prohibited

    def provided (self, ctd_instance):
        """C{True} iff the given instance has been explicitly given a value
        for the attribute.

        This is used for things like only generating an XML attribute
        assignment when a value was originally given (even if that value
        happens to be the default).
        """
        return self.__getProvided(ctd_instance)

    def id (self):
        """Tag used within Python code for the attribute.

        This is not used directly in the default code generation template."""
        return self.__id

    def key (self):
        """String used as key within object dictionary when storing attribute value."""
        return self.__key

    def dataType (self):
        """The subclass of L{pyxb.binding.basis.simpleTypeDefinition} of which any attribute value must be an instance."""
        return self.__dataType

    def __getValue (self, ctd_instance):
        """Retrieve the value information for this attribute in a binding instance.

        @param ctd_instance: The instance object from which the attribute is to be retrieved.
        @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition}
        @return: C{(provided, value)} where C{provided} is a C{bool} and
        C{value} is C{None} or an instance of the attribute's datatype.

        """
        return getattr(ctd_instance, self.__key, (False, None))

    def __getProvided (self, ctd_instance):
        return self.__getValue(ctd_instance)[0]

    def value (self, ctd_instance):
        """Get the value of the attribute from the instance."""
        if self.__prohibited:
            raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance)
        return self.__getValue(ctd_instance)[1]

    def __setValue (self, ctd_instance, new_value, provided):
        return setattr(ctd_instance, self.__key, (provided, new_value))

    def reset (self, ctd_instance):
        """Set the value of the attribute in the given instance to be its
        default value, and mark that it has not been provided."""
        self.__setValue(ctd_instance, self.__defaultValue, False)

    def addDOMAttribute (self, dom_support, ctd_instance, element):
        """If this attribute as been set, add the corresponding attribute to the DOM element."""
        ( provided, value ) = self.__getValue(ctd_instance)
        if provided:
            dom_support.addAttribute(element, self.__name, value)
        return self

    def validate (self, ctd_instance):
        """Validate the instance against the requirements imposed by this
        attribute use.

        There is no return value; calls raise an exception if the content does
        not validate.

        @param ctd_instance : An instance of a complex type definition.

        @raise pyxb.ProhibitedAttributeError: when instance has attribute but must not
        @raise pyxb.MissingAttributeError: when instance lacks attribute but
        must have it (including when a required fixed-value attribute is
        missing).
        @raise pyxb.BatchContentValidationError: when instance has attribute but its value is not acceptable
        """
        (provided, value) = self.__getValue(ctd_instance)
        if value is not None:
            if self.__prohibited:
                raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance)
            if self.__required and not provided:
                assert self.__fixed
                raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance)
            self.__dataType._CheckValidValue(value)
            self.__dataType.XsdConstraintsOK(value)
        else:
            if self.__required:
                raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance)

    def set (self, ctd_instance, new_value, from_xml=False):
        """Set the value of the attribute.

        This validates the value against the data type, creating a new instance if necessary.

        @param ctd_instance: The binding instance for which the attribute
        value is to be set
        @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition}
        @param new_value: The value for the attribute
        @type new_value: Any value that is permitted as the input parameter to
        the C{Factory} method of the attribute's datatype.
        @param from_xml: Value C{True} iff the new_value is known to be in
        lexical space and must by converted by the type factory.  If C{False}
        (default) the value is only converted if it is not already an instance
        of the attribute's underlying type.
        """
        provided = True
        assert not isinstance(new_value, xml.dom.Node)
        if new_value is None:
            if self.__required:
                raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance)
            provided = False
        if self.__prohibited:
            raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance)
        if (new_value is not None) and (from_xml or not isinstance(new_value, self.__dataType)):
            new_value = self.__dataType.Factory(new_value, _from_xml=from_xml)
        if self.__fixed and (new_value != self.__defaultValue):
            raise pyxb.AttributeChangeError(type(ctd_instance), self.__name, ctd_instance)
        self.__setValue(ctd_instance, new_value, provided)
        return new_value

    def _description (self, name_only=False, user_documentation=True):
        if name_only:
            return six.text_type(self.__name)
        assert issubclass(self.__dataType, basis._TypeBinding_mixin)
        desc = [ six.text_type(self.__id), ': ', six.text_type(self.__name), ' (', self.__dataType._description(name_only=True, user_documentation=False), '), ' ]
        if self.__required:
            desc.append('required')
        elif self.__prohibited:
            desc.append('prohibited')
        else:
            desc.append('optional')
        if self.__defaultValue is not None:
            desc.append(', ')
            if self.__fixed:
                desc.append('fixed')
            else:
                desc.append('default')
            desc.extend(['=', self.__unicodeDefault ])
        return ''.join(desc)

class AutomatonConfiguration (object):
    """State for a L{pyxb.utils.fac.Automaton} monitoring content for an
    incrementally constructed complex type binding instance.

    @warning: This is not an implementation of
    L{pyxb.utils.fac.Configuration_ABC} because we need the L{step} function
    to return a different type of value."""

    # The binding instance for which content is being built
    __instance = None

    # The underlying configuration when the state is deterministic.  In this
    # case, all updates to the instance content corresponding to the current
    # state have been applied to the instance.  Note that while steps are
    # occurring this instance, as well as those in __multi, might be
    # references to sub-automata.
    __cfg = None

    # A list of pairs when the state is non-deterministic.  The first member
    # of the pair is the configuration; the second is a tuple of closures that
    # must be applied to the instance in order to store the content that was
    # accepted along the path to that configuration.  This is in order of
    # preference based on the location of path candidate declarations in the
    # defining schema.
    __multi = None

    PermittedNondeterminism = 20
    """The maximum amount of unresolved non-determinism that is acceptable.
    If the value is exceeded, a L{pyxb.ContentNondeterminismExceededError}
    exception will be raised."""

    def __init__ (self, instance):
        self.__instance = instance

    def reset (self):
        """Reset the automaton to its initial state.

        Subsequent transitions are expected based on candidate content to be
        supplied through the L{step} method."""
        self.__cfg = self.__instance._Automaton.newConfiguration()
        self.__multi = None

    def nondeterminismCount (self):
        """Return the number of pending configurations.

        The automaton is deterministic if exactly one configuration is
        available."""
        if self.__cfg is not None:
            assert self.__multi is None
            return 1
        return len(self.__multi)

    def step (self, value, element_decl):
        """Attempt a transition from the current state.

        @param value: the content to be supplied.  For success the value must
        be consistent with the recorded symbol (element or wildcard
        declaration) for a transition from the current automaton state.

        @param element_decl: optional
        L{pyxb.binding.content.ElementDeclaration} that is the preferred
        symbol for the transition.

        @return: the cardinal number of successful transitions from the
        current configuration based on the parameters."""

        sym = (value, element_decl)

        # Start with the current configuration(s), assuming we might see
        # non-determinism.
        new_multi = []
        if self.__multi is None:
            multi = [ (self.__cfg, ()) ]
        else:
            multi = self.__multi[:]
        # Collect the complete set of reachable configurations along with the
        # closures that will update the instance content based on the path.
        for (cfg, pending) in multi:
            cand = cfg.candidateTransitions(sym)
            for transition in cand:
                clone_map = {}
                ccfg = cfg.clone(clone_map)
                new_multi.append( (transition.apply(ccfg, clone_map), pending+(transition.consumedSymbol().consumingClosure(sym),)) )
        rv = len(new_multi)
        if 0 == rv:
            # No candidate transitions.  Do not change the state.
            return 0
        if 1 == rv:
            # Deterministic transition.  Save the configuration and apply the
            # corresponding updates.
            self.__multi = None
            (self.__cfg, actions) = new_multi[0]
            for fn in actions:
                fn(self.__instance)
        else:
            # Non-deterministic.  Save everything for subsequent resolution.
            if rv > self.PermittedNondeterminism:
                raise pyxb.ContentNondeterminismExceededError(self.__instance)
            self.__cfg = None
            self.__multi = new_multi
        return rv

    def resolveNondeterminism (self, prefer_accepting=True):
        """Resolve any non-determinism in the automaton state.

        If the automaton has reached a single configuration (was
        deterministic), this does nothing.

        If multiple candidate configurations are available, the best one is
        selected and applied, updating the binding instance with the pending
        content.

        "Best" in this case is determined by optionally eliminating
        configurations that are not accepting, then selecting the path where
        the initial transition sorts highest using the binding sort key (based
        on position in the original schema).

        @keyword prefer_accepting: eliminate non-accepting paths if any
        accepting path is present."""
        if self.__multi is None:
            return
        assert self.__cfg is None
        multi = self.__multi
        if prefer_accepting:
            multi = list(filter(lambda _ts: _ts[0].isAccepting(), self.__multi))
            if 0 == len(multi):
                multi = self.__multi
        # step() will not create an empty multi list, so cannot get here with
        # no configurations available unless nobody even reset the
        # configuration, which would be a usage error.
        assert 0 < len(multi)
        if 1 < len(multi):
            desc = self.__instance._ExpandedName
            if desc is None:
                desc = type(self.__instance)
            _log.warning('Multiple accepting paths for %s', desc)
            '''
            for (cfg, actions) in multi:
                foo = type(self.__instance)()
                for fn in actions:
                    fn(foo)
                print '1: %s ; 2 : %s ; wc: %s' % (foo.first, foo.second, foo.wildcardElements())
            '''
        (self.__cfg, actions) = multi[0]
        self.__multi = None
        for fn in actions:
            fn(self.__instance)

    def acceptableContent (self):
        """Return the sequence of acceptable symbols at this state.

        The list comprises the L{pyxb.binding.content.ElementUse} and
        L{pyxb.binding.content.WildcardUse} instances that are used to
        validate proposed symbols, in preferred order."""
        rv = []
        seen = set()
        multi = self.__multi
        if multi is None:
            multi = [ self.__cfg]
        for cfg in multi:
            for u in cfg.acceptableSymbols():
                if not (u in seen):
                    rv.append(u)
                    seen.add(u)
        return rv

    def isAccepting (self, raise_if_rejecting=False):
        """Return C{True} iff the automaton is in an accepting state.

        If the automaton has unresolved nondeterminism, it is resolved first,
        preferring accepting states."""
        self.resolveNondeterminism(True)
        cfg = self.__cfg
        while cfg.superConfiguration is not None:
            cfg = cfg.superConfiguration
        return cfg.isAccepting()

    def _diagnoseIncompleteContent (self, symbols, symbol_set):
        """Check for incomplete content.

        @raises pyxb.IncompleteElementContentError: if a non-accepting state is found
        @return: the topmost configuration (if accepting)
        """
        # Exit out of any sub-configurations (they might be accepting while
        # the superConfiguration is not)
        cfg = self.__cfg
        while cfg.isAccepting() and (cfg.superConfiguration is not None):
            cfg = cfg.superConfiguration
        if not cfg.isAccepting():
            raise pyxb.IncompleteElementContentError(self.__instance, cfg, symbols, symbol_set)
        return cfg

    def diagnoseIncompleteContent (self):
        """Generate the exception explaining why the content is incomplete."""
        return self._diagnoseIncompleteContent(None, None)

    __preferredSequenceIndex = 0
    __preferredPendingSymbol = None
    __pendingNonElementContent = None

    def __resetPreferredSequence (self, instance):
        self.__preferredSequenceIndex = 0
        self.__preferredPendingSymbol = None
        self.__pendingNonElementContent = None
        vc = instance._validationConfig
        preferred_sequence = None
        if (vc.ALWAYS == vc.contentInfluencesGeneration) or (instance._ContentTypeTag == instance._CT_MIXED and vc.MIXED_ONLY == vc.contentInfluencesGeneration):
            preferred_sequence = instance.orderedContent()
            if instance._ContentTypeTag == instance._CT_MIXED:
                self.__pendingNonElementContent = []
        return preferred_sequence

    def __discardPreferredSequence (self, preferred_sequence, pi=None):
        """Extract non-element content from the sequence and return C{None}."""
        if pi is None:
            pi = self.__preferredSequenceIndex
        nec = self.__pendingNonElementContent
        if nec is not None:
            for csym in preferred_sequence[pi:]:
                if isinstance(csym, pyxb.binding.basis.NonElementContent):
                    nec.append(csym)
        return None

    def __processPreferredSequence (self, preferred_sequence, symbol_set, vc):
        pi = self.__preferredSequenceIndex
        psym = self.__preferredPendingSymbol
        nec = self.__pendingNonElementContent
        if psym is not None:
            _log.info('restoring %s', psym)
            self.__preferredPendingSymbol = None
        while psym is None:
            if pi >= len(preferred_sequence):
                preferred_sequence = self.__discardPreferredSequence(preferred_sequence, pi)
                break
            csym = preferred_sequence[pi]
            pi += 1
            if (nec is not None) and isinstance(csym, pyxb.binding.basis.NonElementContent):
                nec.append(csym)
                continue
            vals = symbol_set.get(csym.elementDeclaration, [])
            if csym.value in vals:
                psym = ( csym.value, csym.elementDeclaration )
                break
            if psym is None:
                # Orphan encountered; response?
                _log.info('orphan %s in content', csym)
                if vc.IGNORE_ONCE == vc.orphanElementInContent:
                    continue
                if vc.GIVE_UP == vc.orphanElementInContent:
                    preferred_sequence = self.__discardPreferredSequence(preferred_sequence, pi)
                    break
                raise pyxb.OrphanElementContentError(self.__instance, csym)
        self.__preferredSequenceIndex = pi
        return (preferred_sequence, psym)

    def sequencedChildren (self):
        """Implement L{pyxb.binding.basis.complexTypeDefinition._validatedChildren}.

        Go there for the interface.
        """

        # We need a fresh automaton configuration corresponding to the type of
        # the binding instance.
        self.reset()
        cfg = self.__cfg

        # The validated sequence
        symbols = []

        # How validation should be done
        instance = self.__instance
        vc = instance._validationConfig

        # The available content, in a map from ElementDeclaration to in-order
        # values.  The key None corresponds to the wildcard content.  Keys are
        # removed when their corresponding content is exhausted.
        symbol_set = instance._symbolSet()

        # The preferred sequence to use, if desired.
        preferred_sequence = self.__resetPreferredSequence(instance)

        # A reference to the data structure holding non-element content.  This
        # is None unless mixed content is allowed, in which case it is a list.
        # The same list is used for the entire operation, though it is reset
        # to be empty after transferring material to the output sequence.
        nec = self.__pendingNonElementContent

        psym = None
        while symbol_set:
            # Find the first acceptable transition.  If there's a preferred
            # symbol to use, try it first.
            selected_xit = None
            psym = None
            if preferred_sequence is not None:
                (preferred_sequence, psym) = self.__processPreferredSequence(preferred_sequence, symbol_set, vc)
            candidates = cfg.candidateTransitions(psym)
            for xit in candidates:
                csym = xit.consumedSymbol()
                if isinstance(csym, ElementUse):
                    ed = csym.elementDeclaration()
                elif isinstance(csym, WildcardUse):
                    ed = None
                else:
                    assert False
                # Check whether we have content that matches the symbol
                matches = symbol_set.get(ed)
                if matches is None:
                    continue
                if not csym.match((matches[0], ed)):
                    continue
                # Commit to this transition and append the selected content
                # after any pending non-element content that is released due
                # to a matched preferred symbol.
                value = matches.pop(0)
                if (psym is not None) and (nec is not None):
                    symbols.extend(nec)
                    nec[:] = []
                symbols.append(basis.ElementContent(csym.matchValue( (value, ed) ), ed))
                selected_xit = xit
                if 0 == len(matches):
                    del symbol_set[ed]
                break
            if selected_xit is None:
                if psym is not None:
                    # Suggestion from content did not work
                    _log.info('invalid %s in content', psym)
                    if vc.IGNORE_ONCE == vc.invalidElementInContent:
                        continue
                    if vc.GIVE_UP == vc.invalidElementInContent:
                        preferred_sequence = self.__discardPreferredSequence(preferred_sequence)
                        continue
                    raise pyxb.InvalidPreferredElementContentError(self.__instance, cfg, symbols, symbol_set, psym)
                break
            cfg = selected_xit.apply(cfg)
        cfg = self._diagnoseIncompleteContent(symbols, symbol_set)
        if symbol_set:
            raise pyxb.UnprocessedElementContentError(self.__instance, cfg, symbols, symbol_set)
        # Validate any remaining material in the preferred sequence.  This
        # also extracts remaining non-element content.  Note there are
        # no more symbols, so any remaining element content is orphan.
        while preferred_sequence is not None:
            (preferred_sequence, psym) = self.__processPreferredSequence(preferred_sequence, symbol_set, vc)
            if psym is not None:
                if not (vc.orphanElementInContent in ( vc.IGNORE_ONCE, vc.GIVE_UP )):
                    raise pyxb.OrphanElementContentError(self.__instance, psym.value)
        if nec is not None:
            symbols.extend(nec)
        return symbols

class _FACSymbol (pyxb.utils.fac.SymbolMatch_mixin):
    """Base class for L{pyxb.utils.fac.Symbol} instances associated with PyXB content models.

    This holds the location in the schema of the L{ElementUse} or
    L{WildcardUse} and documents the methods expected of its children."""

    __xsdLocation = None

    def xsdLocation (self):
        return self.__xsdLocation

    def matchValue (self, sym):
        """Return the value accepted by L{match} for this symbol.

        A match for an element declaration might have resulted in a type
        change for the value (converting it to an acceptable type).  There is
        no safe place to cache the compatible value calculated in the match
        while other candidates are being considered, so we need to
        re-calculate it if the transition is taken.

        If the match could not have changed the value, the value from the
        symbol may be returned immediately."""
        raise NotImplementedError('%s._matchValue' % (type(self).__name__,))

    def consumingClosure (self, sym):
        """Create a closure that will apply the value from C{sym} to a to-be-supplied instance.

        This is necessary for non-deterministic automata, where we can't store
        the value into the instance field until we know that the transition
        will be taken:

        @return: A closure that takes a L{complexTypeDefinition} instance and
        stores the value from invoking L{matchValue} on C{sym} into the
        appropriate slot."""
        raise NotImplementedError('%s._consumingClosure' % (type(self).__name__,))

    def __init__ (self, xsd_location):
        """@param xsd_location: the L{location<pyxb.utils.utility.Location>} of the element use or wildcard declaration."""
        self.__xsdLocation = xsd_location
        super(_FACSymbol, self).__init__()

class ElementUse (_FACSymbol):
    """Information about a schema element declaration reference.

    This is used by the FAC content model to identify the location
    within a schema at which an element use appears.  The L{ElementDeclaration}
    is not sufficient since multiple uses in various schema, possibly in
    different namespaces, may refer to the same declaration but be independent
    uses.
    """

    __elementDeclaration = None

    def elementDeclaration (self):
        """Return the L{element declaration<pyxb.binding.content.ElementDeclaration>} associated with the use."""
        return self.__elementDeclaration

    def elementBinding (self):
        """Return the L{element binding<pyxb.binding.content.ElementDeclaration.elementBinding>} associated with the use.

        Equivalent to L{elementDeclaration}().L{elementBinding()<pyxb.binding.content.ElementDeclaration.elementBinding>}."""
        return self.__elementDeclaration.elementBinding()

    def typeDefinition (self):
        """Return the element type.

        Equivalent to L{elementDeclaration}().L{elementBinding()<pyxb.binding.content.ElementDeclaration.elementBinding>}.L{typeDefinition()<pyxb.binding.basis.element.typeDefinition>}."""
        return self.__elementDeclaration.elementBinding().typeDefinition()

    def __init__ (self, element_declaration, xsd_location):
        super(ElementUse, self).__init__(xsd_location)
        self.__elementDeclaration = element_declaration

    def matchValue (self, sym):
        (value, element_decl) = sym
        (rv, value) = self.__elementDeclaration._matches(value, element_decl)
        assert rv
        return value

    def consumingClosure (self, sym):
        # Defer the potentially-expensive re-invocation of matchValue until
        # the closure is applied.
        return lambda _inst,_eu=self,_sy=sym: _eu.__elementDeclaration.setOrAppend(_inst, _eu.matchValue(_sy))

    def match (self, symbol):
        """Satisfy L{pyxb.utils.fac.SymbolMatch_mixin}.

        Determine whether the proposed content encapsulated in C{symbol} is
        compatible with the element declaration.  If so, the accepted value is
        cached internally and return C{True}; otherwise return C{False}.

        @param symbol: a pair C{(value, element_decl)}.
        L{pyxb.binding.content.ElementDeclaration._matches} is used to
        determine whether the proposed content is compatible with this element
        declaration."""
        (value, element_decl) = symbol
        # NB: this call may change value to be compatible.  Unfortunately, we
        # can't reliably cache that converted value, so just ignore it and
        # we'll recompute it if the candidate transition is taken.
        (rv, value) = self.__elementDeclaration._matches(value, element_decl)
        return rv

    def __str__ (self):
        return '%s per %s' % (self.__elementDeclaration.name(), self.xsdLocation())

class WildcardUse (_FACSymbol):
    """Information about a schema wildcard element.

    This is functionally parallel to L{ElementUse}, but references a
    L{Wildcard} that is unique to this instance.  That L{Wildcard} is not
    incorporated into this class is an artifact of the evolution of PyXB."""

    __wildcardDeclaration = None

    def wildcardDeclaration (self):
        return self.__wildcardDeclaration

    def matchValue (self, sym):
        (value, element_decl) = sym
        return value

    def consumingClosure (self, sym):
        """Create a closure that will apply the value accepted by L{match} to a to-be-supplied instance."""
        return lambda _inst,_av=self.matchValue(sym): _inst._appendWildcardElement(_av)

    def match (self, symbol):
        """Satisfy L{pyxb.utils.fac.SymbolMatch_mixin}.

        Determine whether the proposed content encapsulated in C{symbol} is
        compatible with the wildcard declaration.  If so, the accepted value
        is cached internally and return C{True}; otherwise return C{False}.

        @param symbol: a pair C{(value, element_decl)}.
        L{pyxb.binding.content.Wildcard.matches} is used to determine whether
        the proposed content is compatible with this wildcard.
        """
        (value, element_decl) = symbol
        return self.__wildcardDeclaration.matches(None, value)

    def __init__ (self, wildcard_declaration, xsd_location):
        super(WildcardUse, self).__init__(xsd_location)
        self.__wildcardDeclaration = wildcard_declaration

    def __str__ (self):
        return 'xs:any per %s' % (self.xsdLocation(),)

import collections

# Do not inherit from list; that's obscene, and could cause problems with the
# internal assumptions made by Python.  Instead delegate everything to an
# instance of list that's held internally.  Inherit from the ABC that
# represents list-style data structures so we can identify both lists and
# these things which are not lists.
@pyxb.utils.utility.BackfillComparisons
class _PluralBinding (collections.MutableSequence):
    """Helper for element content that supports multiple occurences.

    This is an adapter for Python list.  Any operation that can mutate an item
    in the list ensures the stored value is compatible with the element for
    which the list holds values."""

    __list = None
    __elementBinding = None

    def __init__ (self, *args, **kw):
        element_binding = kw.pop('element_binding', None)
        if not isinstance(element_binding, basis.element):
            raise ValueError()
        self.__elementBinding = element_binding
        self.__list = []
        self.extend(args)

    def __convert (self, v):
        return self.__elementBinding.compatibleValue(v)

    def __len__ (self):
        return self.__list.__len__()

    def __getitem__ (self, key):
        return self.__list.__getitem__(key)

    def __setitem__ (self, key, value):
        if isinstance(key, slice):
            self.__list.__setitem__(key, [ self.__convert(_v) for _v in value])
        else:
            self.__list.__setitem__(key, self.__convert(value))

    def __delitem__ (self, key):
        self.__list.__delitem__(key)

    def __iter__ (self):
        return self.__list.__iter__()

    def __reversed__ (self):
        return self.__list.__reversed__()

    def __contains__ (self, item):
        return self.__list.__contains__(item)

    # The mutable sequence type methods
    def append (self, x):
        self.__list.append(self.__convert(x))

    def extend (self, x):
        self.__list.extend(map(self.__convert, x))

    def count (self, x):
        return self.__list.count(x)

    def index (self, x, i=0, j=-1):
        return self.__list.index(x, i, j)

    def insert (self, i, x):
        self.__list.insert(i, self.__convert(x))

    def pop (self, i=-1):
        return self.__list.pop(i)

    def remove (self, x):
        self.__list.remove(x)

    def reverse (self):
        self.__list.reverse()

    def sort (self, key=None, reverse=False):
        self.__list.sort(key=key, reverse=reverse)

    def __str__ (self):
        return self.__list.__str__()

    def __hash__ (self):
        return hash(self.__list__)

    def __eq__ (self, other):
        if other is None:
            return False
        if isinstance(other, _PluralBinding):
            return self.__list.__eq__(other.__list)
        return self.__list.__eq__(other)

    def __lt__ (self, other):
        if other is None:
            return False
        if isinstance(other, _PluralBinding):
            return self.__list.__lt__(other.__list)
        return self.__list.__lt__(other)

class ElementDeclaration (object):
    """Aggregate the information relevant to an element of a complex type.

    This includes the L{original tag name<name>}, the spelling of L{the
    corresponding object in Python <id>}, an L{indicator<isPlural>} of whether
    multiple instances might be associated with the field, and other relevant
    information.
    """

    def xsdLocation (self):
        """The L{location<pyxb.utils.utility.Location>} in the schema where the
        element was declared.

        Note that this is not necessarily the same location as its use."""
        return self.__xsdLocation
    __xsdLocation = None

    def name (self):
        """The expanded name of the element.

        @rtype: L{pyxb.namespace.ExpandedName}
        """
        return self.__name
    __name = None

    def id (self):
        """The string name of the binding class field used to hold the element
        values.

        This is the user-visible name, and excepting disambiguation will be
        equal to the local name of the element."""
        return self.__id
    __id = None

    # The dictionary key used to identify the value of the element.  The value
    # is the same as that used for private member variables in the binding
    # class within which the element declaration occurred.
    __key = None

    def elementBinding (self):
        """The L{basis.element} instance identifying the information
        associated with the element declaration.
        """
        return self.__elementBinding
    def _setElementBinding (self, element_binding):
        # Set the element binding for this use.  Only visible at all because
        # we have to define the uses before the element instances have been
        # created.
        self.__elementBinding = element_binding
        return self
    __elementBinding = None

    def isPlural (self):
        """True iff the content model indicates that more than one element
        can legitimately belong to this use.

        This includes elements in particles with maxOccurs greater than one,
        and when multiple elements with the same NCName are declared in the
        same type.
        """
        return self.__isPlural
    __isPlural = False

    def __init__ (self, name, id, key, is_plural, location, element_binding=None):
        """Create an ElementDeclaration instance.

        @param name: The name by which the element is referenced in the XML
        @type name: L{pyxb.namespace.ExpandedName}

        @param id: The Python name for the element within the containing
        L{pyxb.basis.binding.complexTypeDefinition}.  This is a public
        identifier, albeit modified to be unique, and is usually used as the
        name of the element's inspector method or property.
        @type id: C{str}

        @param key: The string used to store the element
        value in the dictionary of the containing
        L{pyxb.basis.binding.complexTypeDefinition}.  This is mangled so
        that it is unique among and is treated as a Python private member.
        @type key: C{str}

        @param is_plural: If C{True}, documents for the corresponding type may
        have multiple instances of this element.  As a consequence, the value
        of the element will be a list.  If C{False}, the value will be C{None}
        if the element is absent, and a reference to an instance of the type
        identified by L{pyxb.binding.basis.element.typeDefinition} if present.
        @type is_plural: C{bool}

        @param element_binding: Reference to the class that serves as the
        binding for the element.
        """
        self.__name = name
        self.__id = id
        self.__key = key
        self.__isPlural = is_plural
        self.__elementBinding = element_binding
        super(ElementDeclaration, self).__init__()

    def defaultValue (self):
        """Return the default value for this element.

        For plural values, this is an empty collection.  For non-plural
        values, it is C{None} unless the element has a default value, in which
        case it is that value.

        @todo: This should recursively support filling in default content, as
        when the plural list requires a non-zero minimum number of entries.
        """
        if self.isPlural():
            return _PluralBinding(element_binding=self.__elementBinding)
        return self.__elementBinding.defaultValue()

    def resetValue (self):
        """Return the reset value for this element.

        For plural values, this is an empty collection.  For non-plural
        values, it is C{None}, corresponding to absence of an assigned
        element.
        """
        if self.isPlural():
            return _PluralBinding(element_binding=self.__elementBinding)
        return None

    def value (self, ctd_instance):
        """Return the value for this use within the given instance.

        Note that this is the L{resetValue()}, not the L{defaultValue()}, if
        the element has not yet been assigned a value."""
        return getattr(ctd_instance, self.__key, self.resetValue())

    def reset (self, ctd_instance):
        """Set the value for this use in the given element to its default."""
        setattr(ctd_instance, self.__key, self.resetValue())
        return self

    def set (self, ctd_instance, value):
        """Set the value of this element in the given instance."""
        if value is None:
            return self.reset(ctd_instance)
        if ctd_instance._isNil():
            raise pyxb.ContentInNilInstanceError(ctd_instance, value)
        assert self.__elementBinding is not None
        if ctd_instance._validationConfig.forBinding or isinstance(value, pyxb.BIND):
            value = self.__elementBinding.compatibleValue(value, is_plural=self.isPlural())
        setattr(ctd_instance, self.__key, value)
        ctd_instance._addContent(basis.ElementContent(value, self))
        return self

    def setOrAppend (self, ctd_instance, value):
        """Invoke either L{set} or L{append}, depending on whether the element
        use is plural."""
        if self.isPlural():
            return self.append(ctd_instance, value)
        return self.set(ctd_instance, value)

    def append (self, ctd_instance, value):
        """Add the given value as another instance of this element within the binding instance.
        @raise pyxb.StructuralBadDocumentError: invoked on an element use that is not plural
        """
        if ctd_instance._isNil():
            raise pyxb.ContentInNilInstanceError(ctd_instance, value)
        if not self.isPlural():
            raise pyxb.NonPluralAppendError(ctd_instance, self, value)
        values = self.value(ctd_instance)
        if ctd_instance._validationConfig.forBinding:
            value = self.__elementBinding.compatibleValue(value)
        values.append(value)
        ctd_instance._addContent(basis.ElementContent(value, self))
        return values

    def toDOM (self, dom_support, parent, value):
        """Convert the given value to DOM as an instance of this element.

        @param dom_support: Helper for managing DOM properties
        @type dom_support: L{pyxb.utils.domutils.BindingDOMSupport}
        @param parent: The DOM node within which this element should be generated.
        @type parent: C{xml.dom.Element}
        @param value: The content for this element.  May be text (if the
        element allows mixed content), or an instance of
        L{basis._TypeBinding_mixin}.

        @raise pyxb.AbstractElementError: the binding to be used is abstract
        """
        if isinstance(value, basis._TypeBinding_mixin):
            element_binding = self.__elementBinding
            if value._substitutesFor(element_binding):
                element_binding = value._element()
            assert element_binding is not None
            if element_binding.abstract():
                raise pyxb.AbstractElementError(self, value)
            element = dom_support.createChildElement(element_binding.name(), parent)
            elt_type = element_binding.typeDefinition()
            val_type = type(value)
            if isinstance(value, basis.complexTypeDefinition):
                if not (isinstance(value, elt_type) or elt_type._RequireXSIType(val_type)):
                    raise pyxb.LogicError('toDOM with implicit value type %s unrecoverable from %s' % (type(value), elt_type))
            else:
                if isinstance(value, basis.STD_union) and isinstance(value, elt_type._MemberTypes):
                    val_type = elt_type
            if dom_support.requireXSIType() or elt_type._RequireXSIType(val_type):
                dom_support.addAttribute(element, pyxb.namespace.XMLSchema_instance.createExpandedName('type'), value._ExpandedName)
            value._toDOM_csc(dom_support, element)
        elif isinstance(value, six.string_types):
            element = dom_support.createChildElement(self.name(), parent)
            element.appendChild(dom_support.document().createTextNode(value))
        else:
            raise pyxb.LogicError('toDOM with unrecognized value type %s: %s' % (type(value), value))

    def _description (self, name_only=False, user_documentation=True):
        if name_only:
            return six.text_type(self.__name)
        desc = [ six.text_type(self.__id), ': ']
        if self.isPlural():
            desc.append('MULTIPLE ')
        desc.append(self.elementBinding()._description(user_documentation=user_documentation))
        return six.u('').join(desc)

    def _matches (self, value, element_decl):
        accept = False
        if element_decl == self:
            accept = True
        elif element_decl is not None:
            # If there's a known element, and it's not this one, the content
            # does not match.  This assumes we handled xsi:type and
            # substitution groups earlier, which may be true.
            accept = False
        elif isinstance(value, xml.dom.Node):
            # If we haven't been able to identify an element for this before,
            # then we don't recognize it, and will have to treat it as a
            # wildcard.
            accept = False
        else:
            # A foreign value which might be usable if we can convert
            # it to a compatible value trivially.
            try:
                value = self.__elementBinding.compatibleValue(value, _convert_string_values=False)
                accept = True
            except (pyxb.ValidationError, pyxb.BindingError):
                pass
        return (accept, value)

    def __str__ (self):
        return 'ED.%s@%x' % (self.__name, id(self))


class Wildcard (object):
    """Placeholder for wildcard objects."""

    NC_any = '##any'            #<<< The namespace constraint "##any"
    NC_not = '##other'          #<<< A flag indicating constraint "##other"
    NC_targetNamespace = '##targetNamespace' #<<< A flag identifying the target namespace
    NC_local = '##local'        #<<< A flag indicating the namespace must be absent

    __namespaceConstraint = None
    def namespaceConstraint (self):
        """A constraint on the namespace for the wildcard.

        Valid values are:

         - L{Wildcard.NC_any}
         - A tuple ( L{Wildcard.NC_not}, a L{namespace<pyxb.namespace.Namespace>} instance )
         - set(of L{namespace<pyxb.namespace.Namespace>} instances)

        Namespaces are represented by their URIs.  Absence is
        represented by C{None}, both in the "not" pair and in the set.
        """
        return self.__namespaceConstraint

    PC_skip = 'skip'
    """No namespace constraint is applied to the wildcard."""

    PC_lax = 'lax'
    """Validate against available uniquely determined declaration."""

    PC_strict = 'strict'
    """Validate against declaration or xsi:type, which must be available."""

    __processContents = None
    """One of L{PC_skip}, L{PC_lax}, L{PC_strict}."""
    def processContents (self):
        """Indicate how this wildcard's contents should be processed."""
        return self.__processContents

    def __normalizeNamespace (self, nsv):
        if nsv is None:
            return None
        if isinstance(nsv, six.string_types):
            nsv = pyxb.namespace.NamespaceForURI(nsv, create_if_missing=True)
        assert isinstance(nsv, pyxb.namespace.Namespace), 'unexpected non-namespace %s' % (nsv,)
        return nsv

    def __init__ (self, *args, **kw):
        """
        @keyword namespace_constraint: Required namespace constraint(s)
        @keyword process_contents: Required"""

        # Namespace constraint and process contents are required parameters.
        nsc = kw['namespace_constraint']
        if isinstance(nsc, tuple):
            nsc = (nsc[0], self.__normalizeNamespace(nsc[1]))
        elif isinstance(nsc, set):
            nsc = set([ self.__normalizeNamespace(_uri) for _uri in nsc ])
        self.__namespaceConstraint = nsc
        self.__processContents = kw['process_contents']
        super(Wildcard, self).__init__()

    def matches (self, instance, value):
        """Return True iff the value is a valid match against this wildcard.

        Validation per U{Wildcard allows Namespace Name<http://www.w3.org/TR/xmlschema-1/#cvc-wildcard-namespace>}.
        """

        ns = None
        if isinstance(value, xml.dom.Node):
            if value.namespaceURI is not None:
                ns = pyxb.namespace.NamespaceForURI(value.namespaceURI)
        elif isinstance(value, basis._TypeBinding_mixin):
            elt = value._element()
            if elt is not None:
                ns = elt.name().namespace()
            else:
                ns = value._ExpandedName.namespace()
        else:
            # Assume that somebody will handle the conversion to xs:anyType
            pass
        if isinstance(ns, pyxb.namespace.Namespace) and ns.isAbsentNamespace():
            ns = None
        if self.NC_any == self.__namespaceConstraint:
            return True
        if isinstance(self.__namespaceConstraint, tuple):
            (_, constrained_ns) = self.__namespaceConstraint
            assert self.NC_not == _
            if ns is None:
                return False
            if constrained_ns == ns:
                return False
            return True
        return ns in self.__namespaceConstraint

## Local Variables:
## fill-column:78
## End:

