summaryrefslogtreecommitdiff
path: root/pyxb/binding/content.py
blob: a7482ad95304c58aecca28a074816fdae4db5f4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
# Copyright 2009, 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{ElementUse} 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{ContentModelTransition}, L{ContentModelState}, and L{ContentModel} are used
to store a deterministic finite automaton which is used to translate between
binding instances and other representations (e.g., DOM nodes)

L{ModelGroupAllAlternative} and L{ModelGroupAll} represent special nodes in
the DFA that support a model group with compositor "all" in a way that does
not result in an exponential state explosion in the DFA.

L{DFAStack} and its related internal classes are used in stream-based
processing of content.

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

import pyxb
import pyxb.namespace
import basis

import xml.dom

class ContentState_mixin (pyxb.cscRoot):
    """Declares methods used by classes that hold state while validating a
    content model component."""

    __contentModel = None

    def __init__ (self, model, **kw):
        self.__contentModel = model
        super(ContentState_mixin, self).__init__(**kw)
        
    def contentModel (self):
        """Get the model associated with the state.
        
        @return The L{ContentModel_mixin} instance for which this instance
        monitors state.  The value is C{None} if this is a non-aggregate state
        object like an L{ElementUse} or L{Wildcard}.
        """
        return self.__contentModel

    def accepts (self, particle_state, instance, value, element_use):
        """Determine whether the provided value can be added to the instance
        without violating state validation.

        This method must not throw any non-catastrophic exceptions; general
        failures should be transformed to a C{False} return value.

        @param particle_state The L{ParticleState} instance serving as the
        parent to this state.  The implementation must inform that state when
        the proposed value completes the content model.

        @param instance An instance of a subclass of
        {basis.complexTypeDefinition}, into which the provided value will be
        stored if it is consistent with the current model state.

        @param value The value that is being validated against the state.

        @param element_use An optional L{ElementUse} instance that specifies
        the element to which the value corresponds.  This will be available
        when the value is extracted by parsing a document, but will be absent
        if the value was passed as a constructor positional parameter.

        @return C{True} if the value was successfully matched against the
        state.  C{False} if the value did not match against the state."""
        raise Exception('ContentState_mixin.accepts not implemented in %s' % (type(self),))

    def notifyFailure (self, sub_state, particle_ok):
        """Invoked by a sub-state to indicate that validation cannot proceed
        in the current state.

        Normally this is used when an intermediate content model must reset
        itself to permit alternative models to be evaluated.

        @param sub_state the state that was unable to accept a value

        @param particle_ok C{True} if the particle that rejected the value is
        in an accepting terminal state

        """
        raise Exception('ContentState_mixin.notifyFailure not implemented in %s' % (type(self),))
        

    def _validate (self, symbol_set, output_sequence):
        raise Exception('ContentState_mixin._validate not implemented in %s' % (type(self),))

class ContentModel_mixin (pyxb.cscRoot):
    """Declares methods used by classes representing content model components."""

    def newState (self, parent_particle_state):
        """Return a L{ContentState_mixin} instance that will validate the
        state of this model component.

        @param parent_particle_state The L{ParticleState} instance for which
        this instance is a term.  C{None} for the top content model of a
        complex data type.
        """
        raise Exception('ContentModel_mixin.newState not implemented in %s' % (type(self),))

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 attribute used in instances to hold the attribute value
    __dataType = None  # PST datatype
    __unicodeDefault = None     # Default value as a unicode string, or None
    __defaultValue = None       # Default value as an instance of datatype, or None
    __fixed = False             # If True, value cannot be changed
    __required = False          # If True, attribute must appear
    __prohibited = False        # If True, attribute must not appear

    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.BadTypeValueError: 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)
        self.__fixed = fixed
        self.__required = required
        self.__prohibited = prohibited

    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):
        """Return True iff the attribute must be assigned a value."""
        return self.__required

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

    def provided (self, ctd_instance):
        """Return 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."""
        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:
            assert value is not None
            dom_support.addAttribute(element, self.__name, value.xsdLiteral())
        return self

    def validate (self, ctd_instance):
        (provided, value) = self.__getValue(ctd_instance)
        if value is not None:
            if self.__prohibited:
                raise pyxb.ProhibitedAttributeError('Value given for prohibited attribute %s' % (self.__name,))
            if self.__required and not provided:
                assert self.__fixed
                raise pyxb.MissingAttributeError('Fixed required attribute %s was never set' % (self.__name,))
            if not self.__dataType._IsValidValue(value):
                raise pyxb.BindingValidationError('Attribute %s value type %s not %s' % (self.__name, type(value), self.__dataType))
            self.__dataType.XsdConstraintsOK(value)
        else:
            if self.__required:
                raise pyxb.MissingAttributeError('Required attribute %s does not have a value' % (self.__name,))
        return True

    def set (self, ctd_instance, new_value):
        """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: An C{xml.dom.Node} instance, or any value that is
        permitted as the input parameter to the C{Factory} method of the
        attribute's datatype.
        """
        provided = True
        if isinstance(new_value, xml.dom.Node):
            unicode_value = self.__name.getAttribute(new_value)
            if unicode_value is None:
                if self.__required:
                    raise pyxb.MissingAttributeError('Required attribute %s from %s not found' % (self.__name, ctd_instance._ExpandedName or type(ctd_instance)))
                provided = False
                unicode_value = self.__unicodeDefault
            if unicode_value is None:
                # Must be optional and absent
                provided = False
                new_value = None
            else:
                new_value = unicode_value
        else:
            assert new_value is not None
        if self.__prohibited:
            raise pyxb.ProhibitedAttributeError('Value given for prohibited attribute %s' % (self.__name,))
        if (new_value is not None) and (not isinstance(new_value, self.__dataType)):
            new_value = self.__dataType.Factory(new_value)
        if self.__fixed and (new_value != self.__defaultValue):
            raise pyxb.AttributeChangeError('Attempt to change value of fixed attribute %s' % (self.__name,))
        self.__setValue(ctd_instance, new_value, provided)
        return new_value

    def _description (self, name_only=False, user_documentation=True):
        if name_only:
            return str(self.__name)
        assert issubclass(self.__dataType, basis._TypeBinding_mixin)
        desc = [ str(self.__id), ': ', str(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 ElementUse (ContentState_mixin, ContentModel_mixin):
    """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 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, element_binding=None):
        """Create an ElementUse 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(ElementUse, self).__init__(None)

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

        @todo: Right now, this returns C{None} for non-plural and an empty
        list for plural elements.  Need to support schema-specified default
        values for simple-type content.
        """
        if self.isPlural():
            return []
        return None

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

    def reset (self, ctd_instance):
        """Set the value for this use in the given element to its default."""
        setattr(ctd_instance, self.__key, self.defaultValue())
        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)
        assert self.__elementBinding is not None
        if basis._TypeBinding_mixin._PerformValidation:
            value = self.__elementBinding.compatibleValue(value, is_plural=self.isPlural())
        setattr(ctd_instance, self.__key, value)
        ctd_instance._addContent(value, self.__elementBinding)
        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 not self.isPlural():
            raise pyxb.StructuralBadDocumentError('Cannot append to element with non-plural multiplicity')
        values = self.value(ctd_instance)
        if basis._TypeBinding_mixin._PerformValidation:
            value = self.__elementBinding.compatibleValue(value)
        values.append(value)
        ctd_instance._addContent(value, self.__elementBinding)
        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}.
        """
        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.DOMGenerationError('Element %s is abstract but content %s not associated with substitution group member' % (self.name(), value))
            element = dom_support.createChildElement(element_binding.name(), parent)
            elt_type = element_binding.typeDefinition()
            val_type = type(value)
            if isinstance(value, basis.complexTypeDefinition):
                assert isinstance(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):
                val_type_qname = value._ExpandedName.localName()
                tns_prefix = dom_support.namespacePrefix(value._ExpandedName.namespace())
                if tns_prefix is not None:
                    val_type_qname = '%s:%s' % (tns_prefix, val_type_qname)
                dom_support.addAttribute(element, pyxb.namespace.XMLSchema_instance.createExpandedName('type'), val_type_qname)
            value._toDOM_csc(dom_support, element)
        elif isinstance(value, (str, unicode)):
            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 str(self.__name)
        desc = [ str(self.__id), ': ']
        if self.isPlural():
            desc.append('MULTIPLE ')
        desc.append(self.elementBinding()._description(user_documentation=user_documentation))
        return ''.join(desc)

    def newState (self, parent_particle_state):
        """Implement parent class method."""
        return self

    def accepts (self, particle_state, instance, value, element_use):
        rv = self._accepts(instance, value, element_use)
        if rv:
            particle_state.incrementCount()
        return rv

    def _accepts (self, instance, value, element_use):
        if element_use == self:
            self.setOrAppend(instance, value)
            return True
        if element_use is not None:
            #print 'WARNING: %s val %s eu %s does not match %s' % (instance, value, element_use, self)
            return False
        assert not isinstance(value, xml.dom.Node)
        try:
            self.setOrAppend(instance, self.__elementBinding.compatibleValue(value, _convert_string_values=False))
            return True
        except pyxb.BadTypeValueError, e:
            pass
        #print '%s %s %s in %s' % (instance, value, element_use, self)
        return False

    def _validate (self, symbol_set, output_sequence):
        values = symbol_set.get(self)
        #print 'values %s' % (values,)
        if values is None:
            return False
        used = values.pop(0)
        output_sequence.append( (self, used) )
        if 0 == len(values):
            del symbol_set[self]
        return True

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

class SequenceState (ContentState_mixin):
    __failed = False
    __satisfied = False

    def __init__ (self, group, parent_particle_state):
        super(SequenceState, self).__init__(group)
        self.__sequence = group
        self.__parentParticleState = parent_particle_state
        self.__particles = group.particles()
        self.__index = -1
        self.__satisfied = False
        self.__failed = False
        #print 'SS.CTOR %s: %d elts' % (self, len(self.__particles))
        self.notifyFailure(None, False)
    
    def accepts (self, particle_state, instance, value, element_use):
        assert self.__parentParticleState == particle_state
        assert not self.__failed
        #print 'SS.ACC %s: %s %s %s' % (self, instance, value, element_use)
        while self.__particleState is not None:
            (consume, underflow_exc) = self.__particleState.step(instance, value, element_use)
            if consume:
                return True
            if underflow_exc is not None:
                self.__failed = True
                raise underflow_exc
        return False

    def notifyFailure (self, sub_state, particle_ok):
        self.__index += 1
        self.__particleState = None
        if self.__index < len(self.__particles):
            self.__particleState = ParticleState(self.__particles[self.__index], self)
        else:
            self.__satisfied = particle_ok
            if particle_ok:
                self.__parentParticleState.incrementCount()
        #print 'SS.NF %s: %d %s %s' % (self, self.__index, particle_ok, self.__particleState)

class ParticleState (pyxb.cscRoot):
    def __init__ (self, particle, parent_state=None):
        self.__particle = particle
        self.__parentState = parent_state
        self.__termState = particle.term().newState(self)
        self.__count = 0
        #print 'PS.CTOR %s: particle %s' % (self, particle)

    def incrementCount (self):
        #print 'PS.IC %s' % (self,)
        self.__count += 1

    def step (self, instance, value, element_use):
        """Attempt to apply the value as a new instance of the particle's term.

        The L{ContentState_mixin} created for the particle's term is consulted
        to determine whether the instance can accept the given value.  If so,
        the particle's maximum occurrence limit is checked; if not, and the
        particle has a parent state, it is informed of the failure.

        @param instance An instance of a subclass of
        {basis.complexTypeDefinition}, into which the provided value will be
        stored if it is consistent with the current model state.

        @param value The value that is being validated against the state.

        @param element_use An optional L{ElementUse} instance that specifies
        the element to which the value corresponds.  This will be available
        when the value is extracted by parsing a document, but will be absent
        if the value was passed as a constructor positional parameter.

        @return C{( consumed, underflow_exc )} A tuple where the first element
        is C{True} iff the provided value was accepted in the current state.
        When this first element is C{False}, the second element will be
        C{None} if the particle's occurrence requirements have been met, and
        is an instance of C{MissingElementError} if the observed number of
        terms is less than the minimum occurrence count.  Depending on
        context, the caller may raise this exception, or may try an
        alternative content model.

        @raise L{pyxb.UnexpectedElementError} if the value satisfies the particle,
        but having done so exceeded the allowable number of instances of the
        term.
        """

        #print 'PS.STEP %s: %s %s %s' % (self, instance, value, element_use)

        # Only try if we're not already at the upper limit on occurrences
        consumed = False
        underflow_exc = None
        if self.__count != self.__particle.maxOccurs():
            consumed = self.__termState.accepts(self, instance, value, element_use)
        #print 'PS.STEP %s: %s' % (self, consumed)
        if consumed:
            if not self.__particle.meetsMaximum(self.__count):
                raise pyxb.UnexpectedElementError('too many')
        else:
            if self.__parentState is not None:
                self.__parentState.notifyFailure(self, self.__particle.satisfiesOccurrences(self.__count))
            if not self.__particle.meetsMinimum(self.__count):
                underflow_exc = pyxb.MissingElementError('too few')
        return (consumed, underflow_exc)

    def __str__ (self):
        particle = self.__particle
        return 'ParticleState(%d:%d,%s:%s)@%x' % (self.__count, particle.minOccurs(), particle.maxOccurs(), particle.term(), id(self))

class ParticleModel (pyxb.cscRoot):
    def minOccurs (self): return self.__minOccurs
    def maxOccurs (self): return self.__maxOccurs
    def term (self): return self.__term

    def meetsMaximum (self, count):
        return (self.__maxOccurs is None) or (count <= self.__maxOccurs)

    def meetsMinimum (self, count):
        return count >= self.__minOccurs

    def satisfiesOccurrences (self, count):
        return self.meetsMinimum(count) and self.meetsMaximum(count)

    def __init__ (self, term, min_occurs=1, max_occurs=1):
        self.__term = term
        self.__minOccurs = min_occurs
        self.__maxOccurs = max_occurs

    def stateStack (self):
        return ParticleState(self)

    def validate (self, symbol_set):
        output_sequence = []
        #print 'Start: %d %s %s : %s' % (self.__minOccurs, self.__maxOccurs, self.__term, symbol_set)
        result = self._validate(symbol_set, output_sequence)
        #print 'End: %s %s %s' % (result, symbol_set, output_sequence)
        if result:
            return (symbol_set, output_sequence)
        return None

    def _validate (self, symbol_set, output_sequence):
        count = 0
        #print 'Validate %d %s PRT %s' % (self.__minOccurs, self.__maxOccurs, self.__term)
        last_size = len(output_sequence)
        while self.meetsMaximum(count) and self.__term._validate(symbol_set, output_sequence):
            #print 'PRT validated, cnt %d, left %s' % (count, symbol_set)
            this_size = len(output_sequence)
            if this_size == last_size:
                # Validated without consuming anything
                if count < self.__minOccurs:
                    count = self.__minOccurs
                break
            count += 1
            last_size = this_size
        result = self.meetsMinimum(count) and self.meetsMaximum(count)
        #print 'END PRT %s validate %s: %s %s %s' % (self.__term, result, self.__minOccurs, count, self.__maxOccurs)
        return result

class _Group (ContentModel_mixin):
    def particles (self): return self.__particles

    def __init__ (self, *particles):
        self.__particles = particles

class GroupChoice (_Group):
    def __init__ (self, *args, **kw):
        super(GroupChoice, self).__init__(*args, **kw)

class GroupAll (_Group):
    def __init__ (self, *args, **kw):
        super(GroupAll, self).__init__(*args, **kw)

class GroupSequence (_Group):
    def __init__ (self, *args, **kw):
        super(GroupSequence, self).__init__(*args, **kw)

    def newState (self, parent_particle_state):
        return SequenceState(self, parent_particle_state)

    def _validate (self, symbol_set, output_sequence):
        for p in self.particles():
            if not p._validate(symbol_set, output_sequence):
                return False
        return True

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

    NC_any = '##any'            #<<< The namespace constraint "##any"
    NC_not = '##other'          #<<< A flag indicating constraint "##other"
    NC_targetNamespace = '##targetNamespace'
    NC_local = '##local'

    __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 None, both in the "not" pair and in the set.
        """
        return self.__namespaceConstraint

    PC_skip = 'skip'            #<<< No constraint is applied
    PC_lax = 'lax'              #<<< Validate against available uniquely determined declaration
    PC_strict = 'strict'        #<<< Validate against declaration or xsi:type which must be available

    # One of PC_*
    __processContents = None
    def processContents (self): return self.__processContents

    def __init__ (self, *args, **kw):
        # Namespace constraint and process contents are required parameters.
        self.__namespaceConstraint = kw['namespace_constraint']
        self.__processContents = kw['process_contents']

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

        Not implemented yet: all wildcards are assumed to match all values.

        """
        return True

    def newState (self, parent_particle_state):
        return self

    def accepts (self, particle_state, instance, value, element_use):
        value_desc = 'value of type %s' % (type(value),)
        if not self.matches(instance, value):
            raise pyxb.UnexpectedContentError(value)
        if not isinstance(value, basis._TypeBinding_mixin):
            print 'NOTE: Created unbound wildcard element from %s' % (value_desc,)
        assert isinstance(instance.wildcardElements(), list), 'Uninitialized wildcard list in %s' % (instance._ExpandedName,)
        instance._appendWildcardElement(value)
        particle_state.incrementCount()
        return True

    def _validate (self, symbol_set, output_sequence):
        # @todo check node against namespace constraint and process contents
        #print 'WARNING: Accepting node as wildcard match without validating.'
        wc_values = symbol_set.get(None)
        if wc_values is None:
            return False
        used = wc_values.pop(0)
        output_sequence.append( (None, used) )
        if 0 == len(wc_values):
            del symbol_set[None]
        return True
        
## Local Variables:
## fill-column:78
## End: