1# -*- coding: utf-8 -*-
2# Copyright 2009-2013, Peter A. Bigot
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain a
6# copy of the License at:
7#
8#            http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""Helper classes that maintain the content model of XMLSchema in the binding
17classes.
18
19L{AttributeUse} and L{ElementDeclaration} record information associated with a binding
20class, for example the types of values, the original XML QName or NCName, and
21the Python field in which the values are stored.  They also provide the
22low-level interface to set and get the corresponding values in a binding
23instance.
24
25L{Wildcard} holds content-related information used in the content model.
26"""
27
28import logging
29import xml.dom
30
31import pyxb
32import pyxb.namespace
33import pyxb.utils.fac
34from pyxb.binding import basis
35import pyxb.utils.utility
36from pyxb.utils import six
37
38_log = logging.getLogger(__name__)
39
40class AttributeUse (pyxb.cscRoot):
41    """A helper class that encapsulates everything we need to know
42    about the way an attribute is used within a binding class.
43
44    Attributes are stored internally as pairs C{(provided, value)}, where
45    C{provided} is a boolean indicating whether a value for the attribute was
46    provided externally, and C{value} is an instance of the attribute
47    datatype.  The C{provided} flag is used to determine whether an XML
48    attribute should be added to a created DOM node when generating the XML
49    corresponding to a binding instance.
50    """
51
52    __name = None
53    """ExpandedName of the attribute"""
54
55    __id = None
56    """Identifier used for this attribute within the owning class"""
57
58    __key = None
59    """Private Python attribute used in instances to hold the attribute value"""
60
61    __dataType = None
62    """The L{pyxb.binding.basis.simpleTypeDefinition} for values of the attribute"""
63
64    __unicodeDefault = None
65    """The default attribute value as a unicode string, or C{None}"""
66
67    __defaultValue = None
68    """The default value as an instance of L{__dataType}, or C{None}"""
69
70    __fixed = False
71    """C{True} if the attribute value cannot be changed"""
72
73    __required = False
74    """C{True} if the attribute must appear in every instance of the type"""
75
76    __prohibited = False
77    """C{True} if the attribute must not appear in any instance of the type"""
78
79    def __init__ (self, name, id, key, data_type, unicode_default=None, fixed=False, required=False, prohibited=False):
80        """Create an AttributeUse instance.
81
82        @param name: The name by which the attribute is referenced in the XML
83        @type name: L{pyxb.namespace.ExpandedName}
84
85        @param id: The Python identifier for the attribute within the
86        containing L{pyxb.basis.binding.complexTypeDefinition}.  This is a
87        public identifier, derived from the local part of the attribute name
88        and modified to be unique, and is usually used as the name of the
89        attribute's inspector method.
90        @type id: C{str}
91
92        @param key: The string used to store the attribute
93        value in the dictionary of the containing
94        L{pyxb.basis.binding.complexTypeDefinition}.  This is mangled so
95        that it is unique among and is treated as a Python private member.
96        @type key: C{str}
97
98        @param data_type: The class reference to the subclass of
99        L{pyxb.binding.basis.simpleTypeDefinition} of which the attribute
100        values must be instances.
101        @type data_type: C{type}
102
103        @keyword unicode_default: The default value of the attribute as
104        specified in the schema, or None if there is no default attribute
105        value.  The default value (of the keyword) is C{None}.
106        @type unicode_default: C{unicode}
107
108        @keyword fixed: If C{True}, indicates that the attribute, if present,
109        must have the value that was given via C{unicode_default}.  The
110        default value is C{False}.
111        @type fixed: C{bool}
112
113        @keyword required: If C{True}, indicates that the attribute must appear
114        in the DOM node used to create an instance of the corresponding
115        L{pyxb.binding.basis.complexTypeDefinition}.  The default value is
116        C{False}.  No more that one of L{required} and L{prohibited} should be
117        assigned C{True}.
118        @type required: C{bool}
119
120        @keyword prohibited: If C{True}, indicates that the attribute must
121        B{not} appear in the DOM node used to create an instance of the
122        corresponding L{pyxb.binding.basis.complexTypeDefinition}.  The
123        default value is C{False}.  No more that one of L{required} and
124        L{prohibited} should be assigned C{True}.
125        @type prohibited: C{bool}
126
127        @raise pyxb.SimpleTypeValueError: the L{unicode_default} cannot be used
128        to initialize an instance of L{data_type}
129        """
130
131        self.__name = name
132        self.__id = id
133        self.__key = key
134        self.__dataType = data_type
135        self.__unicodeDefault = unicode_default
136        if self.__unicodeDefault is not None:
137            self.__defaultValue = self.__dataType.Factory(self.__unicodeDefault, _from_xml=True)
138        self.__fixed = fixed
139        self.__required = required
140        self.__prohibited = prohibited
141        super(AttributeUse, self).__init__()
142
143    def name (self):
144        """The expanded name of the element.
145
146        @rtype: L{pyxb.namespace.ExpandedName}
147        """
148        return self.__name
149
150    def defaultValue (self):
151        """The default value of the attribute."""
152        return self.__defaultValue
153
154    def fixed (self):
155        """C{True} iff the value of the attribute cannot be changed."""
156        return self.__fixed
157
158    def required (self):
159        """C{True} iff the attribute must be assigned a value."""
160        return self.__required
161
162    def prohibited (self):
163        """C{True} iff the attribute must not be assigned a value."""
164        return self.__prohibited
165
166    def provided (self, ctd_instance):
167        """C{True} iff the given instance has been explicitly given a value
168        for the attribute.
169
170        This is used for things like only generating an XML attribute
171        assignment when a value was originally given (even if that value
172        happens to be the default).
173        """
174        return self.__getProvided(ctd_instance)
175
176    def id (self):
177        """Tag used within Python code for the attribute.
178
179        This is not used directly in the default code generation template."""
180        return self.__id
181
182    def key (self):
183        """String used as key within object dictionary when storing attribute value."""
184        return self.__key
185
186    def dataType (self):
187        """The subclass of L{pyxb.binding.basis.simpleTypeDefinition} of which any attribute value must be an instance."""
188        return self.__dataType
189
190    def __getValue (self, ctd_instance):
191        """Retrieve the value information for this attribute in a binding instance.
192
193        @param ctd_instance: The instance object from which the attribute is to be retrieved.
194        @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition}
195        @return: C{(provided, value)} where C{provided} is a C{bool} and
196        C{value} is C{None} or an instance of the attribute's datatype.
197
198        """
199        return getattr(ctd_instance, self.__key, (False, None))
200
201    def __getProvided (self, ctd_instance):
202        return self.__getValue(ctd_instance)[0]
203
204    def value (self, ctd_instance):
205        """Get the value of the attribute from the instance."""
206        if self.__prohibited:
207            raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance)
208        return self.__getValue(ctd_instance)[1]
209
210    def __setValue (self, ctd_instance, new_value, provided):
211        return setattr(ctd_instance, self.__key, (provided, new_value))
212
213    def reset (self, ctd_instance):
214        """Set the value of the attribute in the given instance to be its
215        default value, and mark that it has not been provided."""
216        self.__setValue(ctd_instance, self.__defaultValue, False)
217
218    def addDOMAttribute (self, dom_support, ctd_instance, element):
219        """If this attribute as been set, add the corresponding attribute to the DOM element."""
220        ( provided, value ) = self.__getValue(ctd_instance)
221        if provided:
222            dom_support.addAttribute(element, self.__name, value)
223        return self
224
225    def validate (self, ctd_instance):
226        """Validate the instance against the requirements imposed by this
227        attribute use.
228
229        There is no return value; calls raise an exception if the content does
230        not validate.
231
232        @param ctd_instance : An instance of a complex type definition.
233
234        @raise pyxb.ProhibitedAttributeError: when instance has attribute but must not
235        @raise pyxb.MissingAttributeError: when instance lacks attribute but
236        must have it (including when a required fixed-value attribute is
237        missing).
238        @raise pyxb.BatchContentValidationError: when instance has attribute but its value is not acceptable
239        """
240        (provided, value) = self.__getValue(ctd_instance)
241        if value is not None:
242            if self.__prohibited:
243                raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance)
244            if self.__required and not provided:
245                assert self.__fixed
246                raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance)
247            self.__dataType._CheckValidValue(value)
248            self.__dataType.XsdConstraintsOK(value)
249        else:
250            if self.__required:
251                raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance)
252
253    def set (self, ctd_instance, new_value, from_xml=False):
254        """Set the value of the attribute.
255
256        This validates the value against the data type, creating a new instance if necessary.
257
258        @param ctd_instance: The binding instance for which the attribute
259        value is to be set
260        @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition}
261        @param new_value: The value for the attribute
262        @type new_value: Any value that is permitted as the input parameter to
263        the C{Factory} method of the attribute's datatype.
264        @param from_xml: Value C{True} iff the new_value is known to be in
265        lexical space and must by converted by the type factory.  If C{False}
266        (default) the value is only converted if it is not already an instance
267        of the attribute's underlying type.
268        """
269        provided = True
270        assert not isinstance(new_value, xml.dom.Node)
271        if new_value is None:
272            if self.__required:
273                raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance)
274            provided = False
275        if self.__prohibited:
276            raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance)
277        if (new_value is not None) and (from_xml or not isinstance(new_value, self.__dataType)):
278            new_value = self.__dataType.Factory(new_value, _from_xml=from_xml)
279        if self.__fixed and (new_value != self.__defaultValue):
280            raise pyxb.AttributeChangeError(type(ctd_instance), self.__name, ctd_instance)
281        self.__setValue(ctd_instance, new_value, provided)
282        return new_value
283
284    def _description (self, name_only=False, user_documentation=True):
285        if name_only:
286            return six.text_type(self.__name)
287        assert issubclass(self.__dataType, basis._TypeBinding_mixin)
288        desc = [ six.text_type(self.__id), ': ', six.text_type(self.__name), ' (', self.__dataType._description(name_only=True, user_documentation=False), '), ' ]
289        if self.__required:
290            desc.append('required')
291        elif self.__prohibited:
292            desc.append('prohibited')
293        else:
294            desc.append('optional')
295        if self.__defaultValue is not None:
296            desc.append(', ')
297            if self.__fixed:
298                desc.append('fixed')
299            else:
300                desc.append('default')
301            desc.extend(['=', self.__unicodeDefault ])
302        return ''.join(desc)
303
304class AutomatonConfiguration (object):
305    """State for a L{pyxb.utils.fac.Automaton} monitoring content for an
306    incrementally constructed complex type binding instance.
307
308    @warning: This is not an implementation of
309    L{pyxb.utils.fac.Configuration_ABC} because we need the L{step} function
310    to return a different type of value."""
311
312    # The binding instance for which content is being built
313    __instance = None
314
315    # The underlying configuration when the state is deterministic.  In this
316    # case, all updates to the instance content corresponding to the current
317    # state have been applied to the instance.  Note that while steps are
318    # occurring this instance, as well as those in __multi, might be
319    # references to sub-automata.
320    __cfg = None
321
322    # A list of pairs when the state is non-deterministic.  The first member
323    # of the pair is the configuration; the second is a tuple of closures that
324    # must be applied to the instance in order to store the content that was
325    # accepted along the path to that configuration.  This is in order of
326    # preference based on the location of path candidate declarations in the
327    # defining schema.
328    __multi = None
329
330    PermittedNondeterminism = 20
331    """The maximum amount of unresolved non-determinism that is acceptable.
332    If the value is exceeded, a L{pyxb.ContentNondeterminismExceededError}
333    exception will be raised."""
334
335    def __init__ (self, instance):
336        self.__instance = instance
337
338    def reset (self):
339        """Reset the automaton to its initial state.
340
341        Subsequent transitions are expected based on candidate content to be
342        supplied through the L{step} method."""
343        self.__cfg = self.__instance._Automaton.newConfiguration()
344        self.__multi = None
345
346    def nondeterminismCount (self):
347        """Return the number of pending configurations.
348
349        The automaton is deterministic if exactly one configuration is
350        available."""
351        if self.__cfg is not None:
352            assert self.__multi is None
353            return 1
354        return len(self.__multi)
355
356    def step (self, value, element_decl):
357        """Attempt a transition from the current state.
358
359        @param value: the content to be supplied.  For success the value must
360        be consistent with the recorded symbol (element or wildcard
361        declaration) for a transition from the current automaton state.
362
363        @param element_decl: optional
364        L{pyxb.binding.content.ElementDeclaration} that is the preferred
365        symbol for the transition.
366
367        @return: the cardinal number of successful transitions from the
368        current configuration based on the parameters."""
369
370        sym = (value, element_decl)
371
372        # Start with the current configuration(s), assuming we might see
373        # non-determinism.
374        new_multi = []
375        if self.__multi is None:
376            multi = [ (self.__cfg, ()) ]
377        else:
378            multi = self.__multi[:]
379        # Collect the complete set of reachable configurations along with the
380        # closures that will update the instance content based on the path.
381        for (cfg, pending) in multi:
382            cand = cfg.candidateTransitions(sym)
383            for transition in cand:
384                clone_map = {}
385                ccfg = cfg.clone(clone_map)
386                new_multi.append( (transition.apply(ccfg, clone_map), pending+(transition.consumedSymbol().consumingClosure(sym),)) )
387        rv = len(new_multi)
388        if 0 == rv:
389            # No candidate transitions.  Do not change the state.
390            return 0
391        if 1 == rv:
392            # Deterministic transition.  Save the configuration and apply the
393            # corresponding updates.
394            self.__multi = None
395            (self.__cfg, actions) = new_multi[0]
396            for fn in actions:
397                fn(self.__instance)
398        else:
399            # Non-deterministic.  Save everything for subsequent resolution.
400            if rv > self.PermittedNondeterminism:
401                raise pyxb.ContentNondeterminismExceededError(self.__instance)
402            self.__cfg = None
403            self.__multi = new_multi
404        return rv
405
406    def resolveNondeterminism (self, prefer_accepting=True):
407        """Resolve any non-determinism in the automaton state.
408
409        If the automaton has reached a single configuration (was
410        deterministic), this does nothing.
411
412        If multiple candidate configurations are available, the best one is
413        selected and applied, updating the binding instance with the pending
414        content.
415
416        "Best" in this case is determined by optionally eliminating
417        configurations that are not accepting, then selecting the path where
418        the initial transition sorts highest using the binding sort key (based
419        on position in the original schema).
420
421        @keyword prefer_accepting: eliminate non-accepting paths if any
422        accepting path is present."""
423        if self.__multi is None:
424            return
425        assert self.__cfg is None
426        multi = self.__multi
427        if prefer_accepting:
428            multi = list(filter(lambda _ts: _ts[0].isAccepting(), self.__multi))
429            if 0 == len(multi):
430                multi = self.__multi
431        # step() will not create an empty multi list, so cannot get here with
432        # no configurations available unless nobody even reset the
433        # configuration, which would be a usage error.
434        assert 0 < len(multi)
435        if 1 < len(multi):
436            desc = self.__instance._ExpandedName
437            if desc is None:
438                desc = type(self.__instance)
439            _log.warning('Multiple accepting paths for %s', desc)
440            '''
441            for (cfg, actions) in multi:
442                foo = type(self.__instance)()
443                for fn in actions:
444                    fn(foo)
445                print '1: %s ; 2 : %s ; wc: %s' % (foo.first, foo.second, foo.wildcardElements())
446            '''
447        (self.__cfg, actions) = multi[0]
448        self.__multi = None
449        for fn in actions:
450            fn(self.__instance)
451
452    def acceptableContent (self):
453        """Return the sequence of acceptable symbols at this state.
454
455        The list comprises the L{pyxb.binding.content.ElementUse} and
456        L{pyxb.binding.content.WildcardUse} instances that are used to
457        validate proposed symbols, in preferred order."""
458        rv = []
459        seen = set()
460        multi = self.__multi
461        if multi is None:
462            multi = [ self.__cfg]
463        for cfg in multi:
464            for u in cfg.acceptableSymbols():
465                if not (u in seen):
466                    rv.append(u)
467                    seen.add(u)
468        return rv
469
470    def isAccepting (self, raise_if_rejecting=False):
471        """Return C{True} iff the automaton is in an accepting state.
472
473        If the automaton has unresolved nondeterminism, it is resolved first,
474        preferring accepting states."""
475        self.resolveNondeterminism(True)
476        cfg = self.__cfg
477        while cfg.superConfiguration is not None:
478            cfg = cfg.superConfiguration
479        return cfg.isAccepting()
480
481    def _diagnoseIncompleteContent (self, symbols, symbol_set):
482        """Check for incomplete content.
483
484        @raises pyxb.IncompleteElementContentError: if a non-accepting state is found
485        @return: the topmost configuration (if accepting)
486        """
487        # Exit out of any sub-configurations (they might be accepting while
488        # the superConfiguration is not)
489        cfg = self.__cfg
490        while cfg.isAccepting() and (cfg.superConfiguration is not None):
491            cfg = cfg.superConfiguration
492        if not cfg.isAccepting():
493            raise pyxb.IncompleteElementContentError(self.__instance, cfg, symbols, symbol_set)
494        return cfg
495
496    def diagnoseIncompleteContent (self):
497        """Generate the exception explaining why the content is incomplete."""
498        return self._diagnoseIncompleteContent(None, None)
499
500    __preferredSequenceIndex = 0
501    __preferredPendingSymbol = None
502    __pendingNonElementContent = None
503
504    def __resetPreferredSequence (self, instance):
505        self.__preferredSequenceIndex = 0
506        self.__preferredPendingSymbol = None
507        self.__pendingNonElementContent = None
508        vc = instance._validationConfig
509        preferred_sequence = None
510        if (vc.ALWAYS == vc.contentInfluencesGeneration) or (instance._ContentTypeTag == instance._CT_MIXED and vc.MIXED_ONLY == vc.contentInfluencesGeneration):
511            preferred_sequence = instance.orderedContent()
512            if instance._ContentTypeTag == instance._CT_MIXED:
513                self.__pendingNonElementContent = []
514        return preferred_sequence
515
516    def __discardPreferredSequence (self, preferred_sequence, pi=None):
517        """Extract non-element content from the sequence and return C{None}."""
518        if pi is None:
519            pi = self.__preferredSequenceIndex
520        nec = self.__pendingNonElementContent
521        if nec is not None:
522            for csym in preferred_sequence[pi:]:
523                if isinstance(csym, pyxb.binding.basis.NonElementContent):
524                    nec.append(csym)
525        return None
526
527    def __processPreferredSequence (self, preferred_sequence, symbol_set, vc):
528        pi = self.__preferredSequenceIndex
529        psym = self.__preferredPendingSymbol
530        nec = self.__pendingNonElementContent
531        if psym is not None:
532            _log.info('restoring %s', psym)
533            self.__preferredPendingSymbol = None
534        while psym is None:
535            if pi >= len(preferred_sequence):
536                preferred_sequence = self.__discardPreferredSequence(preferred_sequence, pi)
537                break
538            csym = preferred_sequence[pi]
539            pi += 1
540            if (nec is not None) and isinstance(csym, pyxb.binding.basis.NonElementContent):
541                nec.append(csym)
542                continue
543            vals = symbol_set.get(csym.elementDeclaration, [])
544            if csym.value in vals:
545                psym = ( csym.value, csym.elementDeclaration )
546                break
547            if psym is None:
548                # Orphan encountered; response?
549                _log.info('orphan %s in content', csym)
550                if vc.IGNORE_ONCE == vc.orphanElementInContent:
551                    continue
552                if vc.GIVE_UP == vc.orphanElementInContent:
553                    preferred_sequence = self.__discardPreferredSequence(preferred_sequence, pi)
554                    break
555                raise pyxb.OrphanElementContentError(self.__instance, csym)
556        self.__preferredSequenceIndex = pi
557        return (preferred_sequence, psym)
558
559    def sequencedChildren (self):
560        """Implement L{pyxb.binding.basis.complexTypeDefinition._validatedChildren}.
561
562        Go there for the interface.
563        """
564
565        # We need a fresh automaton configuration corresponding to the type of
566        # the binding instance.
567        self.reset()
568        cfg = self.__cfg
569
570        # The validated sequence
571        symbols = []
572
573        # How validation should be done
574        instance = self.__instance
575        vc = instance._validationConfig
576
577        # The available content, in a map from ElementDeclaration to in-order
578        # values.  The key None corresponds to the wildcard content.  Keys are
579        # removed when their corresponding content is exhausted.
580        symbol_set = instance._symbolSet()
581
582        # The preferred sequence to use, if desired.
583        preferred_sequence = self.__resetPreferredSequence(instance)
584
585        # A reference to the data structure holding non-element content.  This
586        # is None unless mixed content is allowed, in which case it is a list.
587        # The same list is used for the entire operation, though it is reset
588        # to be empty after transferring material to the output sequence.
589        nec = self.__pendingNonElementContent
590
591        psym = None
592        while symbol_set:
593            # Find the first acceptable transition.  If there's a preferred
594            # symbol to use, try it first.
595            selected_xit = None
596            psym = None
597            if preferred_sequence is not None:
598                (preferred_sequence, psym) = self.__processPreferredSequence(preferred_sequence, symbol_set, vc)
599            candidates = cfg.candidateTransitions(psym)
600            for xit in candidates:
601                csym = xit.consumedSymbol()
602                if isinstance(csym, ElementUse):
603                    ed = csym.elementDeclaration()
604                elif isinstance(csym, WildcardUse):
605                    ed = None
606                else:
607                    assert False
608                # Check whether we have content that matches the symbol
609                matches = symbol_set.get(ed)
610                if matches is None:
611                    continue
612                if not csym.match((matches[0], ed)):
613                    continue
614                # Commit to this transition and append the selected content
615                # after any pending non-element content that is released due
616                # to a matched preferred symbol.
617                value = matches.pop(0)
618                if (psym is not None) and (nec is not None):
619                    symbols.extend(nec)
620                    nec[:] = []
621                symbols.append(basis.ElementContent(csym.matchValue( (value, ed) ), ed))
622                selected_xit = xit
623                if 0 == len(matches):
624                    del symbol_set[ed]
625                break
626            if selected_xit is None:
627                if psym is not None:
628                    # Suggestion from content did not work
629                    _log.info('invalid %s in content', psym)
630                    if vc.IGNORE_ONCE == vc.invalidElementInContent:
631                        continue
632                    if vc.GIVE_UP == vc.invalidElementInContent:
633                        preferred_sequence = self.__discardPreferredSequence(preferred_sequence)
634                        continue
635                    raise pyxb.InvalidPreferredElementContentError(self.__instance, cfg, symbols, symbol_set, psym)
636                break
637            cfg = selected_xit.apply(cfg)
638        cfg = self._diagnoseIncompleteContent(symbols, symbol_set)
639        if symbol_set:
640            raise pyxb.UnprocessedElementContentError(self.__instance, cfg, symbols, symbol_set)
641        # Validate any remaining material in the preferred sequence.  This
642        # also extracts remaining non-element content.  Note there are
643        # no more symbols, so any remaining element content is orphan.
644        while preferred_sequence is not None:
645            (preferred_sequence, psym) = self.__processPreferredSequence(preferred_sequence, symbol_set, vc)
646            if psym is not None:
647                if not (vc.orphanElementInContent in ( vc.IGNORE_ONCE, vc.GIVE_UP )):
648                    raise pyxb.OrphanElementContentError(self.__instance, psym.value)
649        if nec is not None:
650            symbols.extend(nec)
651        return symbols
652
653class _FACSymbol (pyxb.utils.fac.SymbolMatch_mixin):
654    """Base class for L{pyxb.utils.fac.Symbol} instances associated with PyXB content models.
655
656    This holds the location in the schema of the L{ElementUse} or
657    L{WildcardUse} and documents the methods expected of its children."""
658
659    __xsdLocation = None
660
661    def xsdLocation (self):
662        return self.__xsdLocation
663
664    def matchValue (self, sym):
665        """Return the value accepted by L{match} for this symbol.
666
667        A match for an element declaration might have resulted in a type
668        change for the value (converting it to an acceptable type).  There is
669        no safe place to cache the compatible value calculated in the match
670        while other candidates are being considered, so we need to
671        re-calculate it if the transition is taken.
672
673        If the match could not have changed the value, the value from the
674        symbol may be returned immediately."""
675        raise NotImplementedError('%s._matchValue' % (type(self).__name__,))
676
677    def consumingClosure (self, sym):
678        """Create a closure that will apply the value from C{sym} to a to-be-supplied instance.
679
680        This is necessary for non-deterministic automata, where we can't store
681        the value into the instance field until we know that the transition
682        will be taken:
683
684        @return: A closure that takes a L{complexTypeDefinition} instance and
685        stores the value from invoking L{matchValue} on C{sym} into the
686        appropriate slot."""
687        raise NotImplementedError('%s._consumingClosure' % (type(self).__name__,))
688
689    def __init__ (self, xsd_location):
690        """@param xsd_location: the L{location<pyxb.utils.utility.Location>} of the element use or wildcard declaration."""
691        self.__xsdLocation = xsd_location
692        super(_FACSymbol, self).__init__()
693
694class ElementUse (_FACSymbol):
695    """Information about a schema element declaration reference.
696
697    This is used by the FAC content model to identify the location
698    within a schema at which an element use appears.  The L{ElementDeclaration}
699    is not sufficient since multiple uses in various schema, possibly in
700    different namespaces, may refer to the same declaration but be independent
701    uses.
702    """
703
704    __elementDeclaration = None
705
706    def elementDeclaration (self):
707        """Return the L{element declaration<pyxb.binding.content.ElementDeclaration>} associated with the use."""
708        return self.__elementDeclaration
709
710    def elementBinding (self):
711        """Return the L{element binding<pyxb.binding.content.ElementDeclaration.elementBinding>} associated with the use.
712
713        Equivalent to L{elementDeclaration}().L{elementBinding()<pyxb.binding.content.ElementDeclaration.elementBinding>}."""
714        return self.__elementDeclaration.elementBinding()
715
716    def typeDefinition (self):
717        """Return the element type.
718
719        Equivalent to L{elementDeclaration}().L{elementBinding()<pyxb.binding.content.ElementDeclaration.elementBinding>}.L{typeDefinition()<pyxb.binding.basis.element.typeDefinition>}."""
720        return self.__elementDeclaration.elementBinding().typeDefinition()
721
722    def __init__ (self, element_declaration, xsd_location):
723        super(ElementUse, self).__init__(xsd_location)
724        self.__elementDeclaration = element_declaration
725
726    def matchValue (self, sym):
727        (value, element_decl) = sym
728        (rv, value) = self.__elementDeclaration._matches(value, element_decl)
729        assert rv
730        return value
731
732    def consumingClosure (self, sym):
733        # Defer the potentially-expensive re-invocation of matchValue until
734        # the closure is applied.
735        return lambda _inst,_eu=self,_sy=sym: _eu.__elementDeclaration.setOrAppend(_inst, _eu.matchValue(_sy))
736
737    def match (self, symbol):
738        """Satisfy L{pyxb.utils.fac.SymbolMatch_mixin}.
739
740        Determine whether the proposed content encapsulated in C{symbol} is
741        compatible with the element declaration.  If so, the accepted value is
742        cached internally and return C{True}; otherwise return C{False}.
743
744        @param symbol: a pair C{(value, element_decl)}.
745        L{pyxb.binding.content.ElementDeclaration._matches} is used to
746        determine whether the proposed content is compatible with this element
747        declaration."""
748        (value, element_decl) = symbol
749        # NB: this call may change value to be compatible.  Unfortunately, we
750        # can't reliably cache that converted value, so just ignore it and
751        # we'll recompute it if the candidate transition is taken.
752        (rv, value) = self.__elementDeclaration._matches(value, element_decl)
753        return rv
754
755    def __str__ (self):
756        return '%s per %s' % (self.__elementDeclaration.name(), self.xsdLocation())
757
758class WildcardUse (_FACSymbol):
759    """Information about a schema wildcard element.
760
761    This is functionally parallel to L{ElementUse}, but references a
762    L{Wildcard} that is unique to this instance.  That L{Wildcard} is not
763    incorporated into this class is an artifact of the evolution of PyXB."""
764
765    __wildcardDeclaration = None
766
767    def wildcardDeclaration (self):
768        return self.__wildcardDeclaration
769
770    def matchValue (self, sym):
771        (value, element_decl) = sym
772        return value
773
774    def consumingClosure (self, sym):
775        """Create a closure that will apply the value accepted by L{match} to a to-be-supplied instance."""
776        return lambda _inst,_av=self.matchValue(sym): _inst._appendWildcardElement(_av)
777
778    def match (self, symbol):
779        """Satisfy L{pyxb.utils.fac.SymbolMatch_mixin}.
780
781        Determine whether the proposed content encapsulated in C{symbol} is
782        compatible with the wildcard declaration.  If so, the accepted value
783        is cached internally and return C{True}; otherwise return C{False}.
784
785        @param symbol: a pair C{(value, element_decl)}.
786        L{pyxb.binding.content.Wildcard.matches} is used to determine whether
787        the proposed content is compatible with this wildcard.
788        """
789        (value, element_decl) = symbol
790        return self.__wildcardDeclaration.matches(None, value)
791
792    def __init__ (self, wildcard_declaration, xsd_location):
793        super(WildcardUse, self).__init__(xsd_location)
794        self.__wildcardDeclaration = wildcard_declaration
795
796    def __str__ (self):
797        return 'xs:any per %s' % (self.xsdLocation(),)
798
799import collections
800
801# Do not inherit from list; that's obscene, and could cause problems with the
802# internal assumptions made by Python.  Instead delegate everything to an
803# instance of list that's held internally.  Inherit from the ABC that
804# represents list-style data structures so we can identify both lists and
805# these things which are not lists.
806@pyxb.utils.utility.BackfillComparisons
807class _PluralBinding (collections.MutableSequence):
808    """Helper for element content that supports multiple occurences.
809
810    This is an adapter for Python list.  Any operation that can mutate an item
811    in the list ensures the stored value is compatible with the element for
812    which the list holds values."""
813
814    __list = None
815    __elementBinding = None
816
817    def __init__ (self, *args, **kw):
818        element_binding = kw.pop('element_binding', None)
819        if not isinstance(element_binding, basis.element):
820            raise ValueError()
821        self.__elementBinding = element_binding
822        self.__list = []
823        self.extend(args)
824
825    def __convert (self, v):
826        return self.__elementBinding.compatibleValue(v)
827
828    def __len__ (self):
829        return self.__list.__len__()
830
831    def __getitem__ (self, key):
832        return self.__list.__getitem__(key)
833
834    def __setitem__ (self, key, value):
835        if isinstance(key, slice):
836            self.__list.__setitem__(key, [ self.__convert(_v) for _v in value])
837        else:
838            self.__list.__setitem__(key, self.__convert(value))
839
840    def __delitem__ (self, key):
841        self.__list.__delitem__(key)
842
843    def __iter__ (self):
844        return self.__list.__iter__()
845
846    def __reversed__ (self):
847        return self.__list.__reversed__()
848
849    def __contains__ (self, item):
850        return self.__list.__contains__(item)
851
852    # The mutable sequence type methods
853    def append (self, x):
854        self.__list.append(self.__convert(x))
855
856    def extend (self, x):
857        self.__list.extend(map(self.__convert, x))
858
859    def count (self, x):
860        return self.__list.count(x)
861
862    def index (self, x, i=0, j=-1):
863        return self.__list.index(x, i, j)
864
865    def insert (self, i, x):
866        self.__list.insert(i, self.__convert(x))
867
868    def pop (self, i=-1):
869        return self.__list.pop(i)
870
871    def remove (self, x):
872        self.__list.remove(x)
873
874    def reverse (self):
875        self.__list.reverse()
876
877    def sort (self, key=None, reverse=False):
878        self.__list.sort(key=key, reverse=reverse)
879
880    def __str__ (self):
881        return self.__list.__str__()
882
883    def __hash__ (self):
884        return hash(self.__list__)
885
886    def __eq__ (self, other):
887        if other is None:
888            return False
889        if isinstance(other, _PluralBinding):
890            return self.__list.__eq__(other.__list)
891        return self.__list.__eq__(other)
892
893    def __lt__ (self, other):
894        if other is None:
895            return False
896        if isinstance(other, _PluralBinding):
897            return self.__list.__lt__(other.__list)
898        return self.__list.__lt__(other)
899
900class ElementDeclaration (object):
901    """Aggregate the information relevant to an element of a complex type.
902
903    This includes the L{original tag name<name>}, the spelling of L{the
904    corresponding object in Python <id>}, an L{indicator<isPlural>} of whether
905    multiple instances might be associated with the field, and other relevant
906    information.
907    """
908
909    def xsdLocation (self):
910        """The L{location<pyxb.utils.utility.Location>} in the schema where the
911        element was declared.
912
913        Note that this is not necessarily the same location as its use."""
914        return self.__xsdLocation
915    __xsdLocation = None
916
917    def name (self):
918        """The expanded name of the element.
919
920        @rtype: L{pyxb.namespace.ExpandedName}
921        """
922        return self.__name
923    __name = None
924
925    def id (self):
926        """The string name of the binding class field used to hold the element
927        values.
928
929        This is the user-visible name, and excepting disambiguation will be
930        equal to the local name of the element."""
931        return self.__id
932    __id = None
933
934    # The dictionary key used to identify the value of the element.  The value
935    # is the same as that used for private member variables in the binding
936    # class within which the element declaration occurred.
937    __key = None
938
939    def elementBinding (self):
940        """The L{basis.element} instance identifying the information
941        associated with the element declaration.
942        """
943        return self.__elementBinding
944    def _setElementBinding (self, element_binding):
945        # Set the element binding for this use.  Only visible at all because
946        # we have to define the uses before the element instances have been
947        # created.
948        self.__elementBinding = element_binding
949        return self
950    __elementBinding = None
951
952    def isPlural (self):
953        """True iff the content model indicates that more than one element
954        can legitimately belong to this use.
955
956        This includes elements in particles with maxOccurs greater than one,
957        and when multiple elements with the same NCName are declared in the
958        same type.
959        """
960        return self.__isPlural
961    __isPlural = False
962
963    def __init__ (self, name, id, key, is_plural, location, element_binding=None):
964        """Create an ElementDeclaration instance.
965
966        @param name: The name by which the element is referenced in the XML
967        @type name: L{pyxb.namespace.ExpandedName}
968
969        @param id: The Python name for the element within the containing
970        L{pyxb.basis.binding.complexTypeDefinition}.  This is a public
971        identifier, albeit modified to be unique, and is usually used as the
972        name of the element's inspector method or property.
973        @type id: C{str}
974
975        @param key: The string used to store the element
976        value in the dictionary of the containing
977        L{pyxb.basis.binding.complexTypeDefinition}.  This is mangled so
978        that it is unique among and is treated as a Python private member.
979        @type key: C{str}
980
981        @param is_plural: If C{True}, documents for the corresponding type may
982        have multiple instances of this element.  As a consequence, the value
983        of the element will be a list.  If C{False}, the value will be C{None}
984        if the element is absent, and a reference to an instance of the type
985        identified by L{pyxb.binding.basis.element.typeDefinition} if present.
986        @type is_plural: C{bool}
987
988        @param element_binding: Reference to the class that serves as the
989        binding for the element.
990        """
991        self.__name = name
992        self.__id = id
993        self.__key = key
994        self.__isPlural = is_plural
995        self.__elementBinding = element_binding
996        super(ElementDeclaration, self).__init__()
997
998    def defaultValue (self):
999        """Return the default value for this element.
1000
1001        For plural values, this is an empty collection.  For non-plural
1002        values, it is C{None} unless the element has a default value, in which
1003        case it is that value.
1004
1005        @todo: This should recursively support filling in default content, as
1006        when the plural list requires a non-zero minimum number of entries.
1007        """
1008        if self.isPlural():
1009            return _PluralBinding(element_binding=self.__elementBinding)
1010        return self.__elementBinding.defaultValue()
1011
1012    def resetValue (self):
1013        """Return the reset value for this element.
1014
1015        For plural values, this is an empty collection.  For non-plural
1016        values, it is C{None}, corresponding to absence of an assigned
1017        element.
1018        """
1019        if self.isPlural():
1020            return _PluralBinding(element_binding=self.__elementBinding)
1021        return None
1022
1023    def value (self, ctd_instance):
1024        """Return the value for this use within the given instance.
1025
1026        Note that this is the L{resetValue()}, not the L{defaultValue()}, if
1027        the element has not yet been assigned a value."""
1028        return getattr(ctd_instance, self.__key, self.resetValue())
1029
1030    def reset (self, ctd_instance):
1031        """Set the value for this use in the given element to its default."""
1032        setattr(ctd_instance, self.__key, self.resetValue())
1033        return self
1034
1035    def set (self, ctd_instance, value):
1036        """Set the value of this element in the given instance."""
1037        if value is None:
1038            return self.reset(ctd_instance)
1039        if ctd_instance._isNil():
1040            raise pyxb.ContentInNilInstanceError(ctd_instance, value)
1041        assert self.__elementBinding is not None
1042        if ctd_instance._validationConfig.forBinding or isinstance(value, pyxb.BIND):
1043            value = self.__elementBinding.compatibleValue(value, is_plural=self.isPlural())
1044        setattr(ctd_instance, self.__key, value)
1045        ctd_instance._addContent(basis.ElementContent(value, self))
1046        return self
1047
1048    def setOrAppend (self, ctd_instance, value):
1049        """Invoke either L{set} or L{append}, depending on whether the element
1050        use is plural."""
1051        if self.isPlural():
1052            return self.append(ctd_instance, value)
1053        return self.set(ctd_instance, value)
1054
1055    def append (self, ctd_instance, value):
1056        """Add the given value as another instance of this element within the binding instance.
1057        @raise pyxb.StructuralBadDocumentError: invoked on an element use that is not plural
1058        """
1059        if ctd_instance._isNil():
1060            raise pyxb.ContentInNilInstanceError(ctd_instance, value)
1061        if not self.isPlural():
1062            raise pyxb.NonPluralAppendError(ctd_instance, self, value)
1063        values = self.value(ctd_instance)
1064        if ctd_instance._validationConfig.forBinding:
1065            value = self.__elementBinding.compatibleValue(value)
1066        values.append(value)
1067        ctd_instance._addContent(basis.ElementContent(value, self))
1068        return values
1069
1070    def toDOM (self, dom_support, parent, value):
1071        """Convert the given value to DOM as an instance of this element.
1072
1073        @param dom_support: Helper for managing DOM properties
1074        @type dom_support: L{pyxb.utils.domutils.BindingDOMSupport}
1075        @param parent: The DOM node within which this element should be generated.
1076        @type parent: C{xml.dom.Element}
1077        @param value: The content for this element.  May be text (if the
1078        element allows mixed content), or an instance of
1079        L{basis._TypeBinding_mixin}.
1080
1081        @raise pyxb.AbstractElementError: the binding to be used is abstract
1082        """
1083        if isinstance(value, basis._TypeBinding_mixin):
1084            element_binding = self.__elementBinding
1085            if value._substitutesFor(element_binding):
1086                element_binding = value._element()
1087            assert element_binding is not None
1088            if element_binding.abstract():
1089                raise pyxb.AbstractElementError(self, value)
1090            element = dom_support.createChildElement(element_binding.name(), parent)
1091            elt_type = element_binding.typeDefinition()
1092            val_type = type(value)
1093            if isinstance(value, basis.complexTypeDefinition):
1094                if not (isinstance(value, elt_type) or elt_type._RequireXSIType(val_type)):
1095                    raise pyxb.LogicError('toDOM with implicit value type %s unrecoverable from %s' % (type(value), elt_type))
1096            else:
1097                if isinstance(value, basis.STD_union) and isinstance(value, elt_type._MemberTypes):
1098                    val_type = elt_type
1099            if dom_support.requireXSIType() or elt_type._RequireXSIType(val_type):
1100                dom_support.addAttribute(element, pyxb.namespace.XMLSchema_instance.createExpandedName('type'), value._ExpandedName)
1101            value._toDOM_csc(dom_support, element)
1102        elif isinstance(value, six.string_types):
1103            element = dom_support.createChildElement(self.name(), parent)
1104            element.appendChild(dom_support.document().createTextNode(value))
1105        elif isinstance(value, _PluralBinding):
1106            for v in value:
1107                self.toDOM(dom_support, parent, v)
1108        else:
1109            raise pyxb.LogicError('toDOM with unrecognized value type %s: %s' % (type(value), value))
1110
1111    def _description (self, name_only=False, user_documentation=True):
1112        if name_only:
1113            return six.text_type(self.__name)
1114        desc = [ six.text_type(self.__id), ': ']
1115        if self.isPlural():
1116            desc.append('MULTIPLE ')
1117        desc.append(self.elementBinding()._description(user_documentation=user_documentation))
1118        return six.u('').join(desc)
1119
1120    def _matches (self, value, element_decl):
1121        accept = False
1122        if element_decl == self:
1123            accept = True
1124        elif element_decl is not None:
1125            # If there's a known element, and it's not this one, the content
1126            # does not match.  This assumes we handled xsi:type and
1127            # substitution groups earlier, which may be true.
1128            accept = False
1129        elif isinstance(value, xml.dom.Node):
1130            # If we haven't been able to identify an element for this before,
1131            # then we don't recognize it, and will have to treat it as a
1132            # wildcard.
1133            accept = False
1134        else:
1135            # A foreign value which might be usable if we can convert
1136            # it to a compatible value trivially.
1137            try:
1138                value = self.__elementBinding.compatibleValue(value, _convert_string_values=False)
1139                accept = True
1140            except (pyxb.ValidationError, pyxb.BindingError):
1141                pass
1142        return (accept, value)
1143
1144    def __str__ (self):
1145        return 'ED.%s@%x' % (self.__name, id(self))
1146
1147
1148class Wildcard (object):
1149    """Placeholder for wildcard objects."""
1150
1151    NC_any = '##any'            #<<< The namespace constraint "##any"
1152    NC_not = '##other'          #<<< A flag indicating constraint "##other"
1153    NC_targetNamespace = '##targetNamespace' #<<< A flag identifying the target namespace
1154    NC_local = '##local'        #<<< A flag indicating the namespace must be absent
1155
1156    __namespaceConstraint = None
1157    def namespaceConstraint (self):
1158        """A constraint on the namespace for the wildcard.
1159
1160        Valid values are:
1161
1162         - L{Wildcard.NC_any}
1163         - A tuple ( L{Wildcard.NC_not}, a L{namespace<pyxb.namespace.Namespace>} instance )
1164         - set(of L{namespace<pyxb.namespace.Namespace>} instances)
1165
1166        Namespaces are represented by their URIs.  Absence is
1167        represented by C{None}, both in the "not" pair and in the set.
1168        """
1169        return self.__namespaceConstraint
1170
1171    PC_skip = 'skip'
1172    """No namespace constraint is applied to the wildcard."""
1173
1174    PC_lax = 'lax'
1175    """Validate against available uniquely determined declaration."""
1176
1177    PC_strict = 'strict'
1178    """Validate against declaration or xsi:type, which must be available."""
1179
1180    __processContents = None
1181    """One of L{PC_skip}, L{PC_lax}, L{PC_strict}."""
1182    def processContents (self):
1183        """Indicate how this wildcard's contents should be processed."""
1184        return self.__processContents
1185
1186    def __normalizeNamespace (self, nsv):
1187        if nsv is None:
1188            return None
1189        if isinstance(nsv, six.string_types):
1190            nsv = pyxb.namespace.NamespaceForURI(nsv, create_if_missing=True)
1191        assert isinstance(nsv, pyxb.namespace.Namespace), 'unexpected non-namespace %s' % (nsv,)
1192        return nsv
1193
1194    def __init__ (self, *args, **kw):
1195        """
1196        @keyword namespace_constraint: Required namespace constraint(s)
1197        @keyword process_contents: Required"""
1198
1199        # Namespace constraint and process contents are required parameters.
1200        nsc = kw['namespace_constraint']
1201        if isinstance(nsc, tuple):
1202            nsc = (nsc[0], self.__normalizeNamespace(nsc[1]))
1203        elif isinstance(nsc, set):
1204            nsc = set([ self.__normalizeNamespace(_uri) for _uri in nsc ])
1205        self.__namespaceConstraint = nsc
1206        self.__processContents = kw['process_contents']
1207        super(Wildcard, self).__init__()
1208
1209    def matches (self, instance, value):
1210        """Return True iff the value is a valid match against this wildcard.
1211
1212        Validation per U{Wildcard allows Namespace Name<http://www.w3.org/TR/xmlschema-1/#cvc-wildcard-namespace>}.
1213        """
1214
1215        ns = None
1216        if isinstance(value, xml.dom.Node):
1217            if value.namespaceURI is not None:
1218                ns = pyxb.namespace.NamespaceForURI(value.namespaceURI)
1219        elif isinstance(value, basis._TypeBinding_mixin):
1220            elt = value._element()
1221            if elt is not None:
1222                ns = elt.name().namespace()
1223            else:
1224                ns = value._ExpandedName.namespace()
1225        else:
1226            # Assume that somebody will handle the conversion to xs:anyType
1227            pass
1228        if isinstance(ns, pyxb.namespace.Namespace) and ns.isAbsentNamespace():
1229            ns = None
1230        if self.NC_any == self.__namespaceConstraint:
1231            return True
1232        if isinstance(self.__namespaceConstraint, tuple):
1233            (_, constrained_ns) = self.__namespaceConstraint
1234            assert self.NC_not == _
1235            if ns is None:
1236                return False
1237            if constrained_ns == ns:
1238                return False
1239            return True
1240        return ns in self.__namespaceConstraint
1241
1242## Local Variables:
1243## fill-column:78
1244## End:
1245