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

Source Code for Module pyxb.binding.content

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