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-2013, 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{ElementDeclaration} 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  import pyxb.utils.fac 
  31  from pyxb.binding import basis 
  32  import pyxb.utils.utility 
  33   
  34  import xml.dom 
  35  import logging 
  36   
  37  _log = logging.getLogger(__name__) 
38 39 -class AttributeUse (pyxb.cscRoot):
40 """A helper class that encapsulates everything we need to know 41 about the way an attribute is used within a binding class. 42 43 Attributes are stored internally as pairs C{(provided, value)}, where 44 C{provided} is a boolean indicating whether a value for the attribute was 45 provided externally, and C{value} is an instance of the attribute 46 datatype. The C{provided} flag is used to determine whether an XML 47 attribute should be added to a created DOM node when generating the XML 48 corresponding to a binding instance. 49 """ 50 51 __name = None 52 """ExpandedName of the attribute""" 53 54 __id = None 55 """Identifier used for this attribute within the owning class""" 56 57 __key = None 58 """Private Python attribute used in instances to hold the attribute value""" 59 60 __dataType = None 61 """The L{pyxb.binding.basis.simpleTypeDefinition} for values of the attribute""" 62 63 __unicodeDefault = None 64 """The default attribute value as a unicode string, or C{None}""" 65 66 __defaultValue = None 67 """The default value as an instance of L{__dataType}, or C{None}""" 68 69 __fixed = False 70 """C{True} if the attribute value cannot be changed""" 71 72 __required = False 73 """C{True} if the attribute must appear in every instance of the type""" 74 75 __prohibited = False 76 """C{True} if the attribute must not appear in any instance of the type""" 77
78 - def __init__ (self, name, id, key, data_type, unicode_default=None, fixed=False, required=False, prohibited=False):
79 """Create an AttributeUse instance. 80 81 @param name: The name by which the attribute is referenced in the XML 82 @type name: L{pyxb.namespace.ExpandedName} 83 84 @param id: The Python identifier for the attribute within the 85 containing L{pyxb.basis.binding.complexTypeDefinition}. This is a 86 public identifier, derived from the local part of the attribute name 87 and modified to be unique, and is usually used as the name of the 88 attribute's inspector method. 89 @type id: C{str} 90 91 @param key: The string used to store the attribute 92 value in the dictionary of the containing 93 L{pyxb.basis.binding.complexTypeDefinition}. This is mangled so 94 that it is unique among and is treated as a Python private member. 95 @type key: C{str} 96 97 @param data_type: The class reference to the subclass of 98 L{pyxb.binding.basis.simpleTypeDefinition} of which the attribute 99 values must be instances. 100 @type data_type: C{type} 101 102 @keyword unicode_default: The default value of the attribute as 103 specified in the schema, or None if there is no default attribute 104 value. The default value (of the keyword) is C{None}. 105 @type unicode_default: C{unicode} 106 107 @keyword fixed: If C{True}, indicates that the attribute, if present, 108 must have the value that was given via C{unicode_default}. The 109 default value is C{False}. 110 @type fixed: C{bool} 111 112 @keyword required: If C{True}, indicates that the attribute must appear 113 in the DOM node used to create an instance of the corresponding 114 L{pyxb.binding.basis.complexTypeDefinition}. The default value is 115 C{False}. No more that one of L{required} and L{prohibited} should be 116 assigned C{True}. 117 @type required: C{bool} 118 119 @keyword prohibited: If C{True}, indicates that the attribute must 120 B{not} appear in the DOM node used to create an instance of the 121 corresponding L{pyxb.binding.basis.complexTypeDefinition}. The 122 default value is C{False}. No more that one of L{required} and 123 L{prohibited} should be assigned C{True}. 124 @type prohibited: C{bool} 125 126 @raise pyxb.SimpleTypeValueError: the L{unicode_default} cannot be used 127 to initialize an instance of L{data_type} 128 """ 129 130 self.__name = name 131 self.__id = id 132 self.__key = key 133 self.__dataType = data_type 134 self.__unicodeDefault = unicode_default 135 if self.__unicodeDefault is not None: 136 self.__defaultValue = self.__dataType.Factory(self.__unicodeDefault, _from_xml=True) 137 self.__fixed = fixed 138 self.__required = required 139 self.__prohibited = prohibited 140 super(AttributeUse, self).__init__()
141
142 - def name (self):
143 """The expanded name of the element. 144 145 @rtype: L{pyxb.namespace.ExpandedName} 146 """ 147 return self.__name
148
149 - def defaultValue (self):
150 """The default value of the attribute.""" 151 return self.__defaultValue
152
153 - def fixed (self):
154 """C{True} iff the value of the attribute cannot be changed.""" 155 return self.__fixed
156
157 - def required (self):
158 """C{True} iff the attribute must be assigned a value.""" 159 return self.__required
160
161 - def prohibited (self):
162 """C{True} iff the attribute must not be assigned a value.""" 163 return self.__prohibited
164
165 - def provided (self, ctd_instance):
166 """C{True} iff the given instance has been explicitly given a value 167 for the attribute. 168 169 This is used for things like only generating an XML attribute 170 assignment when a value was originally given (even if that value 171 happens to be the default). 172 """ 173 return self.__getProvided(ctd_instance)
174
175 - def id (self):
176 """Tag used within Python code for the attribute. 177 178 This is not used directly in the default code generation template.""" 179 return self.__id
180
181 - def key (self):
182 """String used as key within object dictionary when storing attribute value.""" 183 return self.__key
184
185 - def dataType (self):
186 """The subclass of L{pyxb.binding.basis.simpleTypeDefinition} of which any attribute value must be an instance.""" 187 return self.__dataType
188
189 - def __getValue (self, ctd_instance):
190 """Retrieve the value information for this attribute in a binding instance. 191 192 @param ctd_instance: The instance object from which the attribute is to be retrieved. 193 @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition} 194 @return: C{(provided, value)} where C{provided} is a C{bool} and 195 C{value} is C{None} or an instance of the attribute's datatype. 196 197 """ 198 return getattr(ctd_instance, self.__key, (False, None))
199
200 - def __getProvided (self, ctd_instance):
201 return self.__getValue(ctd_instance)[0]
202
203 - def value (self, ctd_instance):
204 """Get the value of the attribute from the instance.""" 205 if self.__prohibited: 206 raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance) 207 return self.__getValue(ctd_instance)[1]
208
209 - def __setValue (self, ctd_instance, new_value, provided):
210 return setattr(ctd_instance, self.__key, (provided, new_value))
211
212 - def reset (self, ctd_instance):
213 """Set the value of the attribute in the given instance to be its 214 default value, and mark that it has not been provided.""" 215 self.__setValue(ctd_instance, self.__defaultValue, False)
216
217 - def addDOMAttribute (self, dom_support, ctd_instance, element):
218 """If this attribute as been set, add the corresponding attribute to the DOM element.""" 219 ( provided, value ) = self.__getValue(ctd_instance) 220 if provided: 221 assert value is not None 222 dom_support.addAttribute(element, self.__name, value.xsdLiteral()) 223 return self
224
225 - def validate (self, ctd_instance):
226 """Validate the instance against the requirements imposed by this 227 attribute use. 228 229 There is no return value; calls raise an exception if the content does 230 not validate. 231 232 @param ctd_instance : An instance of a complex type definition. 233 234 @raise pyxb.ProhibitedAttributeError: when instance has attribute but must not 235 @raise pyxb.MissingAttributeError: when instance lacks attribute but 236 must have it (including when a required fixed-value attribute is 237 missing). 238 @raise pyxb.BatchContentValidationError: when instance has attribute but its value is not acceptable 239 """ 240 (provided, value) = self.__getValue(ctd_instance) 241 if value is not None: 242 if self.__prohibited: 243 raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance) 244 if self.__required and not provided: 245 assert self.__fixed 246 raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance) 247 self.__dataType._CheckValidValue(value) 248 self.__dataType.XsdConstraintsOK(value) 249 else: 250 if self.__required: 251 raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance)
252
253 - def set (self, ctd_instance, new_value, from_xml=False):
254 """Set the value of the attribute. 255 256 This validates the value against the data type, creating a new instance if necessary. 257 258 @param ctd_instance: The binding instance for which the attribute 259 value is to be set 260 @type ctd_instance: subclass of L{pyxb.binding.basis.complexTypeDefinition} 261 @param new_value: The value for the attribute 262 @type new_value: Any value that is permitted as the input parameter to 263 the C{Factory} method of the attribute's datatype. 264 @param from_xml: Value C{True} iff the new_value is known to be in 265 lexical space and must by converted by the type factory. If C{False} 266 (default) the value is only converted if it is not already an instance 267 of the attribute's underlying type. 268 """ 269 provided = True 270 assert not isinstance(new_value, xml.dom.Node) 271 if new_value is None: 272 if self.__required: 273 raise pyxb.MissingAttributeError(type(ctd_instance), self.__name, ctd_instance) 274 provided = False 275 if self.__prohibited: 276 raise pyxb.ProhibitedAttributeError(type(ctd_instance), self.__name, ctd_instance) 277 if (new_value is not None) and (from_xml or not isinstance(new_value, self.__dataType)): 278 new_value = self.__dataType.Factory(new_value, _from_xml=from_xml) 279 if self.__fixed and (new_value != self.__defaultValue): 280 raise pyxb.AttributeChangeError(type(ctd_instance), self.__name, ctd_instance) 281 self.__setValue(ctd_instance, new_value, provided) 282 return new_value
283
284 - def _description (self, name_only=False, user_documentation=True):
285 if name_only: 286 return unicode(self.__name) 287 assert issubclass(self.__dataType, basis._TypeBinding_mixin) 288 desc = [ unicode(self.__id), ': ', unicode(self.__name), ' (', self.__dataType._description(name_only=True, user_documentation=False), '), ' ] 289 if self.__required: 290 desc.append('required') 291 elif self.__prohibited: 292 desc.append('prohibited') 293 else: 294 desc.append('optional') 295 if self.__defaultValue is not None: 296 desc.append(', ') 297 if self.__fixed: 298 desc.append('fixed') 299 else: 300 desc.append('default') 301 desc.extend(['=', self.__unicodeDefault ]) 302 return ''.join(desc)
303
304 -class AutomatonConfiguration (object):
305 """State for a L{pyxb.utils.fac.Automaton} monitoring content for an 306 incrementally constructed complex type binding instance. 307 308 @warning: This is not an implementation of 309 L{pyxb.utils.fac.Configuration_ABC} because we need the L{step} function 310 to return a different type of value.""" 311 312 # The binding instance for which content is being built 313 __instance = None 314 315 # The underlying configuration when the state is deterministic. In this 316 # case, all updates to the instance content corresponding to the current 317 # state have been applied to the instance. Note that while steps are 318 # occurring this instance, as well as those in __multi, might be 319 # references to sub-automata. 320 __cfg = None 321 322 # A list of pairs when the state is non-deterministic. The first member 323 # of the pair is the configuration; the second is a tuple of closures that 324 # must be applied to the instance in order to store the content that was 325 # accepted along the path to that configuration. This is in order of 326 # preference based on the location of path candidate declarations in the 327 # defining schema. 328 __multi = None 329
330 - def __init__ (self, instance):
331 self.__instance = instance
332
333 - def reset (self):
334 """Reset the automaton to its initial state. 335 336 Subsequent transitions are expected based on candidate content to be 337 supplied through the L{step} method.""" 338 self.__cfg = self.__instance._Automaton.newConfiguration() 339 self.__multi = None
340
341 - def nondeterminismCount (self):
342 """Return the number of pending configurations. 343 344 The automaton is deterministic if exactly one configuration is 345 available.""" 346 if self.__cfg is not None: 347 assert self.__multi is None 348 return 1 349 return len(self.__multi)
350
351 - def step (self, value, element_decl):
352 """Attempt a transition from the current state. 353 354 @param value: the content to be supplied. For success the value must 355 be consistent with the recorded symbol (element or wildcard 356 declaration) for a transition from the current automaton state. 357 358 @param element_decl: optional 359 L{pyxb.binding.content.ElementDeclaration} that is the preferred 360 symbol for the transition. 361 362 @return: the cardinal number of successful transitions from the 363 current configuration based on the parameters.""" 364 365 sym = (value, element_decl) 366 367 # Start with the current configuration(s), assuming we might see 368 # non-determinism. 369 new_multi = [] 370 if self.__multi is None: 371 multi = [ (self.__cfg, ()) ] 372 else: 373 multi = self.__multi[:] 374 # Collect the complete set of reachable configurations along with the 375 # closures that will update the instance content based on the path. 376 for (cfg, pending) in multi: 377 cand = cfg.candidateTransitions(sym) 378 for transition in cand: 379 clone_map = {} 380 ccfg = cfg.clone(clone_map) 381 new_multi.append( (transition.apply(ccfg, clone_map), pending+(transition.consumedSymbol().consumingClosure(sym),)) ) 382 rv = len(new_multi) 383 if 0 == rv: 384 # No candidate transitions. Do not change the state. 385 return 0 386 if 1 == rv: 387 # Deterministic transition. Save the configuration and apply the 388 # corresponding updates. 389 self.__multi = None 390 (self.__cfg, actions) = new_multi[0] 391 for fn in actions: 392 fn(self.__instance) 393 else: 394 # Non-deterministic. Save everything for subsequent resolution. 395 self.__cfg = None 396 self.__multi = new_multi 397 return rv
398
399 - def resolveNondeterminism (self, prefer_accepting=True):
400 """Resolve any non-determinism in the automaton state. 401 402 If the automaton has reached a single configuration (was 403 deterministic), this does nothing. 404 405 If multiple candidate configurations are available, the best one is 406 selected and applied, updating the binding instance with the pending 407 content. 408 409 "Best" in this case is determined by optionally eliminating 410 configurations that are not accepting, then selecting the path where 411 the initial transition sorts highest using the binding sort key (based 412 on position in the original schema). 413 414 @keyword prefer_accepting: eliminate non-accepting paths if any 415 accepting path is present.""" 416 if self.__multi is None: 417 return 418 assert self.__cfg is None 419 multi = self.__multi 420 if prefer_accepting: 421 multi = filter(lambda _ts: _ts[0].isAccepting(), self.__multi) 422 if 0 == len(multi): 423 multi = self.__multi 424 # step() will not create an empty multi list, so cannot get here with 425 # no configurations available unless nobody even reset the 426 # configuration, which would be a usage error. 427 assert 0 < len(multi) 428 if 1 < len(multi): 429 desc = self.__instance._ExpandedName 430 if desc is None: 431 desc = type(self.__instance) 432 _log.warning('Multiple accepting paths for %s', desc) 433 ''' 434 for (cfg, actions) in multi: 435 foo = type(self.__instance)() 436 for fn in actions: 437 fn(foo) 438 print '1: %s ; 2 : %s ; wc: %s' % (foo.first, foo.second, foo.wildcardElements()) 439 ''' 440 (self.__cfg, actions) = multi[0] 441 self.__multi = None 442 for fn in actions: 443 fn(self.__instance)
444
445 - def acceptableContent (self):
446 """Return the sequence of acceptable symbols at this state. 447 448 The list comprises the L{pyxb.binding.content.ElementUse} and 449 L{pyxb.binding.content.WildcardUse} instances that are used to 450 validate proposed symbols, in preferred order.""" 451 rv = [] 452 seen = set() 453 multi = self.__multi 454 if multi is None: 455 multi = [ self.__cfg] 456 for cfg in multi: 457 for u in cfg.acceptableSymbols(): 458 if not (u in seen): 459 rv.append(u) 460 seen.add(u) 461 return rv
462
463 - def isAccepting (self, raise_if_rejecting=False):
464 """Return C{True} iff the automaton is in an accepting state. 465 466 If the automaton has unresolved nondeterminism, it is resolved first, 467 preferring accepting states.""" 468 self.resolveNondeterminism(True) 469 cfg = self.__cfg 470 while cfg.superConfiguration is not None: 471 cfg = cfg.superConfiguration 472 return cfg.isAccepting()
473
474 - def _diagnoseIncompleteContent (self, symbols, symbol_set):
475 """Check for incomplete content. 476 477 @raises pyxb.IncompleteElementContentError: if a non-accepting state is found 478 @return: the topmost configuration (if accepting) 479 """ 480 # Exit out of any sub-configurations (they might be accepting while 481 # the superConfiguration is not) 482 cfg = self.__cfg 483 while cfg.isAccepting() and (cfg.superConfiguration is not None): 484 cfg = cfg.superConfiguration 485 if not cfg.isAccepting(): 486 _log.warning('Incomplete content, expect %s' % (' or '.join(map(str, cfg.acceptableSymbols())))) 487 raise pyxb.IncompleteElementContentError(self.__instance, cfg, symbols, symbol_set) 488 return cfg
489
490 - def diagnoseIncompleteContent (self):
491 """Generate the exception explaining why the content is incomplete.""" 492 return self._diagnoseIncompleteContent(None, None)
493 494 __preferredSequenceIndex = 0 495 __preferredPendingSymbol = None 496 __pendingNonElementContent = None 497
498 - def __resetPreferredSequence (self, instance):
499 self.__preferredSequenceIndex = 0 500 self.__preferredPendingSymbol = None 501 self.__pendingNonElementContent = None 502 vc = instance._validationConfig 503 preferred_sequence = None 504 if (vc.ALWAYS == vc.contentInfluencesGeneration) or (instance._ContentTypeTag == instance._CT_MIXED and vc.MIXED_ONLY == vc.contentInfluencesGeneration): 505 preferred_sequence = instance.orderedContent() 506 if instance._ContentTypeTag == instance._CT_MIXED: 507 self.__pendingNonElementContent = [] 508 return preferred_sequence
509
510 - def __discardPreferredSequence (self, preferred_sequence, pi=None):
511 """Extract non-element content from the sequence and return C{None}.""" 512 if pi is None: 513 pi = self.__preferredSequenceIndex 514 nec = self.__pendingNonElementContent 515 if nec is not None: 516 for csym in preferred_sequence[pi:]: 517 if isinstance(csym, pyxb.binding.basis.NonElementContent): 518 nec.append(csym) 519 return None
520
521 - def __processPreferredSequence (self, preferred_sequence, symbol_set, vc):
522 pi = self.__preferredSequenceIndex 523 psym = self.__preferredPendingSymbol 524 nec = self.__pendingNonElementContent 525 if psym is not None: 526 _log.info('restoring %s', psym) 527 self.__preferredPendingSymbol = None 528 while psym is None: 529 if pi >= len(preferred_sequence): 530 preferred_sequence = self.__discardPreferredSequence(preferred_sequence, pi) 531 break 532 csym = preferred_sequence[pi] 533 pi += 1 534 if (nec is not None) and isinstance(csym, pyxb.binding.basis.NonElementContent): 535 nec.append(csym) 536 continue 537 vals = symbol_set.get(csym.elementDeclaration, []) 538 if csym.value in vals: 539 psym = ( csym.value, csym.elementDeclaration ) 540 break 541 if psym is None: 542 # Orphan encountered; response? 543 _log.info('orphan %s in content', csym) 544 if vc.IGNORE_ONCE == vc.orphanElementInContent: 545 continue 546 if vc.GIVE_UP == vc.orphanElementInContent: 547 preferred_sequence = self.__discardPreferredSequence(preferred_sequence, pi) 548 break 549 raise pyxb.OrphanElementContentError(self.__instance, csym) 550 self.__preferredSequenceIndex = pi 551 return (preferred_sequence, psym)
552
553 - def sequencedChildren (self):
554 """Implement L{pyxb.binding.basis.complexTypeDefinition._validatedChildren}. 555 556 Go there for the interface. 557 """ 558 559 # We need a fresh automaton configuration corresponding to the type of 560 # the binding instance. 561 self.reset() 562 cfg = self.__cfg 563 564 # The validated sequence 565 symbols = [] 566 567 # How validation should be done 568 instance = self.__instance 569 vc = instance._validationConfig 570 571 # The available content, in a map from ElementDeclaration to in-order 572 # values. The key None corresponds to the wildcard content. Keys are 573 # removed when their corresponding content is exhausted. 574 symbol_set = instance._symbolSet() 575 576 # The preferred sequence to use, if desired. 577 preferred_sequence = self.__resetPreferredSequence(instance) 578 579 # A reference to the data structure holding non-element content. This 580 # is None unless mixed content is allowed, in which case it is a list. 581 # The same list is used for the entire operation, though it is reset 582 # to be empty after transferring material to the output sequence. 583 nec = self.__pendingNonElementContent 584 585 psym = None 586 while symbol_set: 587 # Find the first acceptable transition. If there's a preferred 588 # symbol to use, try it first. 589 selected_xit = None 590 psym = None 591 if preferred_sequence is not None: 592 (preferred_sequence, psym) = self.__processPreferredSequence(preferred_sequence, symbol_set, vc) 593 candidates = cfg.candidateTransitions(psym) 594 for xit in candidates: 595 csym = xit.consumedSymbol() 596 if isinstance(csym, ElementUse): 597 ed = csym.elementDeclaration() 598 elif isinstance(csym, WildcardUse): 599 ed = None 600 else: 601 assert False 602 # Check whether we have content that matches the symbol 603 matches = symbol_set.get(ed) 604 if matches is None: 605 continue 606 if not csym.match((matches[0], ed)): 607 continue 608 # Commit to this transition and append the selected content 609 # after any pending non-element content that is released due 610 # to a matched preferred symbol. 611 value = matches.pop(0) 612 if (psym is not None) and (nec is not None): 613 symbols.extend(nec) 614 nec[:] = [] 615 symbols.append(basis.ElementContent(csym.matchValue( (value, ed) ), ed)) 616 selected_xit = xit 617 if 0 == len(matches): 618 del symbol_set[ed] 619 break 620 if selected_xit is None: 621 if psym is not None: 622 # Suggestion from content did not work 623 _log.info('invalid %s in content', psym) 624 if vc.IGNORE_ONCE == vc.invalidElementInContent: 625 continue 626 if vc.GIVE_UP == vc.invalidElementInContent: 627 preferred_sequence = self.__discardPreferredSequence(preferred_sequence) 628 continue 629 raise pyxb.InvalidPreferredElementContentError(self.__instance, cfg, symbols, symbol_set, psym) 630 break 631 cfg = selected_xit.apply(cfg) 632 cfg = self._diagnoseIncompleteContent(symbols, symbol_set) 633 if symbol_set: 634 raise pyxb.UnprocessedElementContentError(self.__instance, cfg, symbols, symbol_set) 635 # Validate any remaining material in the preferred sequence. This 636 # also extracts remaining non-element content. Note there are 637 # no more symbols, so any remaining element content is orphan. 638 while preferred_sequence is not None: 639 (preferred_sequence, psym) = self.__processPreferredSequence(preferred_sequence, symbol_set, vc) 640 if psym is not None: 641 if not (vc.orphanElementInContent in ( vc.IGNORE_ONCE, vc.GIVE_UP )): 642 raise pyxb.OrphanElementContentError(self.__instance, psym.value) 643 if nec is not None: 644 symbols.extend(nec) 645 return symbols
646
647 -class _FACSymbol (pyxb.utils.fac.SymbolMatch_mixin):
648 """Base class for L{pyxb.utils.fac.Symbol} instances associated with PyXB content models. 649 650 This holds the location in the schema of the L{ElementUse} or 651 L{WildcardUse} and documents the methods expected of its children.""" 652 653 __xsdLocation = None 654
655 - def xsdLocation (self):
656 return self.__xsdLocation
657
658 - def matchValue (self, sym):
659 """Return the value accepted by L{match} for this symbol. 660 661 A match for an element declaration might have resulted in a type 662 change for the value (converting it to an acceptable type). There is 663 no safe place to cache the compatible value calculated in the match 664 while other candidates are being considered, so we need to 665 re-calculate it if the transition is taken. 666 667 If the match could not have changed the value, the value from the 668 symbol may be returned immediately.""" 669 raise NotImplementedError('%s._matchValue' % (type(self).__name__,))
670
671 - def consumingClosure (self, sym):
672 """Create a closure that will apply the value from C{sym} to a to-be-supplied instance. 673 674 This is necessary for non-deterministic automata, where we can't store 675 the value into the instance field until we know that the transition 676 will be taken: 677 678 @return: A closure that takes a L{complexTypeDefinition} instance and 679 stores the value from invoking L{matchValue} on C{sym} into the 680 appropriate slot.""" 681 raise NotImplementedError('%s._consumingClosure' % (type(self).__name__,))
682
683 - def __init__ (self, xsd_location):
684 """@param xsd_location: the L{location<pyxb.utils.utility.Location>} of the element use or wildcard declaration.""" 685 self.__xsdLocation = xsd_location 686 super(_FACSymbol, self).__init__()
687
688 -class ElementUse (_FACSymbol):
689 """Information about a schema element declaration reference. 690 691 This is used by the FAC content model to identify the location 692 within a schema at which an element use appears. The L{ElementDeclaration} 693 is not sufficient since multiple uses in various schema, possibly in 694 different namespaces, may refer to the same declaration but be independent 695 uses. 696 """ 697 698 __elementDeclaration = None 699
700 - def elementDeclaration (self):
701 """Return the L{element declaration<pyxb.binding.content.ElementDeclaration>} associated with the use.""" 702 return self.__elementDeclaration
703
704 - def elementBinding (self):
705 """Return the L{element binding<pyxb.binding.content.ElementDeclaration.elementBinding>} associated with the use. 706 707 Equivalent to L{elementDeclaration}().L{elementBinding()<pyxb.binding.content.ElementDeclaration.elementBinding>}.""" 708 return self.__elementDeclaration.elementBinding()
709
710 - def typeDefinition (self):
711 """Return the element type. 712 713 Equivalent to L{elementDeclaration}().L{elementBinding()<pyxb.binding.content.ElementDeclaration.elementBinding>}.L{typeDefinition()<pyxb.binding.basis.element.typeDefinition>}.""" 714 return self.__elementDeclaration.elementBinding().typeDefinition()
715
716 - def __init__ (self, element_declaration, xsd_location):
717 super(ElementUse, self).__init__(xsd_location) 718 self.__elementDeclaration = element_declaration
719
720 - def matchValue (self, sym):
721 (value, element_decl) = sym 722 (rv, value) = self.__elementDeclaration._matches(value, element_decl) 723 assert rv 724 return value
725
726 - def consumingClosure (self, sym):
727 # Defer the potentially-expensive re-invocation of matchValue until 728 # the closure is applied. 729 return lambda _inst,_eu=self,_sy=sym: _eu.__elementDeclaration.setOrAppend(_inst, _eu.matchValue(_sy))
730
731 - def match (self, symbol):
732 """Satisfy L{pyxb.utils.fac.SymbolMatch_mixin}. 733 734 Determine whether the proposed content encapsulated in C{symbol} is 735 compatible with the element declaration. If so, the accepted value is 736 cached internally and return C{True}; otherwise return C{False}. 737 738 @param symbol: a pair C{(value, element_decl)}. 739 L{pyxb.binding.content.ElementDeclaration._matches} is used to 740 determine whether the proposed content is compatible with this element 741 declaration.""" 742 (value, element_decl) = symbol 743 # NB: this call may change value to be compatible. Unfortunately, we 744 # can't reliably cache that converted value, so just ignore it and 745 # we'll recompute it if the candidate transition is taken. 746 (rv, value) = self.__elementDeclaration._matches(value, element_decl) 747 return rv
748
749 - def __str__ (self):
750 return '%s per %s' % (self.__elementDeclaration.name(), self.xsdLocation())
751
752 -class WildcardUse (_FACSymbol):
753 """Information about a schema wildcard element. 754 755 This is functionally parallel to L{ElementUse}, but references a 756 L{Wildcard} that is unique to this instance. That L{Wildcard} is not 757 incorporated into this class is an artifact of the evolution of PyXB.""" 758 759 __wildcardDeclaration = None 760
761 - def wildcardDeclaration (self):
762 return self.__wildcardDeclaration
763
764 - def matchValue (self, sym):
765 (value, element_decl) = sym 766 return value
767
768 - def consumingClosure (self, sym):
769 """Create a closure that will apply the value accepted by L{match} to a to-be-supplied instance.""" 770 return lambda _inst,_av=self.matchValue(sym): _inst._appendWildcardElement(_av)
771
772 - def match (self, symbol):
773 """Satisfy L{pyxb.utils.fac.SymbolMatch_mixin}. 774 775 Determine whether the proposed content encapsulated in C{symbol} is 776 compatible with the wildcard declaration. If so, the accepted value 777 is cached internally and return C{True}; otherwise return C{False}. 778 779 @param symbol: a pair C{(value, element_decl)}. 780 L{pyxb.binding.content.Wildcard.matches} is used to determine whether 781 the proposed content is compatible with this wildcard. 782 """ 783 (value, element_decl) = symbol 784 return self.__wildcardDeclaration.matches(None, value)
785
786 - def __init__ (self, wildcard_declaration, xsd_location):
787 super(WildcardUse, self).__init__(xsd_location) 788 self.__wildcardDeclaration = wildcard_declaration
789
790 - def __str__ (self):
791 return 'xs:any per %s' % (self.xsdLocation(),)
792 793 import collections 794 795 # Do not inherit from list; that's obscene, and could cause problems with the 796 # internal assumptions made by Python. Instead delegate everything to an 797 # instance of list that's held internally. Inherit from the ABC that 798 # represents list-style data structures so we can identify both lists and 799 # these things which are not lists. 800 @pyxb.utils.utility.BackfillComparisons
801 -class _PluralBinding (collections.MutableSequence):
802 """Helper for element content that supports multiple occurences. 803 804 This is an adapter for Python list. Any operation that can mutate an item 805 in the list ensures the stored value is compatible with the element for 806 which the list holds values.""" 807 808 __list = None 809 __elementBinding = None 810
811 - def __init__ (self, *args, **kw):
812 element_binding = kw.pop('element_binding', None) 813 if not isinstance(element_binding, basis.element): 814 raise ValueError() 815 self.__elementBinding = element_binding 816 self.__list = [] 817 self.extend(args)
818
819 - def __convert (self, v):
821
822 - def __len__ (self):
823 return self.__list.__len__()
824
825 - def __getitem__ (self, key):
826 return self.__list.__getitem__(key)
827
828 - def __setitem__ (self, key, value):
829 if isinstance(key, slice): 830 self.__list.__setitem__(key, [ self.__convert(_v) for _v in value]) 831 else: 832 self.__list.__setitem__(key, self.__convert(value))
833
834 - def __delitem__ (self, key):
835 self.__list.__delitem__(key)
836
837 - def __iter__ (self):
838 return self.__list.__iter__()
839
840 - def __reversed__ (self):
841 return self.__list.__reversed__()
842
843 - def __contains__ (self, item):
844 return self.__list.__contains__(item)
845 846 # The mutable sequence type methods
847 - def append (self, x):
848 self.__list.append(self.__convert(x))
849
850 - def extend (self, x):
851 self.__list.extend(map(self.__convert, x))
852
853 - def count (self, x):
854 return self.__list.count(x)
855
856 - def index (self, x, i=0, j=-1):
857 return self.__list.index(x, i, j)
858
859 - def insert (self, i, x):
860 self.__list.insert(i, self.__convert(x))
861
862 - def pop (self, i=-1):
863 return self.__list.pop(i)
864
865 - def remove (self, x):
866 self.__list.remove(x)
867
868 - def reverse (self):
869 self.__list.reverse()
870
871 - def sort (self, key=None, reverse=False):
873
874 - def __str__ (self):
875 return self.__list.__str__()
876
877 - def __hash__ (self):
878 return hash(self.__list__)
879
880 - def __eq__ (self, other):
881 if isinstance(other, _PluralBinding): 882 return self.__list.__eq__(other.__list) 883 return self.__list.__eq__(other)
884
885 - def __le__ (self, other):
886 if isinstance(other, _PluralBinding): 887 return self.__list.__le__(other.__list) 888 return self.__list.__le__(other)
889
890 -class ElementDeclaration (object):
891 """Aggregate the information relevant to an element of a complex type. 892 893 This includes the L{original tag name<name>}, the spelling of L{the 894 corresponding object in Python <id>}, an L{indicator<isPlural>} of whether 895 multiple instances might be associated with the field, and other relevant 896 information. 897 """ 898
899 - def xsdLocation (self):
900 """The L{location<pyxb.utils.utility.Location>} in the schema where the 901 element was declared. 902 903 Note that this is not necessarily the same location as its use.""" 904 return self.__xsdLocation
905 __xsdLocation = None 906
907 - def name (self):
908 """The expanded name of the element. 909 910 @rtype: L{pyxb.namespace.ExpandedName} 911 """ 912 return self.__name
913 __name = None 914
915 - def id (self):
916 """The string name of the binding class field used to hold the element 917 values. 918 919 This is the user-visible name, and excepting disambiguation will be 920 equal to the local name of the element.""" 921 return self.__id
922 __id = None 923 924 # The dictionary key used to identify the value of the element. The value 925 # is the same as that used for private member variables in the binding 926 # class within which the element declaration occurred. 927 __key = None 928
929 - def elementBinding (self):
930 """The L{basis.element} instance identifying the information 931 associated with the element declaration. 932 """ 933 return self.__elementBinding
934 - def _setElementBinding (self, element_binding):
935 # Set the element binding for this use. Only visible at all because 936 # we have to define the uses before the element instances have been 937 # created. 938 self.__elementBinding = element_binding 939 return self
940 __elementBinding = None 941
942 - def isPlural (self):
943 """True iff the content model indicates that more than one element 944 can legitimately belong to this use. 945 946 This includes elements in particles with maxOccurs greater than one, 947 and when multiple elements with the same NCName are declared in the 948 same type. 949 """ 950 return self.__isPlural
951 __isPlural = False 952
953 - def __init__ (self, name, id, key, is_plural, location, element_binding=None):
954 """Create an ElementDeclaration instance. 955 956 @param name: The name by which the element is referenced in the XML 957 @type name: L{pyxb.namespace.ExpandedName} 958 959 @param id: The Python name for the element within the containing 960 L{pyxb.basis.binding.complexTypeDefinition}. This is a public 961 identifier, albeit modified to be unique, and is usually used as the 962 name of the element's inspector method or property. 963 @type id: C{str} 964 965 @param key: The string used to store the element 966 value in the dictionary of the containing 967 L{pyxb.basis.binding.complexTypeDefinition}. This is mangled so 968 that it is unique among and is treated as a Python private member. 969 @type key: C{str} 970 971 @param is_plural: If C{True}, documents for the corresponding type may 972 have multiple instances of this element. As a consequence, the value 973 of the element will be a list. If C{False}, the value will be C{None} 974 if the element is absent, and a reference to an instance of the type 975 identified by L{pyxb.binding.basis.element.typeDefinition} if present. 976 @type is_plural: C{bool} 977 978 @param element_binding: Reference to the class that serves as the 979 binding for the element. 980 """ 981 self.__name = name 982 self.__id = id 983 self.__key = key 984 self.__isPlural = is_plural 985 self.__elementBinding = element_binding 986 super(ElementDeclaration, self).__init__()
987
988 - def defaultValue (self):
989 """Return the default value for this element. 990 991 For plural values, this is an empty collection. For non-plural 992 values, it is C{None} unless the element has a default value, in which 993 case it is that value. 994 995 @todo: This should recursively support filling in default content, as 996 when the plural list requires a non-zero minimum number of entries. 997 """ 998 if self.isPlural(): 999 return _PluralBinding(element_binding=self.__elementBinding) 1000 return self.__elementBinding.defaultValue()
1001
1002 - def resetValue (self):
1003 """Return the reset value for this element. 1004 1005 For plural values, this is an empty collection. For non-plural 1006 values, it is C{None}, corresponding to absence of an assigned 1007 element. 1008 """ 1009 if self.isPlural(): 1010 return _PluralBinding(element_binding=self.__elementBinding) 1011 return None
1012
1013 - def value (self, ctd_instance):
1014 """Return the value for this use within the given instance. 1015 1016 Note that this is the L{resetValue()}, not the L{defaultValue()}, if 1017 the element has not yet been assigned a value.""" 1018 return getattr(ctd_instance, self.__key, self.resetValue())
1019
1020 - def reset (self, ctd_instance):
1021 """Set the value for this use in the given element to its default.""" 1022 setattr(ctd_instance, self.__key, self.resetValue()) 1023 return self
1024
1025 - def set (self, ctd_instance, value):
1026 """Set the value of this element in the given instance.""" 1027 if value is None: 1028 return self.reset(ctd_instance) 1029 if ctd_instance._isNil(): 1030 raise pyxb.ContentInNilInstanceError(ctd_instance, value) 1031 assert self.__elementBinding is not None 1032 if ctd_instance._validationConfig.forBinding or isinstance(value, pyxb.BIND): 1033 value = self.__elementBinding.compatibleValue(value, is_plural=self.isPlural()) 1034 setattr(ctd_instance, self.__key, value) 1035 ctd_instance._addContent(basis.ElementContent(value, self)) 1036 return self
1037
1038 - def setOrAppend (self, ctd_instance, value):
1039 """Invoke either L{set} or L{append}, depending on whether the element 1040 use is plural.""" 1041 if self.isPlural(): 1042 return self.append(ctd_instance, value) 1043 return self.set(ctd_instance, value)
1044
1045 - def append (self, ctd_instance, value):
1046 """Add the given value as another instance of this element within the binding instance. 1047 @raise pyxb.StructuralBadDocumentError: invoked on an element use that is not plural 1048 """ 1049 if ctd_instance._isNil(): 1050 raise pyxb.ContentInNilInstanceError(ctd_instance, value) 1051 if not self.isPlural(): 1052 raise pyxb.NonPluralAppendError(ctd_instance, self, value) 1053 values = self.value(ctd_instance) 1054 if ctd_instance._validationConfig.forBinding: 1055 value = self.__elementBinding.compatibleValue(value) 1056 values.append(value) 1057 ctd_instance._addContent(basis.ElementContent(value, self)) 1058 return values
1059
1060 - def toDOM (self, dom_support, parent, value):
1061 """Convert the given value to DOM as an instance of this element. 1062 1063 @param dom_support: Helper for managing DOM properties 1064 @type dom_support: L{pyxb.utils.domutils.BindingDOMSupport} 1065 @param parent: The DOM node within which this element should be generated. 1066 @type parent: C{xml.dom.Element} 1067 @param value: The content for this element. May be text (if the 1068 element allows mixed content), or an instance of 1069 L{basis._TypeBinding_mixin}. 1070 1071 @raise pyxb.AbstractElementError: the binding to be used is abstract 1072 """ 1073 if isinstance(value, basis._TypeBinding_mixin): 1074 element_binding = self.__elementBinding 1075 if value._substitutesFor(element_binding): 1076 element_binding = value._element() 1077 assert element_binding is not None 1078 if element_binding.abstract(): 1079 raise pyxb.AbstractElementError(self, value) 1080 element = dom_support.createChildElement(element_binding.name(), parent) 1081 elt_type = element_binding.typeDefinition() 1082 val_type = type(value) 1083 if isinstance(value, basis.complexTypeDefinition): 1084 assert isinstance(value, elt_type) 1085 else: 1086 if isinstance(value, basis.STD_union) and isinstance(value, elt_type._MemberTypes): 1087 val_type = elt_type 1088 if dom_support.requireXSIType() or elt_type._RequireXSIType(val_type): 1089 val_type_qname = value._ExpandedName.localName() 1090 tns_prefix = dom_support.namespacePrefix(value._ExpandedName.namespace()) 1091 if tns_prefix is not None: 1092 val_type_qname = '%s:%s' % (tns_prefix, val_type_qname) 1093 dom_support.addAttribute(element, pyxb.namespace.XMLSchema_instance.createExpandedName('type'), val_type_qname) 1094 value._toDOM_csc(dom_support, element) 1095 elif isinstance(value, (str, unicode)): 1096 element = dom_support.createChildElement(self.name(), parent) 1097 element.appendChild(dom_support.document().createTextNode(value)) 1098 else: 1099 raise pyxb.LogicError('toDOM with unrecognized value type %s: %s' % (type(value), value))
1100
1101 - def _description (self, name_only=False, user_documentation=True):
1102 if name_only: 1103 return unicode(self.__name) 1104 desc = [ unicode(self.__id), ': '] 1105 if self.isPlural(): 1106 desc.append('MULTIPLE ') 1107 desc.append(self.elementBinding()._description(user_documentation=user_documentation)) 1108 return u''.join(desc)
1109
1110 - def _matches (self, value, element_decl):
1111 accept = False 1112 if element_decl == self: 1113 accept = True 1114 elif element_decl is not None: 1115 # If there's a known element, and it's not this one, the content 1116 # does not match. This assumes we handled xsi:type and 1117 # substitution groups earlier, which may be true. 1118 accept = False 1119 elif isinstance(value, xml.dom.Node): 1120 # If we haven't been able to identify an element for this before, 1121 # then we don't recognize it, and will have to treat it as a 1122 # wildcard. 1123 accept = False 1124 else: 1125 # A foreign value which might be usable if we can convert 1126 # it to a compatible value trivially. 1127 try: 1128 value = self.__elementBinding.compatibleValue(value, _convert_string_values=False) 1129 accept = True 1130 except (pyxb.ValidationError, pyxb.BindingError): 1131 pass 1132 return (accept, value)
1133
1134 - def __str__ (self):
1135 return 'ED.%s@%x' % (self.__name, id(self))
1136
1137 1138 -class Wildcard (object):
1139 """Placeholder for wildcard objects.""" 1140 1141 NC_any = '##any' #<<< The namespace constraint "##any" 1142 NC_not = '##other' #<<< A flag indicating constraint "##other" 1143 NC_targetNamespace = '##targetNamespace' #<<< A flag identifying the target namespace 1144 NC_local = '##local' #<<< A flag indicating the namespace must be absent 1145 1146 __namespaceConstraint = None
1147 - def namespaceConstraint (self):
1148 """A constraint on the namespace for the wildcard. 1149 1150 Valid values are: 1151 1152 - L{Wildcard.NC_any} 1153 - A tuple ( L{Wildcard.NC_not}, a L{namespace<pyxb.namespace.Namespace>} instance ) 1154 - set(of L{namespace<pyxb.namespace.Namespace>} instances) 1155 1156 Namespaces are represented by their URIs. Absence is 1157 represented by C{None}, both in the "not" pair and in the set. 1158 """ 1159 return self.__namespaceConstraint
1160 1161 PC_skip = 'skip' 1162 """No namespace constraint is applied to the wildcard.""" 1163 1164 PC_lax = 'lax' 1165 """Validate against available uniquely determined declaration.""" 1166 1167 PC_strict = 'strict' 1168 """Validate against declaration or xsi:type, which must be available.""" 1169 1170 __processContents = None 1171 """One of L{PC_skip}, L{PC_lax}, L{PC_strict}."""
1172 - def processContents (self):
1173 """Indicate how this wildcard's contents should be processed.""" 1174 return self.__processContents
1175
1176 - def __normalizeNamespace (self, nsv):
1177 if nsv is None: 1178 return None 1179 if isinstance(nsv, basestring): 1180 nsv = pyxb.namespace.NamespaceForURI(nsv, create_if_missing=True) 1181 assert isinstance(nsv, pyxb.namespace.Namespace), 'unexpected non-namespace %s' % (nsv,) 1182 return nsv
1183
1184 - def __init__ (self, *args, **kw):
1185 """ 1186 @keyword namespace_constraint: Required namespace constraint(s) 1187 @keyword process_contents: Required""" 1188 1189 # Namespace constraint and process contents are required parameters. 1190 nsc = kw['namespace_constraint'] 1191 if isinstance(nsc, tuple): 1192 nsc = (nsc[0], self.__normalizeNamespace(nsc[1])) 1193 elif isinstance(nsc, set): 1194 nsc = set([ self.__normalizeNamespace(_uri) for _uri in nsc ]) 1195 self.__namespaceConstraint = nsc 1196 self.__processContents = kw['process_contents'] 1197 super(Wildcard, self).__init__()
1198
1199 - def matches (self, instance, value):
1200 """Return True iff the value is a valid match against this wildcard. 1201 1202 Validation per U{Wildcard allows Namespace Name<http://www.w3.org/TR/xmlschema-1/#cvc-wildcard-namespace>}. 1203 """ 1204 1205 ns = None 1206 if isinstance(value, xml.dom.Node): 1207 if value.namespaceURI is not None: 1208 ns = pyxb.namespace.NamespaceForURI(value.namespaceURI) 1209 elif isinstance(value, basis._TypeBinding_mixin): 1210 elt = value._element() 1211 if elt is not None: 1212 ns = elt.name().namespace() 1213 else: 1214 ns = value._ExpandedName.namespace() 1215 else: 1216 # Assume that somebody will handle the conversion to xs:anyType 1217 pass 1218 if isinstance(ns, pyxb.namespace.Namespace) and ns.isAbsentNamespace(): 1219 ns = None 1220 if self.NC_any == self.__namespaceConstraint: 1221 return True 1222 if isinstance(self.__namespaceConstraint, tuple): 1223 (_, constrained_ns) = self.__namespaceConstraint 1224 assert self.NC_not == _ 1225 if ns is None: 1226 return False 1227 if constrained_ns == ns: 1228 return False 1229 return True 1230 return ns in self.__namespaceConstraint
1231 1232 ## Local Variables: 1233 ## fill-column:78 1234 ## End: 1235