1# This program is free software; you can redistribute it and/or modify it under 2# the terms of the (LGPL) GNU Lesser General Public License as published by the 3# Free Software Foundation; either version 3 of the License, or (at your 4# option) any later version. 5# 6# This program is distributed in the hope that it will be useful, but WITHOUT 7# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 8# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License 9# for more details at ( http://www.gnu.org/licenses/lgpl.html ). 10# 11# You should have received a copy of the GNU Lesser General Public License 12# along with this program; if not, write to the Free Software Foundation, Inc., 13# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 14# written by: Jeff Ortel ( jortel@redhat.com ) 15 16"""Classes representing I{basic} XSD schema objects.""" 17 18from suds import * 19from suds.reader import DocumentReader 20from suds.sax import Namespace 21from suds.transport import TransportError 22from suds.xsd import * 23from suds.xsd.query import * 24from suds.xsd.sxbase import * 25 26from urllib.parse import urljoin 27 28from logging import getLogger 29log = getLogger(__name__) 30 31 32class RestrictionMatcher: 33 """For use with L{NodeFinder} to match restriction.""" 34 def match(self, n): 35 return isinstance(n, Restriction) 36 37 38class TypedContent(Content): 39 """Represents any I{typed} content.""" 40 41 def __init__(self, *args, **kwargs): 42 Content.__init__(self, *args, **kwargs) 43 self.resolved_cache = {} 44 45 def resolve(self, nobuiltin=False): 46 """ 47 Resolve the node's type reference and return the referenced type node. 48 49 Returns self if the type is defined locally, e.g. as a <complexType> 50 subnode. Otherwise returns the referenced external node. 51 52 @param nobuiltin: Flag indicating whether resolving to XSD built-in 53 types should not be allowed. 54 @return: The resolved (true) type. 55 @rtype: L{SchemaObject} 56 57 """ 58 cached = self.resolved_cache.get(nobuiltin) 59 if cached is not None: 60 return cached 61 resolved = self.__resolve_type(nobuiltin) 62 self.resolved_cache[nobuiltin] = resolved 63 return resolved 64 65 def __resolve_type(self, nobuiltin=False): 66 """ 67 Private resolve() worker without any result caching. 68 69 @param nobuiltin: Flag indicating whether resolving to XSD built-in 70 types should not be allowed. 71 @return: The resolved (true) type. 72 @rtype: L{SchemaObject} 73 74 """ 75 # There is no need for a recursive implementation here since a node can 76 # reference an external type node but XSD specification explicitly 77 # states that that external node must not be a reference to yet another 78 # node. 79 qref = self.qref() 80 if qref is None: 81 return self 82 query = TypeQuery(qref) 83 query.history = [self] 84 log.debug("%s, resolving: %s\n using:%s", self.id, qref, query) 85 resolved = query.execute(self.schema) 86 if resolved is None: 87 log.debug(self.schema) 88 raise TypeNotFound(qref) 89 if resolved.builtin() and nobuiltin: 90 return self 91 return resolved 92 93 def qref(self): 94 """ 95 Get the I{type} qualified reference to the referenced XSD type. 96 97 This method takes into account simple types defined through restriction 98 which are detected by determining that self is simple (len == 0) and by 99 finding a restriction child. 100 101 @return: The I{type} qualified reference. 102 @rtype: qref 103 104 """ 105 qref = self.type 106 if qref is None and len(self) == 0: 107 ls = [] 108 m = RestrictionMatcher() 109 finder = NodeFinder(m, 1) 110 finder.find(self, ls) 111 if ls: 112 return ls[0].ref 113 return qref 114 115 116class Complex(SchemaObject): 117 """ 118 Represents an XSD schema <xsd:complexType/> node. 119 120 @cvar childtags: A list of valid child node names. 121 @type childtags: (I{str},...) 122 123 """ 124 125 def childtags(self): 126 return ("all", "any", "attribute", "attributeGroup", "choice", 127 "complexContent", "group", "sequence", "simpleContent") 128 129 def description(self): 130 return ("name",) 131 132 def extension(self): 133 for c in self.rawchildren: 134 if c.extension(): 135 return True 136 return False 137 138 def mixed(self): 139 for c in self.rawchildren: 140 if isinstance(c, SimpleContent) and c.mixed(): 141 return True 142 return False 143 144 145class Group(SchemaObject): 146 """ 147 Represents an XSD schema <xsd:group/> node. 148 149 @cvar childtags: A list of valid child node names. 150 @type childtags: (I{str},...) 151 152 """ 153 154 def childtags(self): 155 return "all", "choice", "sequence" 156 157 def dependencies(self): 158 deps = [] 159 midx = None 160 if self.ref is not None: 161 query = GroupQuery(self.ref) 162 g = query.execute(self.schema) 163 if g is None: 164 log.debug(self.schema) 165 raise TypeNotFound(self.ref) 166 deps.append(g) 167 midx = 0 168 return midx, deps 169 170 def merge(self, other): 171 SchemaObject.merge(self, other) 172 self.rawchildren = other.rawchildren 173 174 def description(self): 175 return "name", "ref" 176 177 178class AttributeGroup(SchemaObject): 179 """ 180 Represents an XSD schema <xsd:attributeGroup/> node. 181 182 @cvar childtags: A list of valid child node names. 183 @type childtags: (I{str},...) 184 185 """ 186 187 def childtags(self): 188 return "attribute", "attributeGroup" 189 190 def dependencies(self): 191 deps = [] 192 midx = None 193 if self.ref is not None: 194 query = AttrGroupQuery(self.ref) 195 ag = query.execute(self.schema) 196 if ag is None: 197 log.debug(self.schema) 198 raise TypeNotFound(self.ref) 199 deps.append(ag) 200 midx = 0 201 return midx, deps 202 203 def merge(self, other): 204 SchemaObject.merge(self, other) 205 self.rawchildren = other.rawchildren 206 207 def description(self): 208 return "name", "ref" 209 210 211class Simple(SchemaObject): 212 """Represents an XSD schema <xsd:simpleType/> node.""" 213 214 def childtags(self): 215 return "any", "list", "restriction" 216 217 def enum(self): 218 for child, ancestry in self.children(): 219 if isinstance(child, Enumeration): 220 return True 221 return False 222 223 def mixed(self): 224 return len(self) 225 226 def description(self): 227 return ("name",) 228 229 def extension(self): 230 for c in self.rawchildren: 231 if c.extension(): 232 return True 233 return False 234 235 def restriction(self): 236 for c in self.rawchildren: 237 if c.restriction(): 238 return True 239 return False 240 241 242class List(SchemaObject): 243 """Represents an XSD schema <xsd:list/> node.""" 244 245 def childtags(self): 246 return () 247 248 def description(self): 249 return ("name",) 250 251 def xslist(self): 252 return True 253 254 255class Restriction(SchemaObject): 256 """Represents an XSD schema <xsd:restriction/> node.""" 257 258 def __init__(self, schema, root): 259 SchemaObject.__init__(self, schema, root) 260 self.ref = root.get("base") 261 262 def childtags(self): 263 return "attribute", "attributeGroup", "enumeration" 264 265 def dependencies(self): 266 deps = [] 267 midx = None 268 if self.ref is not None: 269 query = TypeQuery(self.ref) 270 super = query.execute(self.schema) 271 if super is None: 272 log.debug(self.schema) 273 raise TypeNotFound(self.ref) 274 if not super.builtin(): 275 deps.append(super) 276 midx = 0 277 return midx, deps 278 279 def restriction(self): 280 return True 281 282 def merge(self, other): 283 SchemaObject.merge(self, other) 284 filter = Filter(False, self.rawchildren) 285 self.prepend(self.rawchildren, other.rawchildren, filter) 286 287 def description(self): 288 return ("ref",) 289 290 291class Collection(SchemaObject): 292 """Represents an XSD schema collection (a.k.a. order indicator) node.""" 293 294 def childtags(self): 295 return "all", "any", "choice", "element", "group", "sequence" 296 297 298class All(Collection): 299 """Represents an XSD schema <xsd:all/> node.""" 300 def all(self): 301 return True 302 303 304class Choice(Collection): 305 """Represents an XSD schema <xsd:choice/> node.""" 306 def choice(self): 307 return True 308 309 310class Sequence(Collection): 311 """Represents an XSD schema <xsd:sequence/> node.""" 312 def sequence(self): 313 return True 314 315 316class ComplexContent(SchemaObject): 317 """Represents an XSD schema <xsd:complexContent/> node.""" 318 319 def childtags(self): 320 return "attribute", "attributeGroup", "extension", "restriction" 321 322 def extension(self): 323 for c in self.rawchildren: 324 if c.extension(): 325 return True 326 return False 327 328 def restriction(self): 329 for c in self.rawchildren: 330 if c.restriction(): 331 return True 332 return False 333 334 335class SimpleContent(SchemaObject): 336 """Represents an XSD schema <xsd:simpleContent/> node.""" 337 338 def childtags(self): 339 return "extension", "restriction" 340 341 def extension(self): 342 for c in self.rawchildren: 343 if c.extension(): 344 return True 345 return False 346 347 def restriction(self): 348 for c in self.rawchildren: 349 if c.restriction(): 350 return True 351 return False 352 353 def mixed(self): 354 return len(self) 355 356 357class Enumeration(Content): 358 """Represents an XSD schema <xsd:enumeration/> node.""" 359 360 def __init__(self, schema, root): 361 Content.__init__(self, schema, root) 362 self.name = root.get("value") 363 364 def description(self): 365 return ("name",) 366 367 def enum(self): 368 return True 369 370 371class Element(TypedContent): 372 """Represents an XSD schema <xsd:element/> node.""" 373 374 def __init__(self, schema, root): 375 TypedContent.__init__(self, schema, root) 376 is_reference = self.ref is not None 377 is_top_level = root.parent is schema.root 378 if is_reference or is_top_level: 379 self.form_qualified = True 380 else: 381 form = root.get("form") 382 if form is not None: 383 self.form_qualified = (form == "qualified") 384 nillable = self.root.get("nillable") 385 if nillable is not None: 386 self.nillable = (nillable in ("1", "true")) 387 self.implany() 388 389 def implany(self): 390 """ 391 Set the type to <xsd:any/> when implicit. 392 393 An element has an implicit <xsd:any/> type when it has no body and no 394 explicitly defined type. 395 396 @return: self 397 @rtype: L{Element} 398 399 """ 400 if self.type is None and self.ref is None and self.root.isempty(): 401 self.type = self.anytype() 402 403 def childtags(self): 404 return "any", "attribute", "complexType", "simpleType" 405 406 def extension(self): 407 for c in self.rawchildren: 408 if c.extension(): 409 return True 410 return False 411 412 def restriction(self): 413 for c in self.rawchildren: 414 if c.restriction(): 415 return True 416 return False 417 418 def dependencies(self): 419 deps = [] 420 midx = None 421 e = self.__deref() 422 if e is not None: 423 deps.append(e) 424 midx = 0 425 return midx, deps 426 427 def merge(self, other): 428 SchemaObject.merge(self, other) 429 self.rawchildren = other.rawchildren 430 431 def description(self): 432 return "name", "ref", "type" 433 434 def anytype(self): 435 """Create an xsd:anyType reference.""" 436 p, u = Namespace.xsdns 437 mp = self.root.findPrefix(u) 438 if mp is None: 439 mp = p 440 self.root.addPrefix(p, u) 441 return ":".join((mp, "anyType")) 442 443 def namespace(self, prefix=None): 444 """ 445 Get this schema element's target namespace. 446 447 In case of reference elements, the target namespace is defined by the 448 referenced and not the referencing element node. 449 450 @param prefix: The default prefix. 451 @type prefix: str 452 @return: The schema element's target namespace 453 @rtype: (I{prefix},I{URI}) 454 455 """ 456 e = self.__deref() 457 if e is not None: 458 return e.namespace(prefix) 459 return super(Element, self).namespace() 460 461 def __deref(self): 462 if self.ref is None: 463 return 464 query = ElementQuery(self.ref) 465 e = query.execute(self.schema) 466 if e is None: 467 log.debug(self.schema) 468 raise TypeNotFound(self.ref) 469 return e 470 471 472class Extension(SchemaObject): 473 """Represents an XSD schema <xsd:extension/> node.""" 474 475 def __init__(self, schema, root): 476 SchemaObject.__init__(self, schema, root) 477 self.ref = root.get("base") 478 479 def childtags(self): 480 return ("all", "attribute", "attributeGroup", "choice", "group", 481 "sequence") 482 483 def dependencies(self): 484 deps = [] 485 midx = None 486 if self.ref is not None: 487 query = TypeQuery(self.ref) 488 super = query.execute(self.schema) 489 if super is None: 490 log.debug(self.schema) 491 raise TypeNotFound(self.ref) 492 if not super.builtin(): 493 deps.append(super) 494 midx = 0 495 return midx, deps 496 497 def merge(self, other): 498 SchemaObject.merge(self, other) 499 filter = Filter(False, self.rawchildren) 500 self.prepend(self.rawchildren, other.rawchildren, filter) 501 502 def extension(self): 503 return self.ref is not None 504 505 def description(self): 506 return ("ref",) 507 508 509class Import(SchemaObject): 510 """ 511 Represents an XSD schema <xsd:import/> node. 512 513 @cvar locations: A dictionary of namespace locations. 514 @type locations: dict 515 @ivar ns: The imported namespace. 516 @type ns: str 517 @ivar location: The (optional) location. 518 @type location: namespace-uri 519 @ivar opened: Opened and I{imported} flag. 520 @type opened: boolean 521 522 """ 523 524 locations = {} 525 526 @classmethod 527 def bind(cls, ns, location=None): 528 """ 529 Bind a namespace to a schema location (URI). 530 531 This is used for imports that do not specify a schemaLocation. 532 533 @param ns: A namespace-uri. 534 @type ns: str 535 @param location: The (optional) schema location for the namespace. 536 (default=ns) 537 @type location: str 538 539 """ 540 if location is None: 541 location = ns 542 cls.locations[ns] = location 543 544 def __init__(self, schema, root): 545 SchemaObject.__init__(self, schema, root) 546 self.ns = (None, root.get("namespace")) 547 self.location = root.get("schemaLocation") 548 if self.location is None: 549 self.location = self.locations.get(self.ns[1]) 550 self.opened = False 551 552 def open(self, options, loaded_schemata): 553 """ 554 Open and import the referenced schema. 555 556 @param options: An options dictionary. 557 @type options: L{options.Options} 558 @param loaded_schemata: Already loaded schemata cache (URL --> Schema). 559 @type loaded_schemata: dict 560 @return: The referenced schema. 561 @rtype: L{Schema} 562 563 """ 564 if self.opened: 565 return 566 self.opened = True 567 log.debug("%s, importing ns='%s', location='%s'", self.id, self.ns[1], 568 self.location) 569 result = self.__locate() 570 if result is None: 571 if self.location is None: 572 log.debug("imported schema (%s) not-found", self.ns[1]) 573 else: 574 url = self.location 575 if "://" not in url: 576 url = urljoin(self.schema.baseurl, url) 577 result = (loaded_schemata.get(url) or 578 self.__download(url, loaded_schemata, options)) 579 log.debug("imported:\n%s", result) 580 return result 581 582 def __locate(self): 583 """Find the schema locally.""" 584 if self.ns[1] != self.schema.tns[1]: 585 return self.schema.locate(self.ns) 586 587 def __download(self, url, loaded_schemata, options): 588 """Download the schema.""" 589 try: 590 reader = DocumentReader(options) 591 d = reader.open(url) 592 root = d.root() 593 root.set("url", url) 594 return self.schema.instance(root, url, loaded_schemata, options) 595 except TransportError: 596 msg = "import schema (%s) at (%s), failed" % (self.ns[1], url) 597 log.error("%s, %s", self.id, msg, exc_info=True) 598 raise Exception(msg) 599 600 def description(self): 601 return "ns", "location" 602 603 604class Include(SchemaObject): 605 """ 606 Represents an XSD schema <xsd:include/> node. 607 608 @ivar location: The (optional) location. 609 @type location: namespace-uri 610 @ivar opened: Opened and I{imported} flag. 611 @type opened: boolean 612 613 """ 614 615 locations = {} 616 617 def __init__(self, schema, root): 618 SchemaObject.__init__(self, schema, root) 619 self.location = root.get("schemaLocation") 620 if self.location is None: 621 self.location = self.locations.get(self.ns[1]) 622 self.opened = False 623 624 def open(self, options, loaded_schemata): 625 """ 626 Open and include the referenced schema. 627 628 @param options: An options dictionary. 629 @type options: L{options.Options} 630 @param loaded_schemata: Already loaded schemata cache (URL --> Schema). 631 @type loaded_schemata: dict 632 @return: The referenced schema. 633 @rtype: L{Schema} 634 635 """ 636 if self.opened: 637 return 638 self.opened = True 639 log.debug("%s, including location='%s'", self.id, self.location) 640 url = self.location 641 if "://" not in url: 642 url = urljoin(self.schema.baseurl, url) 643 result = (loaded_schemata.get(url) or 644 self.__download(url, loaded_schemata, options)) 645 log.debug("included:\n%s", result) 646 return result 647 648 def __download(self, url, loaded_schemata, options): 649 """Download the schema.""" 650 try: 651 reader = DocumentReader(options) 652 d = reader.open(url) 653 root = d.root() 654 root.set("url", url) 655 self.__applytns(root) 656 return self.schema.instance(root, url, loaded_schemata, options) 657 except TransportError: 658 msg = "include schema at (%s), failed" % url 659 log.error("%s, %s", self.id, msg, exc_info=True) 660 raise Exception(msg) 661 662 def __applytns(self, root): 663 """Make sure included schema has the same target namespace.""" 664 TNS = "targetNamespace" 665 tns = root.get(TNS) 666 if tns is None: 667 tns = self.schema.tns[1] 668 root.set(TNS, tns) 669 else: 670 if self.schema.tns[1] != tns: 671 raise Exception("%s mismatch" % TNS) 672 673 def description(self): 674 return "location" 675 676 677class Attribute(TypedContent): 678 """Represents an XSD schema <attribute/> node.""" 679 680 def __init__(self, schema, root): 681 TypedContent.__init__(self, schema, root) 682 self.use = root.get("use", default="") 683 684 def childtags(self): 685 return ("restriction",) 686 687 def isattr(self): 688 return True 689 690 def get_default(self): 691 """ 692 Gets the <xsd:attribute default=""/> attribute value. 693 694 @return: The default value for the attribute 695 @rtype: str 696 697 """ 698 return self.root.get("default", default="") 699 700 def optional(self): 701 return self.use != "required" 702 703 def dependencies(self): 704 deps = [] 705 midx = None 706 if self.ref is not None: 707 query = AttrQuery(self.ref) 708 a = query.execute(self.schema) 709 if a is None: 710 log.debug(self.schema) 711 raise TypeNotFound(self.ref) 712 deps.append(a) 713 midx = 0 714 return midx, deps 715 716 def description(self): 717 return "name", "ref", "type" 718 719 720class Any(Content): 721 """Represents an XSD schema <any/> node.""" 722 723 def get_child(self, name): 724 root = self.root.clone() 725 root.set("note", "synthesized (any) child") 726 child = Any(self.schema, root) 727 return child, [] 728 729 def get_attribute(self, name): 730 root = self.root.clone() 731 root.set("note", "synthesized (any) attribute") 732 attribute = Any(self.schema, root) 733 return attribute, [] 734 735 def any(self): 736 return True 737 738 739class Factory: 740 """ 741 @cvar tags: A factory to create object objects based on tag. 742 @type tags: {tag:fn,} 743 744 """ 745 746 tags = { 747 "all": All, 748 "any": Any, 749 "attribute": Attribute, 750 "attributeGroup": AttributeGroup, 751 "choice": Choice, 752 "complexContent": ComplexContent, 753 "complexType": Complex, 754 "element": Element, 755 "enumeration": Enumeration, 756 "extension": Extension, 757 "group": Group, 758 "import": Import, 759 "include": Include, 760 "list": List, 761 "restriction": Restriction, 762 "simpleContent": SimpleContent, 763 "simpleType": Simple, 764 "sequence": Sequence, 765 } 766 767 @classmethod 768 def maptag(cls, tag, fn): 769 """ 770 Map (override) tag => I{class} mapping. 771 772 @param tag: An XSD tag name. 773 @type tag: str 774 @param fn: A function or class. 775 @type fn: fn|class. 776 777 """ 778 cls.tags[tag] = fn 779 780 @classmethod 781 def create(cls, root, schema): 782 """ 783 Create an object based on the root tag name. 784 785 @param root: An XML root element. 786 @type root: L{Element} 787 @param schema: A schema object. 788 @type schema: L{schema.Schema} 789 @return: The created object. 790 @rtype: L{SchemaObject} 791 792 """ 793 fn = cls.tags.get(root.name) 794 if fn is not None: 795 return fn(schema, root) 796 797 @classmethod 798 def build(cls, root, schema, filter=("*",)): 799 """ 800 Build an xsobject representation. 801 802 @param root: An schema XML root. 803 @type root: L{sax.element.Element} 804 @param filter: A tag filter. 805 @type filter: [str,...] 806 @return: A schema object graph. 807 @rtype: L{sxbase.SchemaObject} 808 809 """ 810 children = [] 811 for node in root.getChildren(ns=Namespace.xsdns): 812 if "*" in filter or node.name in filter: 813 child = cls.create(node, schema) 814 if child is None: 815 continue 816 children.append(child) 817 c = cls.build(node, schema, child.childtags()) 818 child.rawchildren = c 819 return children 820 821 @classmethod 822 def collate(cls, children): 823 imports = [] 824 elements = {} 825 attributes = {} 826 types = {} 827 groups = {} 828 agrps = {} 829 for c in children: 830 if isinstance(c, (Import, Include)): 831 imports.append(c) 832 continue 833 if isinstance(c, Attribute): 834 attributes[c.qname] = c 835 continue 836 if isinstance(c, Element): 837 elements[c.qname] = c 838 continue 839 if isinstance(c, Group): 840 groups[c.qname] = c 841 continue 842 if isinstance(c, AttributeGroup): 843 agrps[c.qname] = c 844 continue 845 types[c.qname] = c 846 for i in imports: 847 children.remove(i) 848 return children, imports, attributes, elements, types, groups, agrps 849 850 851####################################################### 852# Static Import Bindings :-( 853####################################################### 854Import.bind( 855 "http://schemas.xmlsoap.org/soap/encoding/", 856 "suds://schemas.xmlsoap.org/soap/encoding/") 857Import.bind( 858 "http://www.w3.org/XML/1998/namespace", 859 "http://www.w3.org/2001/xml.xsd") 860Import.bind( 861 "http://www.w3.org/2001/XMLSchema", 862 "http://www.w3.org/2001/XMLSchema.xsd") 863