Package pyxb :: Package binding :: Module content
[hide private]
[frames] | no frames]

Source Code for Module pyxb.binding.content

   1  # Copyright 2009, Peter A. Bigot 
   2  # 
   3  # Licensed under the Apache License, Version 2.0 (the "License"); you may 
   4  # not use this file except in compliance with the License. You may obtain a 
   5  # copy of the License at: 
   6  # 
   7  #            http://www.apache.org/licenses/LICENSE-2.0 
   8  # 
   9  # Unless required by applicable law or agreed to in writing, software 
  10  # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
  11  # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
  12  # License for the specific language governing permissions and limitations 
  13  # under the License. 
  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   
44 -class AttributeUse (pyxb.cscRoot):
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 # ExpandedName of the attribute 57 __id = None # Identifier used for this attribute within the owning class 58 __key = None # Private attribute used in instances to hold the attribute value 59 __dataType = None # PST datatype 60 __unicodeDefault = None # Default value as a unicode string, or None 61 __defaultValue = None # Default value as an instance of datatype, or None 62 __fixed = False # If True, value cannot be changed 63 __required = False # If True, attribute must appear 64 __prohibited = False # If True, attribute must not appear 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
129 - def name (self):
130 """The expanded name of the element. 131 132 @rtype: L{pyxb.namespace.ExpandedName} 133 """ 134 return self.__name
135
136 - def defaultValue (self):
137 """The default value of the attribute.""" 138 return self.__defaultValue
139
140 - def fixed (self):
141 """C{True} iff the value of the attribute cannot be changed.""" 142 return self.__fixed
143
144 - def required (self):
145 """Return True iff the attribute must be assigned a value.""" 146 return self.__required
147
148 - def prohibited (self):
149 """Return True iff the attribute must not be assigned a value.""" 150 return self.__prohibited
151
152 - def provided (self, ctd_instance):
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
162 - def id (self):
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
168 - def key (self):
169 """String used as key within object dictionary when storing attribute value.""" 170 return self.__key
171
172 - def dataType (self):
173 """The subclass of L{pyxb.binding.basis.simpleTypeDefinition} of which any attribute value must be an instance.""" 174 return self.__dataType
175
176 - def __getValue (self, ctd_instance):
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
187 - def __getProvided (self, ctd_instance):
188 return self.__getValue(ctd_instance)[0]
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
202 - def addDOMAttribute (self, dom_support, ctd_instance, element):
203 """If this attribute as been set, add the corresponding attribute to the DOM element.""" 204 ( provided, value ) = self.__getValue(ctd_instance) 205 if provided: 206 assert value is not None 207 dom_support.addAttribute(element, self.__name, value.xsdLiteral()) 208 return self
209
210 - def validate (self, ctd_instance):
211 (provided, value) = self.__getValue(ctd_instance) 212 if value is not None: 213 if self.__prohibited: 214 raise pyxb.ProhibitedAttributeError('Value given for prohibited attribute %s' % (self.__name,)) 215 if self.__required and not provided: 216 assert self.__fixed 217 raise pyxb.MissingAttributeError('Fixed required attribute %s was never set' % (self.__name,)) 218 if not self.__dataType._IsValidValue(value): 219 raise pyxb.BindingValidationError('Attribute %s value type %s not %s' % (self.__name, type(value), self.__dataType)) 220 self.__dataType.XsdConstraintsOK(value) 221 else: 222 if self.__required: 223 raise pyxb.MissingAttributeError('Required attribute %s does not have a value' % (self.__name,)) 224 return True
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 # Must be optional and absent 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
284 -class ElementUse (pyxb.cscRoot):
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
293 - def name (self):
294 """The expanded name of the element. 295 296 @rtype: L{pyxb.namespace.ExpandedName} 297 """ 298 return self.__name
299 __name = None 300
301 - def id (self):
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 # The dictionary key used to identify the value of the element. The value 311 # is the same as that used for private member variables in the binding 312 # class within which the element declaration occurred. 313 __key = None 314
315 - def elementBinding (self):
316 """The L{basis.element} instance identifying the information 317 associated with the element declaration. 318 """ 319 return self.__elementBinding
320 - def _setElementBinding (self, element_binding):
321 # Set the element binding for this use. Only visible at all because 322 # we have to define the uses before the element instances have been 323 # created. 324 self.__elementBinding = element_binding 325 return self
326 __elementBinding = None 327
328 - def isPlural (self):
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
373 - def defaultValue (self):
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):
394 """Set the value of this element in the given instance.""" 395 if value is None: 396 return self.reset(ctd_instance) 397 assert self.__elementBinding is not None 398 if basis._TypeBinding_mixin._PerformValidation: 399 value = self.__elementBinding.compatibleValue(value, is_plural=self.isPlural()) 400 setattr(ctd_instance, self.__key, value) 401 ctd_instance._addContent(value, self.__elementBinding) 402 return self
403
404 - def setOrAppend (self, ctd_instance, value):
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):
412 """Add the given value as another instance of this element within the binding instance. 413 @raise pyxb.StructuralBadDocumentError: invoked on an element use that is not plural 414 """ 415 if not self.isPlural(): 416 raise pyxb.StructuralBadDocumentError('Cannot append to element with non-plural multiplicity') 417 values = self.value(ctd_instance) 418 if basis._TypeBinding_mixin._PerformValidation: 419 value = self.__elementBinding.compatibleValue(value) 420 values.append(value) 421 ctd_instance._addContent(value, self.__elementBinding) 422 return values
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):
464 if name_only: 465 return str(self.__name) 466 desc = [ str(self.__id), ': '] 467 if self.isPlural(): 468 desc.append('MULTIPLE ') 469 desc.append(self.elementBinding()._description(user_documentation=user_documentation)) 470 return ''.join(desc)
471
472 -class _DFAState (object):
473 """Base class for a suspended DFA interpretation.""" 474 __contentModel = None 475 __state = None 476
477 - def __init__ (self, content_model, state=1):
478 self.__contentModel = content_model 479 self.__state = state
480
481 - def state (self):
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
487 - def updateState (self, state):
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
515 - def isFinal (self):
516 """Return C{True} iff the current state of the content model is a final state.""" 517 return self.contentModel().isFinal(self.state())
518
519 -class _MGAllState (object):
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
531 - def __init__ (self, model_group):
532 self.__modelGroup = model_group 533 self.__alternatives = self.__modelGroup.alternatives()
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 # I think this is probably a problem, but don't have an 564 # example yet to use to analyze it. The issue is that we've 565 # already committed to executing the automaton; if we end up 566 # in a non-final state, then that execution failed, and 567 # probably the whole validation should just abort. 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 #print 'Failed with alternative %s: %s' % (alt, type(e)) 580 pass 581 return False
582
583 - def isFinal (self):
584 """Return C{True} iff no required alternatives remain.""" 585 for alt in self.__alternatives: 586 # Any required alternative that must consume a symbol prevents 587 # this from being an acceptable final state for the model group. 588 if alt.required() and not alt.contentModel().allowsEpsilonTransitionToFinal(): 589 #print "\n\n***Required alternative %s still present\n\n" % (alt,) 590 return False 591 return True
592
593 -class DFAStack (object):
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
599 - def __init__ (self, content_model):
600 self.__stack = [] 601 self.pushModelState(_DFAState(content_model))
602
603 - def pushModelState (self, model_state):
604 """Add the given model state as the new top (actively executing) model .""" 605 self.__stack.append(model_state) 606 return model_state
607
608 - def isTerminal (self):
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
613 - def popModelState (self):
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
619 - def topModelState (self):
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
647 - def term (self):
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):
656 return self.__currentStateRef
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 # The ElementUse instance used to store a successful match in the 667 # complex type definition instance.
668 - def elementUse (self):
669 return self.__elementUse
670 __elementUse = None 671 672 # Types of transition that can be taken, in order of preferred match 673 TT_element = 0x01 #<<< The transition is on an element 674 TT_modelGroupAll = 0x02 #<<< The transition is on an ALL model group 675 TT_wildcard = 0x03 #<<< The transition is on a wildcard 676 677 # What type of term this transition covers 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 # In a vain attempt at determinism, sort the element transitions 707 # by name 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 # First, identify the element 716 if isinstance(value, xml.dom.Node): 717 # If we got here, it's because we couldn't figure out what element 718 # the node conformed to and had to try the element transitions in 719 # hopes of matching a wildcard. If we couldn't find an element 720 # before, we can't do it now, so just fail. 721 return None 722 try: 723 # The _convert_string_values=False setting prevents string 724 # arguments to element/type constructors from being automatically 725 # converted to another type (like int) if they just happen to be 726 # convertible. Without this, it's too easy to accept a 727 # sub-optimal transition (e.g., match a float when an alternative 728 # string is available). 729 return self.term().compatibleValue(value, _convert_string_values=False) 730 except pyxb.BadTypeValueError, e: 731 # Silently fail the transition 732 pass 733 return None
734
735 - def __validateConsume (self, key, available_symbols_im, output_sequence_im, candidates):
736 # Update candidates to incorporate the path abstraction associated 737 # with the element that is this term. 738 739 # Create a mutable copy of the available symbols 740 next_symbols = available_symbols_im.copy() 741 742 # If the transition is a loop back to the current state, or if the 743 # transition is a simple type definition with variety list, we can 744 # consume multiple instances. Might as well consume all of them. 745 # When we do consume, we can do either one transition, or one 746 # transition for each element in a list/vector. 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 # Make sure we pop from a copy of the available_symbols_im entry value. 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 # No symbol available for this transition 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
814 - def allowsEpsilonTransition (self):
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 # If the element use matched one of the terms in its state, we 849 # would never have gotten here, so don't even try. We're only 850 # walking the terms to see if an ALL or Wildcard is allowed. 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 # Integer 884 __state = None 885 # Sequence of ContentModelTransition instances 886 __transitions = None 887 888 # Map from ElementUse instances to the term that transitions on that use. 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
897 - def state (self):
898 return self.__state
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 # Might get here legitimately if we need to descend into ALL, or 958 # if this value is a wildcard for which we happen to have a 959 # binding class available. 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 # Map from integers to ContentModelState instances 973 __stateMap = None 974 975 # All DFAs begin at this state 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 # No symbols available for transitions in this state. If this 1035 # places us in a final state, we've got a successful path and 1036 # should return it. Otherwise, this path failed, and we go on 1037 # to the next candidate. 1038 if state.allowsEpsilonTransitionToFinal(self): 1039 return (symbols, sequence) 1040 continue 1041 # Collect all the alternatives that are possible by taking 1042 # transitions from this state. 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
1050 -class ModelGroupAllAlternative (pyxb.cscRoot):
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
1058 - def required (self):
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):
1064 #print '%s created MGA alternative model %s required %s' % (self, content_model, required) 1065 self.__contentModel = content_model 1066 self.__required = required
1067 1068
1069 -class ModelGroupAll (pyxb.cscRoot):
1070 """Content model class that represents a ModelGroup with an "all" 1071 compositor.""" 1072 1073 __alternatives = None
1074 - def alternatives (self):
1075 return set(self.__alternatives)
1076
1077 - def __init__ (self, alternatives):
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
1106 -class Wildcard (pyxb.cscRoot):
1107 """Placeholder for wildcard objects.""" 1108 1109 NC_any = '##any' #<<< The namespace constraint "##any" 1110 NC_not = '##other' #<<< A flag indicating constraint "##other" 1111 NC_targetNamespace = '##targetNamespace' 1112 NC_local = '##local' 1113 1114 __namespaceConstraint = None
1115 - def namespaceConstraint (self):
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' #<<< No constraint is applied 1130 PC_lax = 'lax' #<<< Validate against available uniquely determined declaration 1131 PC_strict = 'strict' #<<< Validate against declaration or xsi:type which must be available 1132 1133 # One of PC_* 1134 __processContents = None
1135 - def processContents (self): return self.__processContents
1136
1137 - def __init__ (self, *args, **kw):
1138 # Namespace constraint and process contents are required parameters. 1139 self.__namespaceConstraint = kw['namespace_constraint'] 1140 self.__processContents = kw['process_contents']
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 # @todo check node against namespace constraint and process contents 1149 #print 'WARNING: Accepting node as wildcard match without validating.' 1150 return True
1151 1152 ## Local Variables: 1153 ## fill-column:78 1154 ## End: 1155