1# This program is free software; you can redistribute it and/or modify 2# it under the terms of the (LGPL) GNU Lesser General Public License as 3# published by the Free Software Foundation; either version 3 of the 4# License, or (at your option) any later version. 5# 6# This program is distributed in the hope that it will be useful, 7# but WITHOUT ANY WARRANTY; without even the implied warranty of 8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9# GNU Library Lesser General Public License for more details at 10# ( http://www.gnu.org/licenses/lgpl.html ). 11# 12# You should have received a copy of the GNU Lesser General Public License 13# along with this program; if not, write to the Free Software 14# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15# written by: Jeff Ortel ( jortel@redhat.com ) 16 17""" 18The I{sxbase} module provides I{base} classes representing schema objects. 19""" 20 21from suds import * 22from suds.xsd import * 23from suds.sax.element import Element 24from suds.sax import Namespace 25 26from logging import getLogger 27log = getLogger(__name__) 28 29 30class SchemaObject(UnicodeMixin): 31 """ 32 A schema object is an extension to object with schema awareness. 33 @ivar root: The XML root element. 34 @type root: L{Element} 35 @ivar schema: The schema containing this object. 36 @type schema: L{schema.Schema} 37 @ivar form_qualified: A flag that indicates that @elementFormDefault 38 has a value of I{qualified}. 39 @type form_qualified: boolean 40 @ivar nillable: A flag that indicates that @nillable 41 has a value of I{true}. 42 @type nillable: boolean 43 @ivar default: The default value. 44 @type default: object 45 @ivar rawchildren: A list raw of all children. 46 @type rawchildren: [L{SchemaObject},...] 47 """ 48 49 @classmethod 50 def prepend(cls, d, s, filter=Filter()): 51 """ 52 Prepend schema objects from B{s}ource list to 53 the B{d}estination list while applying the filter. 54 @param d: The destination list. 55 @type d: list 56 @param s: The source list. 57 @type s: list 58 @param filter: A filter that allows items to be prepended. 59 @type filter: L{Filter} 60 """ 61 i = 0 62 for x in s: 63 if x in filter: 64 d.insert(i, x) 65 i += 1 66 67 @classmethod 68 def append(cls, d, s, filter=Filter()): 69 """ 70 Append schema objects from B{s}ource list to 71 the B{d}estination list while applying the filter. 72 @param d: The destination list. 73 @type d: list 74 @param s: The source list. 75 @type s: list 76 @param filter: A filter that allows items to be appended. 77 @type filter: L{Filter} 78 """ 79 for item in s: 80 if item in filter: 81 d.append(item) 82 83 def __init__(self, schema, root): 84 """ 85 @param schema: The containing schema. 86 @type schema: L{schema.Schema} 87 @param root: The XML root node. 88 @type root: L{Element} 89 """ 90 self.schema = schema 91 self.root = root 92 self.id = objid(self) 93 self.name = root.get('name') 94 self.qname = (self.name, schema.tns[1]) 95 self.min = root.get('minOccurs') 96 self.max = root.get('maxOccurs') 97 self.type = root.get('type') 98 self.ref = root.get('ref') 99 self.form_qualified = schema.form_qualified 100 self.nillable = False 101 self.default = root.get('default') 102 self.rawchildren = [] 103 104 def attributes(self, filter=Filter()): 105 """ 106 Get only the attribute content. 107 @param filter: A filter to constrain the result. 108 @type filter: L{Filter} 109 @return: A list of tuples (attr, ancestry) 110 @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..] 111 """ 112 result = [] 113 for child, ancestry in self: 114 if child.isattr() and child in filter: 115 result.append((child, ancestry)) 116 return result 117 118 def children(self, filter=Filter()): 119 """ 120 Get only the I{direct} or non-attribute content. 121 @param filter: A filter to constrain the result. 122 @type filter: L{Filter} 123 @return: A list tuples: (child, ancestry) 124 @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..] 125 """ 126 result = [] 127 for child, ancestry in self: 128 if not child.isattr() and child in filter: 129 result.append((child, ancestry)) 130 return result 131 132 def get_attribute(self, name): 133 """ 134 Get (find) an attribute by name. 135 @param name: A attribute name. 136 @type name: str 137 @return: A tuple: the requested (attribute, ancestry). 138 @rtype: (L{SchemaObject}, [L{SchemaObject},..]) 139 """ 140 for child, ancestry in self.attributes(): 141 if child.name == name: 142 return child, ancestry 143 return None, [] 144 145 def get_child(self, name): 146 """ 147 Get (find) a I{non-attribute} child by name. 148 @param name: A child name. 149 @type name: str 150 @return: A tuple: the requested (child, ancestry). 151 @rtype: (L{SchemaObject}, [L{SchemaObject},..]) 152 """ 153 for child, ancestry in self.children(): 154 if child.any() or child.name == name: 155 return child, ancestry 156 return None, [] 157 158 def namespace(self, prefix=None): 159 """ 160 Get this property's namespace. 161 @param prefix: The default prefix. 162 @type prefix: str 163 @return: The schema's target namespace 164 @rtype: (I{prefix},I{URI}) 165 """ 166 ns = self.schema.tns 167 if ns[0] is None: 168 ns = (prefix, ns[1]) 169 return ns 170 171 def default_namespace(self): 172 return self.root.defaultNamespace() 173 174 def multi_occurrence(self): 175 """ 176 Get whether the node has multiple occurrences, i.e. is a I{collection}. 177 @return: True if it has, False if it has 1 occurrence at most. 178 @rtype: boolean 179 """ 180 max = self.max 181 if max is None: 182 return False 183 if max.isdigit(): 184 return int(max) > 1 185 return max == 'unbounded' 186 187 def optional(self): 188 """ 189 Get whether this type is optional. 190 @return: True if optional, else False 191 @rtype: boolean 192 """ 193 return self.min == '0' 194 195 def required(self): 196 """ 197 Get whether this type is required. 198 @return: True if required, else False 199 @rtype: boolean 200 """ 201 return not self.optional() 202 203 def resolve(self, nobuiltin=False): 204 """ 205 Resolve the node's type reference and return the referenced type node. 206 207 Only schema objects that actually support 'having a type' custom 208 implement this interface while others simply resolve as themselves. 209 @param nobuiltin: Flag indicating whether resolving to an external XSD 210 builtin type should not be allowed. 211 @return: The resolved (true) type. 212 @rtype: L{SchemaObject} 213 """ 214 return self 215 216 def sequence(self): 217 """ 218 Get whether this is a <xs:sequence/>. 219 @return: True if <xs:sequence/>, else False 220 @rtype: boolean 221 """ 222 return False 223 224 def xslist(self): 225 """ 226 Get whether this is a <xs:list/>. 227 @return: True if any, else False 228 @rtype: boolean 229 """ 230 return False 231 232 def all(self): 233 """ 234 Get whether this is an <xs:all/>. 235 @return: True if any, else False 236 @rtype: boolean 237 """ 238 return False 239 240 def choice(self): 241 """ 242 Get whether this is a <xs:choice/>. 243 @return: True if any, else False 244 @rtype: boolean 245 """ 246 return False 247 248 def any(self): 249 """ 250 Get whether this is an <xs:any/>. 251 @return: True if any, else False 252 @rtype: boolean 253 """ 254 return False 255 256 def builtin(self): 257 """ 258 Get whether this is a schema-instance (xs) type. 259 @return: True if any, else False 260 @rtype: boolean 261 """ 262 return False 263 264 def enum(self): 265 """ 266 Get whether this is a simple-type containing an enumeration. 267 @return: True if any, else False 268 @rtype: boolean 269 """ 270 return False 271 272 def isattr(self): 273 """ 274 Get whether the object is a schema I{attribute} definition. 275 @return: True if an attribute, else False. 276 @rtype: boolean 277 """ 278 return False 279 280 def extension(self): 281 """ 282 Get whether the object is an extension of another type. 283 @return: True if an extension, else False. 284 @rtype: boolean 285 """ 286 return False 287 288 def restriction(self): 289 """ 290 Get whether the object is an restriction of another type. 291 @return: True if an restriction, else False. 292 @rtype: boolean 293 """ 294 return False 295 296 def mixed(self): 297 """ 298 Get whether this I{mixed} content. 299 """ 300 return False 301 302 def find(self, qref, classes=()): 303 """ 304 Find a referenced type in self or children. 305 @param qref: A qualified reference. 306 @type qref: qref 307 @param classes: A list of classes used to qualify the match. 308 @type classes: [I{class},...] 309 @return: The referenced type. 310 @rtype: L{SchemaObject} 311 @see: L{qualify()} 312 """ 313 if not len(classes): 314 classes = (self.__class__,) 315 if self.qname == qref and self.__class__ in classes: 316 return self 317 for c in self.rawchildren: 318 p = c.find(qref, classes) 319 if p is not None: 320 return p 321 return None 322 323 def translate(self, value, topython=True): 324 """ 325 Translate a value (type) to/from a Python type. 326 @param value: A value to translate. 327 @return: The converted I{language} type. 328 """ 329 return value 330 331 def childtags(self): 332 """ 333 Get a list of valid child tag names. 334 @return: A list of child tag names. 335 @rtype: [str,...] 336 """ 337 return () 338 339 def dependencies(self): 340 """ 341 Get a list of dependencies for dereferencing. 342 @return: A merge dependency index and a list of dependencies. 343 @rtype: (int, [L{SchemaObject},...]) 344 """ 345 return None, [] 346 347 def autoqualified(self): 348 """ 349 The list of I{auto} qualified attribute values. 350 Qualification means to convert values into I{qref}. 351 @return: A list of attibute names. 352 @rtype: list 353 """ 354 return ['type', 'ref'] 355 356 def qualify(self): 357 """ 358 Convert attribute values, that are references to other 359 objects, into I{qref}. Qualified using the default document namespace. 360 Since many WSDLs are written improperly: when the document does 361 not define its default namespace, the schema target namespace is used 362 to qualify references. 363 """ 364 defns = self.root.defaultNamespace() 365 if Namespace.none(defns): 366 defns = self.schema.tns 367 for a in self.autoqualified(): 368 ref = getattr(self, a) 369 if ref is None: 370 continue 371 if isqref(ref): 372 continue 373 qref = qualify(ref, self.root, defns) 374 log.debug('%s, convert %s="%s" to %s', self.id, a, ref, qref) 375 setattr(self, a, qref) 376 377 def merge(self, other): 378 """ 379 Merge another object as needed. 380 """ 381 other.qualify() 382 for n in ('name', 383 'qname', 384 'min', 385 'max', 386 'default', 387 'type', 388 'nillable', 389 'form_qualified'): 390 if getattr(self, n) is not None: 391 continue 392 v = getattr(other, n) 393 if v is None: 394 continue 395 setattr(self, n, v) 396 397 def content(self, collection=None, filter=Filter(), history=None): 398 """ 399 Get a I{flattened} list of this node's contents. 400 @param collection: A list to fill. 401 @type collection: list 402 @param filter: A filter used to constrain the result. 403 @type filter: L{Filter} 404 @param history: The history list used to prevent cyclic dependency. 405 @type history: list 406 @return: The filled list. 407 @rtype: list 408 """ 409 if collection is None: 410 collection = [] 411 if history is None: 412 history = [] 413 if self in history: 414 return collection 415 history.append(self) 416 if self in filter: 417 collection.append(self) 418 for c in self.rawchildren: 419 c.content(collection, filter, history[:]) 420 return collection 421 422 def str(self, indent=0, history=None): 423 """ 424 Get a string representation of this object. 425 @param indent: The indent. 426 @type indent: int 427 @return: A string. 428 @rtype: str 429 """ 430 if history is None: 431 history = [] 432 if self in history: 433 return '%s ...' % Repr(self) 434 history.append(self) 435 tab = '%*s'%(indent*3, '') 436 result = ['%s<%s' % (tab, self.id)] 437 for n in self.description(): 438 if not hasattr(self, n): 439 continue 440 v = getattr(self, n) 441 if v is None: 442 continue 443 result.append(' %s="%s"' % (n, v)) 444 if len(self): 445 result.append('>') 446 for c in self.rawchildren: 447 result.append('\n') 448 result.append(c.str(indent+1, history[:])) 449 if c.isattr(): 450 result.append('@') 451 result.append('\n%s' % tab) 452 result.append('</%s>' % self.__class__.__name__) 453 else: 454 result.append(' />') 455 return ''.join(result) 456 457 def description(self): 458 """ 459 Get the names used for str() and repr() description. 460 @return: A dictionary of relevant attributes. 461 @rtype: [str,...] 462 """ 463 return () 464 465 def __unicode__(self): 466 return unicode(self.str()) 467 468 def __repr__(self): 469 s = [] 470 s.append('<%s' % self.id) 471 for n in self.description(): 472 if not hasattr(self, n): 473 continue 474 v = getattr(self, n) 475 if v is None: 476 continue 477 s.append(' %s="%s"' % (n, v)) 478 s.append(' />') 479 return ''.join(s) 480 481 def __len__(self): 482 n = 0 483 for x in self: n += 1 484 return n 485 486 def __iter__(self): 487 return Iter(self) 488 489 def __getitem__(self, index): 490 """Returns a contained schema object referenced by its 0-based index. 491 492 Returns None if such an object does not exist. 493 494 """ 495 i = 0 496 for c in self: 497 if i == index: 498 return c 499 i += 1 500 501 502class Iter: 503 """ 504 The content iterator - used to iterate the L{Content} children. The 505 iterator provides a I{view} of the children that is free of container 506 elements such as <sequence/> and <choice/>. 507 @ivar stack: A stack used to control nesting. 508 @type stack: list 509 """ 510 511 class Frame: 512 """ A content iterator frame. """ 513 514 def __init__(self, sx): 515 """ 516 @param sx: A schema object. 517 @type sx: L{SchemaObject} 518 """ 519 self.sx = sx 520 self.items = sx.rawchildren 521 self.index = 0 522 523 def next(self): 524 """ 525 Get the I{next} item in the frame's collection. 526 @return: The next item or None 527 @rtype: L{SchemaObject} 528 """ 529 if self.index < len(self.items): 530 result = self.items[self.index] 531 self.index += 1 532 return result 533 534 def __init__(self, sx): 535 """ 536 @param sx: A schema object. 537 @type sx: L{SchemaObject} 538 """ 539 self.stack = [] 540 self.push(sx) 541 542 def push(self, sx): 543 """ 544 Create a frame and push the specified object. 545 @param sx: A schema object to push. 546 @type sx: L{SchemaObject} 547 """ 548 self.stack.append(Iter.Frame(sx)) 549 550 def pop(self): 551 """ 552 Pop the I{top} frame. 553 @return: The popped frame. 554 @rtype: L{Frame} 555 @raise StopIteration: when stack is empty. 556 """ 557 if len(self.stack): 558 return self.stack.pop() 559 else: 560 raise StopIteration() 561 562 def top(self): 563 """ 564 Get the I{top} frame. 565 @return: The top frame. 566 @rtype: L{Frame} 567 @raise StopIteration: when stack is empty. 568 """ 569 if len(self.stack): 570 return self.stack[-1] 571 else: 572 raise StopIteration() 573 574 def next(self): 575 """ 576 Get the next item. 577 @return: A tuple: the next (child, ancestry). 578 @rtype: (L{SchemaObject}, [L{SchemaObject},..]) 579 @raise StopIteration: A the end. 580 """ 581 frame = self.top() 582 while True: 583 result = frame.next() 584 if result is None: 585 self.pop() 586 return self.next() 587 if isinstance(result, Content): 588 ancestry = [f.sx for f in self.stack] 589 return result, ancestry 590 self.push(result) 591 return self.next() 592 593 def __iter__(self): 594 return self 595 596 597class XBuiltin(SchemaObject): 598 """ 599 Represents an (XSD) schema <xs:*/> node. 600 """ 601 602 def __init__(self, schema, name): 603 """ 604 @param schema: The containing schema. 605 @type schema: L{schema.Schema} 606 """ 607 root = Element(name) 608 SchemaObject.__init__(self, schema, root) 609 self.name = name 610 self.nillable = True 611 612 def namespace(self, prefix=None): 613 return Namespace.xsdns 614 615 def builtin(self): 616 return True 617 618 619class Content(SchemaObject): 620 """ 621 This class represents those schema objects that represent 622 real XML document content. 623 """ 624 pass 625 626 627class NodeFinder: 628 """ 629 Find nodes based on flexable criteria. The I{matcher} 630 may be any object that implements a match(n) method. 631 @ivar matcher: An object used as criteria for match. 632 @type matcher: I{any}.match(n) 633 @ivar limit: Limit the number of matches. 0=unlimited. 634 @type limit: int 635 """ 636 def __init__(self, matcher, limit=0): 637 """ 638 @param matcher: An object used as criteria for match. 639 @type matcher: I{any}.match(n) 640 @param limit: Limit the number of matches. 0=unlimited. 641 @type limit: int 642 """ 643 self.matcher = matcher 644 self.limit = limit 645 646 def find(self, node, list): 647 """ 648 Traverse the tree looking for matches. 649 @param node: A node to match on. 650 @type node: L{SchemaObject} 651 @param list: A list to fill. 652 @type list: list 653 """ 654 if self.matcher.match(node): 655 list.append(node) 656 self.limit -= 1 657 if self.limit == 0: 658 return 659 for c in node.rawchildren: 660 self.find(c, list) 661 return self 662