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  @todo: Document new content model 
  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. The values 113 within the lists are unchanged, as validation does not affect them.""" 114 rv = symbol_set_im.copy() 115 for (k, v) in rv.items(): 116 rv[k] = v[:] 117 return rv
118
119 - def _validateCloneOutputSequence (self, output_sequence_im):
120 return output_sequence_im[:]
121
122 - def _validateReplaceResults (self, symbol_set_out, symbol_set_new, output_sequence_out, output_sequence_new):
123 """In-place update of symbol set and output sequence structures. 124 125 Use this to copy from temporary mutable structures updated by local 126 validation into the structures that were passed in once the validation 127 has succeeded.""" 128 symbol_set_out.clear() 129 symbol_set_out.update(symbol_set_new) 130 output_sequence_out[:] = output_sequence_new
131
132 - def _validate (self, symbol_set, output_sequence):
133 """Determine whether an output sequence created from the symbols can 134 be made consistent with the model. 135 136 The symbol set represents letters in an alphabet; the output sequence 137 orders those letters in a way that satisfies the regular expression 138 expressed in the model. Both are changed as a result of a successful 139 validation; both remain unchanged if the validation failed. In 140 recursing, implementers may assume that C{output_sequence} is 141 monotonic: its length remains unchanged after an invocation iff the 142 symbol set also remains unchanged. The L{_validateCloneSymbolSet}, 143 L{_validateCloneOutputSequence}, and L{_validateReplaceResults} 144 methods are available to help preserve this behavior. 145 146 @param symbol_set: A map from L{ElementUse} instances to a list of 147 values. The order of the values corresponds to the order in which 148 they should appear. A key of C{None} identifies values that are 149 stored as wildcard elements. Values are removed from the lists as 150 they are used; when the last value of a list is removed, its key is 151 removed from the map. Thus an empty dictionary is the indicator that 152 no more symbols are available. 153 154 @param output_sequence: A mutable list to which should be appended 155 tuples C{( eu, val )} where C{eu} is an L{ElementUse} from the set of 156 symbol keys, and C{val} is a value from the corresponding list. A 157 document generated by producing the elements in the given order is 158 expected to validate. 159 160 @return: C{True} iff the model validates. C{symbol_set} and 161 C{output_path} must be unmodified if returns C{False}. 162 """ 163 raise Exception('ContentState_mixin._validate not implemented in %s' % (type(self),))
164 165
166 -class AttributeUse (pyxb.cscRoot):
167 """A helper class that encapsulates everything we need to know 168 about the way an attribute is used within a binding class. 169 170 Attributes are stored internally as pairs C{(provided, value)}, where 171 C{provided} is a boolean indicating whether a value for the attribute was 172 provided externally, and C{value} is an instance of the attribute 173 datatype. The C{provided} flag is used to determine whether an XML 174 attribute should be added to a created DOM node when generating the XML 175 corresponding to a binding instance. 176 """ 177 178 __name = None # ExpandedName of the attribute 179 __id = None # Identifier used for this attribute within the owning class 180 __key = None # Private attribute used in instances to hold the attribute value 181 __dataType = None # PST datatype 182 __unicodeDefault = None # Default value as a unicode string, or None 183 __defaultValue = None # Default value as an instance of datatype, or None 184 __fixed = False # If True, value cannot be changed 185 __required = False # If True, attribute must appear 186 __prohibited = False # If True, attribute must not appear 187
188 - def __init__ (self, name, id, key, data_type, unicode_default=None, fixed=False, required=False, prohibited=False):
189 """Create an AttributeUse instance. 190 191 @param name: The name by which the attribute is referenced in the XML 192 @type name: L{pyxb.namespace.ExpandedName} 193 194 @param id: The Python identifier for the attribute within the 195 containing L{pyxb.basis.binding.complexTypeDefinition}. This is a 196 public identifier, derived from the local part of the attribute name 197 and modified to be unique, and is usually used as the name of the 198 attribute's inspector method. 199 @type id: C{str} 200 201 @param key: The string used to store the attribute 202 value in the dictionary of the containing 203 L{pyxb.basis.binding.complexTypeDefinition}. This is mangled so 204 that it is unique among and is treated as a Python private member. 205 @type key: C{str} 206 207 @param data_type: The class reference to the subclass of 208 L{pyxb.binding.basis.simpleTypeDefinition} of which the attribute 209 values must be instances. 210 @type data_type: C{type} 211 212 @keyword unicode_default: The default value of the attribute as 213 specified in the schema, or None if there is no default attribute 214 value. The default value (of the keyword) is C{None}. 215 @type unicode_default: C{unicode} 216 217 @keyword fixed: If C{True}, indicates that the attribute, if present, 218 must have the value that was given via C{unicode_default}. The 219 default value is C{False}. 220 @type fixed: C{bool} 221 222 @keyword required: If C{True}, indicates that the attribute must appear 223 in the DOM node used to create an instance of the corresponding 224 L{pyxb.binding.basis.complexTypeDefinition}. The default value is 225 C{False}. No more that one of L{required} and L{prohibited} should be 226 assigned C{True}. 227 @type required: C{bool} 228 229 @keyword prohibited: If C{True}, indicates that the attribute must 230 B{not} appear in the DOM node used to create an instance of the 231 corresponding L{pyxb.binding.basis.complexTypeDefinition}. The 232 default value is C{False}. No more that one of L{required} and 233 L{prohibited} should be assigned C{True}. 234 @type prohibited: C{bool} 235 236 @raise pyxb.BadTypeValueError: the L{unicode_default} cannot be used 237 to initialize an instance of L{data_type} 238 """ 239 240 self.__name = name 241 self.__id = id 242 self.__key = key 243 self.__dataType = data_type 244 self.__unicodeDefault = unicode_default 245 if self.__unicodeDefault is not None: 246 self.__defaultValue = self.__dataType.Factory(self.__unicodeDefault) 247 self.__fixed = fixed 248 self.__required = required 249 self.__prohibited = prohibited
250
251 - def name (self):
252 """The expanded name of the element. 253 254 @rtype: L{pyxb.namespace.ExpandedName} 255 """ 256 return self.__name
257
258 - def defaultValue (self):
259 """The default value of the attribute.""" 260 return self.__defaultValue
261
262 - def fixed (self):
263 """C{True} iff the value of the attribute cannot be changed.""" 264 return self.__fixed
265
266 - def required (self):
267 """Return True iff the attribute must be assigned a value.""" 268 return self.__required
269
270 - def prohibited (self):
271 """Return True iff the attribute must not be assigned a value.""" 272 return self.__prohibited
273
274 - def provided (self, ctd_instance):
275 """Return True iff the given instance has been explicitly given a 276 value for the attribute. 277 278 This is used for things like only generating an XML attribute 279 assignment when a value was originally given (even if that value 280 happens to be the default). 281 """ 282 return self.__getProvided(ctd_instance)
283
284 - def id (self):
285 """Tag used within Python code for the attribute. 286 287 This is not used directly in the default code generation template.""" 288 return self.__id
289
290 - def key (self):
291 """String used as key within object dictionary when storing attribute value.""" 292 return self.__key
293
294 - def dataType (self):
295 """The subclass of L{pyxb.binding.basis.simpleTypeDefinition} of which any attribute value must be an instance.""" 296 return self.__dataType
297
298 - def __getValue (self, ctd_instance):
299 """Retrieve the value information for this attribute in a binding instance. 300 301 @param ctd_instance: The instance object from which the attribute is to be retrieved. 302 @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition} 303 @return: C{(provided, value)} where C{provided} is a C{bool} and 304 C{value} is C{None} or an instance of the attribute's datatype. 305 306 """ 307 return getattr(ctd_instance, self.__key, (False, None))
308
309 - def __getProvided (self, ctd_instance):
310 return self.__getValue(ctd_instance)[0]
311
312 - def value (self, ctd_instance):
313 """Get the value of the attribute from the instance.""" 314 return self.__getValue(ctd_instance)[1]
315
316 - def __setValue (self, ctd_instance, new_value, provided):
317 return setattr(ctd_instance, self.__key, (provided, new_value))
318
319 - def reset (self, ctd_instance):
320 """Set the value of the attribute in the given instance to be its 321 default value, and mark that it has not been provided.""" 322 self.__setValue(ctd_instance, self.__defaultValue, False)
323
324 - def addDOMAttribute (self, dom_support, ctd_instance, element):
325 """If this attribute as been set, add the corresponding attribute to the DOM element.""" 326 ( provided, value ) = self.__getValue(ctd_instance) 327 if provided: 328 assert value is not None 329 dom_support.addAttribute(element, self.__name, value.xsdLiteral()) 330 return self
331
332 - def validate (self, ctd_instance):
333 (provided, value) = self.__getValue(ctd_instance) 334 if value is not None: 335 if self.__prohibited: 336 raise pyxb.ProhibitedAttributeError('Value given for prohibited attribute %s' % (self.__name,)) 337 if self.__required and not provided: 338 assert self.__fixed 339 raise pyxb.MissingAttributeError('Fixed required attribute %s was never set' % (self.__name,)) 340 if not self.__dataType._IsValidValue(value): 341 raise pyxb.BindingValidationError('Attribute %s value type %s not %s' % (self.__name, type(value), self.__dataType)) 342 self.__dataType.XsdConstraintsOK(value) 343 else: 344 if self.__required: 345 raise pyxb.MissingAttributeError('Required attribute %s does not have a value' % (self.__name,)) 346 return True
347
348 - def set (self, ctd_instance, new_value):
349 """Set the value of the attribute. 350 351 This validates the value against the data type, creating a new instance if necessary. 352 353 @param ctd_instance: The binding instance for which the attribute 354 value is to be set 355 @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition} 356 @param new_value: The value for the attribute 357 @type new_value: An C{xml.dom.Node} instance, or any value that is 358 permitted as the input parameter to the C{Factory} method of the 359 attribute's datatype. 360 """ 361 provided = True 362 if isinstance(new_value, xml.dom.Node): 363 unicode_value = self.__name.getAttribute(new_value) 364 if unicode_value is None: 365 if self.__required: 366 raise pyxb.MissingAttributeError('Required attribute %s from %s not found' % (self.__name, ctd_instance._ExpandedName or type(ctd_instance))) 367 provided = False 368 unicode_value = self.__unicodeDefault 369 if unicode_value is None: 370 # Must be optional and absent 371 provided = False 372 new_value = None 373 else: 374 new_value = unicode_value 375 else: 376 assert new_value is not None 377 if self.__prohibited: 378 raise pyxb.ProhibitedAttributeError('Value given for prohibited attribute %s' % (self.__name,)) 379 if (new_value is not None) and (not isinstance(new_value, self.__dataType)): 380 new_value = self.__dataType.Factory(new_value) 381 if self.__fixed and (new_value != self.__defaultValue): 382 raise pyxb.AttributeChangeError('Attempt to change value of fixed attribute %s' % (self.__name,)) 383 self.__setValue(ctd_instance, new_value, provided) 384 return new_value
385
386 - def _description (self, name_only=False, user_documentation=True):
387 if name_only: 388 return str(self.__name) 389 assert issubclass(self.__dataType, basis._TypeBinding_mixin) 390 desc = [ str(self.__id), ': ', str(self.__name), ' (', self.__dataType._description(name_only=True, user_documentation=False), '), ' ] 391 if self.__required: 392 desc.append('required') 393 elif self.__prohibited: 394 desc.append('prohibited') 395 else: 396 desc.append('optional') 397 if self.__defaultValue is not None: 398 desc.append(', ') 399 if self.__fixed: 400 desc.append('fixed') 401 else: 402 desc.append('default') 403 desc.extend(['=', self.__unicodeDefault ]) 404 return ''.join(desc)
405
406 -class ElementUse (ContentState_mixin, ContentModel_mixin):
407 """Aggregate the information relevant to an element of a complex type. 408 409 This includes the L{original tag name<name>}, the spelling of L{the 410 corresponding object in Python <id>}, an L{indicator<isPlural>} of whether 411 multiple instances might be associated with the field, and other relevant 412 information.. 413 """ 414
415 - def name (self):
416 """The expanded name of the element. 417 418 @rtype: L{pyxb.namespace.ExpandedName} 419 """ 420 return self.__name
421 __name = None 422
423 - def id (self):
424 """The string name of the binding class field used to hold the element 425 values. 426 427 This is the user-visible name, and excepting disambiguation will be 428 equal to the local name of the element.""" 429 return self.__id
430 __id = None 431 432 # The dictionary key used to identify the value of the element. The value 433 # is the same as that used for private member variables in the binding 434 # class within which the element declaration occurred. 435 __key = None 436
437 - def elementBinding (self):
438 """The L{basis.element} instance identifying the information 439 associated with the element declaration. 440 """ 441 return self.__elementBinding
442 - def _setElementBinding (self, element_binding):
443 # Set the element binding for this use. Only visible at all because 444 # we have to define the uses before the element instances have been 445 # created. 446 self.__elementBinding = element_binding 447 return self
448 __elementBinding = None 449
450 - def isPlural (self):
451 """True iff the content model indicates that more than one element 452 can legitimately belong to this use. 453 454 This includes elements in particles with maxOccurs greater than one, 455 and when multiple elements with the same NCName are declared in the 456 same type. 457 """ 458 return self.__isPlural
459 __isPlural = False 460
461 - def __init__ (self, name, id, key, is_plural, element_binding=None):
462 """Create an ElementUse instance. 463 464 @param name: The name by which the element is referenced in the XML 465 @type name: L{pyxb.namespace.ExpandedName} 466 467 @param id: The Python name for the element within the containing 468 L{pyxb.basis.binding.complexTypeDefinition}. This is a public 469 identifier, albeit modified to be unique, and is usually used as the 470 name of the element's inspector method or property. 471 @type id: C{str} 472 473 @param key: The string used to store the element 474 value in the dictionary of the containing 475 L{pyxb.basis.binding.complexTypeDefinition}. This is mangled so 476 that it is unique among and is treated as a Python private member. 477 @type key: C{str} 478 479 @param is_plural: If C{True}, documents for the corresponding type may 480 have multiple instances of this element. As a consequence, the value 481 of the element will be a list. If C{False}, the value will be C{None} 482 if the element is absent, and a reference to an instance of the type 483 identified by L{pyxb.binding.basis.element.typeDefinition} if present. 484 @type is_plural: C{bool} 485 486 @param element_binding: Reference to the class that serves as the 487 binding for the element. 488 """ 489 self.__name = name 490 self.__id = id 491 self.__key = key 492 self.__isPlural = is_plural 493 self.__elementBinding = element_binding
494
495 - def defaultValue (self):
496 """Return the default value for this element. 497 498 @todo: Right now, this returns C{None} for non-plural and an empty 499 list for plural elements. Need to support schema-specified default 500 values for simple-type content. 501 """ 502 if self.isPlural(): 503 return [] 504 return None
505
506 - def value (self, ctd_instance):
507 """Return the value for this use within the given instance.""" 508 return getattr(ctd_instance, self.__key, self.defaultValue())
509
510 - def reset (self, ctd_instance):
511 """Set the value for this use in the given element to its default.""" 512 setattr(ctd_instance, self.__key, self.defaultValue()) 513 return self
514
515 - def set (self, ctd_instance, value):
516 """Set the value of this element in the given instance.""" 517 if value is None: 518 return self.reset(ctd_instance) 519 assert self.__elementBinding is not None 520 if basis._TypeBinding_mixin._PerformValidation: 521 value = self.__elementBinding.compatibleValue(value, is_plural=self.isPlural()) 522 setattr(ctd_instance, self.__key, value) 523 ctd_instance._addContent(value, self.__elementBinding) 524 return self
525
526 - def setOrAppend (self, ctd_instance, value):
527 """Invoke either L{set} or L{append}, depending on whether the element 528 use is plural.""" 529 if self.isPlural(): 530 return self.append(ctd_instance, value) 531 return self.set(ctd_instance, value)
532
533 - def append (self, ctd_instance, value):
534 """Add the given value as another instance of this element within the binding instance. 535 @raise pyxb.StructuralBadDocumentError: invoked on an element use that is not plural 536 """ 537 if not self.isPlural(): 538 raise pyxb.StructuralBadDocumentError('Cannot append to element with non-plural multiplicity') 539 values = self.value(ctd_instance) 540 if basis._TypeBinding_mixin._PerformValidation: 541 value = self.__elementBinding.compatibleValue(value) 542 values.append(value) 543 ctd_instance._addContent(value, self.__elementBinding) 544 return values
545
546 - def toDOM (self, dom_support, parent, value):
547 """Convert the given value to DOM as an instance of this element. 548 549 @param dom_support: Helper for managing DOM properties 550 @type dom_support: L{pyxb.utils.domutils.BindingDOMSupport} 551 @param parent: The DOM node within which this element should be generated. 552 @type parent: C{xml.dom.Element} 553 @param value: The content for this element. May be text (if the 554 element allows mixed content), or an instance of 555 L{basis._TypeBinding_mixin}. 556 """ 557 if isinstance(value, basis._TypeBinding_mixin): 558 element_binding = self.__elementBinding 559 if value._substitutesFor(element_binding): 560 element_binding = value._element() 561 assert element_binding is not None 562 if element_binding.abstract(): 563 raise pyxb.DOMGenerationError('Element %s is abstract but content %s not associated with substitution group member' % (self.name(), value)) 564 element = dom_support.createChildElement(element_binding.name(), parent) 565 elt_type = element_binding.typeDefinition() 566 val_type = type(value) 567 if isinstance(value, basis.complexTypeDefinition): 568 assert isinstance(value, elt_type) 569 else: 570 if isinstance(value, basis.STD_union) and isinstance(value, elt_type._MemberTypes): 571 val_type = elt_type 572 if dom_support.requireXSIType() or elt_type._RequireXSIType(val_type): 573 val_type_qname = value._ExpandedName.localName() 574 tns_prefix = dom_support.namespacePrefix(value._ExpandedName.namespace()) 575 if tns_prefix is not None: 576 val_type_qname = '%s:%s' % (tns_prefix, val_type_qname) 577 dom_support.addAttribute(element, pyxb.namespace.XMLSchema_instance.createExpandedName('type'), val_type_qname) 578 value._toDOM_csc(dom_support, element) 579 elif isinstance(value, (str, unicode)): 580 element = dom_support.createChildElement(self.name(), parent) 581 element.appendChild(dom_support.document().createTextNode(value)) 582 else: 583 raise pyxb.LogicError('toDOM with unrecognized value type %s: %s' % (type(value), value))
584
585 - def _description (self, name_only=False, user_documentation=True):
586 if name_only: 587 return str(self.__name) 588 desc = [ str(self.__id), ': '] 589 if self.isPlural(): 590 desc.append('MULTIPLE ') 591 desc.append(self.elementBinding()._description(user_documentation=user_documentation)) 592 return ''.join(desc)
593
594 - def newState (self, parent_particle_state):
595 """Implement parent class method.""" 596 return self
597
598 - def accepts (self, particle_state, instance, value, element_use):
599 rv = self._accepts(instance, value, element_use) 600 if rv: 601 particle_state.incrementCount() 602 return rv
603
604 - def _accepts (self, instance, value, element_use):
605 if element_use == self: 606 self.setOrAppend(instance, value) 607 return True 608 if element_use is not None: 609 # If there's a known element, and it's not this one, the content 610 # does not match. This assumes we handled xsi:type and 611 # substitution groups earlier, which may be true. 612 return False 613 if isinstance(value, xml.dom.Node): 614 # If we haven't been able to identify an element for this before, 615 # then we don't recognize it, and will have to treat it as a 616 # wildcard. 617 return False 618 try: 619 self.setOrAppend(instance, self.__elementBinding.compatibleValue(value, _convert_string_values=False)) 620 return True 621 except pyxb.BadTypeValueError, e: 622 pass 623 #print '%s %s %s in %s' % (instance, value, element_use, self) 624 return False
625
626 - def _validate (self, symbol_set, output_sequence):
627 values = symbol_set.get(self) 628 #print 'values %s' % (values,) 629 if values is None: 630 return False 631 used = values.pop(0) 632 output_sequence.append( (self, used) ) 633 if 0 == len(values): 634 del symbol_set[self] 635 return True
636
637 - def __str__ (self):
638 return 'EU.%s@%x' % (self.__name, id(self))
639 640
641 -class Wildcard (ContentState_mixin, ContentModel_mixin):
642 """Placeholder for wildcard objects.""" 643 644 NC_any = '##any' #<<< The namespace constraint "##any" 645 NC_not = '##other' #<<< A flag indicating constraint "##other" 646 NC_targetNamespace = '##targetNamespace' 647 NC_local = '##local' 648 649 __namespaceConstraint = None
650 - def namespaceConstraint (self):
651 """A constraint on the namespace for the wildcard. 652 653 Valid values are: 654 655 - L{Wildcard.NC_any} 656 - A tuple ( L{Wildcard.NC_not}, a L{namespace<pyxb.namespace.Namespace>} instance ) 657 - set(of L{namespace<pyxb.namespace.Namespace>} instances) 658 659 Namespaces are represented by their URIs. Absence is 660 represented by None, both in the "not" pair and in the set. 661 """ 662 return self.__namespaceConstraint
663 664 PC_skip = 'skip' #<<< No constraint is applied 665 PC_lax = 'lax' #<<< Validate against available uniquely determined declaration 666 PC_strict = 'strict' #<<< Validate against declaration or xsi:type which must be available 667 668 # One of PC_* 669 __processContents = None
670 - def processContents (self): return self.__processContents
671
672 - def __normalizeNamespace (self, nsv):
673 if nsv is None: 674 return None 675 if isinstance(nsv, basestring): 676 nsv = pyxb.namespace.NamespaceForURI(nsv, create_if_missing=True) 677 assert isinstance(nsv, pyxb.namespace.Namespace), 'unexpected non-namespace %s' % (nsv,) 678 return nsv
679
680 - def __init__ (self, *args, **kw):
681 # Namespace constraint and process contents are required parameters. 682 nsc = kw['namespace_constraint'] 683 if isinstance(nsc, tuple): 684 nsc = (nsc[0], self.__normalizeNamespace(nsc[1])) 685 elif isinstance(nsc, set): 686 nsc = set([ self.__normalizeNamespace(_uri) for _uri in nsc ]) 687 self.__namespaceConstraint = nsc 688 self.__processContents = kw['process_contents']
689
690 - def matches (self, instance, value):
691 """Return True iff the value is a valid match against this wildcard. 692 693 Validation per U{Wildcard allows Namespace Name<http://www.w3.org/TR/xmlschema-1/#cvc-wildcard-namespace>}. 694 """ 695 696 ns = None 697 if isinstance(value, xml.dom.Node): 698 if value.namespaceURI is not None: 699 ns = pyxb.namespace.NamespaceForURI(value.namespaceURI) 700 elif isinstance(value, basis._TypeBinding_mixin): 701 elt = value._element() 702 if elt is not None: 703 ns = elt.name().namespace() 704 else: 705 ns = value._ExpandedName.namespace() 706 else: 707 raise pyxb.LogicError('Need namespace from value') 708 if isinstance(ns, pyxb.namespace.Namespace) and ns.isAbsentNamespace(): 709 ns = None 710 if self.NC_any == self.__namespaceConstraint: 711 return True 712 if isinstance(self.__namespaceConstraint, tuple): 713 (_, constrained_ns) = self.__namespaceConstraint 714 assert self.NC_not == _ 715 if ns is None: 716 return False 717 if constrained_ns == ns: 718 return False 719 return True 720 return ns in self.__namespaceConstraint
721
722 - def newState (self, parent_particle_state):
723 return self
724
725 - def accepts (self, particle_state, instance, value, element_use):
726 if isinstance(value, xml.dom.Node): 727 value_desc = 'value in %s' % (value.nodeName,) 728 else: 729 value_desc = 'value of type %s' % (type(value),) 730 if not self.matches(instance, value): 731 return False 732 if not isinstance(value, basis._TypeBinding_mixin): 733 print 'NOTE: Created unbound wildcard element from %s' % (value_desc,) 734 assert isinstance(instance.wildcardElements(), list), 'Uninitialized wildcard list in %s' % (instance._ExpandedName,) 735 instance._appendWildcardElement(value) 736 particle_state.incrementCount() 737 return True
738
739 - def _validate (self, symbol_set, output_sequence):
740 # @todo check node against namespace constraint and process contents 741 #print 'WARNING: Accepting node as wildcard match without validating.' 742 wc_values = symbol_set.get(None) 743 if wc_values is None: 744 return False 745 used = wc_values.pop(0) 746 output_sequence.append( (None, used) ) 747 if 0 == len(wc_values): 748 del symbol_set[None] 749 return True
750
751 -class SequenceState (ContentState_mixin):
752 __failed = False 753 __satisfied = False 754
755 - def __init__ (self, group, parent_particle_state):
756 super(SequenceState, self).__init__(group) 757 self.__sequence = group 758 self.__parentParticleState = parent_particle_state 759 self.__particles = group.particles() 760 self.__index = -1 761 self.__satisfied = False 762 self.__failed = False 763 self.notifyFailure(None, False)
764 #print 'SS.CTOR %s: %d elts' % (self, len(self.__particles)) 765
766 - def accepts (self, particle_state, instance, value, element_use):
767 assert self.__parentParticleState == particle_state 768 assert not self.__failed 769 #print 'SS.ACC %s: %s %s %s' % (self, instance, value, element_use) 770 while self.__particleState is not None: 771 (consume, underflow_exc) = self.__particleState.step(instance, value, element_use) 772 if consume: 773 return True 774 if underflow_exc is not None: 775 self.__failed = True 776 raise underflow_exc 777 return False
778
779 - def _verifyComplete (self, parent_particle_state):
780 while self.__particleState is not None: 781 self.__particleState.verifyComplete()
782
783 - def notifyFailure (self, sub_state, particle_ok):
784 self.__index += 1 785 self.__particleState = None 786 if self.__index < len(self.__particles): 787 self.__particleState = ParticleState(self.__particles[self.__index], self) 788 else: 789 self.__satisfied = particle_ok 790 if particle_ok: 791 self.__parentParticleState.incrementCount()
792 #print 'SS.NF %s: %d %s %s' % (self, self.__index, particle_ok, self.__particleState) 793
794 -class ChoiceState (ContentState_mixin):
795 - def __init__ (self, group, parent_particle_state):
796 self.__parentParticleState = parent_particle_state 797 super(ChoiceState, self).__init__(group) 798 self.__choices = [ ParticleState(_p, self) for _p in group.particles() ] 799 self.__activeChoice = None
800 #print 'CS.CTOR %s: %d choices' % (self, len(self.__choices)) 801
802 - def accepts (self, particle_state, instance, value, element_use):
803 #print 'CS.ACC %s %s: %s %s %s' % (self, self.__activeChoice, instance, value, element_use) 804 if self.__activeChoice is None: 805 for choice in self.__choices: 806 #print 'CS.ACC %s candidate %s' % (self, choice) 807 try: 808 (consume, underflow_exc) = choice.step(instance, value, element_use) 809 except Exception, e: 810 consume = False 811 underflow_exc = e 812 #print 'CS.ACC %s: candidate %s : %s' % (self, choice, consume) 813 if consume: 814 self.__activeChoice = choice 815 self.__choices = None 816 return True 817 return False 818 (consume, underflow_exc) = self.__activeChoice.step(instance, value, element_use) 819 #print 'CS.ACC %s : active choice %s %s %s' % (self, self.__activeChoice, consume, underflow_exc) 820 if consume: 821 return True 822 if underflow_exc is not None: 823 self.__failed = True 824 raise underflow_exc 825 return False
826
827 - def _verifyComplete (self, parent_particle_state):
828 rv = True 829 #print 'CS.VC %s: %s' % (self, self.__activeChoice) 830 if self.__activeChoice is None: 831 # Use self.__activeChoice as the iteration value so that it's 832 # non-None when notifyFailure is invoked. 833 for self.__activeChoice in self.__choices: 834 try: 835 #print 'CS.VC: try %s' % (self.__activeChoice,) 836 self.__activeChoice.verifyComplete() 837 return 838 except Exception, e: 839 pass 840 #print 'Missing components %s' % ("\n".join([ "\n ".join([str(_p2.term()) for _p2 in _p.particle().term().particles()]) for _p in self.__choices ]),) 841 raise pyxb.MissingContentError('choice') 842 self.__activeChoice.verifyComplete()
843
844 - def notifyFailure (self, sub_state, particle_ok):
845 #print 'CS.NF %s %s' % (self, particle_ok) 846 if particle_ok and (self.__activeChoice is not None): 847 self.__parentParticleState.incrementCount() 848 pass
849
850 -class AllState (ContentState_mixin):
851 __activeChoice = None 852 __needRetry = False
853 - def __init__ (self, group, parent_particle_state):
854 self.__parentParticleState = parent_particle_state 855 super(AllState, self).__init__(group) 856 self.__choices = set([ ParticleState(_p, self) for _p in group.particles() ])
857 #print 'AS.CTOR %s: %d choices' % (self, len(self.__choices)) 858
859 - def accepts (self, particle_state, instance, value, element_use):
860 #print 'AS.ACC %s %s: %s %s %s' % (self, self.__activeChoice, instance, value, element_use) 861 self.__needRetry = True 862 while self.__needRetry: 863 self.__needRetry = False 864 if self.__activeChoice is None: 865 for choice in self.__choices: 866 #print 'AS.ACC %s candidate %s' % (self, choice) 867 try: 868 (consume, underflow_exc) = choice.step(instance, value, element_use) 869 except Exception, e: 870 consume = False 871 underflow_exc = e 872 #print 'AS.ACC %s: candidate %s : %s' % (self, choice, consume) 873 if consume: 874 self.__activeChoice = choice 875 self.__choices.discard(self.__activeChoice) 876 return True 877 return False 878 (consume, underflow_exc) = self.__activeChoice.step(instance, value, element_use) 879 #print 'AS.ACC %s : active choice %s %s %s' % (self, self.__activeChoice, consume, underflow_exc) 880 if consume: 881 return True 882 if underflow_exc is not None: 883 self.__failed = True 884 raise underflow_exc 885 return False
886
887 - def _verifyComplete (self, parent_particle_state):
888 #print 'AS.VC %s: %s, %d left' % (self, self.__activeChoice, len(self.__choices)) 889 if self.__activeChoice is not None: 890 self.__activeChoice.verifyComplete() 891 while self.__choices: 892 self.__activeChoice = self.__choices.pop() 893 self.__activeChoice.verifyComplete()
894
895 - def notifyFailure (self, sub_state, particle_ok):
896 #print 'AS.NF %s %s' % (self, particle_ok) 897 self.__needRetry = True 898 self.__activeChoice = None 899 if particle_ok and (0 == len(self.__choices)): 900 self.__parentParticleState.incrementCount()
901
902 -class ParticleState (pyxb.cscRoot):
903 - def __init__ (self, particle, parent_state=None):
904 self.__particle = particle 905 self.__parentState = parent_state 906 self.__count = -1 907 #print 'PS.CTOR %s: particle %s' % (self, particle) 908 self.incrementCount()
909
910 - def particle (self): return self.__particle
911
912 - def incrementCount (self):
913 #print 'PS.IC %s' % (self,) 914 self.__count += 1 915 self.__termState = self.__particle.term().newState(self) 916 self.__tryAccept = True
917
918 - def verifyComplete (self):
919 # @TODO@ Set a flag so we can make verifyComplete safe to call 920 # multiple times? 921 #print 'PS.VC %s entry' % (self,) 922 if not self.__particle.satisfiesOccurrences(self.__count): 923 self.__termState._verifyComplete(self) 924 if not self.__particle.satisfiesOccurrences(self.__count): 925 print 'PS.VC %s incomplete' % (self,) 926 raise pyxb.MissingContentError('incomplete') 927 if self.__parentState is not None: 928 self.__parentState.notifyFailure(self, True)
929
930 - def step (self, instance, value, element_use):
931 """Attempt to apply the value as a new instance of the particle's term. 932 933 The L{ContentState_mixin} created for the particle's term is consulted 934 to determine whether the instance can accept the given value. If so, 935 the particle's maximum occurrence limit is checked; if not, and the 936 particle has a parent state, it is informed of the failure. 937 938 @param instance: An instance of a subclass of 939 {basis.complexTypeDefinition}, into which the provided value will be 940 stored if it is consistent with the current model state. 941 942 @param value: The value that is being validated against the state. 943 944 @param element_use: An optional L{ElementUse} instance that specifies 945 the element to which the value corresponds. This will be available 946 when the value is extracted by parsing a document, but will be absent 947 if the value was passed as a constructor positional parameter. 948 949 @return: C{( consumed, underflow_exc )} A tuple where the first element 950 is C{True} iff the provided value was accepted in the current state. 951 When this first element is C{False}, the second element will be 952 C{None} if the particle's occurrence requirements have been met, and 953 is an instance of C{MissingElementError} if the observed number of 954 terms is less than the minimum occurrence count. Depending on 955 context, the caller may raise this exception, or may try an 956 alternative content model. 957 958 @raise pyxb.UnexpectedElementError: if the value satisfies the particle, 959 but having done so exceeded the allowable number of instances of the 960 term. 961 """ 962 963 #print 'PS.STEP %s: %s %s %s' % (self, instance, value, element_use) 964 965 # Only try if we're not already at the upper limit on occurrences 966 consumed = False 967 underflow_exc = None 968 969 # We can try the value against the term if we aren't at the maximum 970 # count for the term. Also, if we fail to consume, but as a side 971 # effect of the test the term may have reset itself, we can try again. 972 self.__tryAccept = True 973 while self.__tryAccept and (self.__count != self.__particle.maxOccurs()): 974 self.__tryAccept = False 975 consumed = self.__termState.accepts(self, instance, value, element_use) 976 #print 'PS.STEP %s: ta %s %s' % (self, self.__tryAccept, consumed) 977 self.__tryAccept = self.__tryAccept and (not consumed) 978 #print 'PS.STEP %s: %s' % (self, consumed) 979 if consumed: 980 if not self.__particle.meetsMaximum(self.__count): 981 raise pyxb.UnexpectedElementError('too many') 982 else: 983 if self.__parentState is not None: 984 self.__parentState.notifyFailure(self, self.__particle.satisfiesOccurrences(self.__count)) 985 if not self.__particle.meetsMinimum(self.__count): 986 # @TODO@ Use better exception; changing this will require 987 # changing some unit tests. 988 #underflow_exc = pyxb.MissingElementError('too few') 989 underflow_exc = pyxb.UnrecognizedContentError('too few') 990 return (consumed, underflow_exc)
991
992 - def __str__ (self):
993 particle = self.__particle 994 return 'ParticleState(%d:%d,%s:%s)@%x' % (self.__count, particle.minOccurs(), particle.maxOccurs(), particle.term(), id(self))
995
996 -class ParticleModel (ContentModel_mixin):
997 """Content model dealing with particles: terms with occurrence restrictions""" 998
999 - def minOccurs (self): return self.__minOccurs
1000 - def maxOccurs (self): return self.__maxOccurs
1001 - def term (self): return self.__term
1002
1003 - def meetsMaximum (self, count):
1004 """@return: C{True} iff there is no maximum on term occurrence, or the 1005 provided count does not exceed that maximum""" 1006 return (self.__maxOccurs is None) or (count <= self.__maxOccurs)
1007
1008 - def meetsMinimum (self, count):
1009 """@return: C{True} iff the provided count meets the minimum number of 1010 occurrences""" 1011 return count >= self.__minOccurs
1012
1013 - def satisfiesOccurrences (self, count):
1014 """@return: C{True} iff the provided count satisfies the occurrence 1015 requirements""" 1016 return self.meetsMinimum(count) and self.meetsMaximum(count)
1017
1018 - def __init__ (self, term, min_occurs=1, max_occurs=1):
1019 self.__term = term 1020 self.__minOccurs = min_occurs 1021 self.__maxOccurs = max_occurs
1022
1023 - def newState (self):
1024 return ParticleState(self)
1025
1026 - def validate (self, symbol_set):
1027 """Determine whether the particle requirements are satisfiable by the 1028 given symbol set. 1029 1030 The symbol set represents letters in an alphabet. If those letters 1031 can be combined in a way that satisfies the regular expression 1032 expressed in the model, a satisfying sequence is returned and the 1033 symbol set is reduced by the letters used to form the sequence. If 1034 the content model cannot be satisfied, C{None} is returned and the 1035 symbol set remains unchanged. 1036 1037 @param symbol_set: A map from L{ElementUse} instances to a list of 1038 values. The order of the values corresponds to the order in which 1039 they should appear. A key of C{None} identifies values that are 1040 stored as wildcard elements. Values are removed from the lists as 1041 they are used; when the last value of a list is removed, its key is 1042 removed from the map. Thus an empty dictionary is the indicator that 1043 no more symbols are available. 1044 1045 @return: returns C{None}, or a list of tuples C{( eu, val )} where 1046 C{eu} is an L{ElementUse} from the set of symbol keys, and C{val} is a 1047 value from the corresponding list. 1048 """ 1049 1050 output_sequence = [] 1051 #print 'Start: %d %s %s : %s' % (self.__minOccurs, self.__maxOccurs, self.__term, symbol_set) 1052 result = self._validate(symbol_set, output_sequence) 1053 #print 'End: %s %s %s' % (result, symbol_set, output_sequence) 1054 if result: 1055 return (symbol_set, output_sequence) 1056 return None
1057
1058 - def _validate (self, symbol_set, output_sequence):
1059 symbol_set_mut = self._validateCloneSymbolSet(symbol_set) 1060 output_sequence_mut = self._validateCloneOutputSequence(output_sequence) 1061 count = 0 1062 #print 'VAL start %s: %d %s' % (self.__term, self.__minOccurs, self.__maxOccurs) 1063 last_size = len(output_sequence_mut) 1064 while (count != self.__maxOccurs) and self.__term._validate(symbol_set_mut, output_sequence_mut): 1065 #print 'VAL %s old cnt %d, left %s' % (self.__term, count, symbol_set_mut) 1066 this_size = len(output_sequence_mut) 1067 if this_size == last_size: 1068 # Validated without consuming anything. Assume we can 1069 # continue to do so, jump to the minimum, and exit. 1070 if count < self.__minOccurs: 1071 count = self.__minOccurs 1072 break 1073 count += 1 1074 last_size = this_size 1075 result = self.satisfiesOccurrences(count) 1076 if (result): 1077 self._validateReplaceResults(symbol_set, symbol_set_mut, output_sequence, output_sequence_mut) 1078 #print 'VAL end PRT %s res %s: %s %s %s' % (self.__term, result, self.__minOccurs, count, self.__maxOccurs) 1079 return result
1080
1081 -class _Group (ContentModel_mixin):
1082 """Base class for content information pertaining to a U{model 1083 group<http://www.w3.org/TR/xmlschema-1/#Model_Groups>}. 1084 1085 There is a specific subclass for each group compositor. 1086 """ 1087 1088 _StateClass = None 1089 """A reference to a L{ContentState_mixin} class that maintains state when 1090 validating an instance of this group.""" 1091
1092 - def particles (self): return self.__particles
1093
1094 - def __init__ (self, *particles):
1095 self.__particles = particles
1096
1097 - def newState (self, parent_particle_state):
1098 return self._StateClass(self, parent_particle_state)
1099 1100 # All and Sequence share the same validation code, so it's up here.
1101 - def _validate (self, symbol_set, output_sequence):
1102 symbol_set_mut = self._validateCloneSymbolSet(symbol_set) 1103 output_sequence_mut = self._validateCloneOutputSequence(output_sequence) 1104 for p in self.particles(): 1105 if not p._validate(symbol_set_mut, output_sequence_mut): 1106 return False 1107 self._validateReplaceResults(symbol_set, symbol_set_mut, output_sequence, output_sequence_mut) 1108 return True
1109 1110
1111 -class GroupChoice (_Group):
1112 _StateClass = ChoiceState 1113 1114 # Choice requires a different validation algorithm
1115 - def _validate (self, symbol_set, output_sequence):
1116 reset_mutables = True 1117 for p in self.particles(): 1118 if reset_mutables: 1119 symbol_set_mut = self._validateCloneSymbolSet(symbol_set) 1120 output_sequence_mut = self._validateCloneOutputSequence(output_sequence) 1121 if p._validate(symbol_set_mut, output_sequence_mut): 1122 self._validateReplaceResults(symbol_set, symbol_set_mut, output_sequence, output_sequence_mut) 1123 return True 1124 reset_mutables = len(output_sequence) != len(output_sequence_mut) 1125 return False
1126
1127 -class GroupAll (_Group):
1128 _StateClass = AllState
1129
1130 -class GroupSequence (_Group):
1131 _StateClass = SequenceState
1132 1133 ## Local Variables: 1134 ## fill-column:78 1135 ## End: 1136