1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Helper classes that maintain the content model of XMLSchema in the binding
16 classes.
17
18 L{AttributeUse} and L{ElementUse} record information associated with a binding
19 class, for example the types of values, the original XML QName or NCName, and
20 the Python field in which the values are stored. They also provide the
21 low-level interface to set and get the corresponding values in a binding
22 instance.
23
24
25
26 L{Wildcard} holds content-related information used in the content model.
27 """
28
29 import pyxb
30 import pyxb.namespace
31 import basis
32
33 import xml.dom
34
35 -class ContentState_mixin (pyxb.cscRoot):
36 """Declares methods used by classes that hold state while validating a
37 content model component."""
38
39 - def accepts (self, particle_state, instance, value, element_use):
40 """Determine whether the provided value can be added to the instance
41 without violating state validation.
42
43 This method must not throw any non-catastrophic exceptions; general
44 failures should be transformed to a C{False} return value.
45
46 @param particle_state: The L{ParticleState} instance serving as the
47 parent to this state. The implementation must inform that state when
48 the proposed value completes the content model.
49
50 @param instance: An instance of a subclass of
51 {basis.complexTypeDefinition}, into which the provided value will be
52 stored if it is consistent with the current model state.
53
54 @param value: The value that is being validated against the state.
55
56 @param element_use: An optional L{ElementUse} instance that specifies
57 the element to which the value corresponds. This will be available
58 when the value is extracted by parsing a document, but will be absent
59 if the value was passed as a constructor positional parameter.
60
61 @return: C{True} if the value was successfully matched against the
62 state. C{False} if the value did not match against the state."""
63 raise Exception('ContentState_mixin.accepts not implemented in %s' % (type(self),))
64
65 - def notifyFailure (self, sub_state, particle_ok):
66 """Invoked by a sub-state to indicate that validation cannot proceed
67 in the current state.
68
69 Normally this is used when an intermediate content model must reset
70 itself to permit alternative models to be evaluated.
71
72 @param sub_state: the state that was unable to accept a value
73
74 @param particle_ok: C{True} if the particle that rejected the value is
75 in an accepting terminal state
76
77 """
78 raise Exception('ContentState_mixin.notifyFailure not implemented in %s' % (type(self),))
79
80 - def _verifyComplete (self, parent_particle_state):
81 """Determine whether the deep state is complete without further elements.
82
83 No-op for non-aggregate state. For aggregate state, all contained
84 particles should be checked to see whether the overall model can be
85 satisfied if no additional elements are provided.
86
87 This method does not have a meaningful return value; violations of the
88 content model should produce the corresponding exception (generally,
89 L{MissingContentError}).
90
91 @param parent_particle_state: the L{ParticleState} for which this state
92 is the term.
93 """
94 pass
95
96 -class ContentModel_mixin (pyxb.cscRoot):
97 """Declares methods used by classes representing content model components."""
98
99 - def newState (self, parent_particle_state):
100 """Return a L{ContentState_mixin} instance that will validate the
101 state of this model component.
102
103 @param parent_particle_state: The L{ParticleState} instance for which
104 this instance is a term. C{None} for the top content model of a
105 complex data type.
106 """
107 raise Exception('ContentModel_mixin.newState not implemented in %s' % (type(self),))
108
109 - def _validateCloneSymbolSet (self, symbol_set_im):
110 """Create a mutable copy of the symbol set.
111
112 The top-level map is copied, as are the lists of values to which the
113 symbols map. The values within the lists are unchanged, as validation
114 does not affect them."""
115 rv = symbol_set_im.copy()
116 for (k, v) in rv.items():
117 rv[k] = v[:]
118 return rv
119
120 - def _validateCloneOutputSequence (self, output_sequence_im):
121 """Create a mutable copy of the output sequence."""
122 return output_sequence_im[:]
123
124 - def _validateReplaceResults (self, symbol_set_out, symbol_set_new, output_sequence_out, output_sequence_new):
125 """In-place update of symbol set and output sequence structures.
126
127 Use this to copy from temporary mutable structures updated by local
128 validation into the structures that were passed in once the validation
129 has succeeded."""
130 symbol_set_out.clear()
131 symbol_set_out.update(symbol_set_new)
132 output_sequence_out[:] = output_sequence_new
133
134 - def _validate (self, symbol_set, output_sequence):
135 """Determine whether an output sequence created from the symbols can
136 be made consistent with the model.
137
138 The symbol set represents letters in an alphabet; the output sequence
139 orders those letters in a way that satisfies the regular expression
140 expressed in the model. Both are changed as a result of a successful
141 validation; both remain unchanged if the validation failed. In
142 recursing, implementers may assume that C{output_sequence} is
143 monotonic: its length remains unchanged after an invocation iff the
144 symbol set also remains unchanged. The L{_validateCloneSymbolSet},
145 L{_validateCloneOutputSequence}, and L{_validateReplaceResults}
146 methods are available to help preserve this behavior.
147
148 @param symbol_set: A map from L{ElementUse} instances to a list of
149 values. The order of the values corresponds to the order in which
150 they should appear. A key of C{None} identifies values that are
151 stored as wildcard elements. Values are removed from the lists as
152 they are used; when the last value of a list is removed, its key is
153 removed from the map. Thus an empty dictionary is the indicator that
154 no more symbols are available.
155
156 @param output_sequence: A mutable list to which should be appended
157 tuples C{( eu, val )} where C{eu} is an L{ElementUse} from the set of
158 symbol keys, and C{val} is a value from the corresponding list. A
159 document generated by producing the elements in the given order is
160 expected to validate.
161
162 @return: C{True} iff the model validates. C{symbol_set} and
163 C{output_path} will be unchanged if this returns C{False}.
164 """
165 raise Exception('ContentState_mixin._validate not implemented in %s' % (type(self),))
166
167
169 """A helper class that encapsulates everything we need to know
170 about the way an attribute is used within a binding class.
171
172 Attributes are stored internally as pairs C{(provided, value)}, where
173 C{provided} is a boolean indicating whether a value for the attribute was
174 provided externally, and C{value} is an instance of the attribute
175 datatype. The C{provided} flag is used to determine whether an XML
176 attribute should be added to a created DOM node when generating the XML
177 corresponding to a binding instance.
178 """
179
180 __name = None
181 """ExpandedName of the attribute"""
182
183 __id = None
184 """Identifier used for this attribute within the owning class"""
185
186 __key = None
187 """Private Python attribute used in instances to hold the attribute value"""
188
189 __dataType = None
190 """The L{pyxb.binding.basis.simpleTypeDefinition} for values of the attribute"""
191
192 __unicodeDefault = None
193 """The default attribute value as a unicode string, or C{None}"""
194
195 __defaultValue = None
196 """The default value as an instance of L{__dataType}, or C{None}"""
197
198 __fixed = False
199 """C{True} if the attribute value cannot be changed"""
200
201 __required = False
202 """C{True} if the attribute must appear in every instance of the type"""
203
204 __prohibited = False
205 """C{True} if the attribute must not appear in any instance of the type"""
206
207 - def __init__ (self, name, id, key, data_type, unicode_default=None, fixed=False, required=False, prohibited=False):
208 """Create an AttributeUse instance.
209
210 @param name: The name by which the attribute is referenced in the XML
211 @type name: L{pyxb.namespace.ExpandedName}
212
213 @param id: The Python identifier for the attribute within the
214 containing L{pyxb.basis.binding.complexTypeDefinition}. This is a
215 public identifier, derived from the local part of the attribute name
216 and modified to be unique, and is usually used as the name of the
217 attribute's inspector method.
218 @type id: C{str}
219
220 @param key: The string used to store the attribute
221 value in the dictionary of the containing
222 L{pyxb.basis.binding.complexTypeDefinition}. This is mangled so
223 that it is unique among and is treated as a Python private member.
224 @type key: C{str}
225
226 @param data_type: The class reference to the subclass of
227 L{pyxb.binding.basis.simpleTypeDefinition} of which the attribute
228 values must be instances.
229 @type data_type: C{type}
230
231 @keyword unicode_default: The default value of the attribute as
232 specified in the schema, or None if there is no default attribute
233 value. The default value (of the keyword) is C{None}.
234 @type unicode_default: C{unicode}
235
236 @keyword fixed: If C{True}, indicates that the attribute, if present,
237 must have the value that was given via C{unicode_default}. The
238 default value is C{False}.
239 @type fixed: C{bool}
240
241 @keyword required: If C{True}, indicates that the attribute must appear
242 in the DOM node used to create an instance of the corresponding
243 L{pyxb.binding.basis.complexTypeDefinition}. The default value is
244 C{False}. No more that one of L{required} and L{prohibited} should be
245 assigned C{True}.
246 @type required: C{bool}
247
248 @keyword prohibited: If C{True}, indicates that the attribute must
249 B{not} appear in the DOM node used to create an instance of the
250 corresponding L{pyxb.binding.basis.complexTypeDefinition}. The
251 default value is C{False}. No more that one of L{required} and
252 L{prohibited} should be assigned C{True}.
253 @type prohibited: C{bool}
254
255 @raise pyxb.BadTypeValueError: the L{unicode_default} cannot be used
256 to initialize an instance of L{data_type}
257 """
258
259 self.__name = name
260 self.__id = id
261 self.__key = key
262 self.__dataType = data_type
263 self.__unicodeDefault = unicode_default
264 if self.__unicodeDefault is not None:
265 self.__defaultValue = self.__dataType.Factory(self.__unicodeDefault, _from_xml=True)
266 self.__fixed = fixed
267 self.__required = required
268 self.__prohibited = prohibited
269
271 """The expanded name of the element.
272
273 @rtype: L{pyxb.namespace.ExpandedName}
274 """
275 return self.__name
276
278 """The default value of the attribute."""
279 return self.__defaultValue
280
282 """C{True} iff the value of the attribute cannot be changed."""
283 return self.__fixed
284
286 """C{True} iff the attribute must be assigned a value."""
287 return self.__required
288
290 """C{True} iff the attribute must not be assigned a value."""
291 return self.__prohibited
292
294 """C{True} iff the given instance has been explicitly given a value
295 for the attribute.
296
297 This is used for things like only generating an XML attribute
298 assignment when a value was originally given (even if that value
299 happens to be the default).
300 """
301 return self.__getProvided(ctd_instance)
302
304 """Tag used within Python code for the attribute.
305
306 This is not used directly in the default code generation template."""
307 return self.__id
308
310 """String used as key within object dictionary when storing attribute value."""
311 return self.__key
312
314 """The subclass of L{pyxb.binding.basis.simpleTypeDefinition} of which any attribute value must be an instance."""
315 return self.__dataType
316
318 """Retrieve the value information for this attribute in a binding instance.
319
320 @param ctd_instance: The instance object from which the attribute is to be retrieved.
321 @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition}
322 @return: C{(provided, value)} where C{provided} is a C{bool} and
323 C{value} is C{None} or an instance of the attribute's datatype.
324
325 """
326 return getattr(ctd_instance, self.__key, (False, None))
327
330
331 - def value (self, ctd_instance):
332 """Get the value of the attribute from the instance."""
333 return self.__getValue(ctd_instance)[1]
334
335 - def __setValue (self, ctd_instance, new_value, provided):
336 return setattr(ctd_instance, self.__key, (provided, new_value))
337
338 - def reset (self, ctd_instance):
339 """Set the value of the attribute in the given instance to be its
340 default value, and mark that it has not been provided."""
341 self.__setValue(ctd_instance, self.__defaultValue, False)
342
350
379
380 - def set (self, ctd_instance, new_value):
381 """Set the value of the attribute.
382
383 This validates the value against the data type, creating a new instance if necessary.
384
385 @param ctd_instance: The binding instance for which the attribute
386 value is to be set
387 @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition}
388 @param new_value: The value for the attribute
389 @type new_value: An C{xml.dom.Node} instance, or any value that is
390 permitted as the input parameter to the C{Factory} method of the
391 attribute's datatype.
392 """
393 provided = True
394 from_xml = False
395 if isinstance(new_value, xml.dom.Node):
396 from_xml = True
397 unicode_value = self.__name.getAttribute(new_value)
398 if unicode_value is None:
399 if self.__required:
400 raise pyxb.MissingAttributeError('Required attribute %s from %s not found' % (self.__name, ctd_instance._ExpandedName or type(ctd_instance)))
401 provided = False
402 unicode_value = self.__unicodeDefault
403 if unicode_value is None:
404
405 provided = False
406 new_value = None
407 else:
408 new_value = unicode_value
409 else:
410 assert new_value is not None
411 if self.__prohibited:
412 raise pyxb.ProhibitedAttributeError('Value given for prohibited attribute %s' % (self.__name,))
413 if (new_value is not None) and (not isinstance(new_value, self.__dataType)):
414 new_value = self.__dataType.Factory(new_value, _from_xml=from_xml)
415 if self.__fixed and (new_value != self.__defaultValue):
416 raise pyxb.AttributeChangeError('Attempt to change value of fixed attribute %s' % (self.__name,))
417 self.__setValue(ctd_instance, new_value, provided)
418 return new_value
419
420 - def _description (self, name_only=False, user_documentation=True):
421 if name_only:
422 return str(self.__name)
423 assert issubclass(self.__dataType, basis._TypeBinding_mixin)
424 desc = [ str(self.__id), ': ', str(self.__name), ' (', self.__dataType._description(name_only=True, user_documentation=False), '), ' ]
425 if self.__required:
426 desc.append('required')
427 elif self.__prohibited:
428 desc.append('prohibited')
429 else:
430 desc.append('optional')
431 if self.__defaultValue is not None:
432 desc.append(', ')
433 if self.__fixed:
434 desc.append('fixed')
435 else:
436 desc.append('default')
437 desc.extend(['=', self.__unicodeDefault ])
438 return ''.join(desc)
439
440 -class ElementUse (ContentState_mixin, ContentModel_mixin):
441 """Aggregate the information relevant to an element of a complex type.
442
443 This includes the L{original tag name<name>}, the spelling of L{the
444 corresponding object in Python <id>}, an L{indicator<isPlural>} of whether
445 multiple instances might be associated with the field, and other relevant
446 information.
447 """
448
450 """The expanded name of the element.
451
452 @rtype: L{pyxb.namespace.ExpandedName}
453 """
454 return self.__name
455 __name = None
456
458 """The string name of the binding class field used to hold the element
459 values.
460
461 This is the user-visible name, and excepting disambiguation will be
462 equal to the local name of the element."""
463 return self.__id
464 __id = None
465
466
467
468
469 __key = None
470
472 """The L{basis.element} instance identifying the information
473 associated with the element declaration.
474 """
475 return self.__elementBinding
482 __elementBinding = None
483
485 """True iff the content model indicates that more than one element
486 can legitimately belong to this use.
487
488 This includes elements in particles with maxOccurs greater than one,
489 and when multiple elements with the same NCName are declared in the
490 same type.
491 """
492 return self.__isPlural
493 __isPlural = False
494
495 - def __init__ (self, name, id, key, is_plural, element_binding=None):
496 """Create an ElementUse instance.
497
498 @param name: The name by which the element is referenced in the XML
499 @type name: L{pyxb.namespace.ExpandedName}
500
501 @param id: The Python name for the element within the containing
502 L{pyxb.basis.binding.complexTypeDefinition}. This is a public
503 identifier, albeit modified to be unique, and is usually used as the
504 name of the element's inspector method or property.
505 @type id: C{str}
506
507 @param key: The string used to store the element
508 value in the dictionary of the containing
509 L{pyxb.basis.binding.complexTypeDefinition}. This is mangled so
510 that it is unique among and is treated as a Python private member.
511 @type key: C{str}
512
513 @param is_plural: If C{True}, documents for the corresponding type may
514 have multiple instances of this element. As a consequence, the value
515 of the element will be a list. If C{False}, the value will be C{None}
516 if the element is absent, and a reference to an instance of the type
517 identified by L{pyxb.binding.basis.element.typeDefinition} if present.
518 @type is_plural: C{bool}
519
520 @param element_binding: Reference to the class that serves as the
521 binding for the element.
522 """
523 self.__name = name
524 self.__id = id
525 self.__key = key
526 self.__isPlural = is_plural
527 self.__elementBinding = element_binding
528
530 """Return the default value for this element.
531
532 @todo: Right now, this returns C{None} for non-plural and an empty
533 list for plural elements. Need to support schema-specified default
534 values for simple-type content.
535 """
536 if self.isPlural():
537 return []
538 return None
539
540 - def value (self, ctd_instance):
541 """Return the value for this use within the given instance."""
542 return getattr(ctd_instance, self.__key, self.defaultValue())
543
544 - def reset (self, ctd_instance):
545 """Set the value for this use in the given element to its default."""
546 setattr(ctd_instance, self.__key, self.defaultValue())
547 return self
548
549 - def set (self, ctd_instance, value):
559
561 """Invoke either L{set} or L{append}, depending on whether the element
562 use is plural."""
563 if self.isPlural():
564 return self.append(ctd_instance, value)
565 return self.set(ctd_instance, value)
566
567 - def append (self, ctd_instance, value):
579
580 - def toDOM (self, dom_support, parent, value):
581 """Convert the given value to DOM as an instance of this element.
582
583 @param dom_support: Helper for managing DOM properties
584 @type dom_support: L{pyxb.utils.domutils.BindingDOMSupport}
585 @param parent: The DOM node within which this element should be generated.
586 @type parent: C{xml.dom.Element}
587 @param value: The content for this element. May be text (if the
588 element allows mixed content), or an instance of
589 L{basis._TypeBinding_mixin}.
590 """
591 if isinstance(value, basis._TypeBinding_mixin):
592 element_binding = self.__elementBinding
593 if value._substitutesFor(element_binding):
594 element_binding = value._element()
595 assert element_binding is not None
596 if element_binding.abstract():
597 raise pyxb.DOMGenerationError('Element %s is abstract but content %s not associated with substitution group member' % (self.name(), value))
598 element = dom_support.createChildElement(element_binding.name(), parent)
599 elt_type = element_binding.typeDefinition()
600 val_type = type(value)
601 if isinstance(value, basis.complexTypeDefinition):
602 assert isinstance(value, elt_type)
603 else:
604 if isinstance(value, basis.STD_union) and isinstance(value, elt_type._MemberTypes):
605 val_type = elt_type
606 if dom_support.requireXSIType() or elt_type._RequireXSIType(val_type):
607 val_type_qname = value._ExpandedName.localName()
608 tns_prefix = dom_support.namespacePrefix(value._ExpandedName.namespace())
609 if tns_prefix is not None:
610 val_type_qname = '%s:%s' % (tns_prefix, val_type_qname)
611 dom_support.addAttribute(element, pyxb.namespace.XMLSchema_instance.createExpandedName('type'), val_type_qname)
612 value._toDOM_csc(dom_support, element)
613 elif isinstance(value, (str, unicode)):
614 element = dom_support.createChildElement(self.name(), parent)
615 element.appendChild(dom_support.document().createTextNode(value))
616 else:
617 raise pyxb.LogicError('toDOM with unrecognized value type %s: %s' % (type(value), value))
618
619 - def _description (self, name_only=False, user_documentation=True):
627
628
629 - def newState (self, parent_particle_state):
630 """Implement parent class method."""
631 return self
632
633
634 - def accepts (self, particle_state, instance, value, element_use):
640
641 - def _accepts (self, instance, value, element_use):
664
665
666 - def _validate (self, symbol_set, output_sequence):
667 values = symbol_set.get(self)
668
669 if values is None:
670 return False
671 used = values.pop(0)
672 output_sequence.append( (self, used) )
673 if 0 == len(values):
674 del symbol_set[self]
675 return True
676
678 return 'EU.%s@%x' % (self.__name, id(self))
679
680
681 -class Wildcard (ContentState_mixin, ContentModel_mixin):
682 """Placeholder for wildcard objects."""
683
684 NC_any = '##any'
685 NC_not = '##other'
686 NC_targetNamespace = '##targetNamespace'
687 NC_local = '##local'
688
689 __namespaceConstraint = None
691 """A constraint on the namespace for the wildcard.
692
693 Valid values are:
694
695 - L{Wildcard.NC_any}
696 - A tuple ( L{Wildcard.NC_not}, a L{namespace<pyxb.namespace.Namespace>} instance )
697 - set(of L{namespace<pyxb.namespace.Namespace>} instances)
698
699 Namespaces are represented by their URIs. Absence is
700 represented by C{None}, both in the "not" pair and in the set.
701 """
702 return self.__namespaceConstraint
703
704 PC_skip = 'skip'
705 """No namespace constraint is applied to the wildcard."""
706
707 PC_lax = 'lax'
708 """Validate against available uniquely determined declaration."""
709
710 PC_strict = 'strict'
711 """Validate against declaration or xsi:type, which must be available."""
712
713 __processContents = None
714 """One of L{PC_skip}, L{PC_lax}, L{PC_strict}."""
715 - def processContents (self):
716 """Indicate how this wildcard's contents should be processed."""
717 return self.__processContents
718
726
728 """
729 @keyword namespace_constraint: Required namespace constraint(s)
730 @keyword process_contents: Required"""
731
732
733 nsc = kw['namespace_constraint']
734 if isinstance(nsc, tuple):
735 nsc = (nsc[0], self.__normalizeNamespace(nsc[1]))
736 elif isinstance(nsc, set):
737 nsc = set([ self.__normalizeNamespace(_uri) for _uri in nsc ])
738 self.__namespaceConstraint = nsc
739 self.__processContents = kw['process_contents']
740
741 - def matches (self, instance, value):
773
774
775 - def newState (self, parent_particle_state):
777
778
779 - def accepts (self, particle_state, instance, value, element_use):
793
794
795 - def _validate (self, symbol_set, output_sequence):
796
797
798 wc_values = symbol_set.get(None)
799 if wc_values is None:
800 return False
801 used = wc_values.pop(0)
802 output_sequence.append( (None, used) )
803 if 0 == len(wc_values):
804 del symbol_set[None]
805 return True
806
808 """Represent the state of validation against a sequence of particles."""
809
810 __sequence = None
811 __particleState = None
812 __parentParticleState = None
813
814 __index = -1
815
816 __failed = False
817 """C{True} iff the content provided is in conflict with the sequence
818 requirements.
819
820 Specifically, the model requires content that has not been provided. Set
821 within L{accepts}. This state is sticky."""
822
823 __satisfied = False
824 """C{True} iff the content provided is consistent with the sequence
825 requirements.
826
827 Specifically, nothing has been presented with conflicts with the model.
828 Set within L{notifyFailure}."""
829
830 - def __init__ (self, group, parent_particle_state):
840
841
842
843 - def accepts (self, particle_state, instance, value, element_use):
856
857
861
862
872
873
875 - def __init__ (self, group, parent_particle_state):
880
881
882
883 - def accepts (self, particle_state, instance, value, element_use):
884
885
886 if self.__activeChoice is None:
887 for choice in self.__choices:
888
889 try:
890 (consume, underflow_exc) = choice.step(instance, value, element_use)
891 except Exception, e:
892 consume = False
893 underflow_exc = e
894
895 if consume:
896 self.__activeChoice = choice
897 self.__choices = None
898 return True
899 return False
900 (consume, underflow_exc) = self.__activeChoice.step(instance, value, element_use)
901
902 if consume:
903 return True
904 if underflow_exc is not None:
905 self.__failed = True
906 raise underflow_exc
907 return False
908
909
926
927
933
988
989 -class ParticleState (pyxb.cscRoot):
990
991 __parentState = None
992 """The L{ContentState_mixin} which contains the mode for which this is state."""
993
994 __termState = None
995 """A L{ContentState_mixin} instance for one occurrence of this particle's term."""
996
997 __tryAccept = None
998 """A flag indicating whether a proposed value should be applied to the
999 state by L{step}."""
1000
1001 - def __init__ (self, particle, parent_state=None):
1002 self.__particle = particle
1003 self.__parentState = parent_state
1004 self.__count = -1
1005
1006 self.incrementCount()
1007
1008 __particle = None
1009 """The L{ParticleModel} for which this represents state."""
1010
1011 - def particle (self):
1012 """The L{ParticleModel} for which this represents state."""
1013 return self.__particle
1014
1015 __count = None
1016 """The number of times this particle's term has been matched."""
1017
1018 - def incrementCount (self):
1019 """Reset for a new occurrence of the particle's term."""
1020
1021 self.__count += 1
1022 self.__termState = self.__particle.term().newState(self)
1023 self.__tryAccept = True
1024
1025 - def verifyComplete (self):
1026 """Check whether the particle's occurrence constraints are satisfied.
1027
1028 @raise pyxb.MissingContentError: Particle requires additional content to be satisfied."""
1029
1030
1031
1032
1033
1034
1035 if not self.__particle.satisfiesOccurrences(self.__count):
1036 self.__termState._verifyComplete(self)
1037
1038
1039 if not self.__particle.satisfiesOccurrences(self.__count):
1040
1041 raise pyxb.MissingContentError('incomplete')
1042
1043
1044 if self.__parentState is not None:
1045 self.__parentState.notifyFailure(self, True)
1046
1047 - def step (self, instance, value, element_use):
1048 """Attempt to apply the value as a new instance of the particle's term.
1049
1050 The L{ContentState_mixin} created for the particle's term is consulted
1051 to determine whether the instance can accept the given value. If so,
1052 the particle's maximum occurrence limit is checked; if not, and the
1053 particle has a parent state, it is informed of the failure.
1054
1055 @param instance: An instance of a subclass of
1056 {basis.complexTypeDefinition}, into which the provided value will be
1057 stored if it is consistent with the current model state.
1058
1059 @param value: The value that is being validated against the state.
1060
1061 @param element_use: An optional L{ElementUse} instance that specifies
1062 the element to which the value corresponds. This will be available
1063 when the value is extracted by parsing a document, but will be absent
1064 if the value was passed as a constructor positional parameter.
1065
1066 @return: C{( consumed, underflow_exc )} A tuple where the first element
1067 is C{True} iff the provided value was accepted in the current state.
1068 When this first element is C{False}, the second element will be
1069 C{None} if the particle's occurrence requirements have been met, and
1070 is an instance of C{MissingElementError} if the observed number of
1071 terms is less than the minimum occurrence count. Depending on
1072 context, the caller may raise this exception, or may try an
1073 alternative content model.
1074
1075 @raise pyxb.UnexpectedElementError: if the value satisfies the particle,
1076 but having done so exceeded the allowable number of instances of the
1077 term.
1078 """
1079
1080
1081
1082
1083 consumed = False
1084 underflow_exc = None
1085
1086
1087
1088
1089 self.__tryAccept = True
1090 while self.__tryAccept and (self.__count != self.__particle.maxOccurs()):
1091 self.__tryAccept = False
1092 consumed = self.__termState.accepts(self, instance, value, element_use)
1093
1094 self.__tryAccept = self.__tryAccept and (not consumed)
1095
1096 if consumed:
1097 if not self.__particle.meetsMaximum(self.__count):
1098 raise pyxb.UnexpectedElementError('too many')
1099 else:
1100 if self.__parentState is not None:
1101 self.__parentState.notifyFailure(self, self.__particle.satisfiesOccurrences(self.__count))
1102 if not self.__particle.meetsMinimum(self.__count):
1103
1104
1105 underflow_exc = pyxb.MissingElementError(content=value, container=instance)
1106 return (consumed, underflow_exc)
1107
1108 - def __str__ (self):
1109 particle = self.__particle
1110 return 'ParticleState(%d:%d,%s:%s)@%x' % (self.__count, particle.minOccurs(), particle.maxOccurs(), particle.term(), id(self))
1111
1112 -class ParticleModel (ContentModel_mixin):
1113 """Content model dealing with particles: terms with occurrence restrictions"""
1114
1115 __minOccurs = None
1116 - def minOccurs (self):
1117 """The minimum number of times the term must occur.
1118
1119 This will be a non-negative integer."""
1120 return self.__minOccurs
1121
1122 __maxOccurs = None
1123 - def maxOccurs (self):
1124 """The maximum number of times the term may occur.
1125
1126 This will be a positive integer, or C{None} indicating an unbounded
1127 number of occurrences."""
1128 return self.__maxOccurs
1129
1130 __term = None
1131 """The L{ContentModel_mixin} for a single occurrence."""
1133 """The term for a single occurrence."""
1134 return self.__term
1135
1136 - def meetsMaximum (self, count):
1137 """@return: C{True} iff there is no maximum on term occurrence, or the
1138 provided count does not exceed that maximum"""
1139 return (self.__maxOccurs is None) or (count <= self.__maxOccurs)
1140
1141 - def meetsMinimum (self, count):
1142 """@return: C{True} iff the provided count meets the minimum number of
1143 occurrences"""
1144 return count >= self.__minOccurs
1145
1146 - def satisfiesOccurrences (self, count):
1147 """@return: C{True} iff the provided count satisfies the occurrence
1148 requirements"""
1149 return self.meetsMinimum(count) and self.meetsMaximum(count)
1150
1151 - def __init__ (self, term, min_occurs=1, max_occurs=1):
1152 self.__term = term
1153 self.__minOccurs = min_occurs
1154 self.__maxOccurs = max_occurs
1155
1156
1157 - def newState (self):
1158 return ParticleState(self)
1159
1160 - def validate (self, symbol_set):
1161 """Determine whether the particle requirements are satisfiable by the
1162 given symbol set.
1163
1164 The symbol set represents letters in an alphabet. If those letters
1165 can be combined in a way that satisfies the regular expression
1166 expressed in the model, a satisfying sequence is returned and the
1167 symbol set is reduced by the letters used to form the sequence. If
1168 the content model cannot be satisfied, C{None} is returned and the
1169 symbol set remains unchanged.
1170
1171 @param symbol_set: A map from L{ElementUse} instances to a list of
1172 values. The order of the values corresponds to the order in which
1173 they should appear. A key of C{None} identifies values that are
1174 stored as wildcard elements. Values are removed from the lists as
1175 they are used; when the last value of a list is removed, its key is
1176 removed from the map. Thus an empty dictionary is the indicator that
1177 no more symbols are available.
1178
1179 @return: returns C{None}, or a list of tuples C{( eu, val )} where
1180 C{eu} is an L{ElementUse} from the set of symbol keys, and C{val} is a
1181 value from the corresponding list.
1182 """
1183
1184 output_sequence = []
1185
1186 result = self._validate(symbol_set, output_sequence)
1187
1188 if result:
1189 return (symbol_set, output_sequence)
1190 return None
1191
1192
1193 - def _validate (self, symbol_set, output_sequence):
1194 symbol_set_mut = self._validateCloneSymbolSet(symbol_set)
1195 output_sequence_mut = self._validateCloneOutputSequence(output_sequence)
1196 count = 0
1197
1198 last_size = len(output_sequence_mut)
1199 while (count != self.__maxOccurs) and self.__term._validate(symbol_set_mut, output_sequence_mut):
1200
1201 this_size = len(output_sequence_mut)
1202 if this_size == last_size:
1203
1204
1205 if count < self.__minOccurs:
1206 count = self.__minOccurs
1207 break
1208 count += 1
1209 last_size = this_size
1210 result = self.satisfiesOccurrences(count)
1211 if (result):
1212 self._validateReplaceResults(symbol_set, symbol_set_mut, output_sequence, output_sequence_mut)
1213
1214 return result
1215
1216 -class _Group (ContentModel_mixin):
1217 """Base class for content information pertaining to a U{model
1218 group<http://www.w3.org/TR/xmlschema-1/#Model_Groups>}.
1219
1220 There is a specific subclass for each group compositor.
1221 """
1222
1223 _StateClass = None
1224 """A reference to a L{ContentState_mixin} class that maintains state when
1225 validating an instance of this group."""
1226
1227 __particles = None
1228 """List of L{ParticleModel}s comprising the group."""
1229
1230 - def particles (self):
1231 """The sequence of particles comprising the group"""
1232 return self.__particles
1233
1236
1237
1238
1239 - def newState (self, parent_particle_state):
1240 return self._StateClass(self, parent_particle_state)
1241
1242
1243
1244 - def _validate (self, symbol_set, output_sequence):
1252
1253
1273
1276
1279
1280
1281
1282
1283