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