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