From 353f65dc686061a288671131d3395a43b228836b Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Fri, 23 Nov 2007 14:42:22 -0800 Subject: [PATCH] import part of SOAPpy so I can make some modifications to it... [git-p4: depot-paths = "//depot/": change = 1101] --- SOAPpy/Config.py | 202 ++ SOAPpy/Errors.py | 79 + SOAPpy/NS.py | 104 + SOAPpy/Parser.py | 1024 ++++++++ SOAPpy/SOAPBuilder.py | 620 +++++ SOAPpy/Types.py | 1734 +++++++++++++ SOAPpy/Utilities.py | 178 ++ SOAPpy/__init__.py | 15 + SOAPpy/version.py | 2 + SOAPpy/wstools/Namespaces.py | 92 + SOAPpy/wstools/TimeoutSocket.py | 179 ++ SOAPpy/wstools/UserTuple.py | 99 + SOAPpy/wstools/Utility.py | 839 +++++++ SOAPpy/wstools/WSDLTools.py | 1336 ++++++++++ SOAPpy/wstools/XMLSchema.py | 2976 +++++++++++++++++++++++ SOAPpy/wstools/XMLname.py | 88 + SOAPpy/wstools/__init__.py | 36 + SOAPpy/wstools/test/__init__.py | 5 + SOAPpy/wstools/test/test_t1.py | 20 + SOAPpy/wstools/test/test_wsdl.py | 160 ++ SOAPpy/wstools/test/test_wstools.py | 37 + SOAPpy/wstools/test/test_wstools_net.py | 20 + 22 files changed, 9845 insertions(+) create mode 100644 SOAPpy/Config.py create mode 100644 SOAPpy/Errors.py create mode 100644 SOAPpy/NS.py create mode 100644 SOAPpy/Parser.py create mode 100644 SOAPpy/SOAPBuilder.py create mode 100644 SOAPpy/Types.py create mode 100644 SOAPpy/Utilities.py create mode 100644 SOAPpy/__init__.py create mode 100644 SOAPpy/version.py create mode 100755 SOAPpy/wstools/Namespaces.py create mode 100755 SOAPpy/wstools/TimeoutSocket.py create mode 100755 SOAPpy/wstools/UserTuple.py create mode 100755 SOAPpy/wstools/Utility.py create mode 100755 SOAPpy/wstools/WSDLTools.py create mode 100755 SOAPpy/wstools/XMLSchema.py create mode 100755 SOAPpy/wstools/XMLname.py create mode 100644 SOAPpy/wstools/__init__.py create mode 100644 SOAPpy/wstools/test/__init__.py create mode 100644 SOAPpy/wstools/test/test_t1.py create mode 100644 SOAPpy/wstools/test/test_wsdl.py create mode 100644 SOAPpy/wstools/test/test_wstools.py create mode 100644 SOAPpy/wstools/test/test_wstools_net.py diff --git a/SOAPpy/Config.py b/SOAPpy/Config.py new file mode 100644 index 0000000..3ec4dfe --- /dev/null +++ b/SOAPpy/Config.py @@ -0,0 +1,202 @@ +""" +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id: Config.py,v 1.9 2004/01/31 04:20:05 warnes Exp $' +from version import __version__ + +import copy, socket +from types import * + +from NS import NS + +################################################################################ +# Configuration class +################################################################################ + +class SOAPConfig: + __readonly = ('SSLserver', 'SSLclient', 'GSIserver', 'GSIclient') + + def __init__(self, config = None, **kw): + d = self.__dict__ + + if config: + if not isinstance(config, SOAPConfig): + raise AttributeError, \ + "initializer must be SOAPConfig instance" + + s = config.__dict__ + + for k, v in s.items(): + if k[0] != '_': + d[k] = v + else: + # Setting debug also sets returnFaultInfo, + # dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut + self.debug = 0 + self.dumpFaultInfo = 1 + # Setting namespaceStyle sets typesNamespace, typesNamespaceURI, + # schemaNamespace, and schemaNamespaceURI + self.namespaceStyle = '1999' + self.strictNamespaces = 0 + self.typed = 1 + self.buildWithNamespacePrefix = 1 + self.returnAllAttrs = 0 + + # Strict checking of range for floats and doubles + self.strict_range = 0 + + # Default encoding for dictionary keys + self.dict_encoding = 'ascii' + + # New argument name handling mechanism. See + # README.MethodParameterNaming for details + self.specialArgs = 1 + + # If unwrap_results=1 and there is only element in the struct, + # SOAPProxy will assume that this element is the result + # and return it rather than the struct containing it. + # Otherwise SOAPproxy will return the struct with all the + # elements as attributes. + self.unwrap_results = 1 + + # Automatically convert SOAP complex types, and + # (recursively) public contents into the corresponding + # python types. (Private subobjects have names that start + # with '_'.) + # + # Conversions: + # - faultType --> raise python exception + # - arrayType --> array + # - compoundType --> dictionary + # + self.simplify_objects = 0 + + # Per-class authorization method. If this is set, before + # calling a any class method, the specified authorization + # method will be called. If it returns 1, the method call + # will proceed, otherwise the call will throw with an + # authorization error. + self.authMethod = None + + # Globus Support if pyGlobus.io available + try: + from pyGlobus import io; + d['GSIserver'] = 1 + d['GSIclient'] = 1 + except: + d['GSIserver'] = 0 + d['GSIclient'] = 0 + + + # Server SSL support if M2Crypto.SSL available + try: + from M2Crypto import SSL + d['SSLserver'] = 1 + except: + d['SSLserver'] = 0 + + # Client SSL support if socket.ssl available + try: + from socket import ssl + d['SSLclient'] = 1 + except: + d['SSLclient'] = 0 + + for k, v in kw.items(): + if k[0] != '_': + setattr(self, k, v) + + def __setattr__(self, name, value): + if name in self.__readonly: + raise AttributeError, "readonly configuration setting" + + d = self.__dict__ + + if name in ('typesNamespace', 'typesNamespaceURI', + 'schemaNamespace', 'schemaNamespaceURI'): + + if name[-3:] == 'URI': + base, uri = name[:-3], 1 + else: + base, uri = name, 0 + + if type(value) == StringType: + if NS.NSMAP.has_key(value): + n = (value, NS.NSMAP[value]) + elif NS.NSMAP_R.has_key(value): + n = (NS.NSMAP_R[value], value) + else: + raise AttributeError, "unknown namespace" + elif type(value) in (ListType, TupleType): + if uri: + n = (value[1], value[0]) + else: + n = (value[0], value[1]) + else: + raise AttributeError, "unknown namespace type" + + d[base], d[base + 'URI'] = n + + try: + d['namespaceStyle'] = \ + NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])] + except: + d['namespaceStyle'] = '' + + elif name == 'namespaceStyle': + value = str(value) + + if not NS.STMAP.has_key(value): + raise AttributeError, "unknown namespace style" + + d[name] = value + n = d['typesNamespace'] = NS.STMAP[value][0] + d['typesNamespaceURI'] = NS.NSMAP[n] + n = d['schemaNamespace'] = NS.STMAP[value][1] + d['schemaNamespaceURI'] = NS.NSMAP[n] + + elif name == 'debug': + d[name] = \ + d['returnFaultInfo'] = \ + d['dumpHeadersIn'] = \ + d['dumpHeadersOut'] = \ + d['dumpSOAPIn'] = \ + d['dumpSOAPOut'] = value + + else: + d[name] = value + + +Config = SOAPConfig() diff --git a/SOAPpy/Errors.py b/SOAPpy/Errors.py new file mode 100644 index 0000000..88e2984 --- /dev/null +++ b/SOAPpy/Errors.py @@ -0,0 +1,79 @@ +""" +################################################################################ +# +# SOAPpy - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# Gregory Warnes (gregory_r_warnes@groton.pfizer.com) +# Christopher Blunck (blunck@gst.com) +# +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id: Errors.py,v 1.4 2004/01/31 04:20:05 warnes Exp $' +from version import __version__ + +import exceptions + +################################################################################ +# Exceptions +################################################################################ +class Error(exceptions.Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return "" % self.msg + __repr__ = __str__ + def __call__(self): + return (msg,) + +class RecursionError(Error): + pass + +class UnknownTypeError(Error): + pass + +class HTTPError(Error): + # indicates an HTTP protocol error + def __init__(self, code, msg): + self.code = code + self.msg = msg + def __str__(self): + return "" % (self.code, self.msg) + __repr__ = __str__ + def __call___(self): + return (self.code, self.msg, ) + +class UnderflowError(exceptions.ArithmeticError): + pass + diff --git a/SOAPpy/NS.py b/SOAPpy/NS.py new file mode 100644 index 0000000..658a20e --- /dev/null +++ b/SOAPpy/NS.py @@ -0,0 +1,104 @@ +""" +################################################################################ +# +# SOAPpy - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# Gregory Warnes (gregory_r_warnes@groton.pfizer.com) +# Christopher Blunck (blunck@gst.com) +# +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +from __future__ import nested_scopes + +ident = '$Id: NS.py,v 1.3 2004/01/31 04:20:05 warnes Exp $' +from version import __version__ + +############################################################################## +# Namespace Class +################################################################################ +def invertDict(dict): + d = {} + + for k, v in dict.items(): + d[v] = k + + return d + +class NS: + XML = "http://www.w3.org/XML/1998/namespace" + + ENV = "http://schemas.xmlsoap.org/soap/envelope/" + ENC = "http://schemas.xmlsoap.org/soap/encoding/" + + XSD = "http://www.w3.org/1999/XMLSchema" + XSD2 = "http://www.w3.org/2000/10/XMLSchema" + XSD3 = "http://www.w3.org/2001/XMLSchema" + + XSD_L = [XSD, XSD2, XSD3] + EXSD_L= [ENC, XSD, XSD2, XSD3] + + XSI = "http://www.w3.org/1999/XMLSchema-instance" + XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" + XSI3 = "http://www.w3.org/2001/XMLSchema-instance" + XSI_L = [XSI, XSI2, XSI3] + + URN = "http://soapinterop.org/xsd" + + # For generated messages + XML_T = "xml" + ENV_T = "SOAP-ENV" + ENC_T = "SOAP-ENC" + XSD_T = "xsd" + XSD2_T= "xsd2" + XSD3_T= "xsd3" + XSI_T = "xsi" + XSI2_T= "xsi2" + XSI3_T= "xsi3" + URN_T = "urn" + + NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2, + XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3, + URN_T: URN} + NSMAP_R = invertDict(NSMAP) + + STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T), + '2001': (XSD3_T, XSI3_T)} + STMAP_R = invertDict(STMAP) + + def __init__(self): + raise Error, "Don't instantiate this" + + + diff --git a/SOAPpy/Parser.py b/SOAPpy/Parser.py new file mode 100644 index 0000000..aee364d --- /dev/null +++ b/SOAPpy/Parser.py @@ -0,0 +1,1024 @@ +# SOAPpy modules +from Config import Config +from Types import * +from NS import NS +from Utilities import * + +import string +import fpconst +import xml.sax +from wstools.XMLname import fromXMLname + +try: from M2Crypto import SSL +except: pass + +ident = '$Id: Parser.py,v 1.14 2004/01/31 04:20:05 warnes Exp $' +from version import __version__ + + +################################################################################ +# SOAP Parser +################################################################################ +class RefHolder: + def __init__(self, name, frame): + self.name = name + self.parent = frame + self.pos = len(frame) + self.subpos = frame.namecounts.get(name, 0) + + def __repr__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + + def __str__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + +class SOAPParser(xml.sax.handler.ContentHandler): + class Frame: + def __init__(self, name, kind = None, attrs = {}, rules = {}): + self.name = name + self.kind = kind + self.attrs = attrs + self.rules = rules + + self.contents = [] + self.names = [] + self.namecounts = {} + self.subattrs = [] + + def append(self, name, data, attrs): + self.names.append(name) + self.contents.append(data) + self.subattrs.append(attrs) + + if self.namecounts.has_key(name): + self.namecounts[name] += 1 + else: + self.namecounts[name] = 1 + + def _placeItem(self, name, value, pos, subpos = 0, attrs = None): + self.contents[pos] = value + + if attrs: + self.attrs.update(attrs) + + def __len__(self): + return len(self.contents) + + def __repr__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + + def __init__(self, rules = None): + xml.sax.handler.ContentHandler.__init__(self) + self.body = None + self.header = None + self.attrs = {} + self._data = None + self._next = "E" # Keeping state for message validity + self._stack = [self.Frame('SOAP')] + + # Make two dictionaries to store the prefix <-> URI mappings, and + # initialize them with the default + self._prem = {NS.XML_T: NS.XML} + self._prem_r = {NS.XML: NS.XML_T} + self._ids = {} + self._refs = {} + self._rules = rules + + def startElementNS(self, name, qname, attrs): + # Workaround two sax bugs + if name[0] == None and name[1][0] == ' ': + name = (None, name[1][1:]) + else: + name = tuple(name) + + # First some checking of the layout of the message + + if self._next == "E": + if name[1] != 'Envelope': + raise Error, "expected `SOAP-ENV:Envelope', gto `%s:%s'" % \ + (self._prem_r[name[0]], name[1]) + if name[0] != NS.ENV: + raise faultType, ("%s:VersionMismatch" % NS.ENV_T, + "Don't understand version `%s' Envelope" % name[0]) + else: + self._next = "HorB" + elif self._next == "HorB": + if name[0] == NS.ENV and name[1] in ("Header", "Body"): + self._next = None + else: + raise Error, \ + "expected `SOAP-ENV:Header' or `SOAP-ENV:Body', " \ + "got `%s'" % self._prem_r[name[0]] + ':' + name[1] + elif self._next == "B": + if name == (NS.ENV, "Body"): + self._next = None + else: + raise Error, "expected `SOAP-ENV:Body', got `%s'" % \ + self._prem_r[name[0]] + ':' + name[1] + elif self._next == "": + raise Error, "expected nothing, got `%s'" % \ + self._prem_r[name[0]] + ':' + name[1] + + if len(self._stack) == 2: + rules = self._rules + else: + try: + rules = self._stack[-1].rules[name[1]] + except: + rules = None + + if type(rules) not in (NoneType, DictType): + kind = rules + else: + kind = attrs.get((NS.ENC, 'arrayType')) + + if kind != None: + del attrs._attrs[(NS.ENC, 'arrayType')] + + i = kind.find(':') + if i >= 0: + kind = (self._prem[kind[:i]], kind[i + 1:]) + else: + kind = None + + self.pushFrame(self.Frame(name[1], kind, attrs._attrs, rules)) + + self._data = [] # Start accumulating + + def pushFrame(self, frame): + self._stack.append(frame) + + def popFrame(self): + return self._stack.pop() + + def endElementNS(self, name, qname): + # Workaround two sax bugs + if name[0] == None and name[1][0] == ' ': + ns, name = None, name[1][1:] + else: + ns, name = tuple(name) + + name = fromXMLname(name) # convert to SOAP 1.2 XML name encoding + + if self._next == "E": + raise Error, "didn't get SOAP-ENV:Envelope" + if self._next in ("HorB", "B"): + raise Error, "didn't get SOAP-ENV:Body" + + cur = self.popFrame() + attrs = cur.attrs + + idval = None + + if attrs.has_key((None, 'id')): + idval = attrs[(None, 'id')] + + if self._ids.has_key(idval): + raise Error, "duplicate id `%s'" % idval + + del attrs[(None, 'id')] + + root = 1 + + if len(self._stack) == 3: + if attrs.has_key((NS.ENC, 'root')): + root = int(attrs[(NS.ENC, 'root')]) + + # Do some preliminary checks. First, if root="0" is present, + # the element must have an id. Next, if root="n" is present, + # n something other than 0 or 1, raise an exception. + + if root == 0: + if idval == None: + raise Error, "non-root element must have an id" + elif root != 1: + raise Error, "SOAP-ENC:root must be `0' or `1'" + + del attrs[(NS.ENC, 'root')] + + while 1: + href = attrs.get((None, 'href')) + if href: + if href[0] != '#': + raise Error, "Non-local hrefs are not yet suppported." + if self._data != None and string.join(self._data, "").strip() != '': + raise Error, "hrefs can't have data" + + href = href[1:] + + if self._ids.has_key(href): + data = self._ids[href] + else: + data = RefHolder(name, self._stack[-1]) + + if self._refs.has_key(href): + self._refs[href].append(data) + else: + self._refs[href] = [data] + + del attrs[(None, 'href')] + + break + + kind = None + + if attrs: + for i in NS.XSI_L: + if attrs.has_key((i, 'type')): + kind = attrs[(i, 'type')] + del attrs[(i, 'type')] + + if kind != None: + i = kind.find(':') + if i >= 0: + kind = (self._prem[kind[:i]], kind[i + 1:]) + else: +# XXX What to do here? (None, kind) is just going to fail in convertType + kind = (None, kind) + + null = 0 + + if attrs: + for i in (NS.XSI, NS.XSI2): + if attrs.has_key((i, 'null')): + null = attrs[(i, 'null')] + del attrs[(i, 'null')] + + if attrs.has_key((NS.XSI3, 'nil')): + null = attrs[(NS.XSI3, 'nil')] + del attrs[(NS.XSI3, 'nil')] + + + ## Check for nil + + # check for nil='true' + if type(null) in (StringType, UnicodeType): + if null.lower() == 'true': + null = 1 + + # check for nil=1, but watch out for string values + try: + null = int(null) + except ValueError, e: + if not e[0].startswith("invalid literal for int()"): + raise e + null = 0 + + if null: + if len(cur) or \ + (self._data != None and string.join(self._data, "").strip() != ''): + raise Error, "nils can't have data" + + data = None + + break + + if len(self._stack) == 2: + if (ns, name) == (NS.ENV, "Header"): + self.header = data = headerType(attrs = attrs) + self._next = "B" + break + elif (ns, name) == (NS.ENV, "Body"): + self.body = data = bodyType(attrs = attrs) + self._next = "" + break + elif len(self._stack) == 3 and self._next == None: + if (ns, name) == (NS.ENV, "Fault"): + data = faultType() + self._next = "" + break + + if cur.rules != None: + rule = cur.rules + + if type(rule) in (StringType, UnicodeType): +# XXX Need a namespace here + rule = (None, rule) + elif type(rule) == ListType: + rule = tuple(rule) + +# XXX What if rule != kind? + if callable(rule): + data = rule(string.join(self._data, "")) + elif type(rule) == DictType: + data = structType(name = (ns, name), attrs = attrs) + else: + data = self.convertType(string.join(self._data, ""), + rule, attrs) + + break + + if (kind == None and cur.kind != None) or \ + (kind == (NS.ENC, 'Array')): + kind = cur.kind + + if kind == None: + kind = 'ur-type[%d]' % len(cur) + else: + kind = kind[1] + + if len(cur.namecounts) == 1: + elemsname = cur.names[0] + else: + elemsname = None + + data = self.startArray((ns, name), kind, attrs, elemsname) + + break + + if len(self._stack) == 3 and kind == None and \ + len(cur) == 0 and \ + (self._data == None or string.join(self._data, "").strip() == ''): + data = structType(name = (ns, name), attrs = attrs) + break + + if len(cur) == 0 and ns != NS.URN: + # Nothing's been added to the current frame so it must be a + # simple type. + + if kind == None: + # If the current item's container is an array, it will + # have a kind. If so, get the bit before the first [, + # which is the type of the array, therefore the type of + # the current item. + + kind = self._stack[-1].kind + + if kind != None: + i = kind[1].find('[') + if i >= 0: + kind = (kind[0], kind[1][:i]) + elif ns != None: + kind = (ns, name) + + if kind != None: + try: + data = self.convertType(string.join(self._data, ""), + kind, attrs) + except UnknownTypeError: + data = None + else: + data = None + + if data == None: + if self._data == None: + data = '' + else: + data = string.join(self._data, "") + + if len(attrs) == 0: + try: data = str(data) + except: pass + + break + + data = structType(name = (ns, name), attrs = attrs) + + break + + if isinstance(data, compoundType): + for i in range(len(cur)): + v = cur.contents[i] + data._addItem(cur.names[i], v, cur.subattrs[i]) + + if isinstance(v, RefHolder): + v.parent = data + + if root: + self._stack[-1].append(name, data, attrs) + + if idval != None: + self._ids[idval] = data + + if self._refs.has_key(idval): + for i in self._refs[idval]: + i.parent._placeItem(i.name, data, i.pos, i.subpos, attrs) + + del self._refs[idval] + + self.attrs[id(data)] = attrs + + if isinstance(data, anyType): + data._setAttrs(attrs) + + self._data = None # Stop accumulating + + def endDocument(self): + if len(self._refs) == 1: + raise Error, \ + "unresolved reference " + self._refs.keys()[0] + elif len(self._refs) > 1: + raise Error, \ + "unresolved references " + ', '.join(self._refs.keys()) + + def startPrefixMapping(self, prefix, uri): + self._prem[prefix] = uri + self._prem_r[uri] = prefix + + def endPrefixMapping(self, prefix): + try: + del self._prem_r[self._prem[prefix]] + del self._prem[prefix] + except: + pass + + def characters(self, c): + if self._data != None: + self._data.append(c) + + arrayre = '^(?:(?P[^:]*):)?' \ + '(?P[^[]+)' \ + '(?:\[(?P,*)\])?' \ + '(?:\[(?P\d+(?:,\d+)*)?\])$' + + def startArray(self, name, kind, attrs, elemsname): + if type(self.arrayre) == StringType: + self.arrayre = re.compile (self.arrayre) + + offset = attrs.get((NS.ENC, "offset")) + + if offset != None: + del attrs[(NS.ENC, "offset")] + + try: + if offset[0] == '[' and offset[-1] == ']': + offset = int(offset[1:-1]) + if offset < 0: + raise Exception + else: + raise Exception + except: + raise AttributeError, "invalid Array offset" + else: + offset = 0 + + try: + m = self.arrayre.search(kind) + + if m == None: + raise Exception + + t = m.group('type') + + if t == 'ur-type': + return arrayType(None, name, attrs, offset, m.group('rank'), + m.group('asize'), elemsname) + elif m.group('ns') != None: + return typedArrayType(None, name, + (self._prem[m.group('ns')], t), attrs, offset, + m.group('rank'), m.group('asize'), elemsname) + else: + return typedArrayType(None, name, (None, t), attrs, offset, + m.group('rank'), m.group('asize'), elemsname) + except: + raise AttributeError, "invalid Array type `%s'" % kind + + # Conversion + + class DATETIMECONSTS: + SIGNre = '(?P-?)' + CENTURYre = '(?P\d{2,})' + YEARre = '(?P\d{2})' + MONTHre = '(?P\d{2})' + DAYre = '(?P\d{2})' + HOURre = '(?P\d{2})' + MINUTEre = '(?P\d{2})' + SECONDre = '(?P\d{2}(?:\.\d*)?)' + TIMEZONEre = '(?PZ)|(?P[-+])(?P\d{2}):' \ + '(?P\d{2})' + BOSre = '^\s*' + EOSre = '\s*$' + + __allres = {'sign': SIGNre, 'century': CENTURYre, 'year': YEARre, + 'month': MONTHre, 'day': DAYre, 'hour': HOURre, + 'minute': MINUTEre, 'second': SECONDre, 'timezone': TIMEZONEre, + 'b': BOSre, 'e': EOSre} + + dateTime = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)sT' \ + '%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % __allres + timeInstant = dateTime + timePeriod = dateTime + time = '%(b)s%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % \ + __allres + date = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)s' \ + '(%(timezone)s)?%(e)s' % __allres + century = '%(b)s%(sign)s%(century)s(%(timezone)s)?%(e)s' % __allres + gYearMonth = '%(b)s%(sign)s%(century)s%(year)s-%(month)s' \ + '(%(timezone)s)?%(e)s' % __allres + gYear = '%(b)s%(sign)s%(century)s%(year)s(%(timezone)s)?%(e)s' % \ + __allres + year = gYear + gMonthDay = '%(b)s--%(month)s-%(day)s(%(timezone)s)?%(e)s' % __allres + recurringDate = gMonthDay + gDay = '%(b)s---%(day)s(%(timezone)s)?%(e)s' % __allres + recurringDay = gDay + gMonth = '%(b)s--%(month)s--(%(timezone)s)?%(e)s' % __allres + month = gMonth + + recurringInstant = '%(b)s%(sign)s(%(century)s|-)(%(year)s|-)-' \ + '(%(month)s|-)-(%(day)s|-)T' \ + '(%(hour)s|-):(%(minute)s|-):(%(second)s|-)' \ + '(%(timezone)s)?%(e)s' % __allres + + duration = '%(b)s%(sign)sP' \ + '((?P\d+)Y)?' \ + '((?P\d+)M)?' \ + '((?P\d+)D)?' \ + '((?PT)' \ + '((?P\d+)H)?' \ + '((?P\d+)M)?' \ + '((?P\d*(?:\.\d*)?)S)?)?%(e)s' % \ + __allres + + timeDuration = duration + + # The extra 31 on the front is: + # - so the tuple is 1-based + # - so months[month-1] is December's days if month is 1 + + months = (31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + + def convertDateTime(self, value, kind): + def getZoneOffset(d): + zoffs = 0 + + try: + if d['zulu'] == None: + zoffs = 60 * int(d['tzhour']) + int(d['tzminute']) + if d['tzsign'] != '-': + zoffs = -zoffs + except TypeError: + pass + + return zoffs + + def applyZoneOffset(months, zoffs, date, minfield, posday = 1): + if zoffs == 0 and (minfield > 4 or 0 <= date[5] < 60): + return date + + if minfield > 5: date[5] = 0 + if minfield > 4: date[4] = 0 + + if date[5] < 0: + date[4] += int(date[5]) / 60 + date[5] %= 60 + + date[4] += zoffs + + if minfield > 3 or 0 <= date[4] < 60: return date + + date[3] += date[4] / 60 + date[4] %= 60 + + if minfield > 2 or 0 <= date[3] < 24: return date + + date[2] += date[3] / 24 + date[3] %= 24 + + if minfield > 1: + if posday and date[2] <= 0: + date[2] += 31 # zoffs is at most 99:59, so the + # day will never be less than -3 + return date + + while 1: + # The date[1] == 3 (instead of == 2) is because we're + # going back a month, so we need to know if the previous + # month is February, so we test if this month is March. + + leap = minfield == 0 and date[1] == 3 and \ + date[0] % 4 == 0 and \ + (date[0] % 100 != 0 or date[0] % 400 == 0) + + if 0 < date[2] <= months[date[1]] + leap: break + + date[2] += months[date[1] - 1] + leap + + date[1] -= 1 + + if date[1] > 0: break + + date[1] = 12 + + if minfield > 0: break + + date[0] -= 1 + + return date + + try: + exp = getattr(self.DATETIMECONSTS, kind) + except AttributeError: + return None + + if type(exp) == StringType: + exp = re.compile(exp) + setattr (self.DATETIMECONSTS, kind, exp) + + m = exp.search(value) + + try: + if m == None: + raise Exception + + d = m.groupdict() + f = ('century', 'year', 'month', 'day', + 'hour', 'minute', 'second') + fn = len(f) # Index of first non-None value + r = [] + + if kind in ('duration', 'timeDuration'): + if d['sep'] != None and d['hour'] == None and \ + d['minute'] == None and d['second'] == None: + raise Exception + + f = f[1:] + + for i in range(len(f)): + s = d[f[i]] + + if s != None: + if f[i] == 'second': + s = float(s) + else: + try: s = int(s) + except ValueError: s = long(s) + + if i < fn: fn = i + + r.append(s) + + if fn > len(r): # Any non-Nones? + raise Exception + + if d['sign'] == '-': + r[fn] = -r[fn] + + return tuple(r) + + if kind == 'recurringInstant': + for i in range(len(f)): + s = d[f[i]] + + if s == None or s == '-': + if i > fn: + raise Exception + s = None + else: + if i < fn: + fn = i + + if f[i] == 'second': + s = float(s) + else: + try: + s = int(s) + except ValueError: + s = long(s) + + r.append(s) + + s = r.pop(0) + + if fn == 0: + r[0] += s * 100 + else: + fn -= 1 + + if fn < len(r) and d['sign'] == '-': + r[fn] = -r[fn] + + cleanDate(r, fn) + + return tuple(applyZoneOffset(self.DATETIMECONSTS.months, + getZoneOffset(d), r, fn, 0)) + + r = [0, 0, 1, 1, 0, 0, 0] + + for i in range(len(f)): + field = f[i] + + s = d.get(field) + + if s != None: + if field == 'second': + s = float(s) + else: + try: + s = int(s) + except ValueError: + s = long(s) + + if i < fn: + fn = i + + r[i] = s + + if fn > len(r): # Any non-Nones? + raise Exception + + s = r.pop(0) + + if fn == 0: + r[0] += s * 100 + else: + fn -= 1 + + if d.get('sign') == '-': + r[fn] = -r[fn] + + cleanDate(r, fn) + + zoffs = getZoneOffset(d) + + if zoffs: + r = applyZoneOffset(self.DATETIMECONSTS.months, zoffs, r, fn) + + if kind == 'century': + return r[0] / 100 + + s = [] + + for i in range(1, len(f)): + if d.has_key(f[i]): + s.append(r[i - 1]) + + if len(s) == 1: + return s[0] + return tuple(s) + except Exception, e: + raise Error, "invalid %s value `%s' - %s" % (kind, value, e) + + intlimits = \ + { + 'nonPositiveInteger': (0, None, 0), + 'non-positive-integer': (0, None, 0), + 'negativeInteger': (0, None, -1), + 'negative-integer': (0, None, -1), + 'long': (1, -9223372036854775808L, + 9223372036854775807L), + 'int': (0, -2147483648L, 2147483647), + 'short': (0, -32768, 32767), + 'byte': (0, -128, 127), + 'nonNegativeInteger': (0, 0, None), + 'non-negative-integer': (0, 0, None), + 'positiveInteger': (0, 1, None), + 'positive-integer': (0, 1, None), + 'unsignedLong': (1, 0, 18446744073709551615L), + 'unsignedInt': (0, 0, 4294967295L), + 'unsignedShort': (0, 0, 65535), + 'unsignedByte': (0, 0, 255), + } + floatlimits = \ + { + 'float': (7.0064923216240861E-46, -3.4028234663852886E+38, + 3.4028234663852886E+38), + 'double': (2.4703282292062327E-324, -1.7976931348623158E+308, + 1.7976931348623157E+308), + } + zerofloatre = '[1-9]' + + + + + + def convertType(self, d, t, attrs, config=Config): + return self.convertToBasicTypes(d, t, attrs, config) + + + def convertToSOAPpyTypes(self, d, t, attrs, config=Config): + pass + + + def convertToBasicTypes(self, d, t, attrs, config=Config): + dnn = d or '' + + if t[0] in NS.EXSD_L: + if t[1] == "integer": + try: + d = int(d) + if len(attrs): + d = long(d) + except: + d = long(d) + return d + if self.intlimits.has_key (t[1]): # integer types + l = self.intlimits[t[1]] + try: d = int(d) + except: d = long(d) + + if l[1] != None and d < l[1]: + raise UnderflowError, "%s too small" % d + if l[2] != None and d > l[2]: + raise OverflowError, "%s too large" % d + + if l[0] or len(attrs): + return long(d) + return d + if t[1] == "string": + if len(attrs): + return unicode(dnn) + try: + return str(dnn) + except: + return dnn + if t[1] == "boolean": + d = d.strip().lower() + if d in ('0', 'false'): + return 0 + if d in ('1', 'true'): + return 1 + raise AttributeError, "invalid boolean value" + if t[1] in ('double','float'): + l = self.floatlimits[t[1]] + s = d.strip().lower() + + d = float(s) + + if config.strict_range: + if d < l[1]: raise UnderflowError + if d > l[2]: raise OverflowError + else: + # some older SOAP impementations (notably SOAP4J, + # Apache SOAP) return "infinity" instead of "INF" + # so check the first 3 characters for a match. + if s == "nan": + return fpconst.NaN + elif s[0:3] in ("inf", "+inf"): + return fpconst.PosInf + elif s[0:3] == "-inf": + return fpconst.NegInf + + if fpconst.isNaN(d): + if s != 'nan': + raise ValueError, "invalid %s: %s" % (t[1], s) + elif fpconst.isNegInf(d): + if s != '-inf': + raise UnderflowError, "%s too small: %s" % (t[1], s) + elif fpconst.isPosInf(d): + if s != 'inf': + raise OverflowError, "%s too large: %s" % (t[1], s) + elif d < 0 and d < l[1]: + raise UnderflowError, "%s too small: %s" % (t[1], s) + elif d > 0 and ( d < l[0] or d > l[2] ): + raise OverflowError, "%s too large: %s" % (t[1], s) + elif d == 0: + if type(self.zerofloatre) == StringType: + self.zerofloatre = re.compile(self.zerofloatre) + + if self.zerofloatre.search(s): + raise UnderflowError, "invalid %s: %s" % (t[1], s) + + return d + if t[1] in ("dateTime", "date", "timeInstant", "time"): + return self.convertDateTime(d, t[1]) + if t[1] == "decimal": + return float(d) + if t[1] in ("language", "QName", "NOTATION", "NMTOKEN", "Name", + "NCName", "ID", "IDREF", "ENTITY"): + return collapseWhiteSpace(d) + if t[1] in ("IDREFS", "ENTITIES", "NMTOKENS"): + d = collapseWhiteSpace(d) + return d.split() + if t[0] in NS.XSD_L: + if t[1] in ("base64", "base64Binary"): + if d: + return base64.decodestring(d) + else: + return '' + if t[1] == "hexBinary": + if d: + return decodeHexString(d) + else: + return + if t[1] == "anyURI": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] in ("normalizedString", "token"): + return collapseWhiteSpace(d) + if t[0] == NS.ENC: + if t[1] == "base64": + if d: + return base64.decodestring(d) + else: + return '' + if t[0] == NS.XSD: + if t[1] == "binary": + try: + e = attrs[(None, 'encoding')] + + if d: + if e == 'hex': + return decodeHexString(d) + elif e == 'base64': + return base64.decodestring(d) + else: + return '' + except: + pass + + raise Error, "unknown or missing binary encoding" + if t[1] == "uri": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] == "recurringInstant": + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD2, NS.ENC): + if t[1] == "uriReference": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] == "timePeriod": + return self.convertDateTime(d, t[1]) + if t[1] in ("century", "year"): + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD, NS.XSD2, NS.ENC): + if t[1] == "timeDuration": + return self.convertDateTime(d, t[1]) + if t[0] == NS.XSD3: + if t[1] == "anyURI": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] in ("gYearMonth", "gMonthDay"): + return self.convertDateTime(d, t[1]) + if t[1] == "gYear": + return self.convertDateTime(d, t[1]) + if t[1] == "gMonth": + return self.convertDateTime(d, t[1]) + if t[1] == "gDay": + return self.convertDateTime(d, t[1]) + if t[1] == "duration": + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD2, NS.XSD3): + if t[1] == "token": + return collapseWhiteSpace(d) + if t[1] == "recurringDate": + return self.convertDateTime(d, t[1]) + if t[1] == "month": + return self.convertDateTime(d, t[1]) + if t[1] == "recurringDay": + return self.convertDateTime(d, t[1]) + if t[0] == NS.XSD2: + if t[1] == "CDATA": + return collapseWhiteSpace(d) + + raise UnknownTypeError, "unknown type `%s'" % (t[0] + ':' + t[1]) + + +################################################################################ +# call to SOAPParser that keeps all of the info +################################################################################ +def _parseSOAP(xml_str, rules = None): + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + + parser = xml.sax.make_parser() + t = SOAPParser(rules = rules) + parser.setContentHandler(t) + e = xml.sax.handler.ErrorHandler() + parser.setErrorHandler(e) + + inpsrc = xml.sax.xmlreader.InputSource() + inpsrc.setByteStream(StringIO(xml_str)) + + # turn on namespace mangeling + parser.setFeature(xml.sax.handler.feature_namespaces,1) + + try: + parser.parse(inpsrc) + except xml.sax.SAXParseException, e: + parser._parser = None + raise e + + return t + +################################################################################ +# SOAPParser's more public interface +################################################################################ +def parseSOAP(xml_str, attrs = 0): + t = _parseSOAP(xml_str) + + if attrs: + return t.body, t.attrs + return t.body + + +def parseSOAPRPC(xml_str, header = 0, body = 0, attrs = 0, rules = None): + #config=Config, unwrap_outer=1): + + t = _parseSOAP(xml_str, rules = rules) + p = t.body[0] + + # Empty string, for RPC this translates into a void + if type(p) in (type(''), type(u'')) and p in ('', u''): + name = "Response" + for k in t.body.__dict__.keys(): + if k[0] != "_": + name = k + p = structType(name) + + if header or body or attrs: + ret = (p,) + if header : ret += (t.header,) + if body: ret += (t.body,) + if attrs: ret += (t.attrs,) + return ret + else: + return p diff --git a/SOAPpy/SOAPBuilder.py b/SOAPpy/SOAPBuilder.py new file mode 100644 index 0000000..e375a41 --- /dev/null +++ b/SOAPpy/SOAPBuilder.py @@ -0,0 +1,620 @@ +""" +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id: SOAPBuilder.py,v 1.19 2004/04/10 04:28:46 irjudson Exp $' +from version import __version__ + +import cgi +import copy +from wstools.XMLname import toXMLname, fromXMLname +import fpconst + +# SOAPpy modules +from Config import Config +from NS import NS +from Types import * + +# Test whether this Python version has Types.BooleanType +# If it doesn't have it, then False and True are serialized as integers +try: + BooleanType + pythonHasBooleanType = 1 +except NameError: + pythonHasBooleanType = 0 + +################################################################################ +# SOAP Builder +################################################################################ +class SOAPBuilder: + _xml_top = '\n' + _xml_enc_top = '\n' + _env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \ + NS.__dict__ + _env_bot = '\n' % NS.__dict__ + + # Namespaces potentially defined in the Envelope tag. + + _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T, + NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T, + NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T} + + def __init__(self, args = (), kw = {}, method = None, namespace = None, + header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8', + use_refs = 0, config = Config, noroot = 0): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + self.args = args + self.kw = kw + self.envelope = envelope + self.encoding = encoding + self.method = method + self.namespace = namespace + self.header = header + self.methodattrs= methodattrs + self.use_refs = use_refs + self.config = config + self.out = [] + self.tcounter = 0 + self.ncounter = 1 + self.icounter = 1 + self.envns = {} + self.ids = {} + self.depth = 0 + self.multirefs = [] + self.multis = 0 + self.body = not isinstance(args, bodyType) + self.noroot = noroot + + def build(self): + if Config.debug: print "In build." + ns_map = {} + + # Cache whether typing is on or not + typed = self.config.typed + + if self.header: + # Create a header. + self.dump(self.header, "Header", typed = typed) + self.header = None # Wipe it out so no one is using it. + + if self.body: + # Call genns to record that we've used SOAP-ENV. + self.depth += 1 + body_ns = self.genns(ns_map, NS.ENV)[0] + self.out.append("<%sBody>\n" % body_ns) + + if self.method: + self.depth += 1 + a = '' + if self.methodattrs: + for (k, v) in self.methodattrs.items(): + a += ' %s="%s"' % (k, v) + + if self.namespace: # Use the namespace info handed to us + methodns, n = self.genns(ns_map, self.namespace) + else: + methodns, n = '', '' + + self.out.append('<%s%s%s%s%s>\n' % ( + methodns, self.method, n, a, self.genroot(ns_map))) + + try: + if type(self.args) != TupleType: + args = (self.args,) + else: + args = self.args + + for i in args: + self.dump(i, typed = typed, ns_map = ns_map) + + if hasattr(self.config, "argsOrdering") and self.config.argsOrdering.has_key(self.method): + for k in self.config.argsOrdering.get(self.method): + self.dump(self.kw.get(k), k, typed = typed, ns_map = ns_map) + else: + for (k, v) in self.kw.items(): + self.dump(v, k, typed = typed, ns_map = ns_map) + + except RecursionError: + if self.use_refs == 0: + # restart + b = SOAPBuilder(args = self.args, kw = self.kw, + method = self.method, namespace = self.namespace, + header = self.header, methodattrs = self.methodattrs, + envelope = self.envelope, encoding = self.encoding, + use_refs = 1, config = self.config) + return b.build() + raise + + if self.method: + self.out.append("\n" % (methodns, self.method)) + self.depth -= 1 + + if self.body: + # dump may add to self.multirefs, but the for loop will keep + # going until it has used all of self.multirefs, even those + # entries added while in the loop. + + self.multis = 1 + + for obj, tag in self.multirefs: + self.dump(obj, tag, typed = typed, ns_map = ns_map) + + self.out.append("\n" % body_ns) + self.depth -= 1 + + if self.envelope: + e = map (lambda ns: ' xmlns:%s="%s"' % (ns[1], ns[0]), + self.envns.items()) + + self.out = ['<', self._env_top] + e + ['>\n'] + \ + self.out + \ + [self._env_bot] + + if self.encoding != None: + self.out.insert(0, self._xml_enc_top % self.encoding) + return ''.join(self.out).encode(self.encoding) + + self.out.insert(0, self._xml_top) + return ''.join(self.out) + + def gentag(self): + if Config.debug: print "In gentag." + self.tcounter += 1 + return "v%d" % self.tcounter + + def genns(self, ns_map, nsURI): + if nsURI == None: + return ('', '') + + if type(nsURI) == TupleType: # already a tuple + if len(nsURI) == 2: + ns, nsURI = nsURI + else: + ns, nsURI = None, nsURI[0] + else: + ns = None + + if ns_map.has_key(nsURI): + return (ns_map[nsURI] + ':', '') + + if self._env_ns.has_key(nsURI): + ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI] + return (ns + ':', '') + + if not ns: + ns = "ns%d" % self.ncounter + self.ncounter += 1 + ns_map[nsURI] = ns + if self.config.buildWithNamespacePrefix: + return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI)) + else: + return ('', ' xmlns="%s"' % (nsURI)) + + def genroot(self, ns_map): + if self.noroot: + return '' + + if self.depth != 2: + return '' + + ns, n = self.genns(ns_map, NS.ENC) + return ' %sroot="%d"%s' % (ns, not self.multis, n) + + # checkref checks an element to see if it needs to be encoded as a + # multi-reference element or not. If it returns None, the element has + # been handled and the caller can continue with subsequent elements. + # If it returns a string, the string should be included in the opening + # tag of the marshaled element. + + def checkref(self, obj, tag, ns_map): + if self.depth < 2: + return '' + + if not self.ids.has_key(id(obj)): + n = self.ids[id(obj)] = self.icounter + self.icounter = n + 1 + + if self.use_refs == 0: + return '' + + if self.depth == 2: + return ' id="i%d"' % n + + self.multirefs.append((obj, tag)) + else: + if self.use_refs == 0: + raise RecursionError, "Cannot serialize recursive object" + + n = self.ids[id(obj)] + + if self.multis and self.depth == 2: + return ' id="i%d"' % n + + self.out.append('<%s href="#i%d"%s/>\n' % + (tag, n, self.genroot(ns_map))) + return None + + # dumpers + + def dump(self, obj, tag = None, typed = 1, ns_map = {}): + if Config.debug: print "In dump.", "obj=", obj + ns_map = ns_map.copy() + self.depth += 1 + + if type(tag) not in (NoneType, StringType, UnicodeType): + raise KeyError, "tag must be a string or None" + + try: + meth = getattr(self, "dump_" + type(obj).__name__) + except AttributeError: + if type(obj) == LongType: + obj_type = "integer" + elif pythonHasBooleanType and type(obj) == BooleanType: + obj_type = "boolean" + else: + obj_type = type(obj).__name__ + + self.out.append(self.dumper(None, obj_type, obj, tag, typed, + ns_map, self.genroot(ns_map))) + else: + meth(obj, tag, typed, ns_map) + + + self.depth -= 1 + + # generic dumper + def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {}, + rootattr = '', id = '', + xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s\n'): + if Config.debug: print "In dumper." + + if nsURI == None: + nsURI = self.config.typesNamespaceURI + + tag = tag or self.gentag() + + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + a = n = t = '' + if typed and obj_type: + ns, n = self.genns(ns_map, nsURI) + ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0] + t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n) + + try: a = obj._marshalAttrs(ns_map, self) + except: pass + + try: data = obj._marshalData() + except: + if (obj_type != "string"): # strings are already encoded + data = cgi.escape(str(obj)) + else: + data = obj + + + return xml % {"tag": tag, "type": t, "data": data, "root": rootattr, + "id": id, "attrs": a} + + def dump_float(self, obj, tag, typed = 1, ns_map = {}): + if Config.debug: print "In dump_float." + tag = tag or self.gentag() + + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + if Config.strict_range: + doubleType(obj) + + if fpconst.isPosInf(obj): + obj = "INF" + elif fpconst.isNegInf(obj): + obj = "-INF" + elif fpconst.isNaN(obj): + obj = "NaN" + else: + obj = str(obj) + + # Note: python 'float' is actually a SOAP 'double'. + self.out.append(self.dumper(None, "double", obj, tag, typed, ns_map, + self.genroot(ns_map))) + + def dump_string(self, obj, tag, typed = 0, ns_map = {}): + if Config.debug: print "In dump_string." + tag = tag or self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: data = obj._marshalData() + except: data = obj + + self.out.append(self.dumper(None, "string", cgi.escape(data), tag, + typed, ns_map, self.genroot(ns_map), id)) + + dump_str = dump_string # For Python 2.2+ + dump_unicode = dump_string + + def dump_None(self, obj, tag, typed = 0, ns_map = {}): + if Config.debug: print "In dump_None." + tag = tag or self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0] + + self.out.append('<%s %snull="1"%s/>\n' % + (tag, ns, self.genroot(ns_map))) + + dump_NoneType = dump_None # For Python 2.2+ + + def dump_list(self, obj, tag, typed = 1, ns_map = {}): + if Config.debug: print "In dump_list.", "obj=", obj + tag = tag or self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + if type(obj) == InstanceType: + data = obj.data + else: + data = obj + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: + sample = data[0] + empty = 0 + except: + # preserve type if present + if getattr(obj,"_typed",None) and getattr(obj,"_type",None): + if getattr(obj, "_complexType", None): + sample = typedArrayType(typed=obj._type, + complexType = obj._complexType) + sample._typename = obj._type + obj._ns = NS.URN + else: + sample = typedArrayType(typed=obj._type) + else: + sample = structType() + empty = 1 + + # First scan list to see if all are the same type + same_type = 1 + + if not empty: + for i in data[1:]: + if type(sample) != type(i) or \ + (type(sample) == InstanceType and \ + sample.__class__ != i.__class__): + same_type = 0 + break + + ndecl = '' + if same_type: + if (isinstance(sample, structType)) or \ + type(sample) == DictType or \ + (isinstance(sample, anyType) and \ + (getattr(sample, "_complexType", None) and \ + sample._complexType)): # force to urn struct + try: + tns = obj._ns or NS.URN + except: + tns = NS.URN + + ns, ndecl = self.genns(ns_map, tns) + + try: + typename = sample._typename + except: + typename = "SOAPStruct" + + t = ns + typename + + elif isinstance(sample, anyType): + ns = sample._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ns, ndecl = self.genns(ns_map, ns) + t = ns + sample._type + else: + t = 'ur-type' + else: + typename = type(sample).__name__ + + # For Python 2.2+ + if type(sample) == StringType: typename = 'string' + + # HACK: python 'float' is actually a SOAP 'double'. + if typename=="float": typename="double" + t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ + typename + + else: + t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ + "ur-type" + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + ens, edecl = self.genns(ns_map, NS.ENC) + ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI) + + self.out.append( + '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' % + (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl, + self.genroot(ns_map), id, a)) + + typed = not same_type + + try: elemsname = obj._elemsname + except: elemsname = "item" + + for i in data: + self.dump(i, elemsname, typed, ns_map) + + self.out.append('\n' % tag) + + dump_tuple = dump_list + + def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}): + if Config.debug: print "In dump_dictionary." + tag = tag or self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + self.out.append('<%s%s%s%s>\n' % + (tag, id, a, self.genroot(ns_map))) + + for (k, v) in obj.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + self.out.append('\n' % tag) + + dump_dict = dump_dictionary # For Python 2.2+ + + def dump_instance(self, obj, tag, typed = 1, ns_map = {}): + if Config.debug: print "In dump_instance.", "obj=", obj, "tag=", tag + if not tag: + # If it has a name use it. + if isinstance(obj, anyType) and obj._name: + tag = obj._name + else: + tag = self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + if isinstance(obj, arrayType): # Array + self.dump_list(obj, tag, typed, ns_map) + return + + if isinstance(obj, faultType): # Fault + cns, cdecl = self.genns(ns_map, NS.ENC) + vns, vdecl = self.genns(ns_map, NS.ENV) + self.out.append('''<%sFault %sroot="1"%s%s> +%s +%s +''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring)) + if hasattr(obj, "detail"): + self.dump(obj.detail, "detail", typed, ns_map) + self.out.append("\n" % vns) + return + + r = self.genroot(ns_map) + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + if isinstance(obj, voidType): # void + self.out.append("<%s%s%s>\n" % (tag, a, r, tag)) + return + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + if isinstance(obj, structType): + # Check for namespace + ndecl = '' + ns = obj._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ns, ndecl = self.genns(ns_map, ns) + tag = ns + tag + self.out.append("<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r)) + + keylist = obj.__dict__.keys() + + # first write out items with order information + if hasattr(obj, '_keyord'): + for i in range(len(obj._keyord)): + self.dump(obj._aslist(i), obj._keyord[i], 1, ns_map) + keylist.remove(obj._keyord[i]) + + # now write out the rest + for k in keylist: + if (k[0] != "_"): + self.dump(getattr(obj,k), k, 1, ns_map) + + if isinstance(obj, bodyType): + self.multis = 1 + + for v, k in self.multirefs: + self.dump(v, k, typed = typed, ns_map = ns_map) + + self.out.append('\n' % tag) + + elif isinstance(obj, anyType): + t = '' + + if typed: + ns = obj._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ons, ondecl = self.genns(ns_map, ns) + ins, indecl = self.genns(ns_map, + self.config.schemaNamespaceURI) + t = ' %stype="%s%s"%s%s' % \ + (ins, ons, obj._type, ondecl, indecl) + + self.out.append('<%s%s%s%s%s>%s\n' % + (tag, t, id, a, r, obj._marshalData(), tag)) + + else: # Some Class + self.out.append('<%s%s%s>\n' % (tag, id, r)) + + for (k, v) in obj.__dict__.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + self.out.append('\n' % tag) + + +################################################################################ +# SOAPBuilder's more public interface +################################################################################ +def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None, + methodattrs=None,envelope=1,encoding='UTF-8',config=Config,noroot = 0): + t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace, + header=header, methodattrs=methodattrs,envelope=envelope, + encoding=encoding, config=config,noroot=noroot) + return t.build() diff --git a/SOAPpy/Types.py b/SOAPpy/Types.py new file mode 100644 index 0000000..b4d9a57 --- /dev/null +++ b/SOAPpy/Types.py @@ -0,0 +1,1734 @@ +""" +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id: Types.py,v 1.17 2004/09/11 03:03:33 warnes Exp $' +from version import __version__ + +from __future__ import nested_scopes + +import UserList +import base64 +import cgi +import urllib +import copy +import re +import time +from types import * + +# SOAPpy modules +from Errors import * +from NS import NS +from Utilities import encodeHexString, cleanDate +from Config import Config + +############################################################################### +# Utility functions +############################################################################### + +def isPrivate(name): return name[0]=='_' +def isPublic(name): return name[0]!='_' + +############################################################################### +# Types and Wrappers +############################################################################### + +class anyType: + _validURIs = (NS.XSD, NS.XSD2, NS.XSD3, NS.ENC) + + def __init__(self, data = None, name = None, typed = 1, attrs = None): + if self.__class__ == anyType: + raise Error, "anyType can't be instantiated directly" + + if type(name) in (ListType, TupleType): + self._ns, self._name = name + else: + self._ns = self._validURIs[0] + self._name = name + + self._typed = typed + self._attrs = {} + + self._cache = None + self._type = self._typeName() + + self._data = self._checkValueSpace(data) + + if attrs != None: + self._setAttrs(attrs) + + def __str__(self): + if hasattr(self,'_name') and self._name: + return "<%s %s at %d>" % (self.__class__, self._name, id(self)) + return "<%s at %d>" % (self.__class__, id(self)) + + __repr__ = __str__ + + def _checkValueSpace(self, data): + return data + + def _marshalData(self): + return str(self._data) + + def _marshalAttrs(self, ns_map, builder): + a = '' + + for attr, value in self._attrs.items(): + ns, n = builder.genns(ns_map, attr[0]) + a += n + ' %s%s="%s"' % \ + (ns, attr[1], cgi.escape(str(value), 1)) + + return a + + def _fixAttr(self, attr): + if type(attr) in (StringType, UnicodeType): + attr = (None, attr) + elif type(attr) == ListType: + attr = tuple(attr) + elif type(attr) != TupleType: + raise AttributeError, "invalid attribute type" + + if len(attr) != 2: + raise AttributeError, "invalid attribute length" + + if type(attr[0]) not in (NoneType, StringType, UnicodeType): + raise AttributeError, "invalid attribute namespace URI type" + + return attr + + def _getAttr(self, attr): + attr = self._fixAttr(attr) + + try: + return self._attrs[attr] + except: + return None + + def _setAttr(self, attr, value): + attr = self._fixAttr(attr) + + if type(value) is StringType: + value = unicode(value) + + self._attrs[attr] = value + + + def _setAttrs(self, attrs): + if type(attrs) in (ListType, TupleType): + for i in range(0, len(attrs), 2): + self._setAttr(attrs[i], attrs[i + 1]) + + return + + if type(attrs) == DictType: + d = attrs + elif isinstance(attrs, anyType): + d = attrs._attrs + else: + raise AttributeError, "invalid attribute type" + + for attr, value in d.items(): + self._setAttr(attr, value) + + def _setMustUnderstand(self, val): + self._setAttr((NS.ENV, "mustUnderstand"), val) + + def _getMustUnderstand(self): + return self._getAttr((NS.ENV, "mustUnderstand")) + + def _setActor(self, val): + self._setAttr((NS.ENV, "actor"), val) + + def _getActor(self): + return self._getAttr((NS.ENV, "actor")) + + def _typeName(self): + return self.__class__.__name__[:-4] + + def _validNamespaceURI(self, URI, strict): + if not hasattr(self, '_typed') or not self._typed: + return None + if URI in self._validURIs: + return URI + if not strict: + return self._ns + raise AttributeError, \ + "not a valid namespace for type %s" % self._type + +class voidType(anyType): + pass + +class stringType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type:" % self._type + + return data + +class untypedType(stringType): + def __init__(self, data = None, name = None, attrs = None): + stringType.__init__(self, data, name, 0, attrs) + +class IDType(stringType): pass +class NCNameType(stringType): pass +class NameType(stringType): pass +class ENTITYType(stringType): pass +class IDREFType(stringType): pass +class languageType(stringType): pass +class NMTOKENType(stringType): pass +class QNameType(stringType): pass + +class tokenType(anyType): + _validURIs = (NS.XSD2, NS.XSD3) + __invalidre = '[\n\t]|^ | $| ' + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + if type(self.__invalidre) == StringType: + self.__invalidre = re.compile(self.__invalidre) + + if self.__invalidre.search(data): + raise ValueError, "invalid %s value" % self._type + + return data + +class normalizedStringType(anyType): + _validURIs = (NS.XSD3,) + __invalidre = '[\n\r\t]' + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + if type(self.__invalidre) == StringType: + self.__invalidre = re.compile(self.__invalidre) + + if self.__invalidre.search(data): + raise ValueError, "invalid %s value" % self._type + + return data + +class CDATAType(normalizedStringType): + _validURIs = (NS.XSD2,) + +class booleanType(anyType): + def __int__(self): + return self._data + + __nonzero__ = __int__ + + def _marshalData(self): + return ['false', 'true'][self._data] + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if data in (0, '0', 'false', ''): + return 0 + if data in (1, '1', 'true'): + return 1 + raise ValueError, "invalid %s value" % self._type + +class decimalType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType): + raise Error, "invalid %s value" % self._type + + return data + +class floatType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType) or \ + data < -3.4028234663852886E+38 or \ + data > 3.4028234663852886E+38: + raise ValueError, "invalid %s value: %s" % (self._type, repr(data)) + + return data + + def _marshalData(self): + return "%.18g" % self._data # More precision + +class doubleType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType) or \ + data < -1.7976931348623158E+308 or \ + data > 1.7976931348623157E+308: + raise ValueError, "invalid %s value: %s" % (self._type, repr(data)) + + return data + + def _marshalData(self): + return "%.18g" % self._data # More precision + +class durationType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + try: + # A tuple or a scalar is OK, but make them into a list + + if type(data) == TupleType: + data = list(data) + elif type(data) != ListType: + data = [data] + + if len(data) > 6: + raise Exception, "too many values" + + # Now check the types of all the components, and find + # the first nonzero element along the way. + + f = -1 + + for i in range(len(data)): + if data[i] == None: + data[i] = 0 + continue + + if type(data[i]) not in \ + (IntType, LongType, FloatType): + raise Exception, "element %d a bad type" % i + + if data[i] and f == -1: + f = i + + # If they're all 0, just use zero seconds. + + if f == -1: + self._cache = 'PT0S' + + return (0,) * 6 + + # Make sure only the last nonzero element has a decimal fraction + # and only the first element is negative. + + d = -1 + + for i in range(f, len(data)): + if data[i]: + if d != -1: + raise Exception, \ + "all except the last nonzero element must be " \ + "integers" + if data[i] < 0 and i > f: + raise Exception, \ + "only the first nonzero element can be negative" + elif data[i] != long(data[i]): + d = i + + # Pad the list on the left if necessary. + + if len(data) < 6: + n = 6 - len(data) + f += n + d += n + data = [0] * n + data + + # Save index of the first nonzero element and the decimal + # element for _marshalData. + + self.__firstnonzero = f + self.__decimal = d + + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + t = 0 + + if d[self.__firstnonzero] < 0: + s = '-P' + else: + s = 'P' + + t = 0 + + for i in range(self.__firstnonzero, len(d)): + if d[i]: + if i > 2 and not t: + s += 'T' + t = 1 + if self.__decimal == i: + s += "%g" % abs(d[i]) + else: + s += "%d" % long(abs(d[i])) + s += ['Y', 'M', 'D', 'H', 'M', 'S'][i] + + self._cache = s + + return self._cache + +class timeDurationType(durationType): + _validURIs = (NS.XSD, NS.XSD2, NS.ENC) + +class dateTimeType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.time() + + if (type(data) in (IntType, LongType)): + data = list(time.gmtime(data)[:6]) + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[:6]) + data[5] += f + elif type(data) in (ListType, TupleType): + if len(data) < 6: + raise Exception, "not enough values" + if len(data) > 9: + raise Exception, "too many values" + + data = list(data[:6]) + + cleanDate(data) + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02d-%02dT%02d:%02d:%02d" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + f = d[5] - int(d[5]) + if f != 0: + s += ("%g" % f)[1:] + s += 'Z' + + self._cache = s + + return self._cache + +class recurringInstantType(anyType): + _validURIs = (NS.XSD,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = list(time.gmtime(time.time())[:6]) + if (type(data) in (IntType, LongType)): + data = list(time.gmtime(data)[:6]) + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[:6]) + data[5] += f + elif type(data) in (ListType, TupleType): + if len(data) < 1: + raise Exception, "not enough values" + if len(data) > 9: + raise Exception, "too many values" + + data = list(data[:6]) + + if len(data) < 6: + data += [0] * (6 - len(data)) + + f = len(data) + + for i in range(f): + if data[i] == None: + if f < i: + raise Exception, \ + "only leftmost elements can be none" + else: + f = i + break + + cleanDate(data, f) + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + e = list(d) + neg = '' + + if not e[0]: + e[0] = '--' + else: + if e[0] < 0: + neg = '-' + e[0] = abs(e[0]) + if e[0] < 100: + e[0] = '-' + "%02d" % e[0] + else: + e[0] = "%04d" % e[0] + + for i in range(1, len(e)): + if e[i] == None or (i < 3 and e[i] == 0): + e[i] = '-' + else: + if e[i] < 0: + neg = '-' + e[i] = abs(e[i]) + + e[i] = "%02d" % e[i] + + if d[5]: + f = abs(d[5] - int(d[5])) + + if f: + e[5] += ("%g" % f)[1:] + + s = "%s%s-%s-%sT%s:%s:%sZ" % ((neg,) + tuple(e)) + + self._cache = s + + return self._cache + +class timeInstantType(dateTimeType): + _validURIs = (NS.XSD, NS.XSD2, NS.ENC) + +class timePeriodType(dateTimeType): + _validURIs = (NS.XSD2, NS.ENC) + +class timeType(anyType): + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[3:6] + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[3:6]) + data[2] += f + elif type(data) in (IntType, LongType): + data = time.gmtime(data)[3:6] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[3:6] + elif len(data) > 3: + raise Exception, "too many values" + + data = [None, None, None] + list(data) + + if len(data) < 6: + data += [0] * (6 - len(data)) + + cleanDate(data, 3) + + data = data[3:] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = '' + + s = time.strftime("%H:%M:%S", (0, 0, 0) + d + (0, 0, -1)) + f = d[2] - int(d[2]) + if f != 0: + s += ("%g" % f)[1:] + s += 'Z' + + self._cache = s + + return self._cache + +class dateType(anyType): + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:3] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[0:3] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:3] + elif len(data) > 3: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 3: + data += [1, 1, 1][len(data):] + + data += [0, 0, 0] + + cleanDate(data) + + data = data[:3] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02d-%02dZ" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class gYearMonthType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:2] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[0:2] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:2] + elif len(data) > 2: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 2: + data += [1, 1][len(data):] + + data += [1, 0, 0, 0] + + cleanDate(data) + + data = data[:2] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02dZ" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class gYearType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:1] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:1] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04dZ" % abs(d) + if d < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class centuryType(anyType): + _validURIs = (NS.XSD2, NS.ENC) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:1] / 100 + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:1] / 100 + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%02dZ" % abs(d) + if d < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class yearType(gYearType): + _validURIs = (NS.XSD2, NS.ENC) + +class gMonthDayType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[1:3] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[1:3] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:2] + elif len(data) > 2: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 2: + data += [1, 1][len(data):] + + data = [0] + data + [0, 0, 0] + + cleanDate(data, 1) + + data = data[1:3] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + self._cache = "--%02d-%02dZ" % self._data + + return self._cache + +class recurringDateType(gMonthDayType): + _validURIs = (NS.XSD2, NS.ENC) + +class gMonthType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[1:2] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[1:2] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + + if data[0] < 1 or data[0] > 12: + raise Exception, "bad value" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + self._cache = "--%02d--Z" % self._data + + return self._cache + +class monthType(gMonthType): + _validURIs = (NS.XSD2, NS.ENC) + +class gDayType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[2:3] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[2:3] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + + if data[0] < 1 or data[0] > 31: + raise Exception, "bad value" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + self._cache = "---%02dZ" % self._data + + return self._cache + +class recurringDayType(gDayType): + _validURIs = (NS.XSD2, NS.ENC) + +class hexBinaryType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = encodeHexString(self._data) + + return self._cache + +class base64BinaryType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = base64.encodestring(self._data) + + return self._cache + +class base64Type(base64BinaryType): + _validURIs = (NS.ENC,) + +class binaryType(anyType): + _validURIs = (NS.XSD, NS.ENC) + + def __init__(self, data, name = None, typed = 1, encoding = 'base64', + attrs = None): + + anyType.__init__(self, data, name, typed, attrs) + + self._setAttr('encoding', encoding) + + def _marshalData(self): + if self._cache == None: + if self._getAttr((None, 'encoding')) == 'base64': + self._cache = base64.encodestring(self._data) + else: + self._cache = encodeHexString(self._data) + + return self._cache + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _setAttr(self, attr, value): + attr = self._fixAttr(attr) + + if attr[1] == 'encoding': + if attr[0] != None or value not in ('base64', 'hex'): + raise AttributeError, "invalid encoding" + + self._cache = None + + anyType._setAttr(self, attr, value) + + +class anyURIType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = urllib.quote(self._data) + + return self._cache + +class uriType(anyURIType): + _validURIs = (NS.XSD,) + +class uriReferenceType(anyURIType): + _validURIs = (NS.XSD2,) + +class NOTATIONType(anyType): + def __init__(self, data, name = None, typed = 1, attrs = None): + + if self.__class__ == NOTATIONType: + raise Error, "a NOTATION can't be instantiated directly" + + anyType.__init__(self, data, name, typed, attrs) + +class ENTITIESType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) in (StringType, UnicodeType): + return (data,) + + if type(data) not in (ListType, TupleType) or \ + filter (lambda x: type(x) not in (StringType, UnicodeType), data): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + return ' '.join(self._data) + +class IDREFSType(ENTITIESType): pass +class NMTOKENSType(ENTITIESType): pass + +class integerType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType): + raise ValueError, "invalid %s value" % self._type + + return data + +class nonPositiveIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data > 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class non_Positive_IntegerType(nonPositiveIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'non-positive-integer' + +class negativeIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data >= 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class negative_IntegerType(negativeIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'negative-integer' + +class longType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -9223372036854775808L or \ + data > 9223372036854775807L: + raise ValueError, "invalid %s value" % self._type + + return data + +class intType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -2147483648L or \ + data > 2147483647: + raise ValueError, "invalid %s value" % self._type + + return data + +class shortType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -32768 or \ + data > 32767: + raise ValueError, "invalid %s value" % self._type + + return data + +class byteType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -128 or \ + data > 127: + raise ValueError, "invalid %s value" % self._type + + return data + +class nonNegativeIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data < 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class non_Negative_IntegerType(nonNegativeIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'non-negative-integer' + +class unsignedLongType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 18446744073709551615L: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedIntType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 4294967295L: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedShortType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 65535: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedByteType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 255: + raise ValueError, "invalid %s value" % self._type + + return data + +class positiveIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data <= 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class positive_IntegerType(positiveIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'positive-integer' + +# Now compound types + +class compoundType(anyType): + def __init__(self, data = None, name = None, typed = 1, attrs = None): + if self.__class__ == compoundType: + raise Error, "a compound can't be instantiated directly" + + anyType.__init__(self, data, name, typed, attrs) + self._keyord = [] + + if type(data) == DictType: + self.__dict__.update(data) + + def _aslist(self, item=None): + if item: + return self.__dict__[self._keyord[item]] + else: + return map( lambda x: self.__dict__[x], self._keyord) + + def _asdict(self, item=None, encoding=Config.dict_encoding): + if item: + if type(item) in (UnicodeType,StringType): + item = item.encode(encoding) + return self.__dict__[item] + else: + retval = {} + def fun(x): retval[x.encode(encoding)] = self.__dict__[x] + + if hasattr(self, '_keyord'): + map( fun, self._keyord) + else: + for name in dir(self): + if isPublic(name): + retval[name] = getattr(self,name) + return retval + + + def __getitem__(self, item): + if type(item) == IntType: + return self.__dict__[self._keyord[item]] + else: + return getattr(self, item) + + def __len__(self): + return len(self._keyord) + + def __nonzero__(self): + return 1 + + def _keys(self): + return filter(lambda x: x[0] != '_', self.__dict__.keys()) + + def _addItem(self, name, value, attrs = None): + + if name in self._keyord: + if type(self.__dict__[name]) != ListType: + self.__dict__[name] = [self.__dict__[name]] + self.__dict__[name].append(value) + else: + self.__dict__[name] = value + self._keyord.append(name) + + def _placeItem(self, name, value, pos, subpos = 0, attrs = None): + + if subpos == 0 and type(self.__dict__[name]) != ListType: + self.__dict__[name] = value + else: + self.__dict__[name][subpos] = value + + self._keyord[pos] = name + + + def _getItemAsList(self, name, default = []): + try: + d = self.__dict__[name] + except: + return default + + if type(d) == ListType: + return d + return [d] + + def __str__(self): + return anyType.__str__(self) + ": " + str(self._asdict()) + + def __repr__(self): + return self.__str__() + +class structType(compoundType): + pass + +class headerType(structType): + _validURIs = (NS.ENV,) + + def __init__(self, data = None, typed = 1, attrs = None): + structType.__init__(self, data, "Header", typed, attrs) + +class bodyType(structType): + _validURIs = (NS.ENV,) + + def __init__(self, data = None, typed = 1, attrs = None): + structType.__init__(self, data, "Body", typed, attrs) + +class arrayType(UserList.UserList, compoundType): + def __init__(self, data = None, name = None, attrs = None, + offset = 0, rank = None, asize = 0, elemsname = None): + + if data: + if type(data) not in (ListType, TupleType): + raise Error, "Data must be a sequence" + + UserList.UserList.__init__(self, data) + compoundType.__init__(self, data, name, 0, attrs) + + self._elemsname = elemsname or "item" + + if data == None: + self._rank = rank + + # According to 5.4.2.2 in the SOAP spec, each element in a + # sparse array must have a position. _posstate keeps track of + # whether we've seen a position or not. It's possible values + # are: + # -1 No elements have been added, so the state is indeterminate + # 0 An element without a position has been added, so no + # elements can have positions + # 1 An element with a position has been added, so all elements + # must have positions + + self._posstate = -1 + + self._full = 0 + + if asize in ('', None): + asize = '0' + + self._dims = map (lambda x: int(x), str(asize).split(',')) + self._dims.reverse() # It's easier to work with this way + self._poss = [0] * len(self._dims) # This will end up + # reversed too + + for i in range(len(self._dims)): + if self._dims[i] < 0 or \ + self._dims[i] == 0 and len(self._dims) > 1: + raise TypeError, "invalid Array dimensions" + + if offset > 0: + self._poss[i] = offset % self._dims[i] + offset = int(offset / self._dims[i]) + + # Don't break out of the loop if offset is 0 so we test all the + # dimensions for > 0. + if offset: + raise AttributeError, "invalid Array offset" + + a = [None] * self._dims[0] + + for i in range(1, len(self._dims)): + b = [] + + for j in range(self._dims[i]): + b.append(copy.deepcopy(a)) + + a = b + + self.data = a + + + def _aslist(self, item=None): + if item: + return self.data[int(item)] + else: + return self.data + + def _asdict(self, item=None, encoding=Config.dict_encoding): + if item: + if type(item) in (UnicodeType,StringType): + item = item.encode(encoding) + return self.data[int(item)] + else: + retval = {} + def fun(x): retval[str(x).encode(encoding)] = self.data[x] + + map( fun, range(len(self.data)) ) + return retval + + def __getitem__(self, item): + try: + return self.data[int(item)] + except ValueError: + return getattr(self, item) + + def __len__(self): + return len(self.data) + + def __nonzero__(self): + return 1 + + def __str__(self): + return anyType.__str__(self) + ": " + str(self._aslist()) + + def _keys(self): + return filter(lambda x: x[0] != '_', self.__dict__.keys()) + + def _addItem(self, name, value, attrs): + if self._full: + raise ValueError, "Array is full" + + pos = attrs.get((NS.ENC, 'position')) + + if pos != None: + if self._posstate == 0: + raise AttributeError, \ + "all elements in a sparse Array must have a " \ + "position attribute" + + self._posstate = 1 + + try: + if pos[0] == '[' and pos[-1] == ']': + pos = map (lambda x: int(x), pos[1:-1].split(',')) + pos.reverse() + + if len(pos) == 1: + pos = pos[0] + + curpos = [0] * len(self._dims) + + for i in range(len(self._dims)): + curpos[i] = pos % self._dims[i] + pos = int(pos / self._dims[i]) + + if pos == 0: + break + + if pos: + raise Exception + elif len(pos) != len(self._dims): + raise Exception + else: + for i in range(len(self._dims)): + if pos[i] >= self._dims[i]: + raise Exception + + curpos = pos + else: + raise Exception + except: + raise AttributeError, \ + "invalid Array element position %s" % str(pos) + else: + if self._posstate == 1: + raise AttributeError, \ + "only elements in a sparse Array may have a " \ + "position attribute" + + self._posstate = 0 + + curpos = self._poss + + a = self.data + + for i in range(len(self._dims) - 1, 0, -1): + a = a[curpos[i]] + + if curpos[0] >= len(a): + a += [None] * (len(a) - curpos[0] + 1) + + a[curpos[0]] = value + + if pos == None: + self._poss[0] += 1 + + for i in range(len(self._dims) - 1): + if self._poss[i] < self._dims[i]: + break + + self._poss[i] = 0 + self._poss[i + 1] += 1 + + if self._dims[-1] and self._poss[-1] >= self._dims[-1]: + self._full = 1 + + def _placeItem(self, name, value, pos, subpos, attrs = None): + curpos = [0] * len(self._dims) + + for i in range(len(self._dims)): + if self._dims[i] == 0: + curpos[0] = pos + break + + curpos[i] = pos % self._dims[i] + pos = int(pos / self._dims[i]) + + if pos == 0: + break + + if self._dims[i] != 0 and pos: + raise Error, "array index out of range" + + a = self.data + + for i in range(len(self._dims) - 1, 0, -1): + a = a[curpos[i]] + + if curpos[0] >= len(a): + a += [None] * (len(a) - curpos[0] + 1) + + a[curpos[0]] = value + +class typedArrayType(arrayType): + def __init__(self, data = None, name = None, typed = None, attrs = None, + offset = 0, rank = None, asize = 0, elemsname = None, complexType = 0): + + arrayType.__init__(self, data, name, attrs, offset, rank, asize, + elemsname) + + self._typed = 1 + self._type = typed + self._complexType = complexType + +class faultType(structType, Error): + def __init__(self, faultcode = "", faultstring = "", detail = None): + self.faultcode = faultcode + self.faultstring = faultstring + if detail != None: + self.detail = detail + + structType.__init__(self, None, 0) + + def _setDetail(self, detail = None): + if detail != None: + self.detail = detail + else: + try: del self.detail + except AttributeError: pass + + def __repr__(self): + if getattr(self, 'detail', None) != None: + return "" % (self.faultcode, + self.faultstring, + self.detail) + else: + return "" % (self.faultcode, self.faultstring) + + __str__ = __repr__ + + def __call__(self): + return (self.faultcode, self.faultstring, self.detail) + +class SOAPException(Exception): + def __init__(self, code="", string="", detail=None): + self.value = ("SOAPpy SOAP Exception", code, string, detail) + self.code = code + self.string = string + self.detail = detail + + def __str__(self): + return repr(self.value) + +class RequiredHeaderMismatch(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +class MethodNotFound(Exception): + def __init__(self, value): + (val, detail) = value.split(":") + self.value = val + self.detail = detail + + def __str__(self): + return repr(self.value, self.detail) + +class AuthorizationFailed(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +class MethodFailed(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +####### +# Convert complex SOAPpy objects to native python equivalents +####### + +def simplify(object, level=0): + """ + Convert the SOAPpy objects and thier contents to simple python types. + + This function recursively converts the passed 'container' object, + and all public subobjects. (Private subobjects have names that + start with '_'.) + + Conversions: + - faultType --> raise python exception + - arrayType --> array + - compoundType --> dictionary + """ + + if level > 10: + return object + + if isinstance( object, faultType ): + if object.faultstring == "Required Header Misunderstood": + raise RequiredHeaderMismatch(object.detail) + elif object.faultstring == "Method Not Found": + raise MethodNotFound(object.detail) + elif object.faultstring == "Authorization Failed": + raise AuthorizationFailed(object.detail) + elif object.faultstring == "Method Failed": + raise MethodFailed(object.detail) + else: + se = SOAPException(object.faultcode, object.faultstring, + object.detail) + raise se + elif isinstance( object, arrayType ): + data = object._aslist() + for k in range(len(data)): + data[k] = simplify(data[k], level=level+1) + return data + elif isinstance( object, compoundType ) or isinstance(object, structType): + data = object._asdict() + for k in data.keys(): + if isPublic(k): + data[k] = simplify(data[k], level=level+1) + return data + elif type(object)==DictType: + for k in object.keys(): + if isPublic(k): + object[k] = simplify(object[k]) + return object + elif type(object)==list: + for k in range(len(object)): + object[k] = simplify(object[k]) + return object + else: + return object + + +def simplify_contents(object, level=0): + """ + Convert the contents of SOAPpy objects to simple python types. + + This function recursively converts the sub-objects contained in a + 'container' object to simple python types. + + Conversions: + - faultType --> raise python exception + - arrayType --> array + - compoundType --> dictionary + """ + + if level>10: return object + + if isinstance( object, faultType ): + for k in object._keys(): + if isPublic(k): + setattr(object, k, simplify(object[k], level=level+1)) + raise object + elif isinstance( object, arrayType ): + data = object._aslist() + for k in range(len(data)): + object[k] = simplify(data[k], level=level+1) + elif isinstance(object, structType): + data = object._asdict() + for k in data.keys(): + if isPublic(k): + setattr(object, k, simplify(data[k], level=level+1)) + elif isinstance( object, compoundType ) : + data = object._asdict() + for k in data.keys(): + if isPublic(k): + object[k] = simplify(data[k], level=level+1) + elif type(object)==DictType: + for k in object.keys(): + if isPublic(k): + object[k] = simplify(object[k]) + elif type(object)==list: + for k in range(len(object)): + object[k] = simplify(object[k]) + + return object + + diff --git a/SOAPpy/Utilities.py b/SOAPpy/Utilities.py new file mode 100644 index 0000000..03d3433 --- /dev/null +++ b/SOAPpy/Utilities.py @@ -0,0 +1,178 @@ +""" +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id: Utilities.py,v 1.4 2004/01/31 04:20:06 warnes Exp $' +from version import __version__ + +import exceptions +import copy +import re +import string +import sys +from types import * + +# SOAPpy modules +from Errors import * + +################################################################################ +# Utility infielders +################################################################################ +def collapseWhiteSpace(s): + return re.sub('\s+', ' ', s).strip() + +def decodeHexString(data): + conv = { + '0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4, + '5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9, + + 'a': 0xa, 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe, + 'f': 0xf, + + 'A': 0xa, 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe, + 'F': 0xf, + } + + ws = string.whitespace + + bin = '' + + i = 0 + + while i < len(data): + if data[i] not in ws: + break + i += 1 + + low = 0 + + while i < len(data): + c = data[i] + + if c in string.whitespace: + break + + try: + c = conv[c] + except KeyError: + raise ValueError, \ + "invalid hex string character `%s'" % c + + if low: + bin += chr(high * 16 + c) + low = 0 + else: + high = c + low = 1 + + i += 1 + + if low: + raise ValueError, "invalid hex string length" + + while i < len(data): + if data[i] not in string.whitespace: + raise ValueError, \ + "invalid hex string character `%s'" % c + + i += 1 + + return bin + +def encodeHexString(data): + h = '' + + for i in data: + h += "%02X" % ord(i) + + return h + +def leapMonth(year, month): + return month == 2 and \ + year % 4 == 0 and \ + (year % 100 != 0 or year % 400 == 0) + +def cleanDate(d, first = 0): + ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61)) + months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds') + + if len(d) != 6: + raise ValueError, "date must have 6 elements" + + for i in range(first, 6): + s = d[i] + + if type(s) == FloatType: + if i < 5: + try: + s = int(s) + except OverflowError: + if i > 0: + raise + s = long(s) + + if s != d[i]: + raise ValueError, "%s must be integral" % names[i] + + d[i] = s + elif type(s) == LongType: + try: s = int(s) + except: pass + elif type(s) != IntType: + raise TypeError, "%s isn't a valid type" % names[i] + + if i == first and s < 0: + continue + + if ranges[i] != None and \ + (s < ranges[i][0] or ranges[i][1] < s): + raise ValueError, "%s out of range" % names[i] + + if first < 6 and d[5] >= 61: + raise ValueError, "seconds out of range" + + if first < 2: + leap = first < 1 and leapMonth(d[0], d[1]) + + if d[2] > months[d[1]] + leap: + raise ValueError, "day out of range" + +def debugHeader(title): + s = '*** ' + title + ' ' + print s + ('*' * (72 - len(s))) + +def debugFooter(title): + print '*' * 72 + sys.stdout.flush() diff --git a/SOAPpy/__init__.py b/SOAPpy/__init__.py new file mode 100644 index 0000000..0e039f8 --- /dev/null +++ b/SOAPpy/__init__.py @@ -0,0 +1,15 @@ + +ident = '$Id: __init__.py,v 1.9 2004/01/31 04:20:06 warnes Exp $' +from version import __version__ + +from Client import * +from Config import * +from Errors import * +from NS import * +from Parser import * +from SOAPBuilder import * +from Server import * +from Types import * +from Utilities import * +import wstools +import WSDL diff --git a/SOAPpy/version.py b/SOAPpy/version.py new file mode 100644 index 0000000..eda6139 --- /dev/null +++ b/SOAPpy/version.py @@ -0,0 +1,2 @@ +__version__="0.11.6" + diff --git a/SOAPpy/wstools/Namespaces.py b/SOAPpy/wstools/Namespaces.py new file mode 100755 index 0000000..60eaa0f --- /dev/null +++ b/SOAPpy/wstools/Namespaces.py @@ -0,0 +1,92 @@ +#! /usr/bin/env python +"""Namespace module, so you don't need PyXML +""" + +try: + from xml.ns import SOAP, SCHEMA, WSDL, XMLNS, DSIG, ENCRYPTION +except: + class SOAP: + ENV = "http://schemas.xmlsoap.org/soap/envelope/" + ENC = "http://schemas.xmlsoap.org/soap/encoding/" + ACTOR_NEXT = "http://schemas.xmlsoap.org/soap/actor/next" + + class SCHEMA: + XSD1 = "http://www.w3.org/1999/XMLSchema" + XSD2 = "http://www.w3.org/2000/10/XMLSchema" + XSD3 = "http://www.w3.org/2001/XMLSchema" + XSD_LIST = [ XSD1, XSD2, XSD3 ] + XSI1 = "http://www.w3.org/1999/XMLSchema-instance" + XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" + XSI3 = "http://www.w3.org/2001/XMLSchema-instance" + XSI_LIST = [ XSI1, XSI2, XSI3 ] + BASE = XSD3 + + class WSDL: + BASE = "http://schemas.xmlsoap.org/wsdl/" + BIND_HTTP = "http://schemas.xmlsoap.org/wsdl/http/" + BIND_MIME = "http://schemas.xmlsoap.org/wsdl/mime/" + BIND_SOAP = "http://schemas.xmlsoap.org/wsdl/soap/" + + class XMLNS: + BASE = "http://www.w3.org/2000/xmlns/" + XML = "http://www.w3.org/XML/1998/namespace" + HTML = "http://www.w3.org/TR/REC-html40" + + class DSIG: + BASE = "http://www.w3.org/2000/09/xmldsig#" + C14N = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315" + C14N_COMM = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315#WithComments" + C14N_EXCL = "http://www.w3.org/2001/10/xml-exc-c14n#" + DIGEST_MD2 = "http://www.w3.org/2000/09/xmldsig#md2" + DIGEST_MD5 = "http://www.w3.org/2000/09/xmldsig#md5" + DIGEST_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1" + ENC_BASE64 = "http://www.w3.org/2000/09/xmldsig#base64" + ENVELOPED = "http://www.w3.org/2000/09/xmldsig#enveloped-signature" + HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1" + SIG_DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1" + SIG_RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + XPATH = "http://www.w3.org/TR/1999/REC-xpath-19991116" + XSLT = "http://www.w3.org/TR/1999/REC-xslt-19991116" + + class ENCRYPTION: + BASE = "http://www.w3.org/2001/04/xmlenc#" + BLOCK_3DES = "http://www.w3.org/2001/04/xmlenc#des-cbc" + BLOCK_AES128 = "http://www.w3.org/2001/04/xmlenc#aes128-cbc" + BLOCK_AES192 = "http://www.w3.org/2001/04/xmlenc#aes192-cbc" + BLOCK_AES256 = "http://www.w3.org/2001/04/xmlenc#aes256-cbc" + DIGEST_RIPEMD160 = "http://www.w3.org/2001/04/xmlenc#ripemd160" + DIGEST_SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256" + DIGEST_SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512" + KA_DH = "http://www.w3.org/2001/04/xmlenc#dh" + KT_RSA_1_5 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5" + KT_RSA_OAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" + STREAM_ARCFOUR = "http://www.w3.org/2001/04/xmlenc#arcfour" + WRAP_3DES = "http://www.w3.org/2001/04/xmlenc#kw-3des" + WRAP_AES128 = "http://www.w3.org/2001/04/xmlenc#kw-aes128" + WRAP_AES192 = "http://www.w3.org/2001/04/xmlenc#kw-aes192" + WRAP_AES256 = "http://www.w3.org/2001/04/xmlenc#kw-aes256" + + +class WSSE: + BASE = "http://schemas.xmlsoap.org/ws/2002/04/secext" + +class WSU: + BASE = "http://schemas.xmlsoap.org/ws/2002/04/utility" + UTILITY = "http://schemas.xmlsoap.org/ws/2002/07/utility" + +class WSR: + PROPERTIES = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceProperties" + LIFETIME = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceLifetime" + +class WSA: + ADDRESS = "http://schemas.xmlsoap.org/ws/2003/03/addressing" + ADDRESS2004 = "http://schemas.xmlsoap.org/ws/2004/03/addressing" + ANONYMOUS = "%s/role/anonymous" %ADDRESS + ANONYMOUS2004 = "%s/role/anonymous" %ADDRESS2004 + FAULT = "http://schemas.xmlsoap.org/ws/2004/03/addressing/fault" + +class WSP: + POLICY = "http://schemas.xmlsoap.org/ws/2002/12/policy" + + + diff --git a/SOAPpy/wstools/TimeoutSocket.py b/SOAPpy/wstools/TimeoutSocket.py new file mode 100755 index 0000000..2d804dd --- /dev/null +++ b/SOAPpy/wstools/TimeoutSocket.py @@ -0,0 +1,179 @@ +"""Based on code from timeout_socket.py, with some tweaks for compatibility. + These tweaks should really be rolled back into timeout_socket, but it's + not totally clear who is maintaining it at this point. In the meantime, + we'll use a different module name for our tweaked version to avoid any + confusion. + + The original timeout_socket is by: + + Scott Cotton + Lloyd Zusman + Phil Mayes + Piers Lauder + Radovan Garabik +""" + +ident = "$Id: TimeoutSocket.py,v 1.2 2003/05/20 21:10:12 warnes Exp $" + +import string, socket, select, errno + +WSAEINVAL = getattr(errno, 'WSAEINVAL', 10022) + + +class TimeoutSocket: + """A socket imposter that supports timeout limits.""" + + def __init__(self, timeout=20, sock=None): + self.timeout = float(timeout) + self.inbuf = '' + if sock is None: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock = sock + self.sock.setblocking(0) + self._rbuf = '' + self._wbuf = '' + + def __getattr__(self, name): + # Delegate to real socket attributes. + return getattr(self.sock, name) + + def connect(self, *addr): + timeout = self.timeout + sock = self.sock + try: + # Non-blocking mode + sock.setblocking(0) + apply(sock.connect, addr) + sock.setblocking(timeout != 0) + return 1 + except socket.error,why: + if not timeout: + raise + sock.setblocking(1) + if len(why.args) == 1: + code = 0 + else: + code, why = why + if code not in ( + errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK + ): + raise + r,w,e = select.select([],[sock],[],timeout) + if w: + try: + apply(sock.connect, addr) + return 1 + except socket.error,why: + if len(why.args) == 1: + code = 0 + else: + code, why = why + if code in (errno.EISCONN, WSAEINVAL): + return 1 + raise + raise TimeoutError('socket connect() timeout.') + + def send(self, data, flags=0): + total = len(data) + next = 0 + while 1: + r, w, e = select.select([],[self.sock], [], self.timeout) + if w: + buff = data[next:next + 8192] + sent = self.sock.send(buff, flags) + next = next + sent + if next == total: + return total + continue + raise TimeoutError('socket send() timeout.') + + def recv(self, amt, flags=0): + if select.select([self.sock], [], [], self.timeout)[0]: + return self.sock.recv(amt, flags) + raise TimeoutError('socket recv() timeout.') + + buffsize = 4096 + handles = 1 + + def makefile(self, mode="r", buffsize=-1): + self.handles = self.handles + 1 + self.mode = mode + return self + + def close(self): + self.handles = self.handles - 1 + if self.handles == 0 and self.sock.fileno() >= 0: + self.sock.close() + + def read(self, n=-1): + if not isinstance(n, type(1)): + n = -1 + if n >= 0: + k = len(self._rbuf) + if n <= k: + data = self._rbuf[:n] + self._rbuf = self._rbuf[n:] + return data + n = n - k + L = [self._rbuf] + self._rbuf = "" + while n > 0: + new = self.recv(max(n, self.buffsize)) + if not new: break + k = len(new) + if k > n: + L.append(new[:n]) + self._rbuf = new[n:] + break + L.append(new) + n = n - k + return "".join(L) + k = max(4096, self.buffsize) + L = [self._rbuf] + self._rbuf = "" + while 1: + new = self.recv(k) + if not new: break + L.append(new) + k = min(k*2, 1024**2) + return "".join(L) + + def readline(self, limit=-1): + data = "" + i = self._rbuf.find('\n') + while i < 0 and not (0 < limit <= len(self._rbuf)): + new = self.recv(self.buffsize) + if not new: break + i = new.find('\n') + if i >= 0: i = i + len(self._rbuf) + self._rbuf = self._rbuf + new + if i < 0: i = len(self._rbuf) + else: i = i+1 + if 0 <= limit < len(self._rbuf): i = limit + data, self._rbuf = self._rbuf[:i], self._rbuf[i:] + return data + + def readlines(self, sizehint = 0): + total = 0 + list = [] + while 1: + line = self.readline() + if not line: break + list.append(line) + total += len(line) + if sizehint and total >= sizehint: + break + return list + + def writelines(self, list): + self.send(''.join(list)) + + def write(self, data): + self.send(data) + + def flush(self): + pass + + +class TimeoutError(Exception): + pass diff --git a/SOAPpy/wstools/UserTuple.py b/SOAPpy/wstools/UserTuple.py new file mode 100755 index 0000000..5b193e7 --- /dev/null +++ b/SOAPpy/wstools/UserTuple.py @@ -0,0 +1,99 @@ +""" +A more or less complete user-defined wrapper around tuple objects. +Adapted version of the standard library's UserList. + +Taken from Stefan Schwarzer's ftputil library, available at +, and used under this license: + + + + +Copyright (C) 1999, Stefan Schwarzer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of the above author nor the names of the + contributors to the software may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" + + + + +# $Id: UserTuple.py,v 1.1 2003/07/21 14:18:54 warnes Exp $ + +#XXX tuple instances (in Python 2.2) contain also: +# __class__, __delattr__, __getattribute__, __hash__, __new__, +# __reduce__, __setattr__, __str__ +# What about these? + +class UserTuple: + def __init__(self, inittuple=None): + self.data = () + if inittuple is not None: + # XXX should this accept an arbitrary sequence? + if type(inittuple) == type(self.data): + self.data = inittuple + elif isinstance(inittuple, UserTuple): + # this results in + # self.data is inittuple.data + # but that's ok for tuples because they are + # immutable. (Builtin tuples behave the same.) + self.data = inittuple.data[:] + else: + # the same applies here; (t is tuple(t)) == 1 + self.data = tuple(inittuple) + def __repr__(self): return repr(self.data) + def __lt__(self, other): return self.data < self.__cast(other) + def __le__(self, other): return self.data <= self.__cast(other) + def __eq__(self, other): return self.data == self.__cast(other) + def __ne__(self, other): return self.data != self.__cast(other) + def __gt__(self, other): return self.data > self.__cast(other) + def __ge__(self, other): return self.data >= self.__cast(other) + def __cast(self, other): + if isinstance(other, UserTuple): return other.data + else: return other + def __cmp__(self, other): + return cmp(self.data, self.__cast(other)) + def __contains__(self, item): return item in self.data + def __len__(self): return len(self.data) + def __getitem__(self, i): return self.data[i] + def __getslice__(self, i, j): + i = max(i, 0); j = max(j, 0) + return self.__class__(self.data[i:j]) + def __add__(self, other): + if isinstance(other, UserTuple): + return self.__class__(self.data + other.data) + elif isinstance(other, type(self.data)): + return self.__class__(self.data + other) + else: + return self.__class__(self.data + tuple(other)) + # dir( () ) contains no __radd__ (at least in Python 2.2) + def __mul__(self, n): + return self.__class__(self.data*n) + __rmul__ = __mul__ + diff --git a/SOAPpy/wstools/Utility.py b/SOAPpy/wstools/Utility.py new file mode 100755 index 0000000..b2bfb3c --- /dev/null +++ b/SOAPpy/wstools/Utility.py @@ -0,0 +1,839 @@ +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. + +ident = "$Id: Utility.py,v 1.15 2004/04/29 01:40:49 boverhof Exp $" + +import types +import string, httplib, smtplib, urllib, socket, weakref +import xml.dom.minidom +from string import join, strip, split +from UserDict import UserDict +from StringIO import StringIO +from TimeoutSocket import TimeoutSocket, TimeoutError +from urlparse import urlparse +from httplib import HTTPConnection, HTTPSConnection +from exceptions import Exception + +try: + from xml.dom.ext import SplitQName +except: + def SplitQName(qname): + '''SplitQName(qname) -> (string, string) + + Split Qualified Name into a tuple of len 2, consisting + of the prefix and the local name. + + (prefix, localName) + + Special Cases: + xmlns -- (localName, 'xmlns') + None -- (None, localName) + ''' + + l = qname.split(':') + if len(l) == 1: + l.insert(0, None) + elif len(l) == 2: + if l[0] == 'xmlns': + l.reverse() + else: + return + return tuple(l) + +class RecursionError(Exception): + """Used to indicate a HTTP redirect recursion.""" + pass + +class HTTPResponse: + """Captures the information in an HTTP response message.""" + + def __init__(self, response): + self.status = response.status + self.reason = response.reason + self.headers = response.msg + self.body = response.read() or None + response.close() + +class TimeoutHTTP(HTTPConnection): + """A custom http connection object that supports socket timeout.""" + def __init__(self, host, port=None, timeout=20): + HTTPConnection.__init__(self, host, port) + self.timeout = timeout + + def connect(self): + self.sock = TimeoutSocket(self.timeout) + self.sock.connect((self.host, self.port)) + + +class TimeoutHTTPS(HTTPSConnection): + """A custom https object that supports socket timeout. Note that this + is not really complete. The builtin SSL support in the Python socket + module requires a real socket (type) to be passed in to be hooked to + SSL. That means our fake socket won't work and our timeout hacks are + bypassed for send and recv calls. Since our hack _is_ in place at + connect() time, it should at least provide some timeout protection.""" + def __init__(self, host, port=None, timeout=20, **kwargs): + HTTPSConnection.__init__(self, str(host), port, **kwargs) + self.timeout = timeout + + def connect(self): + sock = TimeoutSocket(self.timeout) + sock.connect((self.host, self.port)) + realsock = getattr(sock.sock, '_sock', sock.sock) + ssl = socket.ssl(realsock, self.key_file, self.cert_file) + self.sock = httplib.FakeSocket(sock, ssl) + +def urlopen(url, timeout=20, redirects=None): + """A minimal urlopen replacement hack that supports timeouts for http. + Note that this supports GET only.""" + scheme, host, path, params, query, frag = urlparse(url) + if not scheme in ('http', 'https'): + return urllib.urlopen(url) + if params: path = '%s;%s' % (path, params) + if query: path = '%s?%s' % (path, query) + if frag: path = '%s#%s' % (path, frag) + + if scheme == 'https': + # If ssl is not compiled into Python, you will not get an exception + # until a conn.endheaders() call. We need to know sooner, so use + # getattr. + if hasattr(socket, 'ssl'): + conn = TimeoutHTTPS(host, None, timeout) + else: + import M2Crypto + ctx = M2Crypto.SSL.Context() + ctx.set_session_timeout(timeout) + conn = M2Crypto.httpslib.HTTPSConnection(host, ssl_context=ctx) + #conn.set_debuglevel(1) + else: + conn = TimeoutHTTP(host, None, timeout) + + conn.putrequest('GET', path) + conn.putheader('Connection', 'close') + conn.endheaders() + response = None + while 1: + response = conn.getresponse() + if response.status != 100: + break + conn._HTTPConnection__state = httplib._CS_REQ_SENT + conn._HTTPConnection__response = None + + status = response.status + + # If we get an HTTP redirect, we will follow it automatically. + if status >= 300 and status < 400: + location = response.msg.getheader('location') + if location is not None: + response.close() + if redirects is not None and redirects.has_key(location): + raise RecursionError( + 'Circular HTTP redirection detected.' + ) + if redirects is None: + redirects = {} + redirects[location] = 1 + return urlopen(location, timeout, redirects) + raise HTTPResponse(response) + + if not (status >= 200 and status < 300): + raise HTTPResponse(response) + + body = StringIO(response.read()) + response.close() + return body + +class DOM: + """The DOM singleton defines a number of XML related constants and + provides a number of utility methods for DOM related tasks. It + also provides some basic abstractions so that the rest of the + package need not care about actual DOM implementation in use.""" + + # Namespace stuff related to the SOAP specification. + + NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/' + NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/' + + NS_SOAP_ENV_1_2 = 'http://www.w3.org/2001/06/soap-envelope' + NS_SOAP_ENC_1_2 = 'http://www.w3.org/2001/06/soap-encoding' + + NS_SOAP_ENV_ALL = (NS_SOAP_ENV_1_1, NS_SOAP_ENV_1_2) + NS_SOAP_ENC_ALL = (NS_SOAP_ENC_1_1, NS_SOAP_ENC_1_2) + + NS_SOAP_ENV = NS_SOAP_ENV_1_1 + NS_SOAP_ENC = NS_SOAP_ENC_1_1 + + _soap_uri_mapping = { + NS_SOAP_ENV_1_1 : '1.1', + NS_SOAP_ENV_1_2 : '1.2', + } + + SOAP_ACTOR_NEXT_1_1 = 'http://schemas.xmlsoap.org/soap/actor/next' + SOAP_ACTOR_NEXT_1_2 = 'http://www.w3.org/2001/06/soap-envelope/actor/next' + SOAP_ACTOR_NEXT_ALL = (SOAP_ACTOR_NEXT_1_1, SOAP_ACTOR_NEXT_1_2) + + def SOAPUriToVersion(self, uri): + """Return the SOAP version related to an envelope uri.""" + value = self._soap_uri_mapping.get(uri) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP envelope uri: %s' % uri + ) + + def GetSOAPEnvUri(self, version): + """Return the appropriate SOAP envelope uri for a given + human-friendly SOAP version string (e.g. '1.1').""" + attrname = 'NS_SOAP_ENV_%s' % join(split(version, '.'), '_') + value = getattr(self, attrname, None) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP version: %s' % version + ) + + def GetSOAPEncUri(self, version): + """Return the appropriate SOAP encoding uri for a given + human-friendly SOAP version string (e.g. '1.1').""" + attrname = 'NS_SOAP_ENC_%s' % join(split(version, '.'), '_') + value = getattr(self, attrname, None) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP version: %s' % version + ) + + def GetSOAPActorNextUri(self, version): + """Return the right special next-actor uri for a given + human-friendly SOAP version string (e.g. '1.1').""" + attrname = 'SOAP_ACTOR_NEXT_%s' % join(split(version, '.'), '_') + value = getattr(self, attrname, None) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP version: %s' % version + ) + + + # Namespace stuff related to XML Schema. + + NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema' + NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance' + + NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema' + NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance' + + NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema' + NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance' + + NS_XSD_ALL = (NS_XSD_99, NS_XSD_00, NS_XSD_01) + NS_XSI_ALL = (NS_XSI_99, NS_XSI_00, NS_XSI_01) + + NS_XSD = NS_XSD_01 + NS_XSI = NS_XSI_01 + + _xsd_uri_mapping = { + NS_XSD_99 : NS_XSI_99, + NS_XSD_00 : NS_XSI_00, + NS_XSD_01 : NS_XSI_01, + } + + for key, value in _xsd_uri_mapping.items(): + _xsd_uri_mapping[value] = key + + + def InstanceUriForSchemaUri(self, uri): + """Return the appropriate matching XML Schema instance uri for + the given XML Schema namespace uri.""" + return self._xsd_uri_mapping.get(uri) + + def SchemaUriForInstanceUri(self, uri): + """Return the appropriate matching XML Schema namespace uri for + the given XML Schema instance namespace uri.""" + return self._xsd_uri_mapping.get(uri) + + + # Namespace stuff related to WSDL. + + NS_WSDL_1_1 = 'http://schemas.xmlsoap.org/wsdl/' + NS_WSDL_ALL = (NS_WSDL_1_1,) + NS_WSDL = NS_WSDL_1_1 + + NS_SOAP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/' + NS_HTTP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/http/' + NS_MIME_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/mime/' + + NS_SOAP_BINDING_ALL = (NS_SOAP_BINDING_1_1,) + NS_HTTP_BINDING_ALL = (NS_HTTP_BINDING_1_1,) + NS_MIME_BINDING_ALL = (NS_MIME_BINDING_1_1,) + + NS_SOAP_BINDING = NS_SOAP_BINDING_1_1 + NS_HTTP_BINDING = NS_HTTP_BINDING_1_1 + NS_MIME_BINDING = NS_MIME_BINDING_1_1 + + NS_SOAP_HTTP_1_1 = 'http://schemas.xmlsoap.org/soap/http' + NS_SOAP_HTTP_ALL = (NS_SOAP_HTTP_1_1,) + NS_SOAP_HTTP = NS_SOAP_HTTP_1_1 + + + _wsdl_uri_mapping = { + NS_WSDL_1_1 : '1.1', + } + + def WSDLUriToVersion(self, uri): + """Return the WSDL version related to a WSDL namespace uri.""" + value = self._wsdl_uri_mapping.get(uri) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP envelope uri: %s' % uri + ) + + def GetWSDLUri(self, version): + attr = 'NS_WSDL_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + def GetWSDLSoapBindingUri(self, version): + attr = 'NS_SOAP_BINDING_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + def GetWSDLHttpBindingUri(self, version): + attr = 'NS_HTTP_BINDING_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + def GetWSDLMimeBindingUri(self, version): + attr = 'NS_MIME_BINDING_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + def GetWSDLHttpTransportUri(self, version): + attr = 'NS_SOAP_HTTP_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + + # Other xml namespace constants. + NS_XMLNS = 'http://www.w3.org/2000/xmlns/' + + + + def isElement(self, node, name, nsuri=None): + """Return true if the given node is an element with the given + name and optional namespace uri.""" + if node.nodeType != node.ELEMENT_NODE: + return 0 + return node.localName == name and \ + (nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri)) + + def getElement(self, node, name, nsuri=None, default=join): + """Return the first child of node with a matching name and + namespace uri, or the default if one is provided.""" + nsmatch = self.nsUriMatch + ELEMENT_NODE = node.ELEMENT_NODE + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if ((child.localName == name or name is None) and + (nsuri is None or nsmatch(child.namespaceURI, nsuri)) + ): + return child + if default is not join: + return default + raise KeyError, name + + def getElementById(self, node, id, default=join): + """Return the first child of node matching an id reference.""" + attrget = self.getAttr + ELEMENT_NODE = node.ELEMENT_NODE + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if attrget(child, 'id') == id: + return child + if default is not join: + return default + raise KeyError, name + + def getMappingById(self, document, depth=None, element=None, + mapping=None, level=1): + """Create an id -> element mapping of those elements within a + document that define an id attribute. The depth of the search + may be controlled by using the (1-based) depth argument.""" + if document is not None: + element = document.documentElement + mapping = {} + attr = element._attrs.get('id', None) + if attr is not None: + mapping[attr.value] = element + if depth is None or depth > level: + level = level + 1 + ELEMENT_NODE = element.ELEMENT_NODE + for child in element.childNodes: + if child.nodeType == ELEMENT_NODE: + self.getMappingById(None, depth, child, mapping, level) + return mapping + + def getElements(self, node, name, nsuri=None): + """Return a sequence of the child elements of the given node that + match the given name and optional namespace uri.""" + nsmatch = self.nsUriMatch + result = [] + ELEMENT_NODE = node.ELEMENT_NODE + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if ((child.localName == name or name is None) and ( + (nsuri is None) or nsmatch(child.namespaceURI, nsuri))): + result.append(child) + return result + + def hasAttr(self, node, name, nsuri=None): + """Return true if element has attribute with the given name and + optional nsuri. If nsuri is not specified, returns true if an + attribute exists with the given name with any namespace.""" + if nsuri is None: + if node.hasAttribute(name): + return True + return False + return node.hasAttributeNS(nsuri, name) + + def getAttr(self, node, name, nsuri=None, default=join): + """Return the value of the attribute named 'name' with the + optional nsuri, or the default if one is specified. If + nsuri is not specified, an attribute that matches the + given name will be returned regardless of namespace.""" + if nsuri is None: + result = node._attrs.get(name, None) + if result is None: + for item in node._attrsNS.keys(): + if item[1] == name: + result = node._attrsNS[item] + break + else: + result = node._attrsNS.get((nsuri, name), None) + if result is not None: + return result.value + if default is not join: + return default + return '' + + def getAttrs(self, node): + """Return a Collection of all attributes + """ + attrs = {} + for k,v in node._attrs.items(): + attrs[k] = v.value + return attrs + + def getElementText(self, node, preserve_ws=None): + """Return the text value of an xml element node. Leading and trailing + whitespace is stripped from the value unless the preserve_ws flag + is passed with a true value.""" + result = [] + for child in node.childNodes: + nodetype = child.nodeType + if nodetype == child.TEXT_NODE or \ + nodetype == child.CDATA_SECTION_NODE: + result.append(child.nodeValue) + value = join(result, '') + if preserve_ws is None: + value = strip(value) + return value + + def findNamespaceURI(self, prefix, node): + """Find a namespace uri given a prefix and a context node.""" + attrkey = (self.NS_XMLNS, prefix) + DOCUMENT_NODE = node.DOCUMENT_NODE + ELEMENT_NODE = node.ELEMENT_NODE + while 1: + if node.nodeType != ELEMENT_NODE: + node = node.parentNode + continue + result = node._attrsNS.get(attrkey, None) + if result is not None: + return result.value + if hasattr(node, '__imported__'): + raise DOMException('Value for prefix %s not found.' % prefix) + node = node.parentNode + if node.nodeType == DOCUMENT_NODE: + raise DOMException('Value for prefix %s not found.' % prefix) + + def findDefaultNS(self, node): + """Return the current default namespace uri for the given node.""" + attrkey = (self.NS_XMLNS, 'xmlns') + DOCUMENT_NODE = node.DOCUMENT_NODE + ELEMENT_NODE = node.ELEMENT_NODE + while 1: + if node.nodeType != ELEMENT_NODE: + node = node.parentNode + continue + result = node._attrsNS.get(attrkey, None) + if result is not None: + return result.value + if hasattr(node, '__imported__'): + raise DOMException('Cannot determine default namespace.') + node = node.parentNode + if node.nodeType == DOCUMENT_NODE: + raise DOMException('Cannot determine default namespace.') + + def findTargetNS(self, node): + """Return the defined target namespace uri for the given node.""" + attrget = self.getAttr + attrkey = (self.NS_XMLNS, 'xmlns') + DOCUMENT_NODE = node.DOCUMENT_NODE + ELEMENT_NODE = node.ELEMENT_NODE + while 1: + if node.nodeType != ELEMENT_NODE: + node = node.parentNode + continue + result = attrget(node, 'targetNamespace', default=None) + if result is not None: + return result + node = node.parentNode + if node.nodeType == DOCUMENT_NODE: + raise DOMException('Cannot determine target namespace.') + + def getTypeRef(self, element): + """Return (namespaceURI, name) for a type attribue of the given + element, or None if the element does not have a type attribute.""" + typeattr = self.getAttr(element, 'type', default=None) + if typeattr is None: + return None + parts = typeattr.split(':', 1) + if len(parts) == 2: + nsuri = self.findNamespaceURI(parts[0], element) + else: + nsuri = self.findDefaultNS(element) + return (nsuri, parts[1]) + + def importNode(self, document, node, deep=0): + """Implements (well enough for our purposes) DOM node import.""" + nodetype = node.nodeType + if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE): + raise DOMException('Illegal node type for importNode') + if nodetype == node.ENTITY_REFERENCE_NODE: + deep = 0 + clone = node.cloneNode(deep) + self._setOwnerDoc(document, clone) + clone.__imported__ = 1 + return clone + + def _setOwnerDoc(self, document, node): + node.ownerDocument = document + for child in node.childNodes: + self._setOwnerDoc(document, child) + + def nsUriMatch(self, value, wanted, strict=0, tt=type(())): + """Return a true value if two namespace uri values match.""" + if value == wanted or (type(wanted) is tt) and value in wanted: + return 1 + if not strict: + wanted = type(wanted) is tt and wanted or (wanted,) + value = value[-1:] != '/' and value or value[:-1] + for item in wanted: + if item == value or item[:-1] == value: + return 1 + return 0 + + def createDocument(self, nsuri, qname, doctype=None): + """Create a new writable DOM document object.""" + impl = xml.dom.minidom.getDOMImplementation() + return impl.createDocument(nsuri, qname, doctype) + + def loadDocument(self, data): + """Load an xml file from a file-like object and return a DOM + document instance.""" + return xml.dom.minidom.parse(data) + + def loadFromURL(self, url): + """Load an xml file from a URL and return a DOM document.""" + file = urlopen(url) + try: result = self.loadDocument(file) + finally: file.close() + return result + + +class DOMException(Exception): + pass + +DOM = DOM() + + +class Collection(UserDict): + """Helper class for maintaining ordered named collections.""" + default = lambda self,k: k.name + def __init__(self, parent, key=None): + UserDict.__init__(self) + self.parent = weakref.ref(parent) + self.list = [] + self._func = key or self.default + + def __getitem__(self, key): + if type(key) is type(1): + return self.list[key] + return self.data[key] + + def __setitem__(self, key, item): + item.parent = weakref.ref(self) + self.list.append(item) + self.data[key] = item + + def keys(self): + return map(lambda i: self._func(i), self.list) + + def items(self): + return map(lambda i: (self._func(i), i), self.list) + + def values(self): + return self.list + + +class CollectionNS(UserDict): + """Helper class for maintaining ordered named collections.""" + default = lambda self,k: k.name + def __init__(self, parent, key=None): + UserDict.__init__(self) + self.parent = weakref.ref(parent) + self.targetNamespace = None + self.list = [] + self._func = key or self.default + + def __getitem__(self, key): + self.targetNamespace = self.parent().targetNamespace + if type(key) is types.IntType: + return self.list[key] + elif self.__isSequence(key): + nsuri,name = key + return self.data[nsuri][name] + return self.data[self.parent().targetNamespace][key] + + def __setitem__(self, key, item): + item.parent = weakref.ref(self) + self.list.append(item) + targetNamespace = getattr(item, 'targetNamespace', self.parent().targetNamespace) + if not self.data.has_key(targetNamespace): + self.data[targetNamespace] = {} + self.data[targetNamespace][key] = item + + def __isSequence(self, key): + return (type(key) in (types.TupleType,types.ListType) and len(key) == 2) + + def keys(self): + keys = [] + for tns in self.data.keys(): + keys.append(map(lambda i: (tns,self._func(i)), self.data[tns].values())) + return keys + + def items(self): + return map(lambda i: (self._func(i), i), self.list) + + def values(self): + return self.list + + + +# This is a runtime guerilla patch for pulldom (used by minidom) so +# that xml namespace declaration attributes are not lost in parsing. +# We need them to do correct QName linking for XML Schema and WSDL. +# The patch has been submitted to SF for the next Python version. + +from xml.dom.pulldom import PullDOM, START_ELEMENT +if 1: + def startPrefixMapping(self, prefix, uri): + if not hasattr(self, '_xmlns_attrs'): + self._xmlns_attrs = [] + self._xmlns_attrs.append((prefix or 'xmlns', uri)) + self._ns_contexts.append(self._current_context.copy()) + self._current_context[uri] = prefix or '' + + PullDOM.startPrefixMapping = startPrefixMapping + + def startElementNS(self, name, tagName , attrs): + # Retrieve xml namespace declaration attributes. + xmlns_uri = 'http://www.w3.org/2000/xmlns/' + xmlns_attrs = getattr(self, '_xmlns_attrs', None) + if xmlns_attrs is not None: + for aname, value in xmlns_attrs: + attrs._attrs[(xmlns_uri, aname)] = value + self._xmlns_attrs = [] + uri, localname = name + if uri: + # When using namespaces, the reader may or may not + # provide us with the original name. If not, create + # *a* valid tagName from the current context. + if tagName is None: + prefix = self._current_context[uri] + if prefix: + tagName = prefix + ":" + localname + else: + tagName = localname + if self.document: + node = self.document.createElementNS(uri, tagName) + else: + node = self.buildDocument(uri, tagName) + else: + # When the tagname is not prefixed, it just appears as + # localname + if self.document: + node = self.document.createElement(localname) + else: + node = self.buildDocument(None, localname) + + for aname,value in attrs.items(): + a_uri, a_localname = aname + if a_uri == xmlns_uri: + if a_localname == 'xmlns': + qname = a_localname + else: + qname = 'xmlns:' + a_localname + attr = self.document.createAttributeNS(a_uri, qname) + node.setAttributeNodeNS(attr) + elif a_uri: + prefix = self._current_context[a_uri] + if prefix: + qname = prefix + ":" + a_localname + else: + qname = a_localname + attr = self.document.createAttributeNS(a_uri, qname) + node.setAttributeNodeNS(attr) + else: + attr = self.document.createAttribute(a_localname) + node.setAttributeNode(attr) + attr.value = value + + self.lastEvent[1] = [(START_ELEMENT, node), None] + self.lastEvent = self.lastEvent[1] + self.push(node) + + PullDOM.startElementNS = startElementNS + +# +# This is a runtime guerilla patch for minidom so +# that xmlns prefixed attributes dont raise AttributeErrors +# during cloning. +# +# Namespace declarations can appear in any start-tag, must look for xmlns +# prefixed attribute names during cloning. +# +# key (attr.namespaceURI, tag) +# ('http://www.w3.org/2000/xmlns/', u'xsd') +# ('http://www.w3.org/2000/xmlns/', 'xmlns') +# +# xml.dom.minidom.Attr.nodeName = xmlns:xsd +# xml.dom.minidom.Attr.value = = http://www.w3.org/2001/XMLSchema + +if 1: + def _clone_node(node, deep, newOwnerDocument): + """ + Clone a node and give it the new owner document. + Called by Node.cloneNode and Document.importNode + """ + if node.ownerDocument.isSameNode(newOwnerDocument): + operation = xml.dom.UserDataHandler.NODE_CLONED + else: + operation = xml.dom.UserDataHandler.NODE_IMPORTED + if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE: + clone = newOwnerDocument.createElementNS(node.namespaceURI, + node.nodeName) + for attr in node.attributes.values(): + clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value) + + prefix, tag = xml.dom.minidom._nssplit(attr.nodeName) + if prefix == 'xmlns': + a = clone.getAttributeNodeNS(attr.namespaceURI, tag) + elif prefix: + a = clone.getAttributeNodeNS(attr.namespaceURI, tag) + else: + a = clone.getAttributeNodeNS(attr.namespaceURI, attr.nodeName) + a.specified = attr.specified + + if deep: + for child in node.childNodes: + c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument) + clone.appendChild(c) + elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_FRAGMENT_NODE: + clone = newOwnerDocument.createDocumentFragment() + if deep: + for child in node.childNodes: + c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument) + clone.appendChild(c) + + elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE: + clone = newOwnerDocument.createTextNode(node.data) + elif node.nodeType == xml.dom.minidom.Node.CDATA_SECTION_NODE: + clone = newOwnerDocument.createCDATASection(node.data) + elif node.nodeType == xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE: + clone = newOwnerDocument.createProcessingInstruction(node.target, + node.data) + elif node.nodeType == xml.dom.minidom.Node.COMMENT_NODE: + clone = newOwnerDocument.createComment(node.data) + elif node.nodeType == xml.dom.minidom.Node.ATTRIBUTE_NODE: + clone = newOwnerDocument.createAttributeNS(node.namespaceURI, + node.nodeName) + clone.specified = True + clone.value = node.value + elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_TYPE_NODE: + assert node.ownerDocument is not newOwnerDocument + operation = xml.dom.UserDataHandler.NODE_IMPORTED + clone = newOwnerDocument.implementation.createDocumentType( + node.name, node.publicId, node.systemId) + clone.ownerDocument = newOwnerDocument + if deep: + clone.entities._seq = [] + clone.notations._seq = [] + for n in node.notations._seq: + notation = xml.dom.minidom.Notation(n.nodeName, n.publicId, n.systemId) + notation.ownerDocument = newOwnerDocument + clone.notations._seq.append(notation) + if hasattr(n, '_call_user_data_handler'): + n._call_user_data_handler(operation, n, notation) + for e in node.entities._seq: + entity = xml.dom.minidom.Entity(e.nodeName, e.publicId, e.systemId, + e.notationName) + entity.actualEncoding = e.actualEncoding + entity.encoding = e.encoding + entity.version = e.version + entity.ownerDocument = newOwnerDocument + clone.entities._seq.append(entity) + if hasattr(e, '_call_user_data_handler'): + e._call_user_data_handler(operation, n, entity) + else: + # Note the cloning of Document and DocumentType nodes is + # implemenetation specific. minidom handles those cases + # directly in the cloneNode() methods. + raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node)) + + # Check for _call_user_data_handler() since this could conceivably + # used with other DOM implementations (one of the FourThought + # DOMs, perhaps?). + if hasattr(node, '_call_user_data_handler'): + node._call_user_data_handler(operation, node, clone) + return clone + + xml.dom.minidom._clone_node = _clone_node diff --git a/SOAPpy/wstools/WSDLTools.py b/SOAPpy/wstools/WSDLTools.py new file mode 100755 index 0000000..ad4e58e --- /dev/null +++ b/SOAPpy/wstools/WSDLTools.py @@ -0,0 +1,1336 @@ +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. + +ident = "$Id: WSDLTools.py,v 1.23 2004/09/09 04:37:55 boverhof Exp $" + +from Utility import DOM, Collection, CollectionNS +from XMLSchema import XMLSchema, SchemaReader, WSDLToolsAdapter +from Namespaces import WSR, WSA +from StringIO import StringIO +import urllib + + +class WSDLReader: + """A WSDLReader creates WSDL instances from urls and xml data.""" + + # Custom subclasses of WSDLReader may wish to implement a caching + # strategy or other optimizations. Because application needs vary + # so widely, we don't try to provide any caching by default. + + def loadFromStream(self, stream, name=None): + """Return a WSDL instance loaded from a stream object.""" + document = DOM.loadDocument(stream) + wsdl = WSDL() + if name: + wsdl.location = name + elif hasattr(stream, 'name'): + wsdl.location = stream.name + wsdl.load(document) + return wsdl + + def loadFromURL(self, url): + """Return a WSDL instance loaded from the given url.""" + document = DOM.loadFromURL(url) + wsdl = WSDL() + wsdl.location = url + wsdl.load(document) + return wsdl + + def loadFromString(self, data): + """Return a WSDL instance loaded from an xml string.""" + return self.loadFromStream(StringIO(data)) + + def loadFromFile(self, filename): + """Return a WSDL instance loaded from the given file.""" + file = open(filename, 'rb') + try: + wsdl = self.loadFromStream(file) + finally: + file.close() + return wsdl + +class WSDL: + """A WSDL object models a WSDL service description. WSDL objects + may be created manually or loaded from an xml representation + using a WSDLReader instance.""" + + def __init__(self, targetNamespace=None, strict=1): + self.targetNamespace = targetNamespace or 'urn:this-document.wsdl' + self.documentation = '' + self.location = None + self.document = None + self.name = None + self.services = CollectionNS(self) + self.messages = CollectionNS(self) + self.portTypes = CollectionNS(self) + self.bindings = CollectionNS(self) + #self.imports = Collection(self) + self.types = Types(self) + self.extensions = [] + self.strict = strict + + def __del__(self): + if self.document is not None: + self.document.unlink() + + version = '1.1' + + def addService(self, name, documentation='', targetNamespace=None): + if self.services.has_key(name): + raise WSDLError( + 'Duplicate service element: %s' % name + ) + item = Service(name, documentation) + if targetNamespace: + item.targetNamespace = targetNamespace + self.services[name] = item + return item + + def addMessage(self, name, documentation='', targetNamespace=None): + if self.messages.has_key(name): + raise WSDLError( + 'Duplicate message element: %s.' % name + ) + item = Message(name, documentation) + if targetNamespace: + item.targetNamespace = targetNamespace + self.messages[name] = item + return item + + def addPortType(self, name, documentation='', targetNamespace=None): + if self.portTypes.has_key(name): + raise WSDLError( + 'Duplicate portType element: name' + ) + item = PortType(name, documentation) + if targetNamespace: + item.targetNamespace = targetNamespace + self.portTypes[name] = item + return item + + def addBinding(self, name, type, documentation='', targetNamespace=None): + if self.bindings.has_key(name): + raise WSDLError( + 'Duplicate binding element: %s' % name + ) + item = Binding(name, type, documentation) + if targetNamespace: + item.targetNamespace = targetNamespace + self.bindings[name] = item + return item + + #def addImport(self, namespace, location): + # item = ImportElement(namespace, location) + # self.imports[namespace] = item + # return item + + def load(self, document): + # We save a reference to the DOM document to ensure that elements + # saved as "extensions" will continue to have a meaningful context + # for things like namespace references. The lifetime of the DOM + # document is bound to the lifetime of the WSDL instance. + self.document = document + + definitions = DOM.getElement(document, 'definitions', None, None) + if definitions is None: + raise WSDLError( + 'Missing element.' + ) + self.version = DOM.WSDLUriToVersion(definitions.namespaceURI) + NS_WSDL = DOM.GetWSDLUri(self.version) + + self.targetNamespace = DOM.getAttr(definitions, 'targetNamespace', + None, None) + self.name = DOM.getAttr(definitions, 'name', None, None) + self.documentation = GetDocumentation(definitions) + + # Resolve (recursively) any import elements in the document. + imported = {} + base_location = self.location + while 1: + #XXX + imports = [] + for element in DOM.getElements(definitions, 'import', NS_WSDL): + location = DOM.getAttr(element, 'location') + # Resolve relative location, and save + location = urllib.basejoin(base_location, location) + + if not imported.has_key(location): + imports.append(element) + + if not imports: + break + for element in imports: + location = DOM.getAttr(element, 'location') + self._import(document, element, base_location) + location = urllib.basejoin(base_location, location) + imported[location] = 1 + base_location = '' + + #reader = SchemaReader(base_url=self.location) + for element in DOM.getElements(definitions, None, None): + targetNamespace = DOM.getAttr(element, 'targetNamespace') + localName = element.localName + + if not DOM.nsUriMatch(element.namespaceURI, NS_WSDL): + if localName == 'schema': + reader = SchemaReader(base_url=self.location) + schema = reader.loadFromNode(WSDLToolsAdapter(self), element) + schema.setBaseUrl(self.location) + self.types.addSchema(schema) + else: + self.extensions.append(element) + continue + + elif localName == 'message': + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + message = self.addMessage(name, docs, targetNamespace) + parts = DOM.getElements(element, 'part', NS_WSDL) + message.load(parts) + continue + + elif localName == 'portType': + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + ptype = self.addPortType(name, docs, targetNamespace) + #operations = DOM.getElements(element, 'operation', NS_WSDL) + #ptype.load(operations) + ptype.load(element) + continue + + elif localName == 'binding': + name = DOM.getAttr(element, 'name') + type = DOM.getAttr(element, 'type', default=None) + if type is None: + raise WSDLError( + 'Missing type attribute for binding %s.' % name + ) + type = ParseQName(type, element) + docs = GetDocumentation(element) + binding = self.addBinding(name, type, docs, targetNamespace) + operations = DOM.getElements(element, 'operation', NS_WSDL) + binding.load(operations) + binding.load_ex(GetExtensions(element)) + continue + + elif localName == 'service': + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + service = self.addService(name, docs, targetNamespace) + ports = DOM.getElements(element, 'port', NS_WSDL) + service.load(ports) + service.load_ex(GetExtensions(element)) + continue + + elif localName == 'types': + self.types.documentation = GetDocumentation(element) + base_location = DOM.getAttr(element, 'base-location') + if base_location: + element.removeAttribute('base-location') + base_location = base_location or self.location + reader = SchemaReader(base_url=base_location) + for item in DOM.getElements(element, None, None): + if item.localName == 'schema': + schema = reader.loadFromNode(WSDLToolsAdapter(self), item) + # XXX could have been imported + #schema.setBaseUrl(self.location) + schema.setBaseUrl(base_location) + self.types.addSchema(schema) + else: + self.types.addExtension(item) + # XXX remove the attribute + # element.removeAttribute('base-location') + continue + + def _import(self, document, element, base_location=None): + '''Algo take element's children, clone them, + and add them to the main document. Support for relative + locations is a bit complicated. The orig document context + is lost, so we need to store base location in DOM elements + representing , by creating a special temporary + "base-location" attribute, and , by resolving + the relative "location" and storing it as "location". + + document -- document we are loading + element -- DOM Element representing + base_location -- location of document from which this + was gleaned. + ''' + namespace = DOM.getAttr(element, 'namespace', default=None) + location = DOM.getAttr(element, 'location', default=None) + if namespace is None or location is None: + raise WSDLError( + 'Invalid import element (missing namespace or location).' + ) + if base_location: + location = urllib.basejoin(base_location, location) + element.setAttributeNS(None, 'location', location) + + #location = urllib.basejoin(self.location, location) + #obimport = self.addImport(namespace, location) + #obimport._loaded = 1 + + importdoc = DOM.loadFromURL(location) + try: + if location.find('#') > -1: + idref = location.split('#')[-1] + imported = DOM.getElementById(importdoc, idref) + else: + imported = importdoc.documentElement + if imported is None: + raise WSDLError( + 'Import target element not found for: %s' % location + ) + + imported_tns = DOM.findTargetNS(imported) + if imported_tns != namespace: + return + + if imported.localName == 'definitions': + imported_nodes = imported.childNodes + else: + imported_nodes = [imported] + parent = element.parentNode + for node in imported_nodes: + if node.nodeType != node.ELEMENT_NODE: + continue + child = DOM.importNode(document, node, 1) + parent.appendChild(child) + child.setAttribute('targetNamespace', namespace) + attrsNS = imported._attrsNS + for attrkey in attrsNS.keys(): + if attrkey[0] == DOM.NS_XMLNS: + attr = attrsNS[attrkey].cloneNode(1) + child.setAttributeNode(attr) + + #XXX Quick Hack, should be in WSDL Namespace. + if child.localName == 'import': + rlocation = child.getAttributeNS(None, 'location') + alocation = urllib.basejoin(location, rlocation) + child.setAttribute('location', alocation) + elif child.localName == 'types': + child.setAttribute('base-location', location) + + finally: + importdoc.unlink() + return location + +class Element: + """A class that provides common functions for WSDL element classes.""" + def __init__(self, name=None, documentation=''): + self.name = name + self.documentation = documentation + self.extensions = [] + + def addExtension(self, item): + self.extensions.append(item) + + +class ImportElement(Element): + def __init__(self, namespace, location): + self.namespace = namespace + self.location = location + + _loaded = None + + +class Types(Collection): + default = lambda self,k: k.targetNamespace + def __init__(self, parent): + Collection.__init__(self, parent) + self.documentation = '' + self.extensions = [] + + def addSchema(self, schema): + name = schema.targetNamespace + self[name] = schema + return schema + + def addExtension(self, item): + self.extensions.append(item) + + +class Message(Element): + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.parts = Collection(self) + + def addPart(self, name, type=None, element=None): + if self.parts.has_key(name): + raise WSDLError( + 'Duplicate message part element: %s' % name + ) + if type is None and element is None: + raise WSDLError( + 'Missing type or element attribute for part: %s' % name + ) + item = MessagePart(name) + item.element = element + item.type = type + self.parts[name] = item + return item + + def load(self, elements): + for element in elements: + name = DOM.getAttr(element, 'name') + part = MessagePart(name) + self.parts[name] = part + elemref = DOM.getAttr(element, 'element', default=None) + typeref = DOM.getAttr(element, 'type', default=None) + if typeref is None and elemref is None: + raise WSDLError( + 'No type or element attribute for part: %s' % name + ) + if typeref is not None: + part.type = ParseTypeRef(typeref, element) + if elemref is not None: + part.element = ParseTypeRef(elemref, element) + + +class MessagePart(Element): + def __init__(self, name): + Element.__init__(self, name, '') + self.element = None + self.type = None + + def getWSDL(self): + """Return the WSDL object that contains this Message Part.""" + return self.parent().parent().parent().parent() + + def getTypeDefinition(self): + wsdl = self.getWSDL() + nsuri,name = self.type + schema = wsdl.types.get(nsuri, {}) + return schema.get(name) + + def getElementDeclaration(self): + wsdl = self.getWSDL() + nsuri,name = self.element + schema = wsdl.types.get(nsuri, {}) + return schema.get(name) + + +class PortType(Element): + '''PortType has a anyAttribute, thus must provide for an extensible + mechanism for supporting such attributes. ResourceProperties is + specified in WS-ResourceProperties. wsa:Action is specified in + WS-Address. + + Instance Data: + name -- name attribute + resourceProperties -- optional. wsr:ResourceProperties attribute, + value is a QName this is Parsed into a (namespaceURI, name) + that represents a Global Element Declaration. + operations + ''' + + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.operations = Collection(self) + self.resourceProperties = None + + def getWSDL(self): + return self.parent().parent() + + def getResourceProperties(self): + return self.resourceProperties + + def addOperation(self, name, documentation='', parameterOrder=None): + item = Operation(name, documentation, parameterOrder) + self.operations[name] = item + return item + + def load(self, element): + self.name = DOM.getAttr(element, 'name') + self.documentation = GetDocumentation(element) + + if DOM.hasAttr(element, 'ResourceProperties', WSR.PROPERTIES): + rpref = DOM.getAttr(element, 'ResourceProperties', WSR.PROPERTIES) + self.resourceProperties = ParseQName(rpref, element) + + NS_WSDL = DOM.GetWSDLUri(self.getWSDL().version) + elements = DOM.getElements(element, 'operation', NS_WSDL) + for element in elements: + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + param_order = DOM.getAttr(element, 'parameterOrder', default=None) + if param_order is not None: + param_order = param_order.split(' ') + operation = self.addOperation(name, docs, param_order) + + item = DOM.getElement(element, 'input', None, None) + if item is not None: + name = DOM.getAttr(item, 'name') + docs = GetDocumentation(item) + msgref = DOM.getAttr(item, 'message') + message = ParseQName(msgref, item) + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + operation.setInput(message, name, docs, action) + + item = DOM.getElement(element, 'output', None, None) + if item is not None: + name = DOM.getAttr(item, 'name') + docs = GetDocumentation(item) + msgref = DOM.getAttr(item, 'message') + message = ParseQName(msgref, item) + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + operation.setOutput(message, name, docs, action) + + for item in DOM.getElements(element, 'fault', None): + name = DOM.getAttr(item, 'name') + docs = GetDocumentation(item) + msgref = DOM.getAttr(item, 'message') + message = ParseQName(msgref, item) + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + operation.addFault(message, name, docs, action) + + + +class Operation(Element): + def __init__(self, name, documentation='', parameterOrder=None): + Element.__init__(self, name, documentation) + self.parameterOrder = parameterOrder + self.faults = Collection(self) + self.input = None + self.output = None + + def getPortType(self): + return self.parent().parent() + + def getInputAction(self): + """wsa:Action attribute""" + return GetWSAActionInput(self) + + def getInputMessage(self): + if self.input is None: + return None + wsdl = self.getPortType().getWSDL() + return wsdl.messages[self.input.message] + + def getOutputAction(self): + """wsa:Action attribute""" + return GetWSAActionOutput(self) + + def getOutputMessage(self): + if self.output is None: + return None + wsdl = self.getPortType().getWSDL() + return wsdl.messages[self.output.message] + + def getFaultAction(self, name): + """wsa:Action attribute""" + return GetWSAActionFault(self, name) + + def getFaultMessage(self, name): + wsdl = self.getPortType().getWSDL() + return wsdl.messages[self.faults[name].message] + + def addFault(self, message, name, documentation='', action=None): + if self.faults.has_key(name): + raise WSDLError( + 'Duplicate fault element: %s' % name + ) + item = MessageRole('fault', message, name, documentation, action) + self.faults[name] = item + return item + + def setInput(self, message, name='', documentation='', action=None): + self.input = MessageRole('input', message, name, documentation, action) + return self.input + + def setOutput(self, message, name='', documentation='', action=None): + self.output = MessageRole('output', message, name, documentation, action) + return self.output + + +class MessageRole(Element): + def __init__(self, type, message, name='', documentation='', action=None): + Element.__init__(self, name, documentation) + self.message = message + self.type = type + self.action = action + + +class Binding(Element): + def __init__(self, name, type, documentation=''): + Element.__init__(self, name, documentation) + self.operations = Collection(self) + self.type = type + + def getWSDL(self): + """Return the WSDL object that contains this binding.""" + return self.parent().parent() + + def getPortType(self): + """Return the PortType object associated with this binding.""" + return self.getWSDL().portTypes[self.type] + + def findBinding(self, kind): + for item in self.extensions: + if isinstance(item, kind): + return item + return None + + def findBindings(self, kind): + return [ item for item in self.extensions if isinstance(item, kind) ] + + def addOperationBinding(self, name, documentation=''): + item = OperationBinding(name, documentation) + self.operations[name] = item + return item + + def load(self, elements): + for element in elements: + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + opbinding = self.addOperationBinding(name, docs) + opbinding.load_ex(GetExtensions(element)) + + item = DOM.getElement(element, 'input', None, None) + if item is not None: + mbinding = MessageRoleBinding('input') + mbinding.documentation = GetDocumentation(item) + opbinding.input = mbinding + mbinding.load_ex(GetExtensions(item)) + + item = DOM.getElement(element, 'output', None, None) + if item is not None: + mbinding = MessageRoleBinding('output') + mbinding.documentation = GetDocumentation(item) + opbinding.output = mbinding + mbinding.load_ex(GetExtensions(item)) + + for item in DOM.getElements(element, 'fault', None): + name = DOM.getAttr(item, 'name') + mbinding = MessageRoleBinding('fault', name) + mbinding.documentation = GetDocumentation(item) + opbinding.faults[name] = mbinding + mbinding.load_ex(GetExtensions(item)) + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_SOAP_BINDING_ALL and name == 'binding': + transport = DOM.getAttr(e, 'transport', default=None) + style = DOM.getAttr(e, 'style', default='document') + ob = SoapBinding(transport, style) + self.addExtension(ob) + continue + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'binding': + verb = DOM.getAttr(e, 'verb') + ob = HttpBinding(verb) + self.addExtension(ob) + continue + else: + self.addExtension(e) + + +class OperationBinding(Element): + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.input = None + self.output = None + self.faults = Collection(self) + + def getBinding(self): + """Return the parent Binding object of the operation binding.""" + return self.parent().parent() + + def getOperation(self): + """Return the abstract Operation associated with this binding.""" + return self.getBinding().getPortType().operations[self.name] + + def findBinding(self, kind): + for item in self.extensions: + if isinstance(item, kind): + return item + return None + + def findBindings(self, kind): + return [ item for item in self.extensions if isinstance(item, kind) ] + + def addInputBinding(self, binding): + if self.input is None: + self.input = MessageRoleBinding('input') + self.input.addExtension(binding) + return binding + + def addOutputBinding(self, binding): + if self.output is None: + self.output = MessageRoleBinding('output') + self.output.addExtension(binding) + return binding + + def addFaultBinding(self, name, binding): + fault = self.get(name, None) + if fault is None: + fault = MessageRoleBinding('fault', name) + fault.addExtension(binding) + return binding + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_SOAP_BINDING_ALL and name == 'operation': + soapaction = DOM.getAttr(e, 'soapAction', default=None) + style = DOM.getAttr(e, 'style', default=None) + ob = SoapOperationBinding(soapaction, style) + self.addExtension(ob) + continue + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'operation': + location = DOM.getAttr(e, 'location') + ob = HttpOperationBinding(location) + self.addExtension(ob) + continue + else: + self.addExtension(e) + + +class MessageRoleBinding(Element): + def __init__(self, type, name='', documentation=''): + Element.__init__(self, name, documentation) + self.type = type + + def findBinding(self, kind): + for item in self.extensions: + if isinstance(item, kind): + return item + return None + + def findBindings(self, kind): + return [ item for item in self.extensions if isinstance(item, kind) ] + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_SOAP_BINDING_ALL and name == 'body': + encstyle = DOM.getAttr(e, 'encodingStyle', default=None) + namespace = DOM.getAttr(e, 'namespace', default=None) + parts = DOM.getAttr(e, 'parts', default=None) + use = DOM.getAttr(e, 'use', default=None) + if use is None: + raise WSDLError( + 'Invalid soap:body binding element.' + ) + ob = SoapBodyBinding(use, namespace, encstyle, parts) + self.addExtension(ob) + continue + + elif ns in DOM.NS_SOAP_BINDING_ALL and name == 'fault': + encstyle = DOM.getAttr(e, 'encodingStyle', default=None) + namespace = DOM.getAttr(e, 'namespace', default=None) + name = DOM.getAttr(e, 'name', default=None) + use = DOM.getAttr(e, 'use', default=None) + if use is None or name is None: + raise WSDLError( + 'Invalid soap:fault binding element.' + ) + ob = SoapFaultBinding(name, use, namespace, encstyle) + self.addExtension(ob) + continue + + elif ns in DOM.NS_SOAP_BINDING_ALL and name in ( + 'header', 'headerfault' + ): + encstyle = DOM.getAttr(e, 'encodingStyle', default=None) + namespace = DOM.getAttr(e, 'namespace', default=None) + message = DOM.getAttr(e, 'message') + part = DOM.getAttr(e, 'part') + use = DOM.getAttr(e, 'use') + if name == 'header': + _class = SoapHeaderBinding + else: + _class = SoapHeaderFaultBinding + message = ParseQName(message, e) + ob = _class(message, part, use, namespace, encstyle) + self.addExtension(ob) + continue + + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'urlReplacement': + ob = HttpUrlReplacementBinding() + self.addExtension(ob) + continue + + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'urlEncoded': + ob = HttpUrlEncodedBinding() + self.addExtension(ob) + continue + + elif ns in DOM.NS_MIME_BINDING_ALL and name == 'multipartRelated': + ob = MimeMultipartRelatedBinding() + self.addExtension(ob) + ob.load_ex(GetExtensions(e)) + continue + + elif ns in DOM.NS_MIME_BINDING_ALL and name == 'content': + part = DOM.getAttr(e, 'part', default=None) + type = DOM.getAttr(e, 'type', default=None) + ob = MimeContentBinding(part, type) + self.addExtension(ob) + continue + + elif ns in DOM.NS_MIME_BINDING_ALL and name == 'mimeXml': + part = DOM.getAttr(e, 'part', default=None) + ob = MimeXmlBinding(part) + self.addExtension(ob) + continue + + else: + self.addExtension(e) + + +class Service(Element): + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.ports = Collection(self) + + def getWSDL(self): + return self.parent().parent() + + def addPort(self, name, binding, documentation=''): + item = Port(name, binding, documentation) + self.ports[name] = item + return item + + def load(self, elements): + for element in elements: + name = DOM.getAttr(element, 'name', default=None) + docs = GetDocumentation(element) + binding = DOM.getAttr(element, 'binding', default=None) + if name is None or binding is None: + raise WSDLError( + 'Invalid port element.' + ) + binding = ParseQName(binding, element) + port = self.addPort(name, binding, docs) + port.load_ex(GetExtensions(element)) + + def load_ex(self, elements): + for e in elements: + self.addExtension(e) + + +class Port(Element): + def __init__(self, name, binding, documentation=''): + Element.__init__(self, name, documentation) + self.binding = binding + + def getService(self): + """Return the Service object associated with this port.""" + return self.parent().parent() + + def getBinding(self): + """Return the Binding object that is referenced by this port.""" + wsdl = self.getService().getWSDL() + return wsdl.bindings[self.binding] + + def getPortType(self): + """Return the PortType object that is referenced by this port.""" + wsdl = self.getService().getWSDL() + binding = wsdl.bindings[self.binding] + return wsdl.portTypes[binding.type] + + def getAddressBinding(self): + """A convenience method to obtain the extension element used + as the address binding for the port, or None if undefined.""" + for item in self.extensions: + if isinstance(item, SoapAddressBinding) or \ + isinstance(item, HttpAddressBinding): + return item + raise WSDLError( + 'No address binding found in port.' + ) + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_SOAP_BINDING_ALL and name == 'address': + location = DOM.getAttr(e, 'location', default=None) + ob = SoapAddressBinding(location) + self.addExtension(ob) + continue + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'address': + location = DOM.getAttr(e, 'location', default=None) + ob = HttpAddressBinding(location) + self.addExtension(ob) + continue + else: + self.addExtension(e) + + +class SoapBinding: + def __init__(self, transport, style='rpc'): + self.transport = transport + self.style = style + + +class SoapAddressBinding: + def __init__(self, location): + self.location = location + + +class SoapOperationBinding: + def __init__(self, soapAction=None, style=None): + self.soapAction = soapAction + self.style = style + + +class SoapBodyBinding: + def __init__(self, use, namespace=None, encodingStyle=None, parts=None): + if not use in ('literal', 'encoded'): + raise WSDLError( + 'Invalid use attribute value: %s' % use + ) + self.encodingStyle = encodingStyle + self.namespace = namespace + if type(parts) in (type(''), type(u'')): + parts = parts.split() + self.parts = parts + self.use = use + +class SoapFaultBinding: + def __init__(self, name, use, namespace=None, encodingStyle=None): + if not use in ('literal', 'encoded'): + raise WSDLError( + 'Invalid use attribute value: %s' % use + ) + self.encodingStyle = encodingStyle + self.namespace = namespace + self.name = name + self.use = use + + +class SoapHeaderBinding: + def __init__(self, message, part, use, namespace=None, encodingStyle=None): + if not use in ('literal', 'encoded'): + raise WSDLError( + 'Invalid use attribute value: %s' % use + ) + self.encodingStyle = encodingStyle + self.namespace = namespace + self.message = message + self.part = part + self.use = use + + tagname = 'header' + +class SoapHeaderFaultBinding(SoapHeaderBinding): + tagname = 'headerfault' + + +class HttpBinding: + def __init__(self, verb): + self.verb = verb + +class HttpAddressBinding: + def __init__(self, location): + self.location = location + + +class HttpOperationBinding: + def __init__(self, location): + self.location = location + +class HttpUrlReplacementBinding: + pass + + +class HttpUrlEncodedBinding: + pass + + +class MimeContentBinding: + def __init__(self, part=None, type=None): + self.part = part + self.type = type + + +class MimeXmlBinding: + def __init__(self, part=None): + self.part = part + + +class MimeMultipartRelatedBinding: + def __init__(self): + self.parts = [] + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_MIME_BINDING_ALL and name == 'part': + self.parts.append(MimePartBinding()) + continue + + +class MimePartBinding: + def __init__(self): + self.items = [] + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_MIME_BINDING_ALL and name == 'content': + part = DOM.getAttr(e, 'part', default=None) + type = DOM.getAttr(e, 'type', default=None) + ob = MimeContentBinding(part, type) + self.items.append(ob) + continue + + elif ns in DOM.NS_MIME_BINDING_ALL and name == 'mimeXml': + part = DOM.getAttr(e, 'part', default=None) + ob = MimeXmlBinding(part) + self.items.append(ob) + continue + + elif ns in DOM.NS_SOAP_BINDING_ALL and name == 'body': + encstyle = DOM.getAttr(e, 'encodingStyle', default=None) + namespace = DOM.getAttr(e, 'namespace', default=None) + parts = DOM.getAttr(e, 'parts', default=None) + use = DOM.getAttr(e, 'use', default=None) + if use is None: + raise WSDLError( + 'Invalid soap:body binding element.' + ) + ob = SoapBodyBinding(use, namespace, encstyle, parts) + self.items.append(ob) + continue + + +class WSDLError(Exception): + pass + + + +def DeclareNSPrefix(writer, prefix, nsuri): + if writer.hasNSPrefix(nsuri): + return + writer.declareNSPrefix(prefix, nsuri) + +def ParseTypeRef(value, element): + parts = value.split(':', 1) + if len(parts) == 1: + return (DOM.findTargetNS(element), value) + nsuri = DOM.findNamespaceURI(parts[0], element) + return (nsuri, parts[1]) + +def ParseQName(value, element): + nameref = value.split(':', 1) + if len(nameref) == 2: + nsuri = DOM.findNamespaceURI(nameref[0], element) + name = nameref[-1] + else: + nsuri = DOM.findTargetNS(element) + name = nameref[-1] + return nsuri, name + +def GetDocumentation(element): + docnode = DOM.getElement(element, 'documentation', None, None) + if docnode is not None: + return DOM.getElementText(docnode) + return '' + +def GetExtensions(element): + return [ item for item in DOM.getElements(element, None, None) + if item.namespaceURI != DOM.NS_WSDL ] + +def GetWSAActionFault(operation, name): + """Find wsa:Action attribute, and return value or WSA.FAULT + for the default. + """ + attr = operation.faults[name].action + if attr is not None: + return attr + return WSA.FAULT + +def GetWSAActionInput(operation): + """Find wsa:Action attribute, and return value or the default.""" + attr = operation.input.action + if attr is not None: + return attr + portType = operation.getPortType() + targetNamespace = portType.getWSDL().targetNamespace + ptName = portType.name + msgName = operation.input.name + if not msgName: + msgName = operation.name + 'Request' + if targetNamespace.endswith('/'): + return '%s%s/%s' %(targetNamespace, ptName, msgName) + return '%s/%s/%s' %(targetNamespace, ptName, msgName) + +def GetWSAActionOutput(operation): + """Find wsa:Action attribute, and return value or the default.""" + attr = operation.output.action + if attr is not None: + return attr + targetNamespace = operation.getPortType().getWSDL().targetNamespace + ptName = operation.getPortType().name + msgName = operation.output.name + if not msgName: + msgName = operation.name + 'Response' + if targetNamespace.endswith('/'): + return '%s%s/%s' %(targetNamespace, ptName, msgName) + return '%s/%s/%s' %(targetNamespace, ptName, msgName) + +def FindExtensions(object, kind, t_type=type(())): + if isinstance(kind, t_type): + result = [] + namespaceURI, name = kind + return [ item for item in object.extensions + if hasattr(item, 'nodeType') \ + and DOM.nsUriMatch(namespaceURI, item.namespaceURI) \ + and item.name == name ] + return [ item for item in object.extensions if isinstance(item, kind) ] + +def FindExtension(object, kind, t_type=type(())): + if isinstance(kind, t_type): + namespaceURI, name = kind + for item in object.extensions: + if hasattr(item, 'nodeType') \ + and DOM.nsUriMatch(namespaceURI, item.namespaceURI) \ + and item.name == name: + return item + else: + for item in object.extensions: + if isinstance(item, kind): + return item + return None + + +class SOAPCallInfo: + """SOAPCallInfo captures the important binding information about a + SOAP operation, in a structure that is easier to work with than + raw WSDL structures.""" + + def __init__(self, methodName): + self.methodName = methodName + self.inheaders = [] + self.outheaders = [] + self.inparams = [] + self.outparams = [] + self.retval = None + + encodingStyle = DOM.NS_SOAP_ENC + documentation = '' + soapAction = None + transport = None + namespace = None + location = None + use = 'encoded' + style = 'rpc' + + def addInParameter(self, name, type, namespace=None, element_type=0): + """Add an input parameter description to the call info.""" + parameter = ParameterInfo(name, type, namespace, element_type) + self.inparams.append(parameter) + return parameter + + def addOutParameter(self, name, type, namespace=None, element_type=0): + """Add an output parameter description to the call info.""" + parameter = ParameterInfo(name, type, namespace, element_type) + self.outparams.append(parameter) + return parameter + + def setReturnParameter(self, name, type, namespace=None, element_type=0): + """Set the return parameter description for the call info.""" + parameter = ParameterInfo(name, type, namespace, element_type) + self.retval = parameter + return parameter + + def addInHeaderInfo(self, name, type, namespace, element_type=0, + mustUnderstand=0): + """Add an input SOAP header description to the call info.""" + headerinfo = HeaderInfo(name, type, namespace, element_type) + if mustUnderstand: + headerinfo.mustUnderstand = 1 + self.inheaders.append(headerinfo) + return headerinfo + + def addOutHeaderInfo(self, name, type, namespace, element_type=0, + mustUnderstand=0): + """Add an output SOAP header description to the call info.""" + headerinfo = HeaderInfo(name, type, namespace, element_type) + if mustUnderstand: + headerinfo.mustUnderstand = 1 + self.outheaders.append(headerinfo) + return headerinfo + + def getInParameters(self): + """Return a sequence of the in parameters of the method.""" + return self.inparams + + def getOutParameters(self): + """Return a sequence of the out parameters of the method.""" + return self.outparams + + def getReturnParameter(self): + """Return param info about the return value of the method.""" + return self.retval + + def getInHeaders(self): + """Return a sequence of the in headers of the method.""" + return self.inheaders + + def getOutHeaders(self): + """Return a sequence of the out headers of the method.""" + return self.outheaders + + +class ParameterInfo: + """A ParameterInfo object captures parameter binding information.""" + def __init__(self, name, type, namespace=None, element_type=0): + if element_type: + self.element_type = 1 + if namespace is not None: + self.namespace = namespace + self.name = name + self.type = type + + element_type = 0 + namespace = None + default = None + + +class HeaderInfo(ParameterInfo): + """A HeaderInfo object captures SOAP header binding information.""" + def __init__(self, name, type, namespace, element_type=None): + ParameterInfo.__init__(self, name, type, namespace, element_type) + + mustUnderstand = 0 + actor = None + + +def callInfoFromWSDL(port, name): + """Return a SOAPCallInfo given a WSDL port and operation name.""" + wsdl = port.getService().getWSDL() + binding = port.getBinding() + portType = binding.getPortType() + operation = portType.operations[name] + opbinding = binding.operations[name] + messages = wsdl.messages + callinfo = SOAPCallInfo(name) + + addrbinding = port.getAddressBinding() + if not isinstance(addrbinding, SoapAddressBinding): + raise ValueError, 'Unsupported binding type.' + callinfo.location = addrbinding.location + + soapbinding = binding.findBinding(SoapBinding) + if soapbinding is None: + raise ValueError, 'Missing soap:binding element.' + callinfo.transport = soapbinding.transport + callinfo.style = soapbinding.style or 'document' + + soap_op_binding = opbinding.findBinding(SoapOperationBinding) + if soap_op_binding is not None: + callinfo.soapAction = soap_op_binding.soapAction + callinfo.style = soap_op_binding.style or callinfo.style + + parameterOrder = operation.parameterOrder + + if operation.input is not None: + message = messages[operation.input.message] + msgrole = opbinding.input + + mime = msgrole.findBinding(MimeMultipartRelatedBinding) + if mime is not None: + raise ValueError, 'Mime bindings are not supported.' + else: + for item in msgrole.findBindings(SoapHeaderBinding): + part = messages[item.message].parts[item.part] + header = callinfo.addInHeaderInfo( + part.name, + part.element or part.type, + item.namespace, + element_type = part.element and 1 or 0 + ) + header.encodingStyle = item.encodingStyle + + body = msgrole.findBinding(SoapBodyBinding) + if body is None: + raise ValueError, 'Missing soap:body binding.' + callinfo.encodingStyle = body.encodingStyle + callinfo.namespace = body.namespace + callinfo.use = body.use + + if body.parts is not None: + parts = [] + for name in body.parts: + parts.append(message.parts[name]) + else: + parts = message.parts.values() + + for part in parts: + callinfo.addInParameter( + part.name, + part.element or part.type, + element_type = part.element and 1 or 0 + ) + + if operation.output is not None: + try: + message = messages[operation.output.message] + except KeyError: + if self.strict: + raise RuntimeError( + "Recieved message not defined in the WSDL schema: %s" % + operation.output.message) + else: + message = wsdl.addMessage(operation.output.message) + print "Warning:", \ + "Recieved message not defined in the WSDL schema.", \ + "Adding it." + print "Message:", operation.output.message + + msgrole = opbinding.output + + mime = msgrole.findBinding(MimeMultipartRelatedBinding) + if mime is not None: + raise ValueError, 'Mime bindings are not supported.' + else: + for item in msgrole.findBindings(SoapHeaderBinding): + part = messages[item.message].parts[item.part] + header = callinfo.addOutHeaderInfo( + part.name, + part.element or part.type, + item.namespace, + element_type = part.element and 1 or 0 + ) + header.encodingStyle = item.encodingStyle + + body = msgrole.findBinding(SoapBodyBinding) + if body is None: + raise ValueError, 'Missing soap:body binding.' + callinfo.encodingStyle = body.encodingStyle + callinfo.namespace = body.namespace + callinfo.use = body.use + + if body.parts is not None: + parts = [] + for name in body.parts: + parts.append(message.parts[name]) + else: + parts = message.parts.values() + + if parts: + # XXX no idea what this is for, but it breaks everything. jrb + #callinfo.setReturnParameter( + # parts[0].name, + # parts[0].element or parts[0].type, + # element_type = parts[0].element and 1 or 0 + # ) + #for part in parts[1:]: + for part in parts: + callinfo.addOutParameter( + part.name, + part.element or part.type, + element_type = part.element and 1 or 0 + ) + + return callinfo diff --git a/SOAPpy/wstools/XMLSchema.py b/SOAPpy/wstools/XMLSchema.py new file mode 100755 index 0000000..aaaaffc --- /dev/null +++ b/SOAPpy/wstools/XMLSchema.py @@ -0,0 +1,2976 @@ +# Copyright (c) 2003, The Regents of the University of California, +# through Lawrence Berkeley National Laboratory (subject to receipt of +# any required approvals from the U.S. Dept. of Energy). All rights +# reserved. +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. + +ident = "$Id: XMLSchema.py,v 1.38 2004/06/23 20:10:26 boverhof Exp $" + +import types, weakref, urllib, sys +from threading import RLock +from Namespaces import XMLNS +from Utility import DOM, DOMException, Collection, SplitQName +from StringIO import StringIO + +def GetSchema(component): + """convience function for finding the parent XMLSchema instance. + """ + parent = component + while not isinstance(parent, XMLSchema): + parent = parent._parent() + return parent + +class SchemaReader: + """A SchemaReader creates XMLSchema objects from urls and xml data. + """ + def __init__(self, domReader=None, base_url=None): + """domReader -- class must implement DOMAdapterInterface + base_url -- base url string + """ + self.__base_url = base_url + self.__readerClass = domReader + if not self.__readerClass: + self.__readerClass = DOMAdapter + self._includes = {} + self._imports = {} + + def __setImports(self, schema): + """Add dictionary of imports to schema instance. + schema -- XMLSchema instance + """ + for ns,val in schema.imports.items(): + if self._imports.has_key(ns): + schema.addImportSchema(self._imports[ns]) + + def __setIncludes(self, schema): + """Add dictionary of includes to schema instance. + schema -- XMLSchema instance + """ + for schemaLocation, val in schema.includes.items(): + if self._includes.has_key(schemaLocation): + schema.addIncludeSchema(self._imports[schemaLocation]) + + def addSchemaByLocation(self, location, schema): + """provide reader with schema document for a location. + """ + self._includes[location] = schema + + def addSchemaByNamespace(self, schema): + """provide reader with schema document for a targetNamespace. + """ + self._imports[schema.targetNamespace] = schema + + def loadFromNode(self, parent, element): + """element -- DOM node or document + parent -- WSDLAdapter instance + """ + reader = self.__readerClass(element) + schema = XMLSchema(parent) + #HACK to keep a reference + schema.wsdl = parent + schema.setBaseUrl(self.__base_url) + schema.load(reader) + return schema + + def loadFromStream(self, file): + """Return an XMLSchema instance loaded from a file object. + file -- file object + """ + reader = self.__readerClass() + reader.loadDocument(file) + schema = XMLSchema() + schema.setBaseUrl(self.__base_url) + schema.load(reader) + self.__setIncludes(schema) + self.__setImports(schema) + return schema + + def loadFromString(self, data): + """Return an XMLSchema instance loaded from an XML string. + data -- XML string + """ + return self.loadFromStream(StringIO(data)) + + def loadFromURL(self, url): + """Return an XMLSchema instance loaded from the given url. + url -- URL to dereference + """ + if not url.endswith('xsd'): + raise SchemaError, 'unknown file type %s' %url + reader = self.__readerClass() + if self.__base_url: + url = urllib.basejoin(self.__base_url,url) + reader.loadFromURL(url) + schema = XMLSchema() + schema.setBaseUrl(self.__base_url) + schema.load(reader) + self.__setIncludes(schema) + self.__setImports(schema) + return schema + + def loadFromFile(self, filename): + """Return an XMLSchema instance loaded from the given file. + filename -- name of file to open + """ + file = open(filename, 'rb') + try: schema = self.loadFromStream(file) + finally: file.close() + return schema + + +class SchemaError(Exception): + pass + +########################### +# DOM Utility Adapters +########################## +class DOMAdapterInterface: + def hasattr(self, attr, ns=None): + """return true if node has attribute + attr -- attribute to check for + ns -- namespace of attribute, by default None + """ + raise NotImplementedError, 'adapter method not implemented' + + def getContentList(self, *contents): + """returns an ordered list of child nodes + *contents -- list of node names to return + """ + raise NotImplementedError, 'adapter method not implemented' + + def setAttributeDictionary(self, attributes): + """set attribute dictionary + """ + raise NotImplementedError, 'adapter method not implemented' + + def getAttributeDictionary(self): + """returns a dict of node's attributes + """ + raise NotImplementedError, 'adapter method not implemented' + + def getNamespace(self, prefix): + """returns namespace referenced by prefix. + """ + raise NotImplementedError, 'adapter method not implemented' + + def getTagName(self): + """returns tagName of node + """ + raise NotImplementedError, 'adapter method not implemented' + + + def getParentNode(self): + """returns parent element in DOMAdapter or None + """ + raise NotImplementedError, 'adapter method not implemented' + + def loadDocument(self, file): + """load a Document from a file object + file -- + """ + raise NotImplementedError, 'adapter method not implemented' + + def loadFromURL(self, url): + """load a Document from an url + url -- URL to dereference + """ + raise NotImplementedError, 'adapter method not implemented' + + +class DOMAdapter(DOMAdapterInterface): + """Adapter for ZSI.Utility.DOM + """ + def __init__(self, node=None): + """Reset all instance variables. + element -- DOM document, node, or None + """ + if hasattr(node, 'documentElement'): + self.__node = node.documentElement + else: + self.__node = node + self.__attributes = None + + def hasattr(self, attr, ns=None): + """attr -- attribute + ns -- optional namespace, None means unprefixed attribute. + """ + if not self.__attributes: + self.setAttributeDictionary() + if ns: + return self.__attributes.get(ns,{}).has_key(attr) + return self.__attributes.has_key(attr) + + def getContentList(self, *contents): + nodes = [] + ELEMENT_NODE = self.__node.ELEMENT_NODE + for child in DOM.getElements(self.__node, None): + if child.nodeType == ELEMENT_NODE and\ + SplitQName(child.tagName)[1] in contents: + nodes.append(child) + return map(self.__class__, nodes) + + def setAttributeDictionary(self): + self.__attributes = {} + for v in self.__node._attrs.values(): + self.__attributes[v.nodeName] = v.nodeValue + + def getAttributeDictionary(self): + if not self.__attributes: + self.setAttributeDictionary() + return self.__attributes + + def getTagName(self): + return self.__node.tagName + + def getParentNode(self): + if self.__node.parentNode.nodeType == self.__node.ELEMENT_NODE: + return DOMAdapter(self.__node.parentNode) + return None + + def getNamespace(self, prefix): + """prefix -- deference namespace prefix in node's context. + Ascends parent nodes until found. + """ + namespace = None + if prefix == 'xmlns': + namespace = DOM.findDefaultNS(prefix, self.__node) + else: + try: + namespace = DOM.findNamespaceURI(prefix, self.__node) + except DOMException, ex: + if prefix != 'xml': + raise SchemaError, '%s namespace not declared for %s'\ + %(prefix, self.__node._get_tagName()) + namespace = XMLNS.XML + return namespace + + def loadDocument(self, file): + self.__node = DOM.loadDocument(file) + if hasattr(self.__node, 'documentElement'): + self.__node = self.__node.documentElement + + def loadFromURL(self, url): + self.__node = DOM.loadFromURL(url) + if hasattr(self.__node, 'documentElement'): + self.__node = self.__node.documentElement + + +class XMLBase: + """ These class variables are for string indentation. + """ + __indent = 0 + __rlock = RLock() + + def __str__(self): + XMLBase.__rlock.acquire() + XMLBase.__indent += 1 + tmp = "<" + str(self.__class__) + '>\n' + for k,v in self.__dict__.items(): + tmp += "%s* %s = %s\n" %(XMLBase.__indent*' ', k, v) + XMLBase.__indent -= 1 + XMLBase.__rlock.release() + return tmp + + +"""Marker Interface: can determine something about an instances properties by using + the provided convenience functions. + +""" +class DefinitionMarker: + """marker for definitions + """ + pass + +class DeclarationMarker: + """marker for declarations + """ + pass + +class AttributeMarker: + """marker for attributes + """ + pass + +class AttributeGroupMarker: + """marker for attribute groups + """ + pass + +class WildCardMarker: + """marker for wildcards + """ + pass + +class ElementMarker: + """marker for wildcards + """ + pass + +class ReferenceMarker: + """marker for references + """ + pass + +class ModelGroupMarker: + """marker for model groups + """ + pass + +class ExtensionMarker: + """marker for extensions + """ + pass + +class RestrictionMarker: + """marker for restrictions + """ + facets = ['enumeration', 'length', 'maxExclusive', 'maxInclusive',\ + 'maxLength', 'minExclusive', 'minInclusive', 'minLength',\ + 'pattern', 'fractionDigits', 'totalDigits', 'whiteSpace'] + +class SimpleMarker: + """marker for simple type information + """ + pass + +class ComplexMarker: + """marker for complex type information + """ + pass + +class LocalMarker: + """marker for complex type information + """ + pass + + +class MarkerInterface: + def isDefinition(self): + return isinstance(self, DefinitionMarker) + + def isDeclaration(self): + return isinstance(self, DeclarationMarker) + + def isAttribute(self): + return isinstance(self, AttributeMarker) + + def isAttributeGroup(self): + return isinstance(self, AttributeGroupMarker) + + def isElement(self): + return isinstance(self, ElementMarker) + + def isReference(self): + return isinstance(self, ReferenceMarker) + + def isWildCard(self): + return isinstance(self, WildCardMarker) + + def isModelGroup(self): + return isinstance(self, ModelGroupMarker) + + def isExtension(self): + return isinstance(self, ExtensionMarker) + + def isRestriction(self): + return isinstance(self, RestrictionMarker) + + def isSimple(self): + return isinstance(self, SimpleMarker) + + def isComplex(self): + return isinstance(self, ComplexMarker) + + def isLocal(self): + return isinstance(self, LocalMarker) + + +########################################################## +# Schema Components +######################################################### +class XMLSchemaComponent(XMLBase, MarkerInterface): + """ + class variables: + required -- list of required attributes + attributes -- dict of default attribute values, including None. + Value can be a function for runtime dependencies. + contents -- dict of namespace keyed content lists. + 'xsd' content of xsd namespace. + xmlns_key -- key for declared xmlns namespace. + xmlns -- xmlns is special prefix for namespace dictionary + xml -- special xml prefix for xml namespace. + """ + required = [] + attributes = {} + contents = {} + xmlns_key = '' + xmlns = 'xmlns' + xml = 'xml' + + def __init__(self, parent=None): + """parent -- parent instance + instance variables: + attributes -- dictionary of node's attributes + """ + self.attributes = None + self._parent = parent + if self._parent: + self._parent = weakref.ref(parent) + + if not self.__class__ == XMLSchemaComponent\ + and not (type(self.__class__.required) == type(XMLSchemaComponent.required)\ + and type(self.__class__.attributes) == type(XMLSchemaComponent.attributes)\ + and type(self.__class__.contents) == type(XMLSchemaComponent.contents)): + raise RuntimeError, 'Bad type for a class variable in %s' %self.__class__ + + def getTargetNamespace(self): + """return targetNamespace + """ + parent = self + targetNamespace = 'targetNamespace' + tns = self.attributes.get(targetNamespace) + while not tns: + parent = parent._parent() + tns = parent.attributes.get(targetNamespace) + return tns + + def getTypeDefinition(self, attribute): + """attribute -- attribute with a QName value (eg. type). + collection -- check types collection in parent Schema instance + """ + return self.getQNameAttribute('types', attribute) + + def getElementDeclaration(self, attribute): + """attribute -- attribute with a QName value (eg. element). + collection -- check elements collection in parent Schema instance. + """ + return self.getQNameAttribute('elements', attribute) + + def getQNameAttribute(self, collection, attribute): + """returns object instance representing QName --> (namespace,name), + or if does not exist return None. + attribute -- an information item attribute, with a QName value. + collection -- collection in parent Schema instance to search. + """ + obj = None + tdc = self.attributes.get(attribute) + if tdc: + parent = GetSchema(self) + targetNamespace = tdc.getTargetNamespace() + if parent.targetNamespace == targetNamespace: + item = tdc.getName() + try: + obj = getattr(parent, collection)[item] + except KeyError, ex: + raise KeyError, "targetNamespace(%s) collection(%s) has no item(%s)"\ + %(targetNamespace, collection, item) + elif parent.imports.has_key(targetNamespace): + schema = parent.imports[targetNamespace].getSchema() + item = tdc.getName() + try: + obj = getattr(schema, collection)[item] + except KeyError, ex: + raise KeyError, "targetNamespace(%s) collection(%s) has no item(%s)"\ + %(targetNamespace, collection, item) + return obj + + def getXMLNS(self, prefix=None): + """deference prefix or by default xmlns, returns namespace. + """ + if prefix == XMLSchemaComponent.xml: + return XMLNS.XML + parent = self + ns = self.attributes[XMLSchemaComponent.xmlns].get(prefix or\ + XMLSchemaComponent.xmlns_key) + while not ns: + parent = parent._parent() + ns = parent.attributes[XMLSchemaComponent.xmlns].get(prefix or\ + XMLSchemaComponent.xmlns_key) + if not ns and isinstance(parent, WSDLToolsAdapter): + raise SchemaError, 'unknown prefix %s' %prefix + return ns + + def getAttribute(self, attribute): + """return requested attribute or None + """ + return self.attributes.get(attribute) + + def setAttributes(self, node): + """Sets up attribute dictionary, checks for required attributes and + sets default attribute values. attr is for default attribute values + determined at runtime. + + structure of attributes dictionary + ['xmlns'][xmlns_key] -- xmlns namespace + ['xmlns'][prefix] -- declared namespace prefix + [namespace][prefix] -- attributes declared in a namespace + [attribute] -- attributes w/o prefix, default namespaces do + not directly apply to attributes, ie Name can't collide + with QName. + """ + self.attributes = {XMLSchemaComponent.xmlns:{}} + for k,v in node.getAttributeDictionary().items(): + prefix,value = SplitQName(k) + if value == XMLSchemaComponent.xmlns: + self.attributes[value][prefix or XMLSchemaComponent.xmlns_key] = v + elif prefix: + ns = node.getNamespace(prefix) + if not ns: + raise SchemaError, 'no namespace for attribute prefix %s'\ + %prefix + if not self.attributes.has_key(ns): + self.attributes[ns] = {} + elif self.attributes[ns].has_key(value): + raise SchemaError, 'attribute %s declared multiple times in %s'\ + %(value, ns) + self.attributes[ns][value] = v + elif not self.attributes.has_key(value): + self.attributes[value] = v + else: + raise SchemaError, 'attribute %s declared multiple times' %value + + self.__checkAttributes() + self.__setAttributeDefaults() + + #set QNames + for k in ['type', 'element', 'base', 'ref', 'substitutionGroup', 'itemType']: + if self.attributes.has_key(k): + prefix, value = SplitQName(self.attributes.get(k)) + self.attributes[k] = \ + TypeDescriptionComponent((self.getXMLNS(prefix), value)) + + #Union, memberTypes is a whitespace separated list of QNames + for k in ['memberTypes']: + if self.attributes.has_key(k): + qnames = self.attributes[k] + self.attributes[k] = [] + for qname in qnames.split(): + prefix, value = SplitQName(qname) + self.attributes['memberTypes'].append(\ + TypeDescriptionComponent(\ + (self.getXMLNS(prefix), value))) + + def getContents(self, node): + """retrieve xsd contents + """ + return node.getContentList(*self.__class__.contents['xsd']) + + def __setAttributeDefaults(self): + """Looks for default values for unset attributes. If + class variable representing attribute is None, then + it must be defined as an instance variable. + """ + for k,v in self.__class__.attributes.items(): + if v and not self.attributes.has_key(k): + if isinstance(v, types.FunctionType): + self.attributes[k] = v(self) + else: + self.attributes[k] = v + + def __checkAttributes(self): + """Checks that required attributes have been defined, + attributes w/default cannot be required. Checks + all defined attributes are legal, attribute + references are not subject to this test. + """ + for a in self.__class__.required: + if not self.attributes.has_key(a): + raise SchemaError,\ + 'class instance %s, missing required attribute %s'\ + %(self.__class__, a) + for a in self.attributes.keys(): + if (a not in (XMLSchemaComponent.xmlns, XMLNS.XML)) and\ + (a not in self.__class__.attributes.keys()) and not\ + (self.isAttribute() and self.isReference()): + raise SchemaError, '%s, unknown attribute' %a + + +class WSDLToolsAdapter(XMLSchemaComponent): + """WSDL Adapter to grab the attributes from the wsdl document node. + """ + attributes = {'name':None, 'targetNamespace':None} + + def __init__(self, wsdl): + #XMLSchemaComponent.__init__(self, None) + XMLSchemaComponent.__init__(self, parent=wsdl) + self.setAttributes(DOMAdapter(wsdl.document)) + + def getImportSchemas(self): + """returns WSDLTools.WSDL types Collection + """ + return self._parent().types + + +class Notation(XMLSchemaComponent): + """ + parent: + schema + attributes: + id -- ID + name -- NCName, Required + public -- token, Required + system -- anyURI + contents: + annotation? + """ + required = ['name', 'public'] + attributes = {'id':None, 'name':None, 'public':None, 'system':None} + contents = {'xsd':('annotation')} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class Annotation(XMLSchemaComponent): + """ + parent: + all,any,anyAttribute,attribute,attributeGroup,choice,complexContent, + complexType,element,extension,field,group,import,include,key,keyref, + list,notation,redefine,restriction,schema,selector,simpleContent, + simpleType,union,unique + attributes: + id -- ID + contents: + (documentation | appinfo)* + """ + attributes = {'id':None} + contents = {'xsd':('documentation', 'appinfo')} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'documentation': + #print_debug('class %s, documentation skipped' %self.__class__, 5) + continue + elif component == 'appinfo': + #print_debug('class %s, appinfo skipped' %self.__class__, 5) + continue + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + + class Documentation(XMLSchemaComponent): + """ + parent: + annotation + attributes: + source, anyURI + xml:lang, language + contents: + mixed, any + """ + attributes = {'source':None, 'xml:lang':None} + contents = {'xsd':('mixed', 'any')} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'mixed': + #print_debug('class %s, mixed skipped' %self.__class__, 5) + continue + elif component == 'any': + #print_debug('class %s, any skipped' %self.__class__, 5) + continue + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + + class Appinfo(XMLSchemaComponent): + """ + parent: + annotation + attributes: + source, anyURI + contents: + mixed, any + """ + attributes = {'source':None, 'anyURI':None} + contents = {'xsd':('mixed', 'any')} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'mixed': + #print_debug('class %s, mixed skipped' %self.__class__, 5) + continue + elif component == 'any': + #print_debug('class %s, any skipped' %self.__class__, 5) + continue + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + +class XMLSchemaFake: + # This is temporary, for the benefit of WSDL until the real thing works. + def __init__(self, element): + self.targetNamespace = DOM.getAttr(element, 'targetNamespace') + self.element = element + +class XMLSchema(XMLSchemaComponent): + """A schema is a collection of schema components derived from one + or more schema documents, that is, one or more element + information items. It represents the abstract notion of a schema + rather than a single schema document (or other representation). + + + parent: + ROOT + attributes: + id -- ID + version -- token + xml:lang -- language + targetNamespace -- anyURI + attributeFormDefault -- 'qualified' | 'unqualified', 'unqualified' + elementFormDefault -- 'qualified' | 'unqualified', 'unqualified' + blockDefault -- '#all' | list of + ('substitution | 'extension' | 'restriction') + finalDefault -- '#all' | list of + ('extension' | 'restriction' | 'list' | 'union') + + contents: + ((include | import | redefine | annotation)*, + (attribute, attributeGroup, complexType, element, group, + notation, simpleType)*, annotation*)* + + + attributes -- schema attributes + imports -- import statements + includes -- include statements + redefines -- + types -- global simpleType, complexType definitions + elements -- global element declarations + attr_decl -- global attribute declarations + attr_groups -- attribute Groups + model_groups -- model Groups + notations -- global notations + """ + attributes = {'id':None, + 'version':None, + 'xml:lang':None, + 'targetNamespace':None, + 'attributeFormDefault':'unqualified', + 'elementFormDefault':'unqualified', + 'blockDefault':None, + 'finalDefault':None} + contents = {'xsd':('include', 'import', 'redefine', 'annotation', 'attribute',\ + 'attributeGroup', 'complexType', 'element', 'group',\ + 'notation', 'simpleType', 'annotation')} + empty_namespace = '' + + def __init__(self, parent=None): + """parent -- + instance variables: + targetNamespace -- schema's declared targetNamespace, or empty string. + _imported_schemas -- namespace keyed dict of schema dependencies, if + a schema is provided instance will not resolve import statement. + _included_schemas -- schemaLocation keyed dict of component schemas, + if schema is provided instance will not resolve include statement. + _base_url -- needed for relative URLs support, only works with URLs + relative to initial document. + includes -- collection of include statements + imports -- collection of import statements + elements -- collection of global element declarations + types -- collection of global type definitions + attr_decl -- collection of global attribute declarations + attr_groups -- collection of global attribute group definitions + model_groups -- collection of model group definitions + notations -- collection of notations + + """ + self.targetNamespace = None + XMLSchemaComponent.__init__(self, parent) + f = lambda k: k.attributes['name'] + ns = lambda k: k.attributes['namespace'] + sl = lambda k: k.attributes['schemaLocation'] + self.includes = Collection(self, key=sl) + self.imports = Collection(self, key=ns) + self.elements = Collection(self, key=f) + self.types = Collection(self, key=f) + self.attr_decl = Collection(self, key=f) + self.attr_groups = Collection(self, key=f) + self.model_groups = Collection(self, key=f) + self.notations = Collection(self, key=f) + + self._imported_schemas = {} + self._included_schemas = {} + self._base_url = None + + def addImportSchema(self, schema): + """for resolving import statements in Schema instance + schema -- schema instance + _imported_schemas + """ + if not isinstance(schema, XMLSchema): + raise TypeError, 'expecting a Schema instance' + if schema.targetNamespace != self.targetNamespace: + self._imported_schemas[schema.targetNamespace] = schema + else: + raise SchemaError, 'import schema bad targetNamespace' + + def addIncludeSchema(self, schemaLocation, schema): + """for resolving include statements in Schema instance + schemaLocation -- schema location + schema -- schema instance + _included_schemas + """ + if not isinstance(schema, XMLSchema): + raise TypeError, 'expecting a Schema instance' + if not schema.targetNamespace or\ + schema.targetNamespace == self.targetNamespace: + self._included_schemas[schemaLocation] = schema + else: + raise SchemaError, 'include schema bad targetNamespace' + + def setImportSchemas(self, schema_dict): + """set the import schema dictionary, which is used to + reference depedent schemas. + """ + self._imported_schemas = schema_dict + + def getImportSchemas(self): + """get the import schema dictionary, which is used to + reference depedent schemas. + """ + return self._imported_schemas + + def getSchemaNamespacesToImport(self): + """returns tuple of namespaces the schema instance has declared + itself to be depedent upon. + """ + return tuple(self.includes.keys()) + + def setIncludeSchemas(self, schema_dict): + """set the include schema dictionary, which is keyed with + schemaLocation (uri). + This is a means of providing + schemas to the current schema for content inclusion. + """ + self._included_schemas = schema_dict + + def getIncludeSchemas(self): + """get the include schema dictionary, which is keyed with + schemaLocation (uri). + """ + return self._included_schemas + + def getBaseUrl(self): + """get base url, used for normalizing all relative uri's + """ + return self._base_url + + def setBaseUrl(self, url): + """set base url, used for normalizing all relative uri's + """ + self._base_url = url + + def getElementFormDefault(self): + """return elementFormDefault attribute + """ + return self.attributes.get('elementFormDefault') + + def getAttributeFormDefault(self): + """return attributeFormDefault attribute + """ + return self.attributes.get('attributeFormDefault') + + def getBlockDefault(self): + """return blockDefault attribute + """ + return self.attributes.get('blockDefault') + + def getFinalDefault(self): + """return finalDefault attribute + """ + return self.attributes.get('finalDefault') + + def load(self, node): + pnode = node.getParentNode() + if pnode: + pname = SplitQName(pnode.getTagName())[1] + if pname == 'types': + attributes = {} + self.setAttributes(pnode) + attributes.update(self.attributes) + self.setAttributes(node) + for k,v in attributes['xmlns'].items(): + if not self.attributes['xmlns'].has_key(k): + self.attributes['xmlns'][k] = v + else: + self.setAttributes(node) + else: + self.setAttributes(node) + + self.targetNamespace = self.getTargetNamespace() + contents = self.getContents(node) + + indx = 0 + num = len(contents) + while indx < num: + while indx < num: + node = contents[indx] + component = SplitQName(node.getTagName())[1] + + if component == 'include': + tp = self.__class__.Include(self) + tp.fromDom(node) + self.includes[tp.attributes['schemaLocation']] = tp + + schema = tp.getSchema() + if schema.targetNamespace and \ + schema.targetNamespace != self.targetNamespace: + raise SchemaError, 'included schema bad targetNamespace' + + for collection in ['imports','elements','types',\ + 'attr_decl','attr_groups','model_groups','notations']: + for k,v in getattr(schema,collection).items(): + if not getattr(self,collection).has_key(k): + v._parent = weakref.ref(self) + getattr(self,collection)[k] = v + + elif component == 'import': + tp = self.__class__.Import(self) + tp.fromDom(node) + import_ns = tp.getAttribute('namespace') + if import_ns: + if import_ns == self.targetNamespace: + raise SchemaError,\ + 'import and schema have same targetNamespace' + self.imports[import_ns] = tp + else: + self.imports[self.__class__.empty_namespace] = tp + + if not self.getImportSchemas().has_key(import_ns) and\ + tp.getAttribute('schemaLocation'): + self.addImportSchema(tp.getSchema()) + + elif component == 'redefine': + #print_debug('class %s, redefine skipped' %self.__class__, 5) + pass + elif component == 'annotation': + #print_debug('class %s, annotation skipped' %self.__class__, 5) + pass + else: + break + indx += 1 + + # (attribute, attributeGroup, complexType, element, group, + # notation, simpleType)*, annotation*)* + while indx < num: + node = contents[indx] + component = SplitQName(node.getTagName())[1] + + if component == 'attribute': + tp = AttributeDeclaration(self) + tp.fromDom(node) + self.attr_decl[tp.getAttribute('name')] = tp + elif component == 'attributeGroup': + tp = AttributeGroupDefinition(self) + tp.fromDom(node) + self.attr_groups[tp.getAttribute('name')] = tp + elif component == 'complexType': + tp = ComplexType(self) + tp.fromDom(node) + self.types[tp.getAttribute('name')] = tp + elif component == 'element': + tp = ElementDeclaration(self) + tp.fromDom(node) + self.elements[tp.getAttribute('name')] = tp + elif component == 'group': + tp = ModelGroupDefinition(self) + tp.fromDom(node) + self.model_groups[tp.getAttribute('name')] = tp + elif component == 'notation': + tp = Notation(self) + tp.fromDom(node) + self.notations[tp.getAttribute('name')] = tp + elif component == 'simpleType': + tp = SimpleType(self) + tp.fromDom(node) + self.types[tp.getAttribute('name')] = tp + else: + break + indx += 1 + + while indx < num: + node = contents[indx] + component = SplitQName(node.getTagName())[1] + + if component == 'annotation': + #print_debug('class %s, annotation 2 skipped' %self.__class__, 5) + pass + else: + break + indx += 1 + + + class Import(XMLSchemaComponent): + """ + parent: + schema + attributes: + id -- ID + namespace -- anyURI + schemaLocation -- anyURI + contents: + annotation? + """ + attributes = {'id':None, + 'namespace':None, + 'schemaLocation':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self._schema = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + if self.attributes['namespace'] == self._parent().attributes['targetNamespace']: + raise SchemaError, 'namespace of schema and import match' + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + def getSchema(self): + """if schema is not defined, first look for a Schema class instance + in parent Schema. Else if not defined resolve schemaLocation + and create a new Schema class instance, and keep a hard reference. + """ + if not self._schema: + ns = self.attributes['namespace'] + schema = self._parent().getImportSchemas().get(ns) + if not schema and self._parent()._parent: + schema = self._parent()._parent().getImportSchemas().get(ns) + if not schema: + url = self.attributes.get('schemaLocation') + if not url: + raise SchemaError, 'namespace(%s) is unknown' %ns + base_url = self._parent().getBaseUrl() + reader = SchemaReader(base_url=base_url) + reader._imports = self._parent().getImportSchemas() + reader._includes = self._parent().getIncludeSchemas() + self._schema = reader.loadFromURL(url) + return self._schema or schema + + + class Include(XMLSchemaComponent): + """ + parent: + schema + attributes: + id -- ID + schemaLocation -- anyURI, required + contents: + annotation? + """ + required = ['schemaLocation'] + attributes = {'id':None, + 'schemaLocation':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self._schema = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + def getSchema(self): + """if schema is not defined, first look for a Schema class instance + in parent Schema. Else if not defined resolve schemaLocation + and create a new Schema class instance. + """ + if not self._schema: + #schema = self._parent()._parent() + schema = self._parent() + #self._schema = schema.getIncludeSchemas(\ + # self.attributes['schemaLocation']) + self._schema = schema.getIncludeSchemas().get(\ + self.attributes['schemaLocation'] + ) + if not self._schema: + url = self.attributes['schemaLocation'] + reader = SchemaReader(base_url=schema.getBaseUrl()) + reader._imports = schema.getImportSchemas() + reader._includes = schema.getIncludeSchemas() + self._schema = reader.loadFromURL(url) + return self._schema + + +class AttributeDeclaration(XMLSchemaComponent,\ + AttributeMarker,\ + DeclarationMarker): + """ + parent: + schema + attributes: + id -- ID + name -- NCName, required + type -- QName + default -- string + fixed -- string + contents: + annotation?, simpleType? + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'type':None, + 'default':None, + 'fixed':None} + contents = {'xsd':['annotation','simpleType']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + """ No list or union support + """ + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + elif component == 'simpleType': + self.content = AnonymousSimpleType(self) + self.content.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class LocalAttributeDeclaration(AttributeDeclaration,\ + AttributeMarker,\ + LocalMarker,\ + DeclarationMarker): + """ + parent: + complexType, restriction, extension, attributeGroup + attributes: + id -- ID + name -- NCName, required + type -- QName + form -- ('qualified' | 'unqualified'), schema.attributeFormDefault + use -- ('optional' | 'prohibited' | 'required'), optional + default -- string + fixed -- string + contents: + annotation?, simpleType? + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'type':None, + 'form':lambda self: GetSchema(self).getAttributeFormDefault(), + 'use':'optional', + 'default':None, + 'fixed':None} + contents = {'xsd':['annotation','simpleType']} + + def __init__(self, parent): + AttributeDeclaration.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + elif component == 'simpleType': + self.content = AnonymousSimpleType(self) + self.content.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class AttributeWildCard(XMLSchemaComponent,\ + AttributeMarker,\ + DeclarationMarker,\ + WildCardMarker): + """ + parents: + complexType, restriction, extension, attributeGroup + attributes: + id -- ID + namespace -- '##any' | '##other' | + (anyURI* | '##targetNamespace' | '##local'), ##any + processContents -- 'lax' | 'skip' | 'strict', strict + contents: + annotation? + """ + attributes = {'id':None, + 'namespace':'##any', + 'processContents':'strict'} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class AttributeReference(XMLSchemaComponent,\ + AttributeMarker,\ + ReferenceMarker): + """ + parents: + complexType, restriction, extension, attributeGroup + attributes: + id -- ID + ref -- QName, required + use -- ('optional' | 'prohibited' | 'required'), optional + default -- string + fixed -- string + contents: + annotation? + """ + required = ['ref'] + attributes = {'id':None, + 'ref':None, + 'use':'optional', + 'default':None, + 'fixed':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class AttributeGroupDefinition(XMLSchemaComponent,\ + AttributeGroupMarker,\ + DefinitionMarker): + """ + parents: + schema, redefine + attributes: + id -- ID + name -- NCName, required + contents: + annotation?, (attribute | attributeGroup)*, anyAttribute? + """ + required = ['name'] + attributes = {'id':None, + 'name':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for indx in range(len(contents)): + component = SplitQName(contents[indx].getTagName())[1] + if (component == 'annotation') and (not indx): + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + elif (component == 'attribute'): + if contents[indx].hasattr('name'): + content.append(AttributeDeclaration()) + elif contents[indx].hasattr('ref'): + content.append(AttributeReference()) + else: + raise SchemaError, 'Unknown attribute type' + content[-1].fromDom(contents[indx]) + elif (component == 'attributeGroup'): + content.append(AttributeGroupReference()) + content[-1].fromDom(contents[indx]) + elif (component == 'anyAttribute') and (len(contents) == x+1): + content.append(AttributeWildCard()) + content[-1].fromDom(contents[indx]) + else: + raise SchemaError, 'Unknown component (%s)' %(contents[indx].getTagName()) + + self.attr_content = tuple(content) + +class AttributeGroupReference(XMLSchemaComponent,\ + AttributeGroupMarker,\ + ReferenceMarker): + """ + parents: + complexType, restriction, extension, attributeGroup + attributes: + id -- ID + ref -- QName, required + contents: + annotation? + """ + required = ['ref'] + attributes = {'id':None, + 'ref':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + + +###################################################### +# Elements +##################################################### +class IdentityConstrants(XMLSchemaComponent): + """Allow one to uniquely identify nodes in a document and ensure the + integrity of references between them. + + attributes -- dictionary of attributes + selector -- XPath to selected nodes + fields -- list of XPath to key field + """ + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.selector = None + self.fields = None + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + fields = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + elif component == 'selector': + self.selector = self.Selector(self) + self.selector.fromDom(i) + continue + elif component == 'field': + fields.append(self.Field(self)) + fields[-1].fromDom(i) + continue + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.fields = tuple(fields) + + + class Constraint(XMLSchemaComponent): + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + class Selector(Constraint): + """ + parent: + unique, key, keyref + attributes: + id -- ID + xpath -- XPath subset, required + contents: + annotation? + """ + required = ['xpath'] + attributes = {'id':None, + 'xpath':None} + contents = {'xsd':['annotation']} + + class Field(Constraint): + """ + parent: + unique, key, keyref + attributes: + id -- ID + xpath -- XPath subset, required + contents: + annotation? + """ + required = ['xpath'] + attributes = {'id':None, + 'xpath':None} + contents = {'xsd':['annotation']} + + +class Unique(IdentityConstrants): + """ Enforce fields are unique w/i a specified scope. + + parent: + element + attributes: + id -- ID + name -- NCName, required + contents: + annotation?, selector, field+ + """ + required = ['name'] + attributes = {'id':None, + 'name':None} + contents = {'xsd':['annotation', 'selector', 'field']} + + +class Key(IdentityConstrants): + """ Enforce fields are unique w/i a specified scope, and all + field values are present w/i document. Fields cannot + be nillable. + + parent: + element + attributes: + id -- ID + name -- NCName, required + contents: + annotation?, selector, field+ + """ + required = ['name'] + attributes = {'id':None, + 'name':None} + contents = {'xsd':['annotation', 'selector', 'field']} + + +class KeyRef(IdentityConstrants): + """ Ensure a match between two sets of values in an + instance. + parent: + element + attributes: + id -- ID + name -- NCName, required + refer -- QName, required + contents: + annotation?, selector, field+ + """ + required = ['name', 'refer'] + attributes = {'id':None, + 'name':None, + 'refer':None} + contents = {'xsd':['annotation', 'selector', 'field']} + + +class ElementDeclaration(XMLSchemaComponent,\ + ElementMarker,\ + DeclarationMarker): + """ + parents: + schema + attributes: + id -- ID + name -- NCName, required + type -- QName + default -- string + fixed -- string + nillable -- boolean, false + abstract -- boolean, false + substitutionGroup -- QName + block -- ('#all' | ('substition' | 'extension' | 'restriction')*), + schema.blockDefault + final -- ('#all' | ('extension' | 'restriction')*), + schema.finalDefault + contents: + annotation?, (simpleType,complexType)?, (key | keyref | unique)* + + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'type':None, + 'default':None, + 'fixed':None, + 'nillable':0, + 'abstract':0, + 'substitutionGroup':None, + 'block':lambda self: self._parent().getBlockDefault(), + 'final':lambda self: self._parent().getFinalDefault()} + contents = {'xsd':['annotation', 'simpleType', 'complexType', 'key',\ + 'keyref', 'unique']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.constraints = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + constraints = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + elif component == 'simpleType' and not self.content: + self.content = AnonymousSimpleType(self) + self.content.fromDom(i) + elif component == 'complexType' and not self.content: + self.content = LocalComplexType(self) + self.content.fromDom(i) + elif component == 'key': + constraints.append(Key(self)) + constraints[-1].fromDom(i) + elif component == 'keyref': + constraints.append(KeyRef(self)) + constraints[-1].fromDom(i) + elif component == 'unique': + constraints.append(Unique(self)) + constraints[-1].fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.constraints = tuple(constraints) + + +class LocalElementDeclaration(ElementDeclaration,\ + LocalMarker): + """ + parents: + all, choice, sequence + attributes: + id -- ID + name -- NCName, required + form -- ('qualified' | 'unqualified'), schema.elementFormDefault + type -- QName + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + default -- string + fixed -- string + nillable -- boolean, false + block -- ('#all' | ('extension' | 'restriction')*), schema.blockDefault + contents: + annotation?, (simpleType,complexType)?, (key | keyref | unique)* + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'form':lambda self: GetSchema(self).getElementFormDefault(), + 'type':None, + 'minOccurs':'1', + 'maxOccurs':'1', + 'default':None, + 'fixed':None, + 'nillable':0, + 'abstract':0, + 'block':lambda self: GetSchema(self).getBlockDefault()} + contents = {'xsd':['annotation', 'simpleType', 'complexType', 'key',\ + 'keyref', 'unique']} + + +class ElementReference(XMLSchemaComponent,\ + ElementMarker,\ + ReferenceMarker): + """ + parents: + all, choice, sequence + attributes: + id -- ID + ref -- QName, required + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + contents: + annotation? + """ + required = ['ref'] + attributes = {'id':None, + 'ref':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.annotation = None + self.setAttributes(node) + for i in self.getContents(node): + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class ElementWildCard(LocalElementDeclaration,\ + WildCardMarker): + """ + parents: + choice, sequence + attributes: + id -- ID + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + namespace -- '##any' | '##other' | + (anyURI* | '##targetNamespace' | '##local'), ##any + processContents -- 'lax' | 'skip' | 'strict', strict + contents: + annotation? + """ + required = [] + attributes = {'id':None, + 'minOccurs':'1', + 'maxOccurs':'1', + 'namespace':'##any', + 'processContents':'strict'} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.annotation = None + self.setAttributes(node) + for i in self.getContents(node): + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +###################################################### +# Model Groups +##################################################### +class Sequence(XMLSchemaComponent,\ + ModelGroupMarker): + """ + parents: + complexType, extension, restriction, group, choice, sequence + attributes: + id -- ID + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + + contents: + annotation?, (element | group | choice | sequence | any)* + """ + attributes = {'id':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation', 'element', 'group', 'choice', 'sequence',\ + 'any']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'element': + if i.hasattr('ref'): + content.append(ElementReference(self)) + else: + content.append(LocalElementDeclaration(self)) + elif component == 'group': + content.append(ModelGroupReference(self)) + elif component == 'choice': + content.append(Choice(self)) + elif component == 'sequence': + content.append(Sequence(self)) + elif component == 'any': + content.append(ElementWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + content[-1].fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + +class All(XMLSchemaComponent,\ + ModelGroupMarker): + """ + parents: + complexType, extension, restriction, group + attributes: + id -- ID + minOccurs -- '0' | '1', 1 + maxOccurs -- '1', 1 + + contents: + annotation?, element* + """ + attributes = {'id':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation', 'element']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'element': + if i.hasattr('ref'): + content.append(ElementReference(self)) + else: + content.append(LocalElementDeclaration(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + content[-1].fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + +class Choice(XMLSchemaComponent,\ + ModelGroupMarker): + """ + parents: + complexType, extension, restriction, group, choice, sequence + attributes: + id -- ID + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + + contents: + annotation?, (element | group | choice | sequence | any)* + """ + attributes = {'id':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation', 'element', 'group', 'choice', 'sequence',\ + 'any']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'element': + if i.hasattr('ref'): + content.append(ElementReference(self)) + else: + content.append(LocalElementDeclaration(self)) + elif component == 'group': + content.append(ModelGroupReference(self)) + elif component == 'choice': + content.append(Choice(self)) + elif component == 'sequence': + content.append(Sequence(self)) + elif component == 'any': + content.append(ElementWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + content[-1].fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + +class ModelGroupDefinition(XMLSchemaComponent,\ + ModelGroupMarker,\ + DefinitionMarker): + """ + parents: + redefine, schema + attributes: + id -- ID + name -- NCName, required + + contents: + annotation?, (all | choice | sequence)? + """ + required = ['name'] + attributes = {'id':None, + 'name':None} + contents = {'xsd':['annotation', 'all', 'choice', 'sequence']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'all' and not self.content: + self.content = All(self) + elif component == 'choice' and not self.content: + self.content = Choice(self) + elif component == 'sequence' and not self.content: + self.content = Sequence(self) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class ModelGroupReference(XMLSchemaComponent,\ + ModelGroupMarker,\ + ReferenceMarker): + """ + parents: + choice, complexType, extension, restriction, sequence + attributes: + id -- ID + ref -- NCName, required + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + + contents: + annotation? + """ + required = ['ref'] + attributes = {'id':None, + 'ref':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + + +class ComplexType(XMLSchemaComponent,\ + DefinitionMarker,\ + ComplexMarker): + """ + parents: + redefine, schema + attributes: + id -- ID + name -- NCName, required + mixed -- boolean, false + abstract -- boolean, false + block -- ('#all' | ('extension' | 'restriction')*), schema.blockDefault + final -- ('#all' | ('extension' | 'restriction')*), schema.finalDefault + + contents: + annotation?, (simpleContent | complexContent | + ((group | all | choice | sequence)?, (attribute | attributeGroup)*, anyAttribute?)) + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'mixed':0, + 'abstract':0, + 'block':lambda self: self._parent().getBlockDefault(), + 'final':lambda self: self._parent().getFinalDefault()} + contents = {'xsd':['annotation', 'simpleContent', 'complexContent',\ + 'group', 'all', 'choice', 'sequence', 'attribute', 'attributeGroup',\ + 'anyAttribute', 'any']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + #XXX ugly + if not num: + return + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + component = SplitQName(contents[indx].getTagName())[1] + + self.content = None + if component == 'simpleContent': + self.content = self.__class__.SimpleContent(self) + self.content.fromDom(contents[indx]) + elif component == 'complexContent': + self.content = self.__class__.ComplexContent(self) + self.content.fromDom(contents[indx]) + else: + if component == 'all': + self.content = All(self) + elif component == 'choice': + self.content = Choice(self) + elif component == 'sequence': + self.content = Sequence(self) + elif component == 'group': + self.content = ModelGroupReference(self) + + if self.content: + self.content.fromDom(contents[indx]) + indx += 1 + + self.attr_content = [] + while indx < num: + component = SplitQName(contents[indx].getTagName())[1] + if component == 'attribute': + if contents[indx].hasattr('ref'): + self.attr_content.append(AttributeReference(self)) + else: + self.attr_content.append(LocalAttributeDeclaration(self)) + elif component == 'attributeGroup': + self.attr_content.append(AttributeGroupReference(self)) + elif component == 'anyAttribute': + self.attr_content.append(AttributeWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(contents[indx].getTagName()) + self.attr_content[-1].fromDom(contents[indx]) + indx += 1 + + class _DerivedType(XMLSchemaComponent): + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.derivation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'restriction' and not self.derivation: + self.derivation = self.__class__.Restriction(self) + elif component == 'extension' and not self.derivation: + self.derivation = self.__class__.Extension(self) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.derivation.fromDom(i) + + class ComplexContent(_DerivedType,\ + ComplexMarker): + """ + parents: + complexType + attributes: + id -- ID + mixed -- boolean, false + + contents: + annotation?, (restriction | extension) + """ + attributes = {'id':None, + 'mixed':0 } + contents = {'xsd':['annotation', 'restriction', 'extension']} + + class _DerivationBase(XMLSchemaComponent): + """, + parents: + complexContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (group | all | choice | sequence)?, + (attribute | attributeGroup)*, anyAttribute? + """ + required = ['base'] + attributes = {'id':None, + 'base':None } + contents = {'xsd':['annotation', 'group', 'all', 'choice',\ + 'sequence', 'attribute', 'attributeGroup', 'anyAttribute']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + #XXX ugly + if not num: + return + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + component = SplitQName(contents[indx].getTagName())[1] + + if component == 'all': + self.content = All(self) + self.content.fromDom(contents[indx]) + indx += 1 + elif component == 'choice': + self.content = Choice(self) + self.content.fromDom(contents[indx]) + indx += 1 + elif component == 'sequence': + self.content = Sequence(self) + self.content.fromDom(contents[indx]) + indx += 1 + elif component == 'group': + self.content = ModelGroupReference(self) + self.content.fromDom(contents[indx]) + indx += 1 + else: + self.content = None + + self.attr_content = [] + while indx < num: + component = SplitQName(contents[indx].getTagName())[1] + if component == 'attribute': + if contents[indx].hasattr('ref'): + self.attr_content.append(AttributeReference(self)) + else: + self.attr_content.append(LocalAttributeDeclaration(self)) + elif component == 'attributeGroup': + if contents[indx].hasattr('ref'): + self.attr_content.append(AttributeGroupReference(self)) + else: + self.attr_content.append(AttributeGroupDefinition(self)) + elif component == 'anyAttribute': + self.attr_content.append(AttributeWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(contents[indx].getTagName()) + self.attr_content[-1].fromDom(contents[indx]) + indx += 1 + + class Extension(_DerivationBase, + ExtensionMarker): + """ + parents: + complexContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (group | all | choice | sequence)?, + (attribute | attributeGroup)*, anyAttribute? + """ + pass + + class Restriction(_DerivationBase,\ + RestrictionMarker): + """ + parents: + complexContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (group | all | choice | sequence)?, + (attribute | attributeGroup)*, anyAttribute? + """ + pass + + + class SimpleContent(_DerivedType,\ + SimpleMarker): + """ + parents: + complexType + attributes: + id -- ID + + contents: + annotation?, (restriction | extension) + """ + attributes = {'id':None} + contents = {'xsd':['annotation', 'restriction', 'extension']} + + class Extension(XMLSchemaComponent,\ + ExtensionMarker): + """ + parents: + simpleContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (attribute | attributeGroup)*, anyAttribute? + """ + required = ['base'] + attributes = {'id':None, + 'base':None } + contents = {'xsd':['annotation', 'attribute', 'attributeGroup', + 'anyAttribute']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + component = SplitQName(contents[indx].getTagName())[1] + + content = [] + while indx < num: + component = SplitQName(contents[indx].getTagName())[1] + if component == 'attribute': + if contents[indx].hasattr('ref'): + content.append(AttributeReference(self)) + else: + content.append(LocalAttributeDeclaration(self)) + elif component == 'attributeGroup': + content.append(AttributeGroupReference(self)) + elif component == 'anyAttribute': + content.append(AttributeWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)'\ + %(contents[indx].getTagName()) + content[-1].fromDom(contents[indx]) + indx += 1 + self.attr_content = tuple(content) + + + class Restriction(XMLSchemaComponent,\ + RestrictionMarker): + """ + parents: + simpleContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, simpleType?, (enumeration | length | + maxExclusive | maxInclusive | maxLength | minExclusive | + minInclusive | minLength | pattern | fractionDigits | + totalDigits | whiteSpace)*, (attribute | attributeGroup)*, + anyAttribute? + """ + required = ['base'] + attributes = {'id':None, + 'base':None } + contents = {'xsd':['annotation', 'simpleType', 'attribute',\ + 'attributeGroup', 'anyAttribute'] + RestrictionMarker.facets} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.content = [] + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + component = SplitQName(contents[indx].getTagName())[1] + + content = [] + while indx < num: + component = SplitQName(contents[indx].getTagName())[1] + if component == 'attribute': + if contents[indx].hasattr('ref'): + content.append(AttributeReference(self)) + else: + content.append(LocalAttributeDeclaration(self)) + elif component == 'attributeGroup': + content.append(AttributeGroupReference(self)) + elif component == 'anyAttribute': + content.append(AttributeWildCard(self)) + elif component == 'simpleType': + self.content.append(LocalSimpleType(self)) + self.content[-1].fromDom(contents[indx]) + else: + raise SchemaError, 'Unknown component (%s)'\ + %(contents[indx].getTagName()) + content[-1].fromDom(contents[indx]) + indx += 1 + self.attr_content = tuple(content) + + +class LocalComplexType(ComplexType,\ + LocalMarker): + """ + parents: + element + attributes: + id -- ID + mixed -- boolean, false + + contents: + annotation?, (simpleContent | complexContent | + ((group | all | choice | sequence)?, (attribute | attributeGroup)*, anyAttribute?)) + """ + required = [] + attributes = {'id':None, + 'mixed':0} + + +class SimpleType(XMLSchemaComponent,\ + DefinitionMarker,\ + SimpleMarker): + """ + parents: + redefine, schema + attributes: + id -- ID + name -- NCName, required + final -- ('#all' | ('extension' | 'restriction' | 'list' | 'union')*), + schema.finalDefault + + contents: + annotation?, (restriction | list | union) + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'final':lambda self: self._parent().getFinalDefault()} + contents = {'xsd':['annotation', 'restriction', 'list', 'union']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + for child in contents: + component = SplitQName(child.getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(child) + continue + break + else: + return + if component == 'restriction': + self.content = self.__class__.Restriction(self) + elif component == 'list': + self.content = self.__class__.List(self) + elif component == 'union': + self.content = self.__class__.Union(self) + else: + raise SchemaError, 'Unknown component (%s)' %(component) + self.content.fromDom(child) + + class Restriction(XMLSchemaComponent,\ + RestrictionMarker): + """ + parents: + simpleType + attributes: + id -- ID + base -- QName, required or simpleType child + + contents: + annotation?, simpleType?, (enumeration | length | + maxExclusive | maxInclusive | maxLength | minExclusive | + minInclusive | minLength | pattern | fractionDigits | + totalDigits | whiteSpace)* + """ + attributes = {'id':None, + 'base':None } + contents = {'xsd':['annotation', 'simpleType']+RestrictionMarker.facets} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for indx in range(len(contents)): + component = SplitQName(contents[indx].getTagName())[1] + if (component == 'annotation') and (not indx): + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + continue + elif (component == 'simpleType') and (not indx or indx == 1): + content.append(AnonymousSimpleType(self)) + content[-1].fromDom(contents[indx]) + elif component in RestrictionMarker.facets: + #print_debug('%s class instance, skipping %s' %(self.__class__, component)) + pass + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + + class Union(XMLSchemaComponent): + """ + parents: + simpleType + attributes: + id -- ID + memberTypes -- list of QNames, required or simpleType child. + + contents: + annotation?, simpleType* + """ + attributes = {'id':None, + 'memberTypes':None } + contents = {'xsd':['annotation', 'simpleType']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for indx in range(len(contents)): + component = SplitQName(contents[indx].getTagName())[1] + if (component == 'annotation') and (not indx): + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + elif (component == 'simpleType'): + content.append(AnonymousSimpleType(self)) + content[-1].fromDom(contents[indx]) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + class List(XMLSchemaComponent): + """ + parents: + simpleType + attributes: + id -- ID + itemType -- QName, required or simpleType child. + + contents: + annotation?, simpleType? + """ + attributes = {'id':None, + 'itemType':None } + contents = {'xsd':['annotation', 'simpleType']} + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + self.content = [] + + for indx in range(len(contents)): + component = SplitQName(contents[indx].getTagName())[1] + if (component == 'annotation') and (not indx): + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + elif (component == 'simpleType'): + self.content = AnonymousSimpleType(self) + self.content.fromDom(contents[indx]) + break + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class AnonymousSimpleType(SimpleType,\ + SimpleMarker): + """ + parents: + attribute, element, list, restriction, union + attributes: + id -- ID + + contents: + annotation?, (restriction | list | union) + """ + required = [] + attributes = {'id':None} + + +class Redefine: + """ + parents: + attributes: + + contents: + """ + pass + +########################### +########################### + + +if sys.version_info[:2] >= (2, 2): + tupleClass = tuple +else: + import UserTuple + tupleClass = UserTuple.UserTuple + +class TypeDescriptionComponent(tupleClass): + """Tuple of length 2, consisting of + a namespace and unprefixed name. + """ + def __init__(self, args): + """args -- (namespace, name) + Remove the name's prefix, irrelevant. + """ + if len(args) != 2: + raise TypeError, 'expecting tuple (namespace, name), got %s' %args + elif args[1].find(':') >= 0: + args = (args[0], SplitQName(args[1])[1]) + tuple.__init__(self, args) + return + + def getTargetNamespace(self): + return self[0] + + def getName(self): + return self[1] + + +''' +import string, types, base64, re +from Utility import DOM, Collection +from StringIO import StringIO + + +class SchemaReader: + """A SchemaReader creates XMLSchema objects from urls and xml data.""" + + def loadFromStream(self, file): + """Return an XMLSchema instance loaded from a file object.""" + document = DOM.loadDocument(file) + schema = XMLSchema() + schema.load(document) + return schema + + def loadFromString(self, data): + """Return an XMLSchema instance loaded from an xml string.""" + return self.loadFromStream(StringIO(data)) + + def loadFromURL(self, url): + """Return an XMLSchema instance loaded from the given url.""" + document = DOM.loadFromURL(url) + schema = XMLSchema() + schema.location = url + schema.load(document) + return schema + + def loadFromFile(self, filename): + """Return an XMLSchema instance loaded from the given file.""" + file = open(filename, 'rb') + try: schema = self.loadFromStream(file) + finally: file.close() + return schema + +class SchemaError(Exception): + pass + +class XMLSchema: + # This is temporary, for the benefit of WSDL until the real thing works. + def __init__(self, element): + self.targetNamespace = DOM.getAttr(element, 'targetNamespace') + self.element = element + +class realXMLSchema: + """A schema is a collection of schema components derived from one + or more schema documents, that is, one or more element + information items. It represents the abstract notion of a schema + rather than a single schema document (or other representation).""" + def __init__(self): + self.simpleTypes = Collection(self) + self.complexTypes = Collection(self) + self.attributes = Collection(self) + self.elements = Collection(self) + self.attrGroups = Collection(self) + self.idConstraints=None + self.modelGroups = None + self.notations = None + self.extensions = [] + + targetNamespace = None + attributeFormDefault = 'unqualified' + elementFormDefault = 'unqualified' + blockDefault = None + finalDefault = None + location = None + version = None + id = None + + def load(self, document): + if document.nodeType == document.DOCUMENT_NODE: + schema = DOM.getElement(document, 'schema', None, None) + else: + schema = document + if schema is None: + raise SchemaError('Missing element.') + + self.namespace = namespace = schema.namespaceURI + if not namespace in DOM.NS_XSD_ALL: + raise SchemaError( + 'Unknown XML schema namespace: %s.' % self.namespace + ) + + for attrname in ( + 'targetNamespace', 'attributeFormDefault', 'elementFormDefault', + 'blockDefault', 'finalDefault', 'version', 'id' + ): + value = DOM.getAttr(schema, attrname, None, None) + if value is not None: + setattr(self, attrname, value) + + + # Resolve imports and includes here? +## imported = {} +## while 1: +## imports = [] +## for element in DOM.getElements(definitions, 'import', NS_WSDL): +## location = DOM.getAttr(element, 'location') +## if not imported.has_key(location): +## imports.append(element) +## if not imports: +## break +## for element in imports: +## self._import(document, element) +## imported[location] = 1 + + for element in DOM.getElements(schema, None, None): + localName = element.localName + + if not DOM.nsUriMatch(element.namespaceURI, namespace): + self.extensions.append(element) + continue + + elif localName == 'message': + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + message = self.addMessage(name, docs) + parts = DOM.getElements(element, 'part', NS_WSDL) + message.load(parts) + continue + + def _import(self, document, element): + namespace = DOM.getAttr(element, 'namespace', default=None) + location = DOM.getAttr(element, 'location', default=None) + if namespace is None or location is None: + raise WSDLError( + 'Invalid import element (missing namespace or location).' + ) + + # Sort-of support relative locations to simplify unit testing. The + # WSDL specification actually doesn't allow relative URLs, so its + # ok that this only works with urls relative to the initial document. + location = urllib.basejoin(self.location, location) + + obimport = self.addImport(namespace, location) + obimport._loaded = 1 + + importdoc = DOM.loadFromURL(location) + try: + if location.find('#') > -1: + idref = location.split('#')[-1] + imported = DOM.getElementById(importdoc, idref) + else: + imported = importdoc.documentElement + if imported is None: + raise WSDLError( + 'Import target element not found for: %s' % location + ) + + imported_tns = DOM.getAttr(imported, 'targetNamespace') + importer_tns = namespace + + if imported_tns != importer_tns: + return + + if imported.localName == 'definitions': + imported_nodes = imported.childNodes + else: + imported_nodes = [imported] + parent = element.parentNode + for node in imported_nodes: + if node.nodeType != node.ELEMENT_NODE: + continue + child = DOM.importNode(document, node, 1) + parent.appendChild(child) + child.setAttribute('targetNamespace', importer_tns) + attrsNS = imported._attrsNS + for attrkey in attrsNS.keys(): + if attrkey[0] == DOM.NS_XMLNS: + attr = attrsNS[attrkey].cloneNode(1) + child.setAttributeNode(attr) + finally: + importdoc.unlink() + + +class Element: + """Common base class for element representation classes.""" + def __init__(self, name=None, documentation=''): + self.name = name + self.documentation = documentation + self.extensions = [] + + def addExtension(self, item): + self.extensions.append(item) + + +class SimpleTypeDefinition: + """Represents an xml schema simple type definition.""" + +class ComplexTypeDefinition: + """Represents an xml schema complex type definition.""" + +class AttributeDeclaration: + """Represents an xml schema attribute declaration.""" + +class ElementDeclaration: + """Represents an xml schema element declaration.""" + def __init__(self, name, type=None, targetNamespace=None): + self.name = name + + targetNamespace = None + annotation = None + nillable = 0 + abstract = 0 + default = None + fixed = None + scope = 'global' + type = None + form = 0 + # Things we will not worry about for now. + id_constraint_defs = None + sub_group_exclude = None + sub_group_affils = None + disallowed_subs = None + + + + + + + + + + +class AttributeGroupDefinition: + """Represents an xml schema attribute group definition.""" + +class IdentityConstraintDefinition: + """Represents an xml schema identity constraint definition.""" + +class ModelGroupDefinition: + """Represents an xml schema model group definition.""" + +class NotationDeclaration: + """Represents an xml schema notation declaration.""" + +class Annotation: + """Represents an xml schema annotation.""" + +class ModelGroup: + """Represents an xml schema model group.""" + +class Particle: + """Represents an xml schema particle.""" + +class WildCard: + """Represents an xml schema wildcard.""" + +class AttributeUse: + """Represents an xml schema attribute use.""" + + +class ElementComponent: + namespace = '' + name = '' + type = None + form = 'qualified | unqualified' + scope = 'global or complex def' + constraint = ('value', 'default | fixed') + nillable = 0 + id_constraint_defs = None + sub_group_affil = None + sub_group_exclusions = None + disallowed_subs = 'substitution, extension, restriction' + abstract = 0 + minOccurs = 1 + maxOccurs = 1 + ref = '' + +class AttributeThing: + name = '' + namespace = '' + typeName = '' + typeUri = '' + scope = 'global | local to complex def' + constraint = ('value:default', 'value:fixed') + use = 'optional | prohibited | required' + +class ElementDataType: + namespace = '' + name = '' + element_form = 'qualified | unqualified' + attr_form = None + type_name = '' + type_uri = '' + def __init__(self, name, namespace, type_name, type_uri): + self.namespace = namespace + self.name = name + # type may be anonymous... + self.type_name = type_name + self.type_uri = type_uri + + def checkValue(self, value, context): + # Delegate value checking to the type of the element. + typeref = (self.type_uri, self.type_name) + handler = context.serializer.getType(typeref) + return handler.checkValue(value, context) + + def serialize(self, name, namespace, value, context, **kwargs): + if context.check_values: + self.checkValue(value, context) + # Delegate serialization to the type of the element. + typeref = (self.type_uri, self.type_name) + handler = context.serializer.getType(typeref) + return handler.serialize(self.name, self.namespace, value, context) + + def deserialize(self, element, context): + if element_is_null(element, context): + return None + # Delegate deserialization to the type of the element. + typeref = (self.type_uri, self.type_name) + handler = context.serializer.getType(typeref) + return handler.deserialize(element, context) + + + +def parse_schema(data): + targetNS = '' + attributeFormDefault = 0 + elementFormDefault = 0 + blockDefault = '' + finalDefault = '' + language = None + version = None + id = '' +''' diff --git a/SOAPpy/wstools/XMLname.py b/SOAPpy/wstools/XMLname.py new file mode 100755 index 0000000..532f02b --- /dev/null +++ b/SOAPpy/wstools/XMLname.py @@ -0,0 +1,88 @@ +"""Translate strings to and from SOAP 1.2 XML name encoding + +Implements rules for mapping application defined name to XML names +specified by the w3 SOAP working group for SOAP version 1.2 in +Appendix A of "SOAP Version 1.2 Part 2: Adjuncts", W3C Working Draft +17, December 2001, + +Also see . + +Author: Gregory R. Warnes +Date:: 2002-04-25 +Version 0.9.0 + +""" + +ident = "$Id: XMLname.py,v 1.2 2003/05/20 21:10:14 warnes Exp $" + +from re import * + + +def _NCNameChar(x): + return x.isalpha() or x.isdigit() or x=="." or x=='-' or x=="_" + + +def _NCNameStartChar(x): + return x.isalpha() or x=="_" + + +def _toUnicodeHex(x): + hexval = hex(ord(x[0]))[2:] + hexlen = len(hexval) + # Make hexval have either 4 or 8 digits by prepending 0's + if (hexlen==1): hexval = "000" + hexval + elif (hexlen==2): hexval = "00" + hexval + elif (hexlen==3): hexval = "0" + hexval + elif (hexlen==4): hexval = "" + hexval + elif (hexlen==5): hexval = "000" + hexval + elif (hexlen==6): hexval = "00" + hexval + elif (hexlen==7): hexval = "0" + hexval + elif (hexlen==8): hexval = "" + hexval + else: raise Exception, "Illegal Value returned from hex(ord(x))" + + return "_x"+ hexval + "_" + + +def _fromUnicodeHex(x): + return eval( r'u"\u'+x[2:-1]+'"' ) + + +def toXMLname(string): + """Convert string to a XML name.""" + if string.find(':') != -1 : + (prefix, localname) = string.split(':',1) + else: + prefix = None + localname = string + + T = unicode(localname) + + N = len(localname) + X = []; + for i in range(N) : + if i< N-1 and T[i]==u'_' and T[i+1]==u'x': + X.append(u'_x005F_') + elif i==0 and N >= 3 and \ + ( T[0]==u'x' or T[0]==u'X' ) and \ + ( T[1]==u'm' or T[1]==u'M' ) and \ + ( T[2]==u'l' or T[2]==u'L' ): + X.append(u'_xFFFF_' + T[0]) + elif (not _NCNameChar(T[i])) or (i==0 and not _NCNameStartChar(T[i])): + X.append(_toUnicodeHex(T[i])) + else: + X.append(T[i]) + + return u''.join(X) + + +def fromXMLname(string): + """Convert XML name to unicode string.""" + + retval = sub(r'_xFFFF_','', string ) + + def fun( matchobj ): + return _fromUnicodeHex( matchobj.group(0) ) + + retval = sub(r'_x[0-9A-Za-z]+_', fun, retval ) + + return retval diff --git a/SOAPpy/wstools/__init__.py b/SOAPpy/wstools/__init__.py new file mode 100644 index 0000000..12a584f --- /dev/null +++ b/SOAPpy/wstools/__init__.py @@ -0,0 +1,36 @@ +#! /usr/bin/env python +"""WSDL parsing services package for Web Services for Python.""" + +ident = "$Id: __init__.py,v 1.8 2004/09/09 23:32:09 boverhof Exp $" + +import WSDLTools +import XMLname +from logging import getLogger as _getLogger +import logging.config as _config + +LOGGING = 'logging.txt' +DEBUG = True + +# +# If LOGGING configuration file is not found, turn off logging +# and use _noLogger class because logging module's performance +# is terrible. +# + +try: + _config.fileConfig(LOGGING) +except: + DEBUG = False + + +class Base: + def __init__(self, module=__name__): + self.logger = _noLogger() + if DEBUG is True: + self.logger = _getLogger('%s-%s(%x)' %(module, self.__class__, id(self))) + +class _noLogger: + def __init__(self, *args): pass + def warning(self, *args): pass + def debug(self, *args): pass + def error(self, *args): pass diff --git a/SOAPpy/wstools/test/__init__.py b/SOAPpy/wstools/test/__init__.py new file mode 100644 index 0000000..d5a5350 --- /dev/null +++ b/SOAPpy/wstools/test/__init__.py @@ -0,0 +1,5 @@ +#! /usr/bin/env python +"""wstools.WSDLTools.WSDLReader tests directory.""" + +import utils + diff --git a/SOAPpy/wstools/test/test_t1.py b/SOAPpy/wstools/test/test_t1.py new file mode 100644 index 0000000..5c33899 --- /dev/null +++ b/SOAPpy/wstools/test/test_t1.py @@ -0,0 +1,20 @@ +############################################################################ +# Joshua R. Boverhof, David W. Robertson, LBNL +# See LBNLCopyright for copyright notice! +########################################################################### +import unittest +import test_wsdl +import utils + +def makeTestSuite(): + suite = unittest.TestSuite() + suite.addTest(test_wsdl.makeTestSuite("services_by_file")) + return suite + +def main(): + loader = utils.MatchTestLoader(True, None, "makeTestSuite") + unittest.main(defaultTest="makeTestSuite", testLoader=loader) + +if __name__ == "__main__" : main() + + diff --git a/SOAPpy/wstools/test/test_wsdl.py b/SOAPpy/wstools/test/test_wsdl.py new file mode 100644 index 0000000..90b0c4d --- /dev/null +++ b/SOAPpy/wstools/test/test_wsdl.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python + +############################################################################ +# Joshua R. Boverhof, David W. Robertson, LBNL +# See LBNLCopyright for copyright notice! +########################################################################### + +import sys, unittest +import ConfigParser +from ZSI.wstools.Utility import DOM +from ZSI.wstools.WSDLTools import WSDLReader +from ZSI.wstools.TimeoutSocket import TimeoutError + +class WSDLToolsTestCase(unittest.TestCase): + + def __init__(self, methodName='runTest'): + unittest.TestCase.__init__(self, methodName) + + def setUp(self): + self.path = nameGenerator.next() + print self.path + sys.stdout.flush() + + def __str__(self): + teststr = unittest.TestCase.__str__(self) + if hasattr(self, "path"): + return "%s: %s" % (teststr, self.path ) + else: + return "%s" % (teststr) + + def checkWSDLCollection(self, tag_name, component, key='name'): + if self.wsdl is None: + return + definition = self.wsdl.document.documentElement + version = DOM.WSDLUriToVersion(definition.namespaceURI) + nspname = DOM.GetWSDLUri(version) + for node in DOM.getElements(definition, tag_name, nspname): + name = DOM.getAttr(node, key) + comp = component[name] + self.failUnlessEqual(eval('comp.%s' %key), name) + + def checkXSDCollection(self, tag_name, component, node, key='name'): + for cnode in DOM.getElements(node, tag_name): + name = DOM.getAttr(cnode, key) + component[name] + + def test_all(self): + try: + if self.path[:7] == 'http://': + self.wsdl = WSDLReader().loadFromURL(self.path) + else: + self.wsdl = WSDLReader().loadFromFile(self.path) + + except TimeoutError: + print "connection timed out" + sys.stdout.flush() + return + except: + self.path = self.path + ": load failed, unable to start" + raise + + try: + self.checkWSDLCollection('service', self.wsdl.services) + except: + self.path = self.path + ": wsdl.services" + raise + + try: + self.checkWSDLCollection('message', self.wsdl.messages) + except: + self.path = self.path + ": wsdl.messages" + raise + + try: + self.checkWSDLCollection('portType', self.wsdl.portTypes) + except: + self.path = self.path + ": wsdl.portTypes" + raise + + try: + self.checkWSDLCollection('binding', self.wsdl.bindings) + except: + self.path = self.path + ": wsdl.bindings" + raise + + try: + self.checkWSDLCollection('import', self.wsdl.imports, key='namespace') + except: + self.path = self.path + ": wsdl.imports" + raise + + try: + for key in self.wsdl.types.keys(): + schema = self.wsdl.types[key] + self.failUnlessEqual(key, schema.getTargetNamespace()) + + definition = self.wsdl.document.documentElement + version = DOM.WSDLUriToVersion(definition.namespaceURI) + nspname = DOM.GetWSDLUri(version) + for node in DOM.getElements(definition, 'types', nspname): + for snode in DOM.getElements(node, 'schema'): + tns = DOM.findTargetNS(snode) + schema = self.wsdl.types[tns] + self.schemaAttributesDeclarations(schema, snode) + self.schemaAttributeGroupDeclarations(schema, snode) + self.schemaElementDeclarations(schema, snode) + self.schemaTypeDefinitions(schema, snode) + except: + self.path = self.path + ": wsdl.types" + raise + + if self.wsdl.extensions: + print 'No check for WSDLTools(%s) Extensions:' %(self.wsdl.name) + for ext in self.wsdl.extensions: print '\t', ext + + def schemaAttributesDeclarations(self, schema, node): + self.checkXSDCollection('attribute', schema.attr_decl, node) + + def schemaAttributeGroupDeclarations(self, schema, node): + self.checkXSDCollection('group', schema.attr_groups, node) + + def schemaElementDeclarations(self, schema, node): + self.checkXSDCollection('element', schema.elements, node) + + def schemaTypeDefinitions(self, schema, node): + self.checkXSDCollection('complexType', schema.types, node) + self.checkXSDCollection('simpleType', schema.types, node) + + +def setUpOptions(section): + cp = ConfigParser.ConfigParser() + cp.read('config.txt') + if not cp.sections(): + print 'fatal error: configuration file config.txt not present' + sys.exit(0) + if not cp.has_section(section): + print '%s section not present in configuration file, exiting' % section + sys.exit(0) + return cp, len(cp.options(section)) + +def getOption(cp, section): + for name, value in cp.items(section): + yield value + +def makeTestSuite(section='services_by_file'): + global nameGenerator + + cp, numTests = setUpOptions(section) + nameGenerator = getOption(cp, section) + suite = unittest.TestSuite() + for i in range(0, numTests): + suite.addTest(unittest.makeSuite(WSDLToolsTestCase, 'test_')) + return suite + + +def main(): + unittest.main(defaultTest="makeTestSuite") + + +if __name__ == "__main__" : main() diff --git a/SOAPpy/wstools/test/test_wstools.py b/SOAPpy/wstools/test/test_wstools.py new file mode 100644 index 0000000..0e0f958 --- /dev/null +++ b/SOAPpy/wstools/test/test_wstools.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +############################################################################ +# Joshua R. Boverhof, David W. Robertson, LBNL +# See LBNLCopyright for copyright notice! +########################################################################### + +import unittest, tarfile, os, ConfigParser +import test_wsdl + + +SECTION='files' +CONFIG_FILE = 'config.txt' + +def extractFiles(section, option): + config = ConfigParser.ConfigParser() + config.read(CONFIG_FILE) + archives = config.get(section, option) + archives = eval(archives) + for file in archives: + tar = tarfile.open(file) + if not os.access(tar.membernames[0], os.R_OK): + for i in tar.getnames(): + tar.extract(i) + +def makeTestSuite(): + suite = unittest.TestSuite() + suite.addTest(test_wsdl.makeTestSuite("services_by_file")) + return suite + +def main(): + extractFiles(SECTION, 'archives') + unittest.main(defaultTest="makeTestSuite") + +if __name__ == "__main__" : main() + + diff --git a/SOAPpy/wstools/test/test_wstools_net.py b/SOAPpy/wstools/test/test_wstools_net.py new file mode 100644 index 0000000..880cff3 --- /dev/null +++ b/SOAPpy/wstools/test/test_wstools_net.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +############################################################################ +# Joshua R. Boverhof, David W. Robertson, LBNL +# See LBNLCopyright for copyright notice! +########################################################################### +import unittest +import test_wsdl + +def makeTestSuite(): + suite = unittest.TestSuite() + suite.addTest(test_wsdl.makeTestSuite("services_by_http")) + return suite + +def main(): + unittest.main(defaultTest="makeTestSuite") + +if __name__ == "__main__" : main() + +