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 L{ContentModelTransition}, L{ContentModelState}, and L{ContentModel} are used
25 to store a deterministic finite automaton which is used to translate between
26 binding instances and other representations (e.g., DOM nodes)
27
28 L{ModelGroupAllAlternative} and L{ModelGroupAll} represent special nodes in
29 the DFA that support a model group with compositor "all" in a way that does
30 not result in an exponential state explosion in the DFA.
31
32 L{DFAStack} and its related internal classes are used in stream-based
33 processing of content.
34
35 L{Wildcard} holds content-related information used in the content model.
36 """
37
38 import pyxb
39 import pyxb.namespace
40 import basis
41
42 import xml.dom
43
45 """A helper class that encapsulates everything we need to know
46 about the way an attribute is used within a binding class.
47
48 Attributes are stored internally as pairs C{(provided, value)}, where
49 C{provided} is a boolean indicating whether a value for the attribute was
50 provided externally, and C{value} is an instance of the attribute
51 datatype. The C{provided} flag is used to determine whether an XML
52 attribute should be added to a created DOM node when generating the XML
53 corresponding to a binding instance.
54 """
55
56 __name = None
57 __id = None
58 __key = None
59 __dataType = None
60 __unicodeDefault = None
61 __defaultValue = None
62 __fixed = False
63 __required = False
64 __prohibited = False
65
66 - def __init__ (self, name, id, key, data_type, unicode_default=None, fixed=False, required=False, prohibited=False):
67 """Create an AttributeUse instance.
68
69 @param name: The name by which the attribute is referenced in the XML
70 @type name: L{pyxb.namespace.ExpandedName}
71
72 @param id: The Python identifier for the attribute within the
73 containing L{pyxb.basis.binding.complexTypeDefinition}. This is a
74 public identifier, derived from the local part of the attribute name
75 and modified to be unique, and is usually used as the name of the
76 attribute's inspector method.
77 @type id: C{str}
78
79 @param key: The string used to store the attribute
80 value in the dictionary of the containing
81 L{pyxb.basis.binding.complexTypeDefinition}. This is mangled so
82 that it is unique among and is treated as a Python private member.
83 @type key: C{str}
84
85 @param data_type: The class reference to the subclass of
86 L{pyxb.binding.basis.simpleTypeDefinition} of which the attribute
87 values must be instances.
88 @type data_type: C{type}
89
90 @keyword unicode_default: The default value of the attribute as
91 specified in the schema, or None if there is no default attribute
92 value. The default value (of the keyword) is C{None}.
93 @type unicode_default: C{unicode}
94
95 @keyword fixed: If C{True}, indicates that the attribute, if present,
96 must have the value that was given via C{unicode_default}. The
97 default value is C{False}.
98 @type fixed: C{bool}
99
100 @keyword required: If C{True}, indicates that the attribute must appear
101 in the DOM node used to create an instance of the corresponding
102 L{pyxb.binding.basis.complexTypeDefinition}. The default value is
103 C{False}. No more that one of L{required} and L{prohibited} should be
104 assigned C{True}.
105 @type required: C{bool}
106
107 @keyword prohibited: If C{True}, indicates that the attribute must
108 B{not} appear in the DOM node used to create an instance of the
109 corresponding L{pyxb.binding.basis.complexTypeDefinition}. The
110 default value is C{False}. No more that one of L{required} and
111 L{prohibited} should be assigned C{True}.
112 @type prohibited: C{bool}
113
114 @raise pyxb.BadTypeValueError: the L{unicode_default} cannot be used
115 to initialize an instance of L{data_type}
116 """
117
118 self.__name = name
119 self.__id = id
120 self.__key = key
121 self.__dataType = data_type
122 self.__unicodeDefault = unicode_default
123 if self.__unicodeDefault is not None:
124 self.__defaultValue = self.__dataType.Factory(self.__unicodeDefault)
125 self.__fixed = fixed
126 self.__required = required
127 self.__prohibited = prohibited
128
130 """The expanded name of the element.
131
132 @rtype: L{pyxb.namespace.ExpandedName}
133 """
134 return self.__name
135
137 """The default value of the attribute."""
138 return self.__defaultValue
139
141 """C{True} iff the value of the attribute cannot be changed."""
142 return self.__fixed
143
145 """Return True iff the attribute must be assigned a value."""
146 return self.__required
147
149 """Return True iff the attribute must not be assigned a value."""
150 return self.__prohibited
151
153 """Return True iff the given instance has been explicitly given a
154 value for the attribute.
155
156 This is used for things like only generating an XML attribute
157 assignment when a value was originally given (even if that value
158 happens to be the default).
159 """
160 return self.__getProvided(ctd_instance)
161
163 """Tag used within Python code for the attribute.
164
165 This is not used directly in the default code generation template."""
166 return self.__id
167
169 """String used as key within object dictionary when storing attribute value."""
170 return self.__key
171
173 """The subclass of L{pyxb.binding.basis.simpleTypeDefinition} of which any attribute value must be an instance."""
174 return self.__dataType
175
177 """Retrieve the value information for this attribute in a binding instance.
178
179 @param ctd_instance: The instance object from which the attribute is to be retrieved.
180 @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition}
181 @return: C{(provided, value)} where C{provided} is a C{bool} and
182 C{value} is C{None} or an instance of the attribute's datatype.
183
184 """
185 return getattr(ctd_instance, self.__key, (False, None))
186
189
190 - def value (self, ctd_instance):
191 """Get the value of the attribute from the instance."""
192 return self.__getValue(ctd_instance)[1]
193
194 - def __setValue (self, ctd_instance, new_value, provided):
195 return setattr(ctd_instance, self.__key, (provided, new_value))
196
197 - def reset (self, ctd_instance):
198 """Set the value of the attribute in the given instance to be its
199 default value, and mark that it has not been provided."""
200 self.__setValue(ctd_instance, self.__defaultValue, False)
201
209
225
226 - def set (self, ctd_instance, new_value):
227 """Set the value of the attribute.
228
229 This validates the value against the data type, creating a new instance if necessary.
230
231 @param ctd_instance: The binding instance for which the attribute
232 value is to be set
233 @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition}
234 @param new_value: The value for the attribute
235 @type new_value: An C{xml.dom.Node} instance, or any value that is
236 permitted as the input parameter to the C{Factory} method of the
237 attribute's datatype.
238 """
239 provided = True
240 if isinstance(new_value, xml.dom.Node):
241 unicode_value = self.__name.getAttribute(new_value)
242 if unicode_value is None:
243 if self.__required:
244 raise pyxb.MissingAttributeError('Required attribute %s from %s not found' % (self.__name, ctd_instance._ExpandedName or type(ctd_instance)))
245 provided = False
246 unicode_value = self.__unicodeDefault
247 if unicode_value is None:
248
249 provided = False
250 new_value = None
251 else:
252 new_value = unicode_value
253 else:
254 assert new_value is not None
255 if self.__prohibited:
256 raise pyxb.ProhibitedAttributeError('Value given for prohibited attribute %s' % (self.__name,))
257 if (new_value is not None) and (not isinstance(new_value, self.__dataType)):
258 new_value = self.__dataType.Factory(new_value)
259 if self.__fixed and (new_value != self.__defaultValue):
260 raise pyxb.AttributeChangeError('Attempt to change value of fixed attribute %s' % (self.__name,))
261 self.__setValue(ctd_instance, new_value, provided)
262 return new_value
263
264 - def _description (self, name_only=False, user_documentation=True):
265 if name_only:
266 return str(self.__name)
267 assert issubclass(self.__dataType, basis._TypeBinding_mixin)
268 desc = [ str(self.__id), ': ', str(self.__name), ' (', self.__dataType._description(name_only=True, user_documentation=False), '), ' ]
269 if self.__required:
270 desc.append('required')
271 elif self.__prohibited:
272 desc.append('prohibited')
273 else:
274 desc.append('optional')
275 if self.__defaultValue is not None:
276 desc.append(', ')
277 if self.__fixed:
278 desc.append('fixed')
279 else:
280 desc.append('default')
281 desc.extend(['=', self.__unicodeDefault ])
282 return ''.join(desc)
283
285 """Aggregate the information relevant to an element of a complex type.
286
287 This includes the L{original tag name<name>}, the spelling of L{the
288 corresponding object in Python <id>}, an L{indicator<isPlural>} of whether
289 multiple instances might be associated with the field, and other relevant
290 information..
291 """
292
294 """The expanded name of the element.
295
296 @rtype: L{pyxb.namespace.ExpandedName}
297 """
298 return self.__name
299 __name = None
300
302 """The string name of the binding class field used to hold the element
303 values.
304
305 This is the user-visible name, and excepting disambiguation will be
306 equal to the local name of the element."""
307 return self.__id
308 __id = None
309
310
311
312
313 __key = None
314
316 """The L{basis.element} instance identifying the information
317 associated with the element declaration.
318 """
319 return self.__elementBinding
326 __elementBinding = None
327
329 """True iff the content model indicates that more than one element
330 can legitimately belong to this use.
331
332 This includes elements in particles with maxOccurs greater than one,
333 and when multiple elements with the same NCName are declared in the
334 same type.
335 """
336 return self.__isPlural
337 __isPlural = False
338
339 - def __init__ (self, name, id, key, is_plural, element_binding=None):
340 """Create an ElementUse instance.
341
342 @param name: The name by which the element is referenced in the XML
343 @type name: L{pyxb.namespace.ExpandedName}
344
345 @param id: The Python name for the element within the containing
346 L{pyxb.basis.binding.complexTypeDefinition}. This is a public
347 identifier, albeit modified to be unique, and is usually used as the
348 name of the element's inspector method or property.
349 @type id: C{str}
350
351 @param key: The string used to store the element
352 value in the dictionary of the containing
353 L{pyxb.basis.binding.complexTypeDefinition}. This is mangled so
354 that it is unique among and is treated as a Python private member.
355 @type key: C{str}
356
357 @param is_plural: If C{True}, documents for the corresponding type may
358 have multiple instances of this element. As a consequence, the value
359 of the element will be a list. If C{False}, the value will be C{None}
360 if the element is absent, and a reference to an instance of the type
361 identified by L{pyxb.binding.basis.element.typeDefinition} if present.
362 @type is_plural: C{bool}
363
364 @param element_binding: Reference to the class that serves as the
365 binding for the element.
366 """
367 self.__name = name
368 self.__id = id
369 self.__key = key
370 self.__isPlural = is_plural
371 self.__elementBinding = element_binding
372
374 """Return the default value for this element.
375
376 @todo: Right now, this returns C{None} for non-plural and an empty
377 list for plural elements. Need to support schema-specified default
378 values for simple-type content.
379 """
380 if self.isPlural():
381 return []
382 return None
383
384 - def value (self, ctd_instance):
385 """Return the value for this use within the given instance."""
386 return getattr(ctd_instance, self.__key, self.defaultValue())
387
388 - def reset (self, ctd_instance):
389 """Set the value for this use in the given element to its default."""
390 setattr(ctd_instance, self.__key, self.defaultValue())
391 return self
392
393 - def set (self, ctd_instance, value):
403
405 """Invoke either L{set} or L{append}, depending on whether the element
406 use is plural."""
407 if self.isPlural():
408 return self.append(ctd_instance, value)
409 return self.set(ctd_instance, value)
410
411 - def append (self, ctd_instance, value):
423
424 - def toDOM (self, dom_support, parent, value):
425 """Convert the given value to DOM as an instance of this element.
426
427 @param dom_support: Helper for managing DOM properties
428 @type dom_support: L{pyxb.utils.domutils.BindingDOMSupport}
429 @param parent: The DOM node within which this element should be generated.
430 @type parent: C{xml.dom.Element}
431 @param value: The content for this element. May be text (if the
432 element allows mixed content), or an instance of
433 L{basis._TypeBinding_mixin}.
434 """
435 if isinstance(value, basis._TypeBinding_mixin):
436 element_binding = self.__elementBinding
437 if value._substitutesFor(element_binding):
438 element_binding = value._element()
439 assert element_binding is not None
440 if element_binding.abstract():
441 raise pyxb.DOMGenerationError('Element %s is abstract but content %s not associated with substitution group member' % (self.name(), value))
442 element = dom_support.createChildElement(element_binding.name(), parent)
443 elt_type = element_binding.typeDefinition()
444 val_type = type(value)
445 if isinstance(value, basis.complexTypeDefinition):
446 assert isinstance(value, elt_type)
447 else:
448 if isinstance(value, basis.STD_union) and isinstance(value, elt_type._MemberTypes):
449 val_type = elt_type
450 if dom_support.requireXSIType() or elt_type._RequireXSIType(val_type):
451 val_type_qname = value._ExpandedName.localName()
452 tns_prefix = dom_support.namespacePrefix(value._ExpandedName.namespace())
453 if tns_prefix is not None:
454 val_type_qname = '%s:%s' % (tns_prefix, val_type_qname)
455 dom_support.addAttribute(element, pyxb.namespace.XMLSchema_instance.createExpandedName('type'), val_type_qname)
456 value._toDOM_csc(dom_support, element)
457 elif isinstance(value, (str, unicode)):
458 element = dom_support.createChildElement(self.name(), parent)
459 element.appendChild(dom_support.document().createTextNode(value))
460 else:
461 raise pyxb.LogicError('toDOM with unrecognized value type %s: %s' % (type(value), value))
462
463 - def _description (self, name_only=False, user_documentation=True):
471
473 """Base class for a suspended DFA interpretation."""
474 __contentModel = None
475 __state = None
476
477 - def __init__ (self, content_model, state=1):
480
482 """The current state of the automaton, represented as an integer."""
483 return self.__state
484 - def contentModel (self):
485 """The L{ContentModel} to which the state belongs."""
486 return self.__contentModel
488 """Change the automaton state recorded in this DFA state."""
489 self.__state = state
490 return self
491
492 - def step (self, dfa_stack, ctd_instance, value, element_use):
493 """Execute a step within the content model.
494
495 This determines whether the current state in the content model allows
496 a transition on the given value. If a transition can be performed,
497 the instance element use corresponding to the value is used to record
498 the value.
499
500 The input value should be an instance of L{basis._TypeBinding_mixin},
501 or a value that can be uniquely converted into such a instance using
502 the transitions from the current state as clues.
503
504 @param dfa_stack: The current state of the parse. Upon return, this may have been augmented with suspended content models.
505 @type dfa_stack: L{DFAStack}
506 @param value: A value upon which transition should occur.
507 @type value: C{xml.dom.Node} or L{basis._TypeBinding_mixin} or other value
508 @return: C{True} iff a transition successfully consumed the value
509 """
510
511 assert isinstance(ctd_instance, basis.complexTypeDefinition)
512 self.__state = self.contentModel().step(ctd_instance, self.state(), value, element_use, dfa_stack)
513 return self.__state is not None
514
516 """Return C{True} iff the current state of the content model is a final state."""
517 return self.contentModel().isFinal(self.state())
518
520 """The state of a suspended interpretation of a L{ModelGroupAll}
521 transition. This state comprises a set of alternatives, and optionally a
522 L{DFAStack} corresponding to the current position within one of the
523 alternatives.
524 """
525
526 __modelGroup = None
527 __alternatives = None
528 __currentStack = None
529 __isFinal = None
530
534
535 - def step (self, dfa_stack, ctd_instance, value, element_use):
536 """Execute a step within the model group.
537
538 If an automaton stack is currently being executed, the step defers to
539 that automaton. If a step is succesfully taken, the invocation
540 returns; otherwise, the automaton stack is discarded.
541
542 If no automaton stack is active, a step is attempted on each automaton
543 remaining in the alternatives. If the step is successful, that
544 automaton is recorded as being the current one for execution, and the
545 invocation returns.
546
547 If no automaton can be found within which progress can be made, the
548 step fails.
549
550 @param dfa_stack: The current state of the parse. Upon return, this
551 may have been augmented with suspended content models.
552 @type dfa_stack: L{DFAStack}
553 @param value: A value upon which transition should occur.
554 @type value: C{xml.dom.Node} or L{basis._TypeBinding_mixin} or other value
555 @return: C{True} iff a transition was found that consumed the value.
556 """
557
558 assert isinstance(ctd_instance, basis.complexTypeDefinition)
559 if self.__currentStack is not None:
560 if self.__currentStack.step(ctd_instance, value, element_use):
561 return True
562 if not self.__currentStack.isTerminal():
563
564
565
566
567
568 print '******** Non-terminal state reached in all group parsing; please contact support'
569 self.__currentStack = None
570 found_match = True
571 for alt in self.__alternatives:
572 try:
573 new_stack = alt.contentModel().initialDFAStack()
574 if new_stack.step(ctd_instance, value, element_use):
575 self.__currentStack = new_stack
576 self.__alternatives.remove(alt)
577 return True
578 except pyxb.BadDocumentError, e:
579
580 pass
581 return False
582
592
594 """A stack of states and content models representing the current status of
595 an interpretation of a content model, including invocations of nested
596 content models reached through L{ModelGroupAll} instances."""
597
598 __stack = None
602
604 """Add the given model state as the new top (actively executing) model ."""
605 self.__stack.append(model_state)
606 return model_state
607
609 """Return C{True} iff the stack is in a state where the top-level
610 model execution has reached a final state."""
611 return (0 == len(self.__stack)) or self.topModelState().isFinal()
612
614 """Remove and return the model state currently being executed."""
615 if 0 == len(self.__stack):
616 raise pyxb.LogicError('Attempt to underflow content model stack')
617 return self.__stack.pop()
618
620 """Return a reference to the model state currently being executed.
621
622 The state is not removed from the stack."""
623 if 0 == len(self.__stack):
624 raise pyxb.LogicError('Attempt to underflow content model stack')
625 return self.__stack[-1]
626
627 - def step (self, ctd_instance, value, element_use):
628 """Take a step using the value and the current model state.
629
630 Execution of the step may add a new model state to the stack.
631
632 @return: C{True} iff the value was consumed by a transition."""
633 assert isinstance(ctd_instance, basis.complexTypeDefinition)
634 if 0 == len(self.__stack):
635 return False
636 ok = self.topModelState().step(self, ctd_instance, value, element_use)
637 if not ok:
638 self.popModelState()
639 return ok
640
641 -class ContentModelTransition (pyxb.cscRoot):
642 """Represents a transition in the content model DFA.
643
644 If the next object in the DOM model conforms to the specified term, it is
645 consumed and the specified next state is entered."""
646
648 """The matching term for this transition to succeed."""
649 if self.__term is None:
650 self.__term = self.__elementUse.elementBinding()
651 assert self.__term is not None
652 return self.__term
653 __term = None
654
655 - def currentStateRef (self):
657 __currentStateRef = None
658 - def _currentStateRef (self, current_state_ref):
659 self.__currentStateRef = current_state_ref
660
661 - def nextState (self):
662 """The next state in the DFA"""
663 return self.__nextState
664 __nextState = None
665
666
667
668 - def elementUse (self):
669 return self.__elementUse
670 __elementUse = None
671
672
673 TT_element = 0x01
674 TT_modelGroupAll = 0x02
675 TT_wildcard = 0x03
676
677
678 __termType = None
679 - def termType (self):
680 return self.__termType
681
682 - def __init__ (self, next_state, element_use=None, term=None):
683 """Create a transition to a new state upon receipt of a term,
684 storing the successful match using the provided ElementUse."""
685 self.__nextState = next_state
686 assert self.__nextState is not None
687 self.__elementUse = element_use
688 if self.__elementUse is not None:
689 self.__term = None
690 self.__termType = self.TT_element
691 else:
692 self.__term = term
693 assert self.__term is not None
694 if isinstance(self.__term, ModelGroupAll):
695 self.__termType = self.TT_modelGroupAll
696 elif isinstance(self.__term, Wildcard):
697 self.__termType = self.TT_wildcard
698 else:
699 raise pyxb.LogicError('Unexpected transition term %s' % (self.__term,))
700
701 - def __cmp__ (self, other):
702 """Sort transitions so elements precede model groups precede
703 wildcards. Also sort within each subsequence."""
704 rv = cmp(self.__termType, other.__termType)
705 if 0 == rv:
706
707
708 if (self.TT_element == self.__termType):
709 rv = cmp(self.__elementUse.name(), other.__elementUse.name())
710 else:
711 rv = cmp(self.__term, other.__term)
712 return rv
713
714 - def __processElementTransition (self, value, element_use):
715
716 if isinstance(value, xml.dom.Node):
717
718
719
720
721 return None
722 try:
723
724
725
726
727
728
729 return self.term().compatibleValue(value, _convert_string_values=False)
730 except pyxb.BadTypeValueError, e:
731
732 pass
733 return None
734
735 - def __validateConsume (self, key, available_symbols_im, output_sequence_im, candidates):
736
737
738
739
740 next_symbols = available_symbols_im.copy()
741
742
743
744
745
746
747 key_type = type(None)
748 elt_plural = False
749 if key is not None:
750 key_type = key.elementBinding().typeDefinition()
751 elt_plural = key.isPlural()
752 multiple_values = False
753 try:
754 iter(next_symbols[key][0])
755 multiple_values = True
756 except TypeError:
757 pass
758
759 if (self.__nextState == self.__currentStateRef.state()):
760 consume_all = True
761 consume_singles = True
762 else:
763 consume_all = False
764 consume_singles = True
765 if consume_all:
766 consumed = next_symbols[key]
767 del next_symbols[key]
768 else:
769
770 next_left = next_symbols[key][:]
771 consumed = [ next_left.pop(0) ]
772 if 0 == len(next_left):
773 del next_symbols[key]
774 else:
775 next_symbols[key] = next_left
776 if consume_singles:
777 output_sequence = output_sequence_im + [ (key, _c) for _c in consumed ]
778 else:
779 output_sequence = output_sequence_im + [ (key, key_type(consumed)) ]
780 assert (not (key in next_symbols)) or (0 < len(next_symbols[key]))
781 candidate = (self.__nextState, next_symbols, output_sequence)
782 candidates.append(candidate)
783 return True
784
785 - def validate (self, available_symbols_im, output_sequence_im, candidates):
786 """Determine whether it is possible to take this transition using the
787 available symbols.
788
789 @param available_symbols_im: As with L{ContentModel.validate}. The
790 map will not be modified by this method.
791
792 @param output_sequence_im: As with the return value of
793 L{ContentModel.validate}. The sequence will not be modified by this
794 event (it is used as a prefix for new candidates).
795
796 @param candidates: A list of candidate validation paths.
797
798 @return: C{True} iff the transition could be made."""
799 if self.TT_element == self.__termType:
800 if not (self.__elementUse in available_symbols_im):
801
802 return False
803 assert 0 < len(available_symbols_im[self.__elementUse])
804 return self.__validateConsume(self.__elementUse, available_symbols_im, output_sequence_im, candidates)
805 elif self.TT_modelGroupAll == self.__termType:
806 return self.term().validate(available_symbols_im, output_sequence_im, self.__nextState, candidates)
807 elif self.TT_wildcard == self.__termType:
808 if not (None in available_symbols_im):
809 return False
810 assert 0 < len(available_symbols_im[None])
811 return self.__validateConsume(None, available_symbols_im, output_sequence_im, candidates)
812 return False
813
815 """Determine whether it is possible to take this transition without
816 consuming any symbols.
817
818 This is only possible if this is a transition to a final state using
819 an "all" model group for which every alternative is effectively
820 optional.
821 """
822 if self.TT_modelGroupAll != self.__termType:
823 return False
824 dfa_state = _MGAllState(self.__term)
825 return dfa_state.isFinal()
826
827 - def attemptTransition (self, ctd_instance, value, element_use, dfa_stack):
828 """Attempt to make the appropriate transition.
829
830 @param ctd_instance: The binding instance for which we are attempting
831 to set values by walking the content model.
832 @type ctd_instance: L{basis.complexTypeDefinition}
833
834 @param value: The potential value that would be consumed if this
835 transition can be made.
836 @type value: C{xml.dom.Node} or L{basis._TypeBinding_mixin}
837
838 @param dfa_stack: The current state of processing this and enclosing
839 content models.
840 @type dfa_stack: L{DFAStack}
841
842 @return: C{True} iff C{value} is acceptable for this transition
843
844 """
845
846 if self.TT_element == self.__termType:
847 element = None
848
849
850
851 if (element_use is not None):
852 return None
853 element = self.__processElementTransition(value, element_use)
854 if element is None:
855 return False
856 self.__elementUse.setOrAppend(ctd_instance, element)
857 return True
858 if self.TT_modelGroupAll == self.__termType:
859 return dfa_stack.pushModelState(_MGAllState(self.__term)).step(dfa_stack, ctd_instance, value, element_use)
860 if self.TT_wildcard == self.__termType:
861 value_desc = 'value of type %s' % (type(value),)
862 if isinstance(value, xml.dom.Node):
863 value_desc = 'DOM node %s' % (pyxb.namespace.ExpandedName(value),)
864 elif not isinstance(value, basis._TypeBinding_mixin):
865 return False
866 if not self.__term.matches(ctd_instance, value):
867 raise pyxb.UnexpectedContentError(value)
868 if not isinstance(value, basis._TypeBinding_mixin):
869 print 'NOTE: Created unbound wildcard element from %s' % (value_desc,)
870 assert isinstance(ctd_instance.wildcardElements(), list), 'Uninitialized wildcard list in %s' % (ctd_instance._ExpandedName,)
871 ctd_instance._appendWildcardElement(value)
872 return True
873 raise pyxb.LogicError('Unexpected transition term %s' % (self.__term,))
874
875 -class ContentModelState (pyxb.cscRoot):
876 """Represents a state in a ContentModel DFA.
877
878 The state identifier is an integer. State 1 is the starting state of the
879 DFA. A flag indicates whether the state is a legitimate final state for
880 the DFA. The transitions are an ordered sequence of
881 ContentModelTransition instances."""
882
883
884 __state = None
885
886 __transitions = None
887
888
889 __elementTermMap = None
890
891 - def isFinal (self):
892 """If True, this state can successfully complete the element
893 reduction."""
894 return self.__isFinal
895 __isFinal = None
896
899
900 - def __init__ (self, state, is_final, transitions):
901 self.__state = state
902 self.__isFinal = is_final
903 self.__transitions = transitions
904 [ _t._currentStateRef(self) for _t in self.__transitions ]
905 self.__transitions.sort()
906 self.__elementTermMap = { }
907 for t in self.__transitions:
908 if t.TT_element == t.termType():
909 assert t.elementUse() is not None
910 self.__elementTermMap[t.elementUse()] = t
911
912 - def transitions (self):
913 return self.__transitions
914
915 - def allowsEpsilonTransitionToFinal (self, content_model):
916 """Determine can reach a final state in the content model without
917 consuming anything."""
918 if self.isFinal():
919 return True
920 for transition in self.__transitions:
921 if transition.allowsEpsilonTransition() and content_model.isFinal(transition.nextState()):
922 return True
923 return False
924
925 - def evaluateContent (self, ctd_instance, value, element_use, dfa_stack):
926 """Try to make a single transition with the given value.
927
928 @param ctd_instance: The binding instance for which we are attempting
929 to set values by walking the content model.
930 @type ctd_instance: L{basis.complexTypeDefinition}
931
932 @param value: The value that would be consumed if a transition can be
933 made.
934 @type value: C{xml.dom.Node} or L{basis._TypeBinding_mixin}
935
936 @param element_use: The L{ElementUse<pyxb.binding.content.ElementUse>}
937 corresponding to the provided value, if known (for example, because
938 the value was parsed from an XML document).
939
940 @param dfa_stack: The current state of processing this and enclosing
941 content models.
942 @type dfa_stack: L{DFAStack}
943
944 @return: If a transition could be taken, the next state in the content
945 model. C{None} if no transition could be taken and this state is
946 final.
947
948 @raise pyxb.UnrecognizedContentError: No transition on C{value} is
949 possible, and this is not a final state.
950 """
951
952 if element_use is not None:
953 transition = self.__elementTermMap.get(element_use)
954 if transition is not None:
955 element_use.setOrAppend(ctd_instance, value)
956 return transition.nextState()
957
958
959
960 for transition in self.__transitions:
961 if transition.attemptTransition(ctd_instance, value, element_use, dfa_stack):
962 return transition.nextState()
963 if self.isFinal():
964 return None
965 raise pyxb.UnrecognizedContentError(value, element_use=element_use)
966
967 -class ContentModel (pyxb.cscRoot):
968 """The ContentModel is a deterministic finite state automaton which can be
969 traversed using a sequence of DOM nodes which are matched on transitions
970 against the legal content model of a complex type."""
971
972
973 __stateMap = None
974
975
976 __InitialState = 1
977
978 - def __init__ (self, state_map=None):
979 self.__stateMap = state_map
980
981 - def initialDFAStack (self):
982 return DFAStack(self)
983
984 - def step (self, ctd_instance, state, value, element_use, dfa_stack):
985 """Perform a single step in the content model. This is a pass-through
986 to L{ContentModelState.evaluateContent} for the appropriate state.
987
988 @param state: The starting state in this content model.
989 @type state: C{int}
990 """
991
992 return self.__stateMap[state].evaluateContent(ctd_instance, value, element_use, dfa_stack)
993
994 - def isFinal (self, state):
996
999
1000 - def validate (self, available_symbols, succeed_at_dead_end=False):
1001 """Determine whether this content model can be satisfied using the
1002 provided elements.
1003
1004 The general idea is to treat the transitions of the DFA as symbols in
1005 an alphabet. For each such transition, a sequence of values is
1006 provided to be associated with the transition. One transition is
1007 permitted for each value associated with the symbol. The symbol (key)
1008 C{None} represents wildcard values.
1009
1010 If a path is found that uses every symbol in valid transitions and
1011 ends in a final state, the return value is a pair consisting of the
1012 unconsumed symbols and a sequence of term, value pairs that define the
1013 acceptable path. If no valid path through the DFA can be taken,
1014 C{None} is returned.
1015
1016 @param available_symbols: A map from leaf DFA terms to a sequence of
1017 values associated with the term in a binding instance. The key
1018 C{None} is used to represent wildcard elements. If a key appears in
1019 this map, it must have at least one value in its sequence.
1020
1021 @param succeed_at_dead_end: If C{True}, states from which no
1022 transition can be made are accepted as final states. This is used
1023 when processing "all" model groups, where the content model for the
1024 current alternative must succeed while retaining the symbols that are
1025 needed for other alternatives.
1026 """
1027
1028 candidates = []
1029 candidates.append( (1, available_symbols, []) )
1030 while 0 < len(candidates):
1031 (state_id, symbols, sequence) = candidates.pop(0)
1032 state = self.__stateMap[state_id]
1033 if 0 == len(symbols):
1034
1035
1036
1037
1038 if state.allowsEpsilonTransitionToFinal(self):
1039 return (symbols, sequence)
1040 continue
1041
1042
1043 num_transitions = 0
1044 for transition in state.transitions():
1045 num_transitions += transition.validate(symbols, sequence, candidates)
1046 if (0 == num_transitions) and succeed_at_dead_end:
1047 return (symbols, sequence)
1048 return None
1049
1051 """Represents a single alternative in an "all" model group."""
1052
1053 - def contentModel (self):
1054 """The content model definition for the alternative."""
1055 return self.__contentModel
1056 __contentModel = None
1057
1059 """True iff this alternative must be present (min_occurs=1)"""
1060 return self.__required
1061 __required = None
1062
1063 - def __init__ (self, content_model, required):
1067
1068
1070 """Content model class that represents a ModelGroup with an "all"
1071 compositor."""
1072
1073 __alternatives = None
1076
1079
1080 - def validate (self, available_symbols_im, output_sequence_im, next_state, candidates):
1081 num_matches = 0
1082 alternatives = set(self.__alternatives)
1083 symbols = available_symbols_im
1084 output_sequence = output_sequence_im[:]
1085 found_match = True
1086 while (0 < len(alternatives)) and found_match:
1087 found_match = False
1088 for alt in alternatives:
1089 path = alt.contentModel().validate(symbols, succeed_at_dead_end=True)
1090 if path is None:
1091 break
1092 (new_symbols, new_sequence) = path
1093 found_match = (0 < len(new_sequence))
1094 if found_match:
1095 output_sequence.extend(new_sequence)
1096 symbols = new_symbols
1097 alternatives.remove(alt)
1098 found_match = True
1099 break
1100 for alt in alternatives:
1101 if alt.required():
1102 return False
1103 candidates.append( (next_state, symbols, output_sequence) )
1104 return True
1105
1107 """Placeholder for wildcard objects."""
1108
1109 NC_any = '##any'
1110 NC_not = '##other'
1111 NC_targetNamespace = '##targetNamespace'
1112 NC_local = '##local'
1113
1114 __namespaceConstraint = None
1116 """A constraint on the namespace for the wildcard.
1117
1118 Valid values are:
1119
1120 - L{Wildcard.NC_any}
1121 - A tuple ( L{Wildcard.NC_not}, a L{namespace<pyxb.namespace.Namespace>} instance )
1122 - set(of L{namespace<pyxb.namespace.Namespace>} instances)
1123
1124 Namespaces are represented by their URIs. Absence is
1125 represented by None, both in the "not" pair and in the set.
1126 """
1127 return self.__namespaceConstraint
1128
1129 PC_skip = 'skip'
1130 PC_lax = 'lax'
1131 PC_strict = 'strict'
1132
1133
1134 __processContents = None
1136
1141
1142 - def matches (self, ctd_instance, value):
1143 """Return True iff the value is a valid match against this wildcard.
1144
1145 Not implemented yet: all wildcards are assumed to match all values.
1146
1147 """
1148
1149
1150 return True
1151
1152
1153
1154
1155