From a98ef1f178022ecab298d6bb08198f466d6856e5 Mon Sep 17 00:00:00 2001 From: Forrest Voight Date: Wed, 3 Aug 2011 15:37:29 -0400 Subject: [PATCH] bundled wstools-0.3 --- wstools/.cvsignore | 1 + wstools/MIMEAttachment.py | 110 ++ wstools/Namespaces.py | 214 +++ wstools/TimeoutSocket.py | 179 +++ wstools/UserTuple.py | 99 ++ wstools/Utility.py | 1382 ++++++++++++++++ wstools/WSDLTools.py | 1668 ++++++++++++++++++++ wstools/XMLSchema.py | 3116 +++++++++++++++++++++++++++++++++++++ wstools/XMLname.py | 90 ++ wstools/__init__.py | 9 + wstools/c14n.py | 433 +++++ wstools/logging.py | 274 ++++ wstools/tests/.cvsignore | 1 + wstools/tests/README | 52 + wstools/tests/__init__.py | 1 + wstools/tests/config.txt | 362 +++++ wstools/tests/schema.tar.gz | Bin 0 -> 68874 bytes wstools/tests/test_t1.py | 20 + wstools/tests/test_wsdl.py | 164 ++ wstools/tests/test_wstools.py | 37 + wstools/tests/test_wstools_net.py | 20 + wstools/tests/xmethods.tar.gz | Bin 0 -> 15209 bytes 22 files changed, 8232 insertions(+), 0 deletions(-) create mode 100644 wstools/.cvsignore create mode 100644 wstools/MIMEAttachment.py create mode 100755 wstools/Namespaces.py create mode 100755 wstools/TimeoutSocket.py create mode 100644 wstools/UserTuple.py create mode 100755 wstools/Utility.py create mode 100755 wstools/WSDLTools.py create mode 100755 wstools/XMLSchema.py create mode 100644 wstools/XMLname.py create mode 100644 wstools/__init__.py create mode 100755 wstools/c14n.py create mode 100644 wstools/logging.py create mode 100644 wstools/tests/.cvsignore create mode 100644 wstools/tests/README create mode 100644 wstools/tests/__init__.py create mode 100644 wstools/tests/config.txt create mode 100644 wstools/tests/schema.tar.gz create mode 100644 wstools/tests/test_t1.py create mode 100644 wstools/tests/test_wsdl.py create mode 100644 wstools/tests/test_wstools.py create mode 100644 wstools/tests/test_wstools_net.py create mode 100644 wstools/tests/xmethods.tar.gz diff --git a/wstools/.cvsignore b/wstools/.cvsignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/wstools/.cvsignore @@ -0,0 +1 @@ +*.pyc diff --git a/wstools/MIMEAttachment.py b/wstools/MIMEAttachment.py new file mode 100644 index 0000000..2376cc8 --- /dev/null +++ b/wstools/MIMEAttachment.py @@ -0,0 +1,110 @@ +#TODO add the license +#I had to rewrite this class because the python MIME email.mime (version 2.5) +#are buggy, they use \n instead \r\n for new line which is not compliant +#to standard! +# http://bugs.python.org/issue5525 + +#TODO do not load all the message in memory stream it from the disk + +import re +import random +import sys + + +#new line +NL='\r\n' + +_width = len(repr(sys.maxint-1)) +_fmt = '%%0%dd' % _width + +class MIMEMessage: + + def __init__(self): + self._files = [] + self._xmlMessage = "" + self._startCID = "" + self._boundary = "" + + def makeBoundary(self): + #create the boundary + msgparts = [] + msgparts.append(self._xmlMessage) + for i in self._files: + msgparts.append(i.read()) + #this sucks, all in memory + alltext = NL.join(msgparts) + self._boundary = _make_boundary(alltext) + #maybe I can save some memory + del alltext + del msgparts + self._startCID = "<" + (_fmt % random.randrange(sys.maxint)) + (_fmt % random.randrange(sys.maxint)) + ">" + + + def toString(self): + '''it return a string with the MIME message''' + if len(self._boundary) == 0: + #the makeBoundary hasn't been called yet + self.makeBoundary() + #ok we have everything let's start to spit the message out + #first the XML + returnstr = NL + "--" + self._boundary + NL + returnstr += "Content-Type: text/xml; charset=\"us-ascii\"" + NL + returnstr += "Content-Transfer-Encoding: 7bit" + NL + returnstr += "Content-Id: " + self._startCID + NL + NL + returnstr += self._xmlMessage + NL + #then the files + for file in self._files: + returnstr += "--" + self._boundary + NL + returnstr += "Content-Type: application/octet-stream" + NL + returnstr += "Content-Transfer-Encoding: binary" + NL + returnstr += "Content-Id: <" + str(id(file)) + ">" + NL + NL + file.seek(0) + returnstr += file.read() + NL + #closing boundary + returnstr += "--" + self._boundary + "--" + NL + return returnstr + + def attachFile(self, file): + ''' + it adds a file to this attachment + ''' + self._files.append(file) + + def addXMLMessage(self, xmlMessage): + ''' + it adds the XML message. we can have only one XML SOAP message + ''' + self._xmlMessage = xmlMessage + + def getBoundary(self): + ''' + this function returns the string used in the mime message as a + boundary. First the write method as to be called + ''' + return self._boundary + + def getStartCID(self): + ''' + This function returns the CID of the XML message + ''' + return self._startCID + + +def _make_boundary(text=None): + #some code taken from python stdlib + # Craft a random boundary. If text is given, ensure that the chosen + # boundary doesn't appear in the text. + token = random.randrange(sys.maxint) + boundary = ('=' * 10) + (_fmt % token) + '==' + if text is None: + return boundary + b = boundary + counter = 0 + while True: + cre = re.compile('^--' + re.escape(b) + '(--)?$', re.MULTILINE) + if not cre.search(text): + break + b = boundary + '.' + str(counter) + counter += 1 + return b + diff --git a/wstools/Namespaces.py b/wstools/Namespaces.py new file mode 100755 index 0000000..46a8b05 --- /dev/null +++ b/wstools/Namespaces.py @@ -0,0 +1,214 @@ +# 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. +"""Namespace module, so you don't need PyXML +""" + +ident = "$Id$" +try: + from xml.ns import SOAP, SCHEMA, WSDL, XMLNS, DSIG, ENCRYPTION + DSIG.C14N = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" + +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/" + BIND_SOAP12 = "http://schemas.xmlsoap.org/wsdl/soap12/" + + 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/2001/REC-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 WSRF_V1_2: + '''OASIS WSRF Specifications Version 1.2 + ''' + class LIFETIME: + XSD_DRAFT1 = "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.xsd" + XSD_DRAFT4 = "http://docs.oasis-open.org/wsrf/2004/11/wsrf-WS-ResourceLifetime-1.2-draft-04.xsd" + + WSDL_DRAFT1 = "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.wsdl" + WSDL_DRAFT4 = "http://docs.oasis-open.org/wsrf/2004/11/wsrf-WS-ResourceLifetime-1.2-draft-04.wsdl" + LATEST = WSDL_DRAFT4 + WSDL_LIST = (WSDL_DRAFT1, WSDL_DRAFT4) + XSD_LIST = (XSD_DRAFT1, XSD_DRAFT4) + + class PROPERTIES: + XSD_DRAFT1 = "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.xsd" + XSD_DRAFT5 = "http://docs.oasis-open.org/wsrf/2004/11/wsrf-WS-ResourceProperties-1.2-draft-05.xsd" + + WSDL_DRAFT1 = "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl" + WSDL_DRAFT5 = "http://docs.oasis-open.org/wsrf/2004/11/wsrf-WS-ResourceProperties-1.2-draft-05.wsdl" + LATEST = WSDL_DRAFT5 + WSDL_LIST = (WSDL_DRAFT1, WSDL_DRAFT5) + XSD_LIST = (XSD_DRAFT1, XSD_DRAFT5) + + class BASENOTIFICATION: + XSD_DRAFT1 = "http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-01.xsd" + + WSDL_DRAFT1 = "http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-01.wsdl" + LATEST = WSDL_DRAFT1 + WSDL_LIST = (WSDL_DRAFT1,) + XSD_LIST = (XSD_DRAFT1,) + + class BASEFAULTS: + XSD_DRAFT1 = "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-BaseFaults-1.2-draft-01.xsd" + XSD_DRAFT3 = "http://docs.oasis-open.org/wsrf/2004/11/wsrf-WS-BaseFaults-1.2-draft-03.xsd" + #LATEST = DRAFT3 + #WSDL_LIST = (WSDL_DRAFT1, WSDL_DRAFT3) + XSD_LIST = (XSD_DRAFT1, XSD_DRAFT3) + +WSRF = WSRF_V1_2 +WSRFLIST = (WSRF_V1_2,) + + +class OASIS: + '''URLs for Oasis specifications + ''' + WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" + UTILITY = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" + + class X509TOKEN: + Base64Binary = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" + STRTransform = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0" + PKCS7 = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#PKCS7" + X509 = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509" + X509PKIPathv1 = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" + X509v3SubjectKeyIdentifier = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3SubjectKeyIdentifier" + + LIFETIME = WSRF_V1_2.LIFETIME.XSD_DRAFT1 + PROPERTIES = WSRF_V1_2.PROPERTIES.XSD_DRAFT1 + BASENOTIFICATION = WSRF_V1_2.BASENOTIFICATION.XSD_DRAFT1 + BASEFAULTS = WSRF_V1_2.BASEFAULTS.XSD_DRAFT1 + + +class APACHE: + '''This name space is defined by AXIS and it is used for the TC in TCapache.py, + Map and file attachment (DataHandler) + ''' + AXIS_NS = "http://xml.apache.org/xml-soap" + + +class WSTRUST: + BASE = "http://schemas.xmlsoap.org/ws/2004/04/trust" + ISSUE = "http://schemas.xmlsoap.org/ws/2004/04/trust/Issue" + +class WSSE: + BASE = "http://schemas.xmlsoap.org/ws/2002/04/secext" + TRUST = WSTRUST.BASE + + +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 WSA200508: + ADDRESS = "http://www.w3.org/2005/08/addressing" + ANONYMOUS = "%s/anonymous" %ADDRESS + FAULT = "%s/fault" %ADDRESS + +class WSA200408: + ADDRESS = "http://schemas.xmlsoap.org/ws/2004/08/addressing" + ANONYMOUS = "%s/role/anonymous" %ADDRESS + FAULT = "%s/fault" %ADDRESS + +class WSA200403: + ADDRESS = "http://schemas.xmlsoap.org/ws/2004/03/addressing" + ANONYMOUS = "%s/role/anonymous" %ADDRESS + FAULT = "%s/fault" %ADDRESS + +class WSA200303: + ADDRESS = "http://schemas.xmlsoap.org/ws/2003/03/addressing" + ANONYMOUS = "%s/role/anonymous" %ADDRESS + FAULT = None + + +WSA = WSA200408 +WSA_LIST = (WSA200508, WSA200408, WSA200403, WSA200303) + +class _WSAW(str): + """ Define ADDRESS attribute to be compatible with WSA* layout """ + ADDRESS = property(lambda s: s) + +WSAW200605 = _WSAW("http://www.w3.org/2006/05/addressing/wsdl") + +WSAW_LIST = (WSAW200605,) + +class WSP: + POLICY = "http://schemas.xmlsoap.org/ws/2002/12/policy" + +class BEA: + SECCONV = "http://schemas.xmlsoap.org/ws/2004/04/sc" + SCTOKEN = "http://schemas.xmlsoap.org/ws/2004/04/security/sc/sct" + +class GLOBUS: + SECCONV = "http://wsrf.globus.org/core/2004/07/security/secconv" + CORE = "http://www.globus.org/namespaces/2004/06/core" + SIG = "http://www.globus.org/2002/04/xmlenc#gssapi-sign" + TOKEN = "http://www.globus.org/ws/2004/09/security/sc#GSSAPI_GSI_TOKEN" + +ZSI_SCHEMA_URI = 'http://www.zolera.com/schemas/ZSI/' diff --git a/wstools/TimeoutSocket.py b/wstools/TimeoutSocket.py new file mode 100755 index 0000000..48b898d --- /dev/null +++ b/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$" + +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/wstools/UserTuple.py b/wstools/UserTuple.py new file mode 100644 index 0000000..b8c3653 --- /dev/null +++ b/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$ + +#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/wstools/Utility.py b/wstools/Utility.py new file mode 100755 index 0000000..df536b9 --- /dev/null +++ b/wstools/Utility.py @@ -0,0 +1,1382 @@ +# 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$" + +import sys, types, httplib, urllib, socket, weakref +from os.path import isfile +from string import join, strip, split +from UserDict import UserDict +from cStringIO import StringIO +from TimeoutSocket import TimeoutSocket, TimeoutError +from urlparse import urlparse +from httplib import HTTPConnection, HTTPSConnection +from exceptions import Exception +try: + from ZSI import _get_idstr +except: + def _get_idstr(pyobj): + '''Python 2.3.x generates a FutureWarning for negative IDs, so + we use a different prefix character to ensure uniqueness, and + call abs() to avoid the warning.''' + x = id(pyobj) + if x < 0: + return 'x%x' % abs(x) + return 'o%x' % x + +import xml.dom.minidom +from xml.dom import Node + +import logging +from c14n import Canonicalize +from Namespaces import SCHEMA, SOAP, XMLNS, ZSI_SCHEMA_URI + + +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) + +# +# python2.3 urllib.basejoin does not remove current directory ./ +# from path and this causes problems on subsequent basejoins. +# +basejoin = urllib.basejoin +if sys.version_info[0:2] < (2, 4, 0, 'final', 0)[0:2]: + #basejoin = lambda base,url: urllib.basejoin(base,url.lstrip('./')) + token = './' + def basejoin(base, url): + if url.startswith(token) is True: + return urllib.basejoin(base,url[2:]) + return urllib.basejoin(base,url) + +class NamespaceError(Exception): + """Used to indicate a Namespace Error.""" + + +class RecursionError(Exception): + """Used to indicate a HTTP redirect recursion.""" + + +class ParseError(Exception): + """Used to indicate a XML parsing error.""" + + +class DOMException(Exception): + """Used to indicate a problem processing DOM.""" + + +class Base: + """Base class for instance level Logging""" + def __init__(self, module=__name__): + self.logger = logging.getLogger('%s-%s(%s)' %(module, self.__class__, _get_idstr(self))) + + +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. + try: + import M2Crypto + except ImportError: + if not hasattr(socket, 'ssl'): + raise RuntimeError, 'no built-in SSL Support' + + conn = TimeoutHTTPS(host, None, timeout) + else: + 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 is None: + raise DOMException('Value for prefix %s not found.' % prefix) + 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 and value is not None: + 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.""" + if isfile(url) is True: + file = open(url, 'r') + else: + file = urlopen(url) + + try: + result = self.loadDocument(file) + except Exception, ex: + file.close() + raise ParseError(('Failed to load document %s' %url,) + ex.args) + else: + file.close() + return result + +DOM = DOM() + + +class MessageInterface: + '''Higher Level Interface, delegates to DOM singleton, must + be subclassed and implement all methods that throw NotImplementedError. + ''' + def __init__(self, sw): + '''Constructor, May be extended, do not override. + sw -- soapWriter instance + ''' + self.sw = None + if type(sw) != weakref.ReferenceType and sw is not None: + self.sw = weakref.ref(sw) + else: + self.sw = sw + + def AddCallback(self, func, *arglist): + self.sw().AddCallback(func, *arglist) + + def Known(self, obj): + return self.sw().Known(obj) + + def Forget(self, obj): + return self.sw().Forget(obj) + + def canonicalize(self): + '''canonicalize the underlying DOM, and return as string. + ''' + raise NotImplementedError, '' + + def createDocument(self, namespaceURI=SOAP.ENV, localName='Envelope'): + '''create Document + ''' + raise NotImplementedError, '' + + def createAppendElement(self, namespaceURI, localName): + '''create and append element(namespaceURI,localName), and return + the node. + ''' + raise NotImplementedError, '' + + def findNamespaceURI(self, qualifiedName): + raise NotImplementedError, '' + + def resolvePrefix(self, prefix): + raise NotImplementedError, '' + + def setAttributeNS(self, namespaceURI, localName, value): + '''set attribute (namespaceURI, localName)=value + ''' + raise NotImplementedError, '' + + def setAttributeType(self, namespaceURI, localName): + '''set attribute xsi:type=(namespaceURI, localName) + ''' + raise NotImplementedError, '' + + def setNamespaceAttribute(self, namespaceURI, prefix): + '''set namespace attribute xmlns:prefix=namespaceURI + ''' + raise NotImplementedError, '' + + +class ElementProxy(Base, MessageInterface): + ''' + ''' + _soap_env_prefix = 'SOAP-ENV' + _soap_enc_prefix = 'SOAP-ENC' + _zsi_prefix = 'ZSI' + _xsd_prefix = 'xsd' + _xsi_prefix = 'xsi' + _xml_prefix = 'xml' + _xmlns_prefix = 'xmlns' + + _soap_env_nsuri = SOAP.ENV + _soap_enc_nsuri = SOAP.ENC + _zsi_nsuri = ZSI_SCHEMA_URI + _xsd_nsuri = SCHEMA.XSD3 + _xsi_nsuri = SCHEMA.XSI3 + _xml_nsuri = XMLNS.XML + _xmlns_nsuri = XMLNS.BASE + + standard_ns = {\ + _xml_prefix:_xml_nsuri, + _xmlns_prefix:_xmlns_nsuri + } + reserved_ns = {\ + _soap_env_prefix:_soap_env_nsuri, + _soap_enc_prefix:_soap_enc_nsuri, + _zsi_prefix:_zsi_nsuri, + _xsd_prefix:_xsd_nsuri, + _xsi_prefix:_xsi_nsuri, + } + name = None + namespaceURI = None + + def __init__(self, sw, message=None): + '''Initialize. + sw -- SoapWriter + ''' + self._indx = 0 + MessageInterface.__init__(self, sw) + Base.__init__(self) + self._dom = DOM + self.node = None + if type(message) in (types.StringType,types.UnicodeType): + self.loadFromString(message) + elif isinstance(message, ElementProxy): + self.node = message._getNode() + else: + self.node = message + self.processorNss = self.standard_ns.copy() + self.processorNss.update(self.reserved_ns) + + def __str__(self): + return self.toString() + + def evaluate(self, expression, processorNss=None): + '''expression -- XPath compiled expression + ''' + from Ft.Xml import XPath + if not processorNss: + context = XPath.Context.Context(self.node, processorNss=self.processorNss) + else: + context = XPath.Context.Context(self.node, processorNss=processorNss) + nodes = expression.evaluate(context) + return map(lambda node: ElementProxy(self.sw,node), nodes) + + ############################################# + # Methods for checking/setting the + # classes (namespaceURI,name) node. + ############################################# + def checkNode(self, namespaceURI=None, localName=None): + ''' + namespaceURI -- namespace of element + localName -- local name of element + ''' + namespaceURI = namespaceURI or self.namespaceURI + localName = localName or self.name + check = False + if localName and self.node: + check = self._dom.isElement(self.node, localName, namespaceURI) + if not check: + raise NamespaceError, 'unexpected node type %s, expecting %s' %(self.node, localName) + + def setNode(self, node=None): + if node: + if isinstance(node, ElementProxy): + self.node = node._getNode() + else: + self.node = node + elif self.node: + node = self._dom.getElement(self.node, self.name, self.namespaceURI, default=None) + if not node: + raise NamespaceError, 'cant find element (%s,%s)' %(self.namespaceURI,self.name) + self.node = node + else: + #self.node = self._dom.create(self.node, self.name, self.namespaceURI, default=None) + self.createDocument(self.namespaceURI, localName=self.name, doctype=None) + + self.checkNode() + + ############################################# + # Wrapper Methods for direct DOM Element Node access + ############################################# + def _getNode(self): + return self.node + + def _getElements(self): + return self._dom.getElements(self.node, name=None) + + def _getOwnerDocument(self): + return self.node.ownerDocument or self.node + + def _getUniquePrefix(self): + '''I guess we need to resolve all potential prefixes + because when the current node is attached it copies the + namespaces into the parent node. + ''' + while 1: + self._indx += 1 + prefix = 'ns%d' %self._indx + try: + self._dom.findNamespaceURI(prefix, self._getNode()) + except DOMException, ex: + break + return prefix + + def _getPrefix(self, node, nsuri): + ''' + Keyword arguments: + node -- DOM Element Node + nsuri -- namespace of attribute value + ''' + try: + if node and (node.nodeType == node.ELEMENT_NODE) and \ + (nsuri == self._dom.findDefaultNS(node)): + return None + except DOMException, ex: + pass + if nsuri == XMLNS.XML: + return self._xml_prefix + if node.nodeType == Node.ELEMENT_NODE: + for attr in node.attributes.values(): + if attr.namespaceURI == XMLNS.BASE \ + and nsuri == attr.value: + return attr.localName + else: + if node.parentNode: + return self._getPrefix(node.parentNode, nsuri) + raise NamespaceError, 'namespaceURI "%s" is not defined' %nsuri + + def _appendChild(self, node): + ''' + Keyword arguments: + node -- DOM Element Node + ''' + if node is None: + raise TypeError, 'node is None' + self.node.appendChild(node) + + def _insertBefore(self, newChild, refChild): + ''' + Keyword arguments: + child -- DOM Element Node to insert + refChild -- DOM Element Node + ''' + self.node.insertBefore(newChild, refChild) + + def _setAttributeNS(self, namespaceURI, qualifiedName, value): + ''' + Keyword arguments: + namespaceURI -- namespace of attribute + qualifiedName -- qualified name of new attribute value + value -- value of attribute + ''' + self.node.setAttributeNS(namespaceURI, qualifiedName, value) + + ############################################# + #General Methods + ############################################# + def isFault(self): + '''check to see if this is a soap:fault message. + ''' + return False + + def getPrefix(self, namespaceURI): + try: + prefix = self._getPrefix(node=self.node, nsuri=namespaceURI) + except NamespaceError, ex: + prefix = self._getUniquePrefix() + self.setNamespaceAttribute(prefix, namespaceURI) + return prefix + + def getDocument(self): + return self._getOwnerDocument() + + def setDocument(self, document): + self.node = document + + def importFromString(self, xmlString): + doc = self._dom.loadDocument(StringIO(xmlString)) + node = self._dom.getElement(doc, name=None) + clone = self.importNode(node) + self._appendChild(clone) + + def importNode(self, node): + if isinstance(node, ElementProxy): + node = node._getNode() + return self._dom.importNode(self._getOwnerDocument(), node, deep=1) + + def loadFromString(self, data): + self.node = self._dom.loadDocument(StringIO(data)) + + def canonicalize(self): + return Canonicalize(self.node) + + def toString(self): + return self.canonicalize() + + def createDocument(self, namespaceURI, localName, doctype=None): + '''If specified must be a SOAP envelope, else may contruct an empty document. + ''' + prefix = self._soap_env_prefix + + if namespaceURI == self.reserved_ns[prefix]: + qualifiedName = '%s:%s' %(prefix,localName) + elif namespaceURI is localName is None: + self.node = self._dom.createDocument(None,None,None) + return + else: + raise KeyError, 'only support creation of document in %s' %self.reserved_ns[prefix] + + document = self._dom.createDocument(nsuri=namespaceURI, qname=qualifiedName, doctype=doctype) + self.node = document.childNodes[0] + + #set up reserved namespace attributes + for prefix,nsuri in self.reserved_ns.items(): + self._setAttributeNS(namespaceURI=self._xmlns_nsuri, + qualifiedName='%s:%s' %(self._xmlns_prefix,prefix), + value=nsuri) + + ############################################# + #Methods for attributes + ############################################# + def hasAttribute(self, namespaceURI, localName): + return self._dom.hasAttr(self._getNode(), name=localName, nsuri=namespaceURI) + + def setAttributeType(self, namespaceURI, localName): + '''set xsi:type + Keyword arguments: + namespaceURI -- namespace of attribute value + localName -- name of new attribute value + + ''' + self.logger.debug('setAttributeType: (%s,%s)', namespaceURI, localName) + value = localName + if namespaceURI: + value = '%s:%s' %(self.getPrefix(namespaceURI),localName) + + xsi_prefix = self.getPrefix(self._xsi_nsuri) + self._setAttributeNS(self._xsi_nsuri, '%s:type' %xsi_prefix, value) + + def createAttributeNS(self, namespace, name, value): + document = self._getOwnerDocument() + ##this function doesn't exist!! it has only two arguments + attrNode = document.createAttributeNS(namespace, name, value) + + def setAttributeNS(self, namespaceURI, localName, value): + ''' + Keyword arguments: + namespaceURI -- namespace of attribute to create, None is for + attributes in no namespace. + localName -- local name of new attribute + value -- value of new attribute + ''' + prefix = None + if namespaceURI: + try: + prefix = self.getPrefix(namespaceURI) + except KeyError, ex: + prefix = 'ns2' + self.setNamespaceAttribute(prefix, namespaceURI) + qualifiedName = localName + if prefix: + qualifiedName = '%s:%s' %(prefix, localName) + self._setAttributeNS(namespaceURI, qualifiedName, value) + + def setNamespaceAttribute(self, prefix, namespaceURI): + ''' + Keyword arguments: + prefix -- xmlns prefix + namespaceURI -- value of prefix + ''' + self._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI) + + ############################################# + #Methods for elements + ############################################# + def createElementNS(self, namespace, qname): + ''' + Keyword arguments: + namespace -- namespace of element to create + qname -- qualified name of new element + ''' + document = self._getOwnerDocument() + node = document.createElementNS(namespace, qname) + return ElementProxy(self.sw, node) + + def createAppendSetElement(self, namespaceURI, localName, prefix=None): + '''Create a new element (namespaceURI,name), append it + to current node, then set it to be the current node. + Keyword arguments: + namespaceURI -- namespace of element to create + localName -- local name of new element + prefix -- if namespaceURI is not defined, declare prefix. defaults + to 'ns1' if left unspecified. + ''' + node = self.createAppendElement(namespaceURI, localName, prefix=None) + node=node._getNode() + self._setNode(node._getNode()) + + def createAppendElement(self, namespaceURI, localName, prefix=None): + '''Create a new element (namespaceURI,name), append it + to current node, and return the newly created node. + Keyword arguments: + namespaceURI -- namespace of element to create + localName -- local name of new element + prefix -- if namespaceURI is not defined, declare prefix. defaults + to 'ns1' if left unspecified. + ''' + declare = False + qualifiedName = localName + if namespaceURI: + try: + prefix = self.getPrefix(namespaceURI) + except: + declare = True + prefix = prefix or self._getUniquePrefix() + if prefix: + qualifiedName = '%s:%s' %(prefix, localName) + node = self.createElementNS(namespaceURI, qualifiedName) + if declare: + node._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI) + self._appendChild(node=node._getNode()) + return node + + def createInsertBefore(self, namespaceURI, localName, refChild): + qualifiedName = localName + prefix = self.getPrefix(namespaceURI) + if prefix: + qualifiedName = '%s:%s' %(prefix, localName) + node = self.createElementNS(namespaceURI, qualifiedName) + self._insertBefore(newChild=node._getNode(), refChild=refChild._getNode()) + return node + + def getElement(self, namespaceURI, localName): + ''' + Keyword arguments: + namespaceURI -- namespace of element + localName -- local name of element + ''' + node = self._dom.getElement(self.node, localName, namespaceURI, default=None) + if node: + return ElementProxy(self.sw, node) + return None + + def getAttributeValue(self, namespaceURI, localName): + ''' + Keyword arguments: + namespaceURI -- namespace of attribute + localName -- local name of attribute + ''' + if self.hasAttribute(namespaceURI, localName): + attr = self.node.getAttributeNodeNS(namespaceURI,localName) + return attr.value + return None + + def getValue(self): + return self._dom.getElementText(self.node, preserve_ws=True) + + ############################################# + #Methods for text nodes + ############################################# + def createAppendTextNode(self, pyobj): + node = self.createTextNode(pyobj) + self._appendChild(node=node._getNode()) + return node + + def createTextNode(self, pyobj): + document = self._getOwnerDocument() + node = document.createTextNode(pyobj) + return ElementProxy(self.sw, node) + + ############################################# + #Methods for retrieving namespaceURI's + ############################################# + def findNamespaceURI(self, qualifiedName): + parts = SplitQName(qualifiedName) + element = self._getNode() + if len(parts) == 1: + return (self._dom.findTargetNS(element), value) + return self._dom.findNamespaceURI(parts[0], element) + + def resolvePrefix(self, prefix): + element = self._getNode() + return self._dom.findNamespaceURI(prefix, element) + + def getSOAPEnvURI(self): + return self._soap_env_nsuri + + def isEmpty(self): + return not self.node + + + +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/wstools/WSDLTools.py b/wstools/WSDLTools.py new file mode 100755 index 0000000..3dd651a --- /dev/null +++ b/wstools/WSDLTools.py @@ -0,0 +1,1668 @@ +# 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$" + +import weakref +from cStringIO import StringIO +from Namespaces import OASIS, XMLNS, WSA, WSA_LIST, WSAW_LIST, WSRF_V1_2, WSRF +from Utility import Collection, CollectionNS, DOM, ElementProxy, basejoin +from XMLSchema import XMLSchema, SchemaReader, WSDLToolsAdapter + + +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 toDom(self): + """ Generate a DOM representation of the WSDL instance. + Not dealing with generating XML Schema, thus the targetNamespace + of all XML Schema elements or types used by WSDL message parts + needs to be specified via import information items. + """ + namespaceURI = DOM.GetWSDLUri(self.version) + self.document = DOM.createDocument(namespaceURI ,'wsdl:definitions') + + # Set up a couple prefixes for easy reading. + child = DOM.getElement(self.document, None) + child.setAttributeNS(None, 'targetNamespace', self.targetNamespace) + child.setAttributeNS(XMLNS.BASE, 'xmlns:wsdl', namespaceURI) + child.setAttributeNS(XMLNS.BASE, 'xmlns:xsd', 'http://www.w3.org/1999/XMLSchema') + child.setAttributeNS(XMLNS.BASE, 'xmlns:soap', 'http://schemas.xmlsoap.org/wsdl/soap/') + child.setAttributeNS(XMLNS.BASE, 'xmlns:tns', self.targetNamespace) + + if self.name: + child.setAttributeNS(None, 'name', self.name) + + # wsdl:import + for item in self.imports: + item.toDom() + # wsdl:message + for item in self.messages: + item.toDom() + # wsdl:portType + for item in self.portTypes: + item.toDom() + # wsdl:binding + for item in self.bindings: + item.toDom() + # wsdl:service + for item in self.services: + item.toDom() + + 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) + + # + # Retrieve all 's, append all children of imported + # document to main document. First iteration grab all original + # 's from document, second iteration grab all + # "imported" from document, etc break out when + # no more 's. + # + imported = [] + base_location = self.location + do_it = True + while do_it: + do_it = False + for element in DOM.getElements(definitions, 'import', NS_WSDL): + location = DOM.getAttr(element, 'location') + + if base_location is not None: + location = basejoin(base_location, location) + + if location not in imported: + do_it = True + self._import(document, element, base_location) + imported.append(location) + else: + definitions.removeChild(element) + + base_location = None + + # + # No more 's, now load up all other + # WSDL information items. + # + 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': + tns = DOM.getAttr(element, 'targetNamespace') + reader = SchemaReader(base_url=self.imports[tns].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 = basejoin(base_location, location) + element.setAttributeNS(None, '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 + + parent.removeChild(element) + + 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 = 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): + item.parent = weakref.ref(self) + self.extensions.append(item) + + def getWSDL(self): + """Return the WSDL object that contains this information item.""" + parent = self + while 1: + # skip any collections + if isinstance(parent, WSDL): + return parent + try: parent = parent.parent() + except: break + + return None + + +class ImportElement(Element): + def __init__(self, namespace, location): + self.namespace = namespace + self.location = location + +# def getWSDL(self): +# """Return the WSDL object that contains this Message Part.""" +# return self.parent().parent() + + def toDom(self): + wsdl = self.getWSDL() + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'import') + epc.setAttributeNS(None, 'namespace', self.namespace) + epc.setAttributeNS(None, 'location', self.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) + +# def getElementDeclaration(self): +# """Return the XMLSchema.ElementDeclaration instance or None""" +# element = None +# if self.element: +# nsuri,name = self.element +# wsdl = self.getWSDL() +# if wsdl.types.has_key(nsuri) and wsdl.types[nsuri].elements.has_key(name): +# element = wsdl.types[nsuri].elements[name] +# return element +# +# def getTypeDefinition(self): +# """Return the XMLSchema.TypeDefinition instance or None""" +# type = None +# if self.type: +# nsuri,name = self.type +# wsdl = self.getWSDL() +# if wsdl.types.has_key(nsuri) and wsdl.types[nsuri].types.has_key(name): +# type = wsdl.types[nsuri].types[name] +# return type + +# def getWSDL(self): +# """Return the WSDL object that contains this Message Part.""" +# return self.parent().parent() + + def toDom(self): + wsdl = self.getWSDL() + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'message') + epc.setAttributeNS(None, 'name', self.name) + + for part in self.parts: + part.toDom(epc._getNode()) + + +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) + + def toDom(self, node): + """node -- node representing message""" + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'part') + epc.setAttributeNS(None, 'name', self.name) + + if self.element is not None: + ns,name = self.element + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, 'element', '%s:%s'%(prefix,name)) + elif self.type is not None: + ns,name = self.type + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, 'type', '%s:%s'%(prefix,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 getTargetNamespace(self): + return self.targetNamespace or self.getWSDL().targetNamespace + + 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) + self.targetNamespace = DOM.getAttr(element, 'targetNamespace') + + for nsuri in WSRF_V1_2.PROPERTIES.XSD_LIST: + if DOM.hasAttr(element, 'ResourceProperties', nsuri): + rpref = DOM.getAttr(element, 'ResourceProperties', nsuri) + 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) + for WSA in WSA_LIST + WSAW_LIST: + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + if action: break + 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) + for WSA in WSA_LIST + WSAW_LIST: + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + if action: break + 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) + for WSA in WSA_LIST + WSAW_LIST: + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + if action: break + operation.addFault(message, name, docs, action) + + def toDom(self): + wsdl = self.getWSDL() + + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'portType') + epc.setAttributeNS(None, 'name', self.name) + if self.resourceProperties: + ns,name = self.resourceProperties + prefix = epc.getPrefix(ns) + epc.setAttributeNS(WSRF.PROPERTIES.LATEST, 'ResourceProperties', + '%s:%s'%(prefix,name)) + + for op in self.operations: + op.toDom(epc._getNode()) + + + +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 getWSDL(self): + """Return the WSDL object that contains this Operation.""" + return self.parent().parent().parent().parent() + + 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) + self.input.parent = weakref.ref(self) + return self.input + + def setOutput(self, message, name='', documentation='', action=None): + self.output = MessageRole('output', message, name, documentation, action) + self.output.parent = weakref.ref(self) + return self.output + + def toDom(self, node): + wsdl = self.getWSDL() + + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'operation') + epc.setAttributeNS(None, 'name', self.name) + node = epc._getNode() + if self.input: + self.input.toDom(node) + if self.output: + self.output.toDom(node) + for fault in self.faults: + fault.toDom(node) + + +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 + + def getWSDL(self): + """Return the WSDL object that contains this information item.""" + parent = self + while 1: + # skip any collections + if isinstance(parent, WSDL): + return parent + try: parent = parent.parent() + except: break + + return None + + def getMessage(self): + """Return the WSDL object that represents the attribute message + (namespaceURI, name) tuple + """ + wsdl = self.getWSDL() + return wsdl.messages[self.message] + + def toDom(self, node): + wsdl = self.getWSDL() + + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), self.type) + if not isinstance(self.message, basestring) and len(self.message) == 2: + ns,name = self.message + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, 'message', '%s:%s' %(prefix,name)) + else: + epc.setAttributeNS(None, 'message', self.message) + + if self.action: + epc.setAttributeNS(WSA.ADDRESS, 'Action', self.action) + + if self.name: + epc.setAttributeNS(None, 'name', self.name) + + +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: + #TODO: addInputBinding? + mbinding = MessageRoleBinding('input') + mbinding.documentation = GetDocumentation(item) + opbinding.input = mbinding + mbinding.load_ex(GetExtensions(item)) + mbinding.parent = weakref.ref(opbinding) + + 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)) + mbinding.parent = weakref.ref(opbinding) + + 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)) + mbinding.parent = weakref.ref(opbinding) + + 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) + + def toDom(self): + wsdl = self.getWSDL() + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'binding') + epc.setAttributeNS(None, 'name', self.name) + + ns,name = self.type + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, 'type', '%s:%s' %(prefix,name)) + + node = epc._getNode() + for ext in self.extensions: + ext.toDom(node) + for op_binding in self.operations: + op_binding.toDom(node) + + +class OperationBinding(Element): + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.input = None + self.output = None + self.faults = Collection(self) + +# def getWSDL(self): +# """Return the WSDL object that contains this binding.""" +# return self.parent().parent().parent().parent() + + + 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.parent = weakref.ref(self) + self.input.addExtension(binding) + return binding + + def addOutputBinding(self, binding): + if self.output is None: + self.output = MessageRoleBinding('output') + self.output.parent = weakref.ref(self) + 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) + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'operation') + epc.setAttributeNS(None, 'name', self.name) + + node = epc._getNode() + for ext in self.extensions: + ext.toDom(node) + if self.input: + self.input.toDom(node) + if self.output: + self.output.toDom(node) + for fault in self.faults: + fault.toDom(node) + + +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) + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), self.type) + + node = epc._getNode() + for item in self.extensions: + if item: item.toDom(node) + + +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) + + def toDom(self): + wsdl = self.getWSDL() + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), "service") + epc.setAttributeNS(None, "name", self.name) + + node = epc._getNode() + for port in self.ports: + port.toDom(node) + + +class Port(Element): + def __init__(self, name, binding, documentation=''): + Element.__init__(self, name, documentation) + self.binding = binding + +# def getWSDL(self): +# return self.parent().parent().getWSDL() + + 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.""" + 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) + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), "port") + epc.setAttributeNS(None, "name", self.name) + + ns,name = self.binding + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, "binding", "%s:%s" %(prefix,name)) + + node = epc._getNode() + for ext in self.extensions: + ext.toDom(node) + + +class SoapBinding: + def __init__(self, transport, style='rpc'): + self.transport = transport + self.style = style + + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'binding') + if self.transport: + epc.setAttributeNS(None, "transport", self.transport) + if self.style: + epc.setAttributeNS(None, "style", self.style) + +class SoapAddressBinding: + def __init__(self, location): + self.location = location + + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'address') + epc.setAttributeNS(None, "location", self.location) + + +class SoapOperationBinding: + def __init__(self, soapAction=None, style=None): + self.soapAction = soapAction + self.style = style + + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'operation') + if self.soapAction: + epc.setAttributeNS(None, 'soapAction', self.soapAction) + if self.style: + epc.setAttributeNS(None, 'style', self.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 + + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'body') + epc.setAttributeNS(None, "use", self.use) + epc.setAttributeNS(None, "namespace", self.namespace) + + +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 + + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'body') + epc.setAttributeNS(None, "use", self.use) + epc.setAttributeNS(None, "name", self.name) + if self.namespace is not None: + epc.setAttributeNS(None, "namespace", self.namespace) + if self.encodingStyle is not None: + epc.setAttributeNS(None, "encodingStyle", self.encodingStyle) + + +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.getTargetNamespace() + 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().getTargetNamespace() + 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: + 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/wstools/XMLSchema.py b/wstools/XMLSchema.py new file mode 100755 index 0000000..eb5b3fe --- /dev/null +++ b/wstools/XMLSchema.py @@ -0,0 +1,3116 @@ +# 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$" + +import types, weakref, sys, warnings +from Namespaces import SCHEMA, XMLNS, SOAP, APACHE +from Utility import DOM, DOMException, Collection, SplitQName, basejoin +from StringIO import StringIO + +# If we have no threading, this should be a no-op +try: + from threading import RLock +except ImportError: + class RLock: + def acquire(): + pass + def release(): + pass + +# +# Collections in XMLSchema class +# +TYPES = 'types' +ATTRIBUTE_GROUPS = 'attr_groups' +ATTRIBUTES = 'attr_decl' +ELEMENTS = 'elements' +MODEL_GROUPS = 'model_groups' +BUILT_IN_NAMESPACES = [SOAP.ENC,] + SCHEMA.XSD_LIST + [APACHE.AXIS_NS] + +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. + """ + + namespaceToSchema = {} + + 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(schemaLocation, 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, url=None): + """Return an XMLSchema instance loaded from a file object. + file -- file object + url -- base location for resolving imports/includes. + """ + reader = self.__readerClass() + reader.loadDocument(file) + schema = XMLSchema() + if url is not None: + schema.setBaseUrl(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, schema=None): + """Return an XMLSchema instance loaded from the given url. + url -- URL to dereference + schema -- Optional XMLSchema instance. + """ + reader = self.__readerClass() + if self.__base_url: + url = basejoin(self.__base_url,url) + + reader.loadFromURL(url) + schema = schema or XMLSchema() + schema.setBaseUrl(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 + """ + if self.__base_url: + filename = basejoin(self.__base_url,filename) + file = open(filename, 'rb') + try: + schema = self.loadFromStream(file, filename) + finally: + file.close() + + return schema + + +class SchemaError(Exception): + pass + +class NoSchemaLocationWarning(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 getNode(self): + return self.__node + + 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. + """ + tag = None + __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 AllMarker(ModelGroupMarker): + """marker for all model group + """ + pass + +class ChoiceMarker(ModelGroupMarker): + """marker for choice model group + """ + pass + +class SequenceMarker(ModelGroupMarker): + """marker for sequence model group + """ + 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 ListMarker: + """marker for simple type list + """ + pass + +class UnionMarker: + """marker for simple type Union + """ + 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 isAll(self): + return isinstance(self, AllMarker) + + def isChoice(self): + return isinstance(self, ChoiceMarker) + + def isSequence(self): + return isinstance(self, SequenceMarker) + + 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) + + def isList(self): + return isinstance(self, ListMarker) + + def isUnion(self): + return isinstance(self, UnionMarker) + + +########################################################## +# 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 getItemTrace(self): + """Returns a node trace up to the item. + """ + item, path, name, ref = self, [], 'name', 'ref' + while not isinstance(item,XMLSchema) and not isinstance(item,WSDLToolsAdapter): + attr = item.getAttribute(name) + if not attr: + attr = item.getAttribute(ref) + if not attr: + path.append('<%s>' %(item.tag)) + else: + path.append('<%s ref="%s">' %(item.tag, attr)) + else: + path.append('<%s name="%s">' %(item.tag,attr)) + + item = item._parent() + try: + tns = item.getTargetNamespace() + except: + tns = '' + path.append('<%s targetNamespace="%s">' %(item.tag, tns)) + path.reverse() + return ''.join(path) + + def getTargetNamespace(self): + """return targetNamespace + """ + parent = self + targetNamespace = 'targetNamespace' + tns = self.attributes.get(targetNamespace) + while not tns and parent and parent._parent is not None: + parent = parent._parent() + tns = parent.attributes.get(targetNamespace) + return tns or '' + + def getAttributeDeclaration(self, attribute): + """attribute -- attribute with a QName value (eg. type). + collection -- check types collection in parent Schema instance + """ + return self.getQNameAttribute(ATTRIBUTES, attribute) + + def getAttributeGroup(self, attribute): + """attribute -- attribute with a QName value (eg. type). + collection -- check types collection in parent Schema instance + """ + return self.getQNameAttribute(ATTRIBUTE_GROUPS, attribute) + + 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 getModelGroup(self, attribute): + """attribute -- attribute with a QName value (eg. ref). + collection -- check model_group collection in parent Schema instance. + """ + return self.getQNameAttribute(MODEL_GROUPS, 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. + """ + tdc = self.getAttributeQName(attribute) + if not tdc: + return + + obj = self.getSchemaItem(collection, tdc.getTargetNamespace(), tdc.getName()) + if obj: + return obj + +# raise SchemaError, 'No schema item "%s" in collection %s' %(tdc, collection) + return + + def getSchemaItem(self, collection, namespace, name): + """returns object instance representing namespace, name, + or if does not exist return None if built-in, else + raise SchemaError. + + namespace -- namespace item defined in. + name -- name of item. + collection -- collection in parent Schema instance to search. + """ + parent = GetSchema(self) + if parent.targetNamespace == namespace: + try: + obj = getattr(parent, collection)[name] + except KeyError, ex: + raise KeyError, 'targetNamespace(%s) collection(%s) has no item(%s)'\ + %(namespace, collection, name) + + return obj + + if not parent.imports.has_key(namespace): + if namespace in BUILT_IN_NAMESPACES: + # built-in just return + # WARNING: expecting import if "redefine" or add to built-in namespace. + return + + raise SchemaError, 'schema "%s" does not import namespace "%s"' %( + parent.targetNamespace, namespace) + + # Lazy Eval + schema = parent.imports[namespace] + if not isinstance(schema, XMLSchema): + schema = schema.getSchema() + if schema is not None: + parent.imports[namespace] = schema + + if schema is None: + if namespace in BUILT_IN_NAMESPACES: + # built-in just return + return + + raise SchemaError, 'no schema instance for imported namespace (%s).'\ + %(namespace) + + if not isinstance(schema, XMLSchema): + raise TypeError, 'expecting XMLSchema instance not "%r"' %schema + + try: + obj = getattr(schema, collection)[name] + except KeyError, ex: + raise KeyError, 'targetNamespace(%s) collection(%s) has no item(%s)'\ + %(namespace, collection, name) + + 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): + if prefix is None: + return '' + raise SchemaError, 'unknown prefix %s' %prefix + return ns + + def getAttribute(self, attribute): + """return requested attribute value or None + """ + if type(attribute) in (list, tuple): + if len(attribute) != 2: + raise LookupError, 'To access attributes must use name or (namespace,name)' + + ns_dict = self.attributes.get(attribute[0]) + if ns_dict is None: + return None + + return ns_dict.get(attribute[1]) + + return self.attributes.get(attribute) + + def getAttributeQName(self, attribute): + """return requested attribute value as (namespace,name) or None + """ + qname = self.getAttribute(attribute) + if isinstance(qname, TypeDescriptionComponent) is True: + return qname + if qname is None: + return None + + prefix,ncname = SplitQName(qname) + namespace = self.getXMLNS(prefix) + return TypeDescriptionComponent((namespace,ncname)) + + def getAttributeName(self): + """return attribute name or None + """ + return self.getAttribute('name') + + 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 + + if not isinstance(self, WSDLToolsAdapter): + 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 is not None and self.attributes.has_key(k) is False: + 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,v in self.attributes.items(): + # attribute #other, ie. not in empty namespace + if type(v) is dict: + continue + + # predefined prefixes xmlns, xml + if a in (XMLSchemaComponent.xmlns, XMLNS.XML): + continue + + if (a not in self.__class__.attributes.keys()) and not\ + (self.isAttribute() and self.isReference()): + raise SchemaError, '%s, unknown attribute(%s,%s)' \ + %(self.getItemTrace(), a, self.attributes[a]) + + +class WSDLToolsAdapter(XMLSchemaComponent): + """WSDL Adapter to grab the attributes from the wsdl document node. + """ + attributes = {'name':None, 'targetNamespace':None} + tag = 'definitions' + + def __init__(self, wsdl): + 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')} + tag = 'notation' + + 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')} + tag = 'annotation' + + 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')} + tag = 'documentation' + + 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')} + tag = '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 == '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 = '' + tag = 'schema' + + 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.__node = None + 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 getNode(self): + """ + Interacting with the underlying DOM tree. + """ + return self.__node + + 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 isElementFormDefaultQualified(self): + return self.attributes.get('elementFormDefault') == 'qualified' + + 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, location=None): + self.__node = 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() + for childNode in self.getContents(node): + component = SplitQName(childNode.getTagName())[1] + + if component == 'include': + tp = self.__class__.Include(self) + tp.fromDom(childNode) + + sl = tp.attributes['schemaLocation'] + schema = tp.getSchema() + + if not self.getIncludeSchemas().has_key(sl): + self.addIncludeSchema(sl, schema) + + self.includes[sl] = tp + + pn = childNode.getParentNode().getNode() + pn.removeChild(childNode.getNode()) + for child in schema.getNode().getNode().childNodes: + pn.appendChild(child.cloneNode(1)) + + 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 + else: + warnings.warn("Not keeping schema component.") + + elif component == 'import': + slocd = SchemaReader.namespaceToSchema + tp = self.__class__.Import(self) + tp.fromDom(childNode) + import_ns = tp.getAttribute('namespace') or\ + self.__class__.empty_namespace + schema = slocd.get(import_ns) + if schema is None: + schema = XMLSchema() + slocd[import_ns] = schema + try: + tp.loadSchema(schema) + except NoSchemaLocationWarning, ex: + # Dependency declaration, hopefully implementation + # is aware of this namespace (eg. SOAP,WSDL,?) + print "IMPORT: ", import_ns + print ex + del slocd[import_ns] + continue + except SchemaError, ex: + #warnings.warn(\ + # ', %s'\ + # %(import_ns, 'failed to load schema instance') + #) + print ex + del slocd[import_ns] + class _LazyEvalImport(str): + '''Lazy evaluation of import, replace entry in self.imports.''' + #attributes = dict(namespace=import_ns) + def getSchema(namespace): + schema = slocd.get(namespace) + if schema is None: + parent = self._parent() + wstypes = parent + if isinstance(parent, WSDLToolsAdapter): + wstypes = parent.getImportSchemas() + schema = wstypes.get(namespace) + if isinstance(schema, XMLSchema): + self.imports[namespace] = schema + return schema + + return None + + self.imports[import_ns] = _LazyEvalImport(import_ns) + continue + else: + tp._schema = schema + + if self.getImportSchemas().has_key(import_ns): + warnings.warn(\ + 'Detected multiple imports of the namespace "%s" '\ + %import_ns) + + self.addImportSchema(schema) + # spec says can have multiple imports of same namespace + # but purpose of import is just dependency declaration. + self.imports[import_ns] = tp + + elif component == 'redefine': + warnings.warn('redefine is ignored') + elif component == 'annotation': + warnings.warn('annotation is ignored') + elif component == 'attribute': + tp = AttributeDeclaration(self) + tp.fromDom(childNode) + self.attr_decl[tp.getAttribute('name')] = tp + elif component == 'attributeGroup': + tp = AttributeGroupDefinition(self) + tp.fromDom(childNode) + self.attr_groups[tp.getAttribute('name')] = tp + elif component == 'element': + tp = ElementDeclaration(self) + tp.fromDom(childNode) + self.elements[tp.getAttribute('name')] = tp + elif component == 'group': + tp = ModelGroupDefinition(self) + tp.fromDom(childNode) + self.model_groups[tp.getAttribute('name')] = tp + elif component == 'notation': + tp = Notation(self) + tp.fromDom(childNode) + self.notations[tp.getAttribute('name')] = tp + elif component == 'complexType': + tp = ComplexType(self) + tp.fromDom(childNode) + self.types[tp.getAttribute('name')] = tp + elif component == 'simpleType': + tp = SimpleType(self) + tp.fromDom(childNode) + self.types[tp.getAttribute('name')] = tp + else: + break + + class Import(XMLSchemaComponent): + """ + parent: + schema + attributes: + id -- ID + namespace -- anyURI + schemaLocation -- anyURI + contents: + annotation? + """ + attributes = {'id':None, + 'namespace':None, + 'schemaLocation':None} + contents = {'xsd':['annotation']} + tag = 'import' + + 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.getTargetNamespace(): + 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 + + def loadSchema(self, schema): + """ + """ + base_url = self._parent().getBaseUrl() + reader = SchemaReader(base_url=base_url) + reader._imports = self._parent().getImportSchemas() + reader._includes = self._parent().getIncludeSchemas() + self._schema = schema + + if not self.attributes.has_key('schemaLocation'): + raise NoSchemaLocationWarning('no schemaLocation attribute in import') + + reader.loadFromURL(self.attributes.get('schemaLocation'), schema) + + + class Include(XMLSchemaComponent): + """ + parent: + schema + attributes: + id -- ID + schemaLocation -- anyURI, required + contents: + annotation? + """ + required = ['schemaLocation'] + attributes = {'id':None, + 'schemaLocation':None} + contents = {'xsd':['annotation']} + tag = 'include' + + 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() + 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() + + # create schema before loading so chameleon include + # will evalute targetNamespace correctly. + self._schema = XMLSchema(schema) + reader.loadFromURL(url, self._schema) + + 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']} + tag = 'attribute' + + 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']} + tag = 'anyAttribute' + + 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']} + tag = 'attribute' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def getAttributeDeclaration(self, attribute='ref'): + return XMLSchemaComponent.getAttributeDeclaration(self, attribute) + + 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', 'attribute', 'attributeGroup', 'anyAttribute']} + tag = 'attributeGroup' + + 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(LocalAttributeDeclaration(self)) + elif contents[indx].hasattr('ref'): + content.append(AttributeReference(self)) + else: + raise SchemaError, 'Unknown attribute type' + content[-1].fromDom(contents[indx]) + elif component == 'attributeGroup': + content.append(AttributeGroupReference(self)) + content[-1].fromDom(contents[indx]) + elif component == 'anyAttribute': + if len(contents) != indx+1: + raise SchemaError, 'anyAttribute is out of order in %s' %self.getItemTrace() + content.append(AttributeWildCard(self)) + 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']} + tag = 'attributeGroup' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def getAttributeGroup(self, attribute='ref'): + """attribute -- attribute with a QName value (eg. type). + collection -- check types collection in parent Schema instance + """ + return XMLSchemaComponent.getAttributeGroup(self, attribute) + + 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']} + tag = 'selector' + + 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']} + tag = 'field' + + +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']} + tag = 'unique' + + +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']} + tag = 'key' + + +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']} + tag = 'keyref' + + +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']} + tag = 'element' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.constraints = () + + def isQualified(self): + """Global elements are always qualified. + """ + return True + + def getAttribute(self, attribute): + """return attribute. + If attribute is type and it's None, and no simple or complex content, + return the default type "xsd:anyType" + """ + value = XMLSchemaComponent.getAttribute(self, attribute) + if attribute != 'type' or value is not None: + return value + + if self.content is not None: + return None + + parent = self + while 1: + nsdict = parent.attributes[XMLSchemaComponent.xmlns] + for k,v in nsdict.items(): + if v not in SCHEMA.XSD_LIST: continue + return TypeDescriptionComponent((v, 'anyType')) + + if isinstance(parent, WSDLToolsAdapter)\ + or not hasattr(parent, '_parent'): + break + + parent = parent._parent() + + raise SchemaError, 'failed to locate the XSD namespace' + + def getElementDeclaration(self, attribute): + raise Warning, 'invalid operation for <%s>' %self.tag + + def getTypeDefinition(self, attribute=None): + """If attribute is None, "type" is assumed, return the corresponding + representation of the global type definition (TypeDefinition), + or the local definition if don't find "type". To maintain backwards + compat, if attribute is provided call base class method. + """ + if attribute: + return XMLSchemaComponent.getTypeDefinition(self, attribute) + gt = XMLSchemaComponent.getTypeDefinition(self, 'type') + if gt: + return gt + return self.content + + def getConstraints(self): + return self._constraints + def setConstraints(self, constraints): + self._constraints = tuple(constraints) + constraints = property(getConstraints, setConstraints, None, "tuple of key, keyref, unique constraints") + + 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 = 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']} + + def isQualified(self): + """ +Local elements can be qualified or unqualifed according + to the attribute form, or the elementFormDefault. By default + local elements are unqualified. + """ + form = self.getAttribute('form') + if form == 'qualified': + return True + if form == 'unqualified': + return False + raise SchemaError, 'Bad form (%s) for element: %s' %(form, self.getItemTrace()) + + +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']} + tag = 'element' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def getElementDeclaration(self, attribute=None): + """If attribute is None, "ref" is assumed, return the corresponding + representation of the global element declaration (ElementDeclaration), + To maintain backwards compat, if attribute is provided call base class method. + """ + if attribute: + return XMLSchemaComponent.getElementDeclaration(self, attribute) + return XMLSchemaComponent.getElementDeclaration(self, 'ref') + + 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']} + tag = 'any' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def isQualified(self): + """ + Global elements are always qualified, but if processContents + are not strict could have dynamically generated local elements. + """ + return GetSchema(self).isElementFormDefaultQualified() + + def getAttribute(self, attribute): + """return attribute. + """ + return XMLSchemaComponent.getAttribute(self, attribute) + + def getTypeDefinition(self, attribute): + raise Warning, 'invalid operation for <%s>' % self.tag + + 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,\ + SequenceMarker): + """ + 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']} + tag = '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) + 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,\ + AllMarker): + """ + 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']} + tag = 'all' + + 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,\ + ChoiceMarker): + """ + 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']} + tag = 'choice' + + 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']} + tag = 'group' + + 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']} + tag = 'group' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def getModelGroupReference(self): + return self.getModelGroup('ref') + + 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']} + tag = 'complexType' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.attr_content = None + + def isMixed(self): + m = self.getAttribute('mixed') + if m == 0 or m == False: + return False + if isinstance(m, basestring) is True: + if m in ('false', '0'): + return False + if m in ('true', '1'): + return True + + raise SchemaError, 'invalid value for attribute mixed(%s): %s'\ + %(m, self.getItemTrace()) + + def getAttributeContent(self): + return self.attr_content + + def getElementDeclaration(self, attribute): + raise Warning, 'invalid operation for <%s>' %self.tag + + def getTypeDefinition(self, attribute): + raise Warning, 'invalid operation for <%s>' %self.tag + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + if not num: + return + + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + if indx < num: + 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): %s' \ + %(contents[indx].getTagName(),self.getItemTrace()) + self.attr_content[-1].fromDom(contents[indx]) + indx += 1 + + class _DerivedType(XMLSchemaComponent): + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + # XXX remove attribute derivation, inconsistent + self.derivation = 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 == '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) + self.content = self.derivation + + 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']} + tag = 'complexContent' + + def isMixed(self): + m = self.getAttribute('mixed') + if m == 0 or m == False: + return False + if isinstance(m, basestring) is True: + if m in ('false', '0'): + return False + if m in ('true', '1'): + return True + raise SchemaError, 'invalid value for attribute mixed(%s): %s'\ + %(m, self.getItemTrace()) + + 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? + """ + tag = 'extension' + + class Restriction(_DerivationBase,\ + RestrictionMarker): + """ + parents: + complexContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (group | all | choice | sequence)?, + (attribute | attributeGroup)*, anyAttribute? + """ + tag = 'restriction' + + + class SimpleContent(_DerivedType,\ + SimpleMarker): + """ + parents: + complexType + attributes: + id -- ID + + contents: + annotation?, (restriction | extension) + """ + attributes = {'id':None} + contents = {'xsd':['annotation', 'restriction', 'extension']} + tag = 'simpleContent' + + 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']} + tag = 'extension' + + 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) + + if num: + 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} + tag = 'restriction' + + 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(AnonymousSimpleType(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} + tag = 'complexType' + + +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']} + tag = 'simpleType' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def getElementDeclaration(self, attribute): + raise Warning, 'invalid operation for <%s>' %self.tag + + def getTypeDefinition(self, attribute): + raise Warning, 'invalid operation for <%s>' %self.tag + + 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} + tag = 'restriction' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.facets = None + + def getAttributeBase(self): + return XMLSchemaComponent.getAttribute(self, 'base') + + def getTypeDefinition(self, attribute='base'): + return XMLSchemaComponent.getTypeDefinition(self, attribute) + + def getSimpleTypeContent(self): + for el in self.content: + if el.isSimple(): return el + return None + + def fromDom(self, node): + self.facets = [] + 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: + self.facets.append(contents[indx]) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + + class Union(XMLSchemaComponent, + UnionMarker): + """ + 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']} + tag = '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) + 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, + ListMarker): + """ + parents: + simpleType + attributes: + id -- ID + itemType -- QName, required or simpleType child. + + contents: + annotation?, simpleType? + """ + attributes = {'id':None, + 'itemType':None } + contents = {'xsd':['annotation', 'simpleType']} + tag = 'list' + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def getItemType(self): + return self.attributes.get('itemType') + + def getTypeDefinition(self, attribute='itemType'): + """ + return the type refered to by itemType attribute or + the simpleType content. If returns None, then the + type refered to by itemType is primitive. + """ + tp = XMLSchemaComponent.getTypeDefinition(self, attribute) + return tp or self.content + + def fromDom(self, node): + self.annotation = None + self.content = None + self.setAttributes(node) + contents = self.getContents(node) + 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,\ + LocalMarker): + """ + parents: + attribute, element, list, restriction, union + attributes: + id -- ID + + contents: + annotation?, (restriction | list | union) + """ + required = [] + attributes = {'id':None} + tag = 'simpleType' + + +class Redefine: + """ + parents: + attributes: + + contents: + """ + tag = 'redefine' + + +########################### +########################### + + +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] + + diff --git a/wstools/XMLname.py b/wstools/XMLname.py new file mode 100644 index 0000000..5961160 --- /dev/null +++ b/wstools/XMLname.py @@ -0,0 +1,90 @@ +"""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$" + +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]) + + if prefix: + return "%s:%s" % (prefix, u''.join(X)) + 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/wstools/__init__.py b/wstools/__init__.py new file mode 100644 index 0000000..5b6f7ef --- /dev/null +++ b/wstools/__init__.py @@ -0,0 +1,9 @@ +#! /usr/bin/env python +"""WSDL parsing services package for Web Services for Python.""" + +ident = "$Id$" + +import WSDLTools +import XMLname +import logging + diff --git a/wstools/c14n.py b/wstools/c14n.py new file mode 100755 index 0000000..33305bf --- /dev/null +++ b/wstools/c14n.py @@ -0,0 +1,433 @@ +#! /usr/bin/env python +'''XML Canonicalization + +Patches Applied to xml.dom.ext.c14n: + http://sourceforge.net/projects/pyxml/ + + [ 1444526 ] c14n.py: http://www.w3.org/TR/xml-exc-c14n/ fix + -- includes [ 829905 ] c14n.py fix for bug #825115, + Date Submitted: 2003-10-24 23:43 + -- include dependent namespace declarations declared in ancestor nodes + (checking attributes and tags), + -- handle InclusiveNamespaces PrefixList parameter + +This module generates canonical XML of a document or element. + http://www.w3.org/TR/2001/REC-xml-c14n-20010315 +and includes a prototype of exclusive canonicalization + http://www.w3.org/Signature/Drafts/xml-exc-c14n + +Requires PyXML 0.7.0 or later. + +Known issues if using Ft.Lib.pDomlette: + 1. Unicode + 2. does not white space normalize attributes of type NMTOKEN and ID? + 3. seems to be include "\n" after importing external entities? + +Note, this version processes a DOM tree, and consequently it processes +namespace nodes as attributes, not from a node's namespace axis. This +permits simple document and element canonicalization without +XPath. When XPath is used, the XPath result node list is passed and used to +determine if the node is in the XPath result list, but little else. + +Authors: + "Joseph M. Reagle Jr." + "Rich Salz" + +$Date$ by $Author$ +''' + +_copyright = '''Copyright 2001, Zolera Systems Inc. All Rights Reserved. +Copyright 2001, MIT. All Rights Reserved. + +Distributed under the terms of: + Python 2.0 License or later. + http://www.python.org/2.0.1/license.html +or + W3C Software License + http://www.w3.org/Consortium/Legal/copyright-software-19980720 +''' + +import string +from xml.dom import Node +try: + from xml.ns import XMLNS +except: + class XMLNS: + BASE = "http://www.w3.org/2000/xmlns/" + XML = "http://www.w3.org/XML/1998/namespace" +try: + import cStringIO + StringIO = cStringIO +except ImportError: + import StringIO + +_attrs = lambda E: (E.attributes and E.attributes.values()) or [] +_children = lambda E: E.childNodes or [] +_IN_XML_NS = lambda n: n.name.startswith("xmlns") +_inclusive = lambda n: n.unsuppressedPrefixes == None + + +# Does a document/PI has lesser/greater document order than the +# first element? +_LesserElement, _Element, _GreaterElement = range(3) + +def _sorter(n1,n2): + '''_sorter(n1,n2) -> int + Sorting predicate for non-NS attributes.''' + + i = cmp(n1.namespaceURI, n2.namespaceURI) + if i: return i + return cmp(n1.localName, n2.localName) + + +def _sorter_ns(n1,n2): + '''_sorter_ns((n,v),(n,v)) -> int + "(an empty namespace URI is lexicographically least)."''' + + if n1[0] == 'xmlns': return -1 + if n2[0] == 'xmlns': return 1 + return cmp(n1[0], n2[0]) + +def _utilized(n, node, other_attrs, unsuppressedPrefixes): + '''_utilized(n, node, other_attrs, unsuppressedPrefixes) -> boolean + Return true if that nodespace is utilized within the node''' + if n.startswith('xmlns:'): + n = n[6:] + elif n.startswith('xmlns'): + n = n[5:] + if (n=="" and node.prefix in ["#default", None]) or \ + n == node.prefix or n in unsuppressedPrefixes: + return 1 + for attr in other_attrs: + if n == attr.prefix: return 1 + # For exclusive need to look at attributes + if unsuppressedPrefixes is not None: + for attr in _attrs(node): + if n == attr.prefix: return 1 + + return 0 + + +def _inclusiveNamespacePrefixes(node, context, unsuppressedPrefixes): + '''http://www.w3.org/TR/xml-exc-c14n/ + InclusiveNamespaces PrefixList parameter, which lists namespace prefixes that + are handled in the manner described by the Canonical XML Recommendation''' + inclusive = [] + if node.prefix: + usedPrefixes = ['xmlns:%s' %node.prefix] + else: + usedPrefixes = ['xmlns'] + + for a in _attrs(node): + if a.nodeName.startswith('xmlns') or not a.prefix: continue + usedPrefixes.append('xmlns:%s' %a.prefix) + + unused_namespace_dict = {} + for attr in context: + n = attr.nodeName + if n in unsuppressedPrefixes: + inclusive.append(attr) + elif n.startswith('xmlns:') and n[6:] in unsuppressedPrefixes: + inclusive.append(attr) + elif n.startswith('xmlns') and n[5:] in unsuppressedPrefixes: + inclusive.append(attr) + elif attr.nodeName in usedPrefixes: + inclusive.append(attr) + elif n.startswith('xmlns:'): + unused_namespace_dict[n] = attr.value + + return inclusive, unused_namespace_dict + +#_in_subset = lambda subset, node: not subset or node in subset +_in_subset = lambda subset, node: subset is None or node in subset # rich's tweak + + +class _implementation: + '''Implementation class for C14N. This accompanies a node during it's + processing and includes the parameters and processing state.''' + + # Handler for each node type; populated during module instantiation. + handlers = {} + + def __init__(self, node, write, **kw): + '''Create and run the implementation.''' + self.write = write + self.subset = kw.get('subset') + self.comments = kw.get('comments', 0) + self.unsuppressedPrefixes = kw.get('unsuppressedPrefixes') + nsdict = kw.get('nsdict', { 'xml': XMLNS.XML, 'xmlns': XMLNS.BASE }) + + # Processing state. + self.state = (nsdict, {'xml':''}, {}, {}) #0422 + + if node.nodeType == Node.DOCUMENT_NODE: + self._do_document(node) + elif node.nodeType == Node.ELEMENT_NODE: + self.documentOrder = _Element # At document element + if not _inclusive(self): + inherited,unused = _inclusiveNamespacePrefixes(node, self._inherit_context(node), + self.unsuppressedPrefixes) + self._do_element(node, inherited, unused=unused) + else: + inherited = self._inherit_context(node) + self._do_element(node, inherited) + elif node.nodeType == Node.DOCUMENT_TYPE_NODE: + pass + else: + raise TypeError, str(node) + + + def _inherit_context(self, node): + '''_inherit_context(self, node) -> list + Scan ancestors of attribute and namespace context. Used only + for single element node canonicalization, not for subset + canonicalization.''' + + # Collect the initial list of xml:foo attributes. + xmlattrs = filter(_IN_XML_NS, _attrs(node)) + + # Walk up and get all xml:XXX attributes we inherit. + inherited, parent = [], node.parentNode + while parent and parent.nodeType == Node.ELEMENT_NODE: + for a in filter(_IN_XML_NS, _attrs(parent)): + n = a.localName + if n not in xmlattrs: + xmlattrs.append(n) + inherited.append(a) + parent = parent.parentNode + return inherited + + + def _do_document(self, node): + '''_do_document(self, node) -> None + Process a document node. documentOrder holds whether the document + element has been encountered such that PIs/comments can be written + as specified.''' + + self.documentOrder = _LesserElement + for child in node.childNodes: + if child.nodeType == Node.ELEMENT_NODE: + self.documentOrder = _Element # At document element + self._do_element(child) + self.documentOrder = _GreaterElement # After document element + elif child.nodeType == Node.PROCESSING_INSTRUCTION_NODE: + self._do_pi(child) + elif child.nodeType == Node.COMMENT_NODE: + self._do_comment(child) + elif child.nodeType == Node.DOCUMENT_TYPE_NODE: + pass + else: + raise TypeError, str(child) + handlers[Node.DOCUMENT_NODE] = _do_document + + + def _do_text(self, node): + '''_do_text(self, node) -> None + Process a text or CDATA node. Render various special characters + as their C14N entity representations.''' + if not _in_subset(self.subset, node): return + s = string.replace(node.data, "&", "&") + s = string.replace(s, "<", "<") + s = string.replace(s, ">", ">") + s = string.replace(s, "\015", " ") + if s: self.write(s) + handlers[Node.TEXT_NODE] = _do_text + handlers[Node.CDATA_SECTION_NODE] = _do_text + + + def _do_pi(self, node): + '''_do_pi(self, node) -> None + Process a PI node. Render a leading or trailing #xA if the + document order of the PI is greater or lesser (respectively) + than the document element. + ''' + if not _in_subset(self.subset, node): return + W = self.write + if self.documentOrder == _GreaterElement: W('\n') + W('') + if self.documentOrder == _LesserElement: W('\n') + handlers[Node.PROCESSING_INSTRUCTION_NODE] = _do_pi + + + def _do_comment(self, node): + '''_do_comment(self, node) -> None + Process a comment node. Render a leading or trailing #xA if the + document order of the comment is greater or lesser (respectively) + than the document element. + ''' + if not _in_subset(self.subset, node): return + if self.comments: + W = self.write + if self.documentOrder == _GreaterElement: W('\n') + W('') + if self.documentOrder == _LesserElement: W('\n') + handlers[Node.COMMENT_NODE] = _do_comment + + + def _do_attr(self, n, value): + ''''_do_attr(self, node) -> None + Process an attribute.''' + + W = self.write + W(' ') + W(n) + W('="') + s = string.replace(value, "&", "&") + s = string.replace(s, "<", "<") + s = string.replace(s, '"', '"') + s = string.replace(s, '\011', ' ') + s = string.replace(s, '\012', ' ') + s = string.replace(s, '\015', ' ') + W(s) + W('"') + + + def _do_element(self, node, initial_other_attrs = [], unused = None): + '''_do_element(self, node, initial_other_attrs = [], unused = {}) -> None + Process an element (and its children).''' + + # Get state (from the stack) make local copies. + # ns_parent -- NS declarations in parent + # ns_rendered -- NS nodes rendered by ancestors + # ns_local -- NS declarations relevant to this element + # xml_attrs -- Attributes in XML namespace from parent + # xml_attrs_local -- Local attributes in XML namespace. + # ns_unused_inherited -- not rendered namespaces, used for exclusive + ns_parent, ns_rendered, xml_attrs = \ + self.state[0], self.state[1].copy(), self.state[2].copy() #0422 + + ns_unused_inherited = unused + if unused is None: + ns_unused_inherited = self.state[3].copy() + + ns_local = ns_parent.copy() + inclusive = _inclusive(self) + xml_attrs_local = {} + + # Divide attributes into NS, XML, and others. + other_attrs = [] + in_subset = _in_subset(self.subset, node) + for a in initial_other_attrs + _attrs(node): + if a.namespaceURI == XMLNS.BASE: + n = a.nodeName + if n == "xmlns:": n = "xmlns" # DOM bug workaround + ns_local[n] = a.nodeValue + elif a.namespaceURI == XMLNS.XML: + if inclusive or (in_subset and _in_subset(self.subset, a)): #020925 Test to see if attribute node in subset + xml_attrs_local[a.nodeName] = a #0426 + else: + if _in_subset(self.subset, a): #020925 Test to see if attribute node in subset + other_attrs.append(a) + +# # TODO: exclusive, might need to define xmlns:prefix here +# if not inclusive and a.prefix is not None and not ns_rendered.has_key('xmlns:%s' %a.prefix): +# ns_local['xmlns:%s' %a.prefix] = ?? + + #add local xml:foo attributes to ancestor's xml:foo attributes + xml_attrs.update(xml_attrs_local) + + # Render the node + W, name = self.write, None + if in_subset: + name = node.nodeName + if not inclusive: + if node.prefix is not None: + prefix = 'xmlns:%s' %node.prefix + else: + prefix = 'xmlns' + + if not ns_rendered.has_key(prefix) and not ns_local.has_key(prefix): + if not ns_unused_inherited.has_key(prefix): + raise RuntimeError,\ + 'For exclusive c14n, unable to map prefix "%s" in %s' %( + prefix, node) + + ns_local[prefix] = ns_unused_inherited[prefix] + del ns_unused_inherited[prefix] + + W('<') + W(name) + + # Create list of NS attributes to render. + ns_to_render = [] + for n,v in ns_local.items(): + + # If default namespace is XMLNS.BASE or empty, + # and if an ancestor was the same + if n == "xmlns" and v in [ XMLNS.BASE, '' ] \ + and ns_rendered.get('xmlns') in [ XMLNS.BASE, '', None ]: + continue + + # "omit namespace node with local name xml, which defines + # the xml prefix, if its string value is + # http://www.w3.org/XML/1998/namespace." + if n in ["xmlns:xml", "xml"] \ + and v in [ 'http://www.w3.org/XML/1998/namespace' ]: + continue + + + # If not previously rendered + # and it's inclusive or utilized + if (n,v) not in ns_rendered.items(): + if inclusive or _utilized(n, node, other_attrs, self.unsuppressedPrefixes): + ns_to_render.append((n, v)) + elif not inclusive: + ns_unused_inherited[n] = v + + # Sort and render the ns, marking what was rendered. + ns_to_render.sort(_sorter_ns) + for n,v in ns_to_render: + self._do_attr(n, v) + ns_rendered[n]=v #0417 + + # If exclusive or the parent is in the subset, add the local xml attributes + # Else, add all local and ancestor xml attributes + # Sort and render the attributes. + if not inclusive or _in_subset(self.subset,node.parentNode): #0426 + other_attrs.extend(xml_attrs_local.values()) + else: + other_attrs.extend(xml_attrs.values()) + other_attrs.sort(_sorter) + for a in other_attrs: + self._do_attr(a.nodeName, a.value) + W('>') + + # Push state, recurse, pop state. + state, self.state = self.state, (ns_local, ns_rendered, xml_attrs, ns_unused_inherited) + for c in _children(node): + _implementation.handlers[c.nodeType](self, c) + self.state = state + + if name: W('' % name) + handlers[Node.ELEMENT_NODE] = _do_element + + +def Canonicalize(node, output=None, **kw): + '''Canonicalize(node, output=None, **kw) -> UTF-8 + + Canonicalize a DOM document/element node and all descendents. + Return the text; if output is specified then output.write will + be called to output the text and None will be returned + Keyword parameters: + nsdict: a dictionary of prefix:uri namespace entries + assumed to exist in the surrounding context + comments: keep comments if non-zero (default is 0) + subset: Canonical XML subsetting resulting from XPath + (default is []) + unsuppressedPrefixes: do exclusive C14N, and this specifies the + prefixes that should be inherited. + ''' + if output: + apply(_implementation, (node, output.write), kw) + else: + s = StringIO.StringIO() + apply(_implementation, (node, s.write), kw) + return s.getvalue() diff --git a/wstools/logging.py b/wstools/logging.py new file mode 100644 index 0000000..9c33b00 --- /dev/null +++ b/wstools/logging.py @@ -0,0 +1,274 @@ +# 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. +# +"""Logging""" +ident = "$Id$" +import os, sys + +WARN = 1 +DEBUG = 2 + + +class ILogger: + '''Logger interface, by default this class + will be used and logging calls are no-ops. + ''' + level = 0 + def __init__(self, msg): + return + def warning(self, *args, **kw): + return + def debug(self, *args, **kw): + return + def error(self, *args, **kw): + return + def setLevel(cls, level): + cls.level = level + setLevel = classmethod(setLevel) + + debugOn = lambda self: self.level >= DEBUG + warnOn = lambda self: self.level >= WARN + + +class BasicLogger(ILogger): + last = '' + + def __init__(self, msg, out=sys.stdout): + self.msg, self.out = msg, out + + def warning(self, msg, *args, **kw): + if self.warnOn() is False: return + if BasicLogger.last != self.msg: + BasicLogger.last = self.msg + print >>self, "---- ", self.msg, " ----" + print >>self, " %s " %self.WARN, + print >>self, msg %args + WARN = '[WARN]' + def debug(self, msg, *args, **kw): + if self.debugOn() is False: return + if BasicLogger.last != self.msg: + BasicLogger.last = self.msg + print >>self, "---- ", self.msg, " ----" + print >>self, " %s " %self.DEBUG, + print >>self, msg %args + DEBUG = '[DEBUG]' + def error(self, msg, *args, **kw): + if BasicLogger.last != self.msg: + BasicLogger.last = self.msg + print >>self, "---- ", self.msg, " ----" + print >>self, " %s " %self.ERROR, + print >>self, msg %args + ERROR = '[ERROR]' + + def write(self, *args): + '''Write convenience function; writes strings. + ''' + for s in args: self.out.write(s) + event = ''.join(*args) + + +_LoggerClass = BasicLogger + +class GridLogger(ILogger): + def debug(self, msg, *args, **kw): + kw['component'] = self.msg + gridLog(event=msg %args, level='DEBUG', **kw) + + def warning(self, msg, *args, **kw): + kw['component'] = self.msg + gridLog(event=msg %args, level='WARNING', **kw) + + def error(self, msg, *args, **kw): + kw['component'] = self.msg + gridLog(event=msg %args, level='ERROR', **kw) + + +# +# Registry of send functions for gridLog +# +GLRegistry = {} + +class GLRecord(dict): + """Grid Logging Best Practices Record, Distributed Logging Utilities + + The following names are reserved: + + event -- log event name + Below is EBNF for the event name part of a log message. + name = ( "." )? + nodot = {RFC3896-chars except "."} + + Suffixes: + start: Immediately before the first action in a task. + end: Immediately after the last action in a task (that succeeded). + error: an error condition that does not correspond to an end event. + + ts -- timestamp + level -- logging level (see levels below) + status -- integer status code + gid -- global grid identifier + gid, cgid -- parent/child identifiers + prog -- program name + + + More info: http://www.cedps.net/wiki/index.php/LoggingBestPractices#Python + + reserved -- list of reserved names, + omitname -- list of reserved names, output only values ('ts', 'event',) + levels -- dict of levels and description + """ + reserved = ('ts', 'event', 'level', 'status', 'gid', 'prog') + omitname = () + levels = dict(FATAL='Component cannot continue, or system is unusable.', + ALERT='Action must be taken immediately.', + CRITICAL='Critical conditions (on the system).', + ERROR='Errors in the component; not errors from elsewhere.', + WARNING='Problems that are recovered from, usually.', + NOTICE='Normal but significant condition.', + INFO='Informational messages that would be useful to a deployer or administrator.', + DEBUG='Lower level information concerning program logic decisions, internal state, etc.', + TRACE='Finest granularity, similar to "stepping through" the component or system.', + ) + + def __init__(self, date=None, **kw): + kw['ts'] = date or self.GLDate() + kw['gid'] = kw.get('gid') or os.getpid() + dict.__init__(self, kw) + + def __str__(self): + """ + """ + from cStringIO import StringIO + s = StringIO(); n = " " + reserved = self.reserved; omitname = self.omitname; levels = self.levels + + for k in ( list(filter(lambda i: self.has_key(i), reserved)) + + list(filter(lambda i: i not in reserved, self.keys())) + ): + v = self[k] + if k in omitname: + s.write( "%s " %self.format[type(v)](v) ) + continue + + if k == reserved[2] and v not in levels: + pass + + s.write( "%s=%s " %(k, self.format[type(v)](v) ) ) + + s.write("\n") + return s.getvalue() + + class GLDate(str): + """Grid logging Date Format + all timestamps should all be in the same time zone (UTC). + Grid timestamp value format that is a highly readable variant of the ISO8601 time standard [1]: + + YYYY-MM-DDTHH:MM:SS.SSSSSSZ + + """ + def __new__(self, args=None): + """args -- datetime (year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]]) + """ + import datetime + args = args or datetime.datetime.utcnow() + l = (args.year, args.month, args.day, args.hour, args.minute, args.second, + args.microsecond, args.tzinfo or 'Z') + + return str.__new__(self, "%04d-%02d-%02dT%02d:%02d:%02d.%06d%s" %l) + + format = { int:str, float:lambda x: "%lf" % x, long:str, str:lambda x:x, + unicode:str, GLDate:str, } + + +def gridLog(**kw): + """Send GLRecord, Distributed Logging Utilities + If the scheme is passed as a keyword parameter + the value is expected to be a callable function + that takes 2 parameters: url, outputStr + + GRIDLOG_ON -- turn grid logging on + GRIDLOG_DEST -- provide URL destination + """ + import os + + if not bool( int(os.environ.get('GRIDLOG_ON', 0)) ): + return + + url = os.environ.get('GRIDLOG_DEST') + if url is None: + return + + ## NOTE: urlparse problem w/customized schemes + try: + scheme = url[:url.find('://')] + send = GLRegistry[scheme] + send( url, str(GLRecord(**kw)), ) + except Exception, ex: + print >>sys.stderr, "*** gridLog failed -- %s" %(str(kw)) + + +def sendUDP(url, outputStr): + from socket import socket, AF_INET, SOCK_DGRAM + idx1 = url.find('://') + 3; idx2 = url.find('/', idx1) + if idx2 < idx1: idx2 = len(url) + netloc = url[idx1:idx2] + host,port = (netloc.split(':')+[80])[0:2] + socket(AF_INET, SOCK_DGRAM).sendto( outputStr, (host,int(port)), ) + +def writeToFile(url, outputStr): + print >> open(url.split('://')[1], 'a+'), outputStr + +GLRegistry["gridlog-udp"] = sendUDP +GLRegistry["file"] = writeToFile + + +def setBasicLogger(): + '''Use Basic Logger. + ''' + setLoggerClass(BasicLogger) + BasicLogger.setLevel(0) + +def setGridLogger(): + '''Use GridLogger for all logging events. + ''' + setLoggerClass(GridLogger) + +def setBasicLoggerWARN(): + '''Use Basic Logger. + ''' + setLoggerClass(BasicLogger) + BasicLogger.setLevel(WARN) + +def setBasicLoggerDEBUG(): + '''Use Basic Logger. + ''' + setLoggerClass(BasicLogger) + BasicLogger.setLevel(DEBUG) + +def setLoggerClass(loggingClass): + '''Set Logging Class. + ''' + +def setLoggerClass(loggingClass): + '''Set Logging Class. + ''' + assert issubclass(loggingClass, ILogger), 'loggingClass must subclass ILogger' + global _LoggerClass + _LoggerClass = loggingClass + +def setLevel(level=0): + '''Set Global Logging Level. + ''' + ILogger.level = level + +def getLevel(): + return ILogger.level + +def getLogger(msg): + '''Return instance of Logging class. + ''' + return _LoggerClass(msg) + + diff --git a/wstools/tests/.cvsignore b/wstools/tests/.cvsignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/wstools/tests/.cvsignore @@ -0,0 +1 @@ +*.pyc diff --git a/wstools/tests/README b/wstools/tests/README new file mode 100644 index 0000000..7c1097a --- /dev/null +++ b/wstools/tests/README @@ -0,0 +1,52 @@ +Two top level modules have been provided to run the tests. "test_wstools.py" +is used to run all of the local tests. "test_wstools_net.py" is used to run +all of the tests that require network access. + +Add the -v option for more informative feedback. + +ADDING TESTS: + 1. For Stand-Alone tests add WSDL FILE to appropriate archive file + Need to add a NEW Archive?: + config.txt [files] "archive" -- tuple of all archive files, + if you need to create a new archive append the archive + name to the 'archive' tuple. + + 2. Edit config.txt section(s): + option -- name by which service will be referenced in test case. + Need an entry under appropriate section(s), this name + must be unique within each section it appears but it may + appear in multiple sections. + + config.txt "test" sections: + Stand-Alone -- add "option" under [services_by_file] + eg. amazon = exports/AmazonWebServices.wsdl + + Network -- add "option" under [services_by_http] + eg. amazon = http://soap.amazon.com/schemas/AmazonWebServices.wsdl + + Broken -- add "option" under [broken] + + 3. Done + + +CONTENTS OF SAMPLE WSDL/XSD: + schema -- Taken from globus-3.0.1(http://www.globus.org) + xmethods -- Taken from XMethods(http://www.xmethods.com) + airport.wsdl + AmazonWebServices.wsdl + books.wsdl + Distance.wsdl + freedb.wsdl + globalweather.wsdl + IHaddock.wsdl + ip2geo.wsdl + magic.wsdl + query.wsdl + RateInfo.wsdl + SHA1Encrypt.wsdl + siteInspect.wsdl + TemperatureService.wsdl + usweather.wsdl + rtf2html.xml + SolveSystem.wsdl.xml + zip2geo.wsdl diff --git a/wstools/tests/__init__.py b/wstools/tests/__init__.py new file mode 100644 index 0000000..ee3ecd2 --- /dev/null +++ b/wstools/tests/__init__.py @@ -0,0 +1 @@ +#! /usr/bin/env python diff --git a/wstools/tests/config.txt b/wstools/tests/config.txt new file mode 100644 index 0000000..6ebbe19 --- /dev/null +++ b/wstools/tests/config.txt @@ -0,0 +1,362 @@ +############################################################################ +# Joshua R. Boverhof, David W. Robertson, LBNL +# See Copyright for copyright notice! +########################################################################### + +########################################################################### +# Config file for the unit test framework. +# Sections below. +########################################################################### + + + +########################################################################## +# SECTION [files] - archives of wsdl/xsd files. +# +########################################################################## +[files] +archives = ('xmethods.tar.gz', 'schema.tar.gz') + +########################################################################## +# SECTION [services_by_file] - all services locally available for +# testing. +########################################################################## +[services_by_file] +ogsi = schema/ogsi/ogsi_service.wsdl +airport = xmethods/airport.wsdl +distance = xmethods/Distance.wsdl +freedb = xmethods/freedb.wsdl +globalweather = xmethods/globalweather.wsdl +IHaddock = xmethods/IHaddock.wsdl +ip2geo = xmethods/ip2geo.wsdl +magic = xmethods/magic.wsdl +query = xmethods/query.wsdl +RateInfo = xmethods/RateInfo.wsdl +SHA1Encrypt = xmethods/SHA1Encrypt.wsdl +siteInsepct = xmethods/siteInspect.wsdl +TemperatureService = xmethods/TemperatureService.wsdl +usweather = xmethods/usweather.wsdl +zip2geo = xmethods/zip2geo.wsdl +SolveSystem = xmethods/SolveSystem.wsdl.xml + +########################################################################## +# SECTION [services_by_http] - +########################################################################## +[services_by_http] + +# no schemas +AbysalSendEmail = http://www.abysal.com/soap/AbysalEmail.wsdl +BNQuoteService = http://www.xmethods.net/sd/2001/BNQuoteService.wsdl +BabelFishService = http://www.xmethods.net/sd/2001/BabelFishService.wsdl +Bible = http://www.stgregorioschurchdc.org/wsdl/Bible.wsdl +Blast = http://xml.nig.ac.jp/wsdl/Blast.wsdl +CATrafficService = http://www.xmethods.net/sd/2001/CATrafficService.wsdl +Calendar = http://www.stgregorioschurchdc.org/wsdl/Calendar.wsdl +ClustalW = http://xml.nig.ac.jp/wsdl/ClustalW.wsdl +CountryInfoLookupService = http://www.cs.uga.edu/~sent/xmethods/CountryInfoLookup.wsdl +CurrencyExchangeService = http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl +DDBJ = http://xml.nig.ac.jp/wsdl/DDBJ.wsdl +DiscordianService = http://www.compkarori.com/wsdl/discordian.wsdl +DistanceService = http://webservices.imacination.com/distance/Distance.jws?wsdl +DocServService = http://docserv.aurigalogic.com/docserv.wsdl +EMWebFunctionWS = http://www.eyemaginations.com/cgi-bin/getWSDL.pl?wsdl=WebFunction.wsdl +Fasta = http://xml.nig.ac.jp/wsdl/Fasta.wsdl +FaxService = http://oneoutbox.com/wsdl/FaxService.wsdl +FreeFaxService = http://www.OneOutBox.com/wsdl/FreeFaxService.wsdl +GetEntry = http://xml.nig.ac.jp/wsdl/GetEntry.wsdl +IBorlandBabelservice = http://ww6.borland.com/webservices/BorlandBabel/BorlandBabel.exe/wsdl/IBorlandBabel +IBorlandChessservice = http://www.danmarinescu.com/WebServices/ChessCGIServer.exe/wsdl/IBorlandChess +IDutchservice = http://www.ebob42.com/cgi-bin/NumberToWordsInDutch.exe/wsdl/IDutch +IEmailServiceservice = http://webservices.matlus.com/scripts/emailwebservice.dll/wsdl/IEmailService +IHeadLineservice = http://www.ebob42.com/cgi-bin/DrBobsClinic.exe/wsdl/IHeadline +IMapQuestservice = http://ww6.borland.com/webservices/MapQuest/MapQuest.exe/wsdl/IMapQuest +IMsSessionBrokerServiceservice = http://webservices.matlus.com/scripts/sessionservice.dll/wsdl/IMsSessionBrokerService +IODCODESPOSTAUXservice = http://www.e-naxos.com/scripts/enwscp.dll/wsdl/IODCODESPOSTAUX +IPGPKeyServerservice = http://www.marotz.se/PGPKeyServer/PGPKeyServiceX.exe/wsdl/IPGPKeyServer +IPrimeGeneratorservice = http://www.jusufdarmawan.com/wsprimegenerator.exe/wsdl/IPrimeGenerator +IRomanservice = http://www.ebob42.com/cgi-bin/Romulan.exe/wsdl/IRoman +ISMSServiceservice = http://sms.idws.com/soap/smsservice.dll/wsdl/ISMSService +ISlashdotHeadlineProviderservice = http://www.marotz.se/scripts/SlashdotHeadlines.exe/wsdl/ISlashdotHeadlineProvider +ISwedishZipInfoservice = http://www.marotz.se/scripts/zipinfo.exe/wsdl/ISwedishZipInfo +ITempConverterservice = http://developerdays.com/cgi-bin/tempconverter.exe/wsdl/ITempConverter +IWSMazeServerservice = http://www.culand.net/WebServices/bin/WSMaze_Server.dll/wsdl/IWSMazeServer +IWagAddressServerSingleservice = http://62.212.78.36/cgi-bin/WagAddressServerSingle.exe/wsdl/IWagAddressServerSingle +IWhoIsservice = http://webservices.matlus.com/scripts/whoiswebservice.dll/wsdl/IWhoIs +Ieconomicservice = http://www.suiyi.com/soap/economic.dll/wsdl/Ieconomic +IgetNumbersservice = http://reto.checkit.ch/Scripts/Lotto.dll/wsdl/IgetNumbers +Iws_Verify_NRICservice = http://www.rightsecurity.biz/NRICWebServices/NRICWebServices.dll/wsdl/Iws_Verify_NRIC +KRSS_DAML_Service = http://digilander.libero.it/mamo78/KRSS_DAML_Service.wsdl +MBWSSoapService = http://www.extensio.com:8080/ExtensioInfoServer/mbsoap/MBWSSoapServices.wsdl +SRS = http://xml.nig.ac.jp/wsdl/SRS.wsdl +ServiceSMS = http://smsserver.dotnetisp.com/servicesms.asmx?WSDL +TemperatureService = http://www.xmethods.net/sd/2001/TemperatureService.wsdl +TxSearch = http://xml.nig.ac.jp/wsdl/TxSearch.wsdl +UrduSOAP = http://www.apniurdu.com/SOAP/Urdu2.wsdl +WSFindMP3 = http://xmlrad.com/WSFindMP3Bin/WSFindMP3.dll/WSDL +WSGenerator = http://xmlrad.com/WSGeneratorBin/WSGenerator.dll/WSDL +WorldTimeService = http://ws.digiposs.com/WorldTime.jws?wsdl +XEMBL = http://www.ebi.ac.uk/xembl/XEMBL.wsdl +XMethodsFilesystemService = http://www.xmethods.net/sd/2001/XMethodsFilesystemService.wsdl +YIM Service = http://www.scdi.org/~avernet/webservice/yim.wsdl +YahooUserPingService = http://www.allesta.net:51110/webservices/wsdl/YahooUserPingService.xml +convert = http://www.cosme.nu/services/convert.php?wsdl +dns = http://www.cosme.nu/services/dns.php?wsdl +eBayWatcherService = http://www.xmethods.net/sd/2001/EBayWatcherService.wsdl +finnwords = http://www.nickhodge.com/nhodge/finnwords/finnwords.wsdl +pop = http://www.cosme.nu/services/pop.php?wsdl + + +#simple types + +ABA = http://www.webservicex.net/aba.asmx?WSDL +AmazonBox = http://www.xmlme.com/WSAmazonBox.asmx?WSDL +AustralianPostCode = http://www.webservicex.net/AustralianPostCode.asmx?WSDL +Autoloan = http://upload.eraserver.net/circle24/autoloan.asmx?wsdl +BNPrice = http://www.abundanttech.com/webservices/bnprice/bnprice.wsdl +BankCode = http://appserver.pepperzak.net/bankcode/BankCodeEJBHome/wsdl.jsp +BarCode = http://www.webservicex.net/barcode.asmx?WSDL +BibleWebservice = http://www.webservicex.net/BibleWebservice.asmx?wsdl +Braille = http://www.webservicex.net/braille.asmx?WSDL +CEqImage = http://www.quisque.com/fr/techno/eqimage/eqimage.asmx?WSDL +CFRSearch = http://www.oakleaf.ws/cfrsearchws/cfrsearchws.asmx?wsdl +CFRSect = http://www.oakleaf.ws/cfrsectws/cfrsectws.asmx?wsdl +CFRToc = http://www.oakleaf.ws/cfrtocws/cfrtocws.asmx?wsdl +CodeGenerator = http://www.esynaps.com/webservices/codegenerator.asmx?WSDL +CreditCardValidator = http://www.richsolutions.com/RichPayments/RichCardValidator.asmx?WSDL +CurrencyConvertor = http://www.webservicex.net/CurrencyConvertor.asmx?wsdl +Currencyws = http://glkev.webs.innerhost.com/glkev_ws/Currencyws.asmx?WSDL +DailyDilbert = http://www.esynaps.com/WebServices/DailyDiblert.asmx?WSDL +DotnetDailyFact = http://www.xmlme.com/WSDailyNet.asmx?WSDL +EMBLNucleotideSequenceWebService = http://www.webservicex.net/EMBLNucleotideSequenceWebService.asmx?wsdl +ElectronicProductsFinder = http://www.xmlme.com/WSElectronics.asmx?WSDL +EncryptionWS = http://test.mapfrepr.net/Encryption/Encryption.asmx?WSDL +Fax = http://ws.acrosscommunications.com/Fax.asmx?WSDL +FinanceService = http://www.webservicex.net/FinanceService.asmx?WSDL +Fortune = http://adrianr.dyndns.org/Fortune/Fortune.wsdl +GetCustomNews = http://www.xmlme.com/WSCustNews.asmx?WSDL +GetLocalTime = http://services.develop.co.za/GetLocalTime.asmx?WSDL +GlobalWeather = http://www.webservicex.net/globalweather.asmx?WSDL +HCPCS = http://www.webservicex.net/hcpcs.asmx?WSDL +IBANFunctions = http://www.bitounis.com/IBAN/IBANFuncs.asmx?WSDL +ICD10 = http://www.webservicex.net/icd10.asmx?WSDL +ICD9 = http://www.webservicex.net/icd9.asmx?WSDL +ICD9Drug = http://www.webservicex.net/icd9drug.asmx?WSDL +ICD9ToICD10 = http://www.webservicex.net/icd9toicd10.asmx?WSDL +ICQ = http://ws.acrosscommunications.com/ICQ.asmx?WSDL +ISearchSwedishPersonservice = http://www.marotz.se/scripts/searchperson.exe/wsdl/ISearchSwedishPerson +InstantMessageAlert = http://www.bindingpoint.com/ws/imalert/imalert.asmx?wsdl +LocalTime = http://www.ripedev.com/webservices/LocalTime.asmx?WSDL +MSProxy = http://www.esynaps.com/WebServices/MsProxy.asmx?WSDL +MXChecker = http://beta2.eraserver.net/webservices/mxchecker/mxchecker.asmx?WSDL +NAICS = http://www.webservicex.net/NAICS.asmx?wsdl +NFLNews = http://www.esynaps.com/WebServices/NFLNews.asmx?WSDL +NumPager = http://ws.acrosscommunications.com/NumPager.asmx?WSDL +OTNNews = http://otn.oracle.com/ws/otnnews?WSDL +Paracite = http://paracite.ecs.soton.ac.uk/paracite.wsdl +Phone = http://ws.acrosscommunications.com/Phone.asmx?WSDL +Puki = http://www.barnaland.is/dev/puki.asmx?WSDL +QueryIP = http://ws.cdyne.com/whoisforip/queryip.asmx?wsdl +Quotes = http://www.seshakiran.com/QuoteService/QuotesService.asmx?wsdl +QuranVerse = http://aspnet.lamaan.com/webservices/QuranVerse.asmx?WSDL +RSAFuncs = http://www.bitounis.com/RSAFunctions/RSAFuncs.asmx?WSDL +RSStoHTML = http://www.webservicex.net/RssToHTML.asmx?WSDL +#SMS = http://ws.acrosscommunications.com/SMS.asmx?WSDL +#SMS_1 = http://www.barnaland.is/dev/sms.asmx?WSDL +SQLDataSoap = http://www.SoapClient.com/xml/SQLDataSoap.wsdl +SecureXML = http://www.securexml.net/securexml/securexml.wsdl +SendSMSWorld = http://www.webservicex.net/sendsmsworld.asmx?WSDL +Shakespeare = http://www.xmlme.com/WSShakespeare.asmx?WSDL +SportingGoodsFinder = http://www.xmlme.com/WSSportingGoods.asmx?WSDL +StockQuote = http://www.webservicex.net/stockquote.asmx?WSDL +StockQuotes = http://www.gama-system.com/webservices/stockquotes.asmx?wsdl +TAP = http://ws.acrosscommunications.com/TAP.asmx?WSDL +UDDIBusinessFinder = http://www.webservicex.net/UDDIBusinessFinder.asmx?WSDL +UKLocation = http://www.webservicex.net/uklocation.asmx?WSDL +UNSPSCConvert = http://www.codemechanisms.co.uk/WebServices/UNSPSC.asmx?WSDL +USWeather = http://www.webservicex.net/usweather.asmx?WSDL +ValidateEmail = http://www.webservicex.net/ValidateEmail.asmx?WSDL +VideoGamesFinder = http://www.xmlme.com/WSVideoGames.asmx?WSDL +WebChart = http://www.gxchart.com/webchart.wsdl +WebSearchWS = http://www.esynaps.com/WebServices/SearchWS.asmx?WSDL +WhoIS = http://ws.cdyne.com/whoisquery/whois.asmx?wsdl +WhoIsService = http://www.esynaps.com/WebServices/WhoIsService.asmx?WSDL +XmlDailyFact = http://www.xmlme.com/WSDailyXml.asmx?WSDL +XmlTracking = http://www.baxglobal.com/xmltracking/xmltracking.asmx?wsdl +XreOnline = http://www.codecube.net/services/xreonline.asmx?WSDL +ZipCodesService = http://webservices.instantlogic.com/zipcodes.ils?wsdl +airport = http://www.webservicex.net/airport.asmx?wsdl +bork = http://www.x-ws.de/cgi-bin/bork/service.wsdl +chat = http://www.x-ws.de/cgi-bin/eliza/chat.wsdl +country = http://www.webservicex.net/country.asmx?wsdl +eSynapsFeed = http://www.esynaps.com/WebServices/eSynapsFeed.asmx?WSDL +eSynapsSerach = http://www.esynaps.com/WebServices/eSynapsSearch.asmx?WSDL +engtoarabic = http://www.dl-me.com/etoaservice/engtoarabic.asmx?WSDL +fWArticleService = http://www.framewerks.com/WebServices/fWArticleService/fwArticles.asmx?WSDL +fax = http://www.webservicex.net/fax.asmx?wsdl +foxcentral = http://www.foxcentral.net/foxcentral.wsdl +iifws = http://www.inkostar.com/wsdl/iifws/iifws.wsdl +imstatus = http://www.x-ws.de/cgi-bin/msn/imstatus.wsdl +periodictable = http://www.webservicex.net/periodictable.asmx?wsdl +piglatin = http://www.aspxpressway.com/maincontent/webservices/piglatin.asmx?wsdl +unitext = http://www.dl-me.com/webservices/unitext.asmx?wsdl +wwhelpservice = http://www.west-wind.com/wconnect/soap/wwhelpservice.wsdl +xmlserver = http://xml.redcoal.net/SMSSOAP/xmlserver.wsdl + +# complex types + +AddFinderService = http://www.lixusnet.com/lixusnet/AddFinder.jws?wsdl +AddressFinder = http://arcweb.esri.com/services/v2/AddressFinder.wsdl +AddressLookup = http://ws.cdyne.com/psaddress/addresslookup.asmx?wsdl +AmazonQuery = http://majordojo.com/amazon_query/amazon_query.wsdl +AmazonSearch = http://soap.amazon.com/schemas/AmazonWebServices.wsdl +BondService = http://www.financialwebservices.ltd.uk/axis/services/bond?wsdl +BusinessNews = http://glkev.webs.innerhost.com/glkev_ws/businessnews.asmx?WSDL +CarRentalQuotesService = http://wavendon.dsdata.co.uk/axis/services/CarRentalQuotes?wsdl +CupScores = http://scores.serviceobjects.com/CupScores.asmx?WSDL +DOTSAddressValidate = http://ws2.serviceobjects.net/av/AddressValidate.asmx?WSDL +DOTSDomainSpy = http://ws2.serviceobjects.net/ds/domainspy.asmx?WSDL +DOTSEmailValidate = http://ws2.serviceobjects.net/ev/EmailValidate.asmx?WSDL +DOTSFastQuote = http://ws2.serviceobjects.net/sq/FastQuote.asmx?WSDL +DOTSFastTax = http://ws2.serviceobjects.net/ft/FastTax.asmx?WSDL +DOTSFastWeather = http://ws2.serviceobjects.net/fw/FastWeather.asmx?WSDL +DOTSGeoCash = http://ws2.serviceobjects.net/gc/GeoCash.asmx?WSDL +DOTSGeoPhone = http://ws2.serviceobjects.net/gp/GeoPhone.asmx?WSDL +DOTSGeoPinPoint = http://ws2.serviceobjects.net/gpp/GeoPinPoint.asmx?WSDL +DOTSLotteryNumbers = http://ws2.serviceobjects.net/ln/lotterynumbers.asmx?WSDL +DOTSPackageTracking = http://ws2.serviceobjects.net/pt/PackTrack.asmx?WSDL +DOTSPatentOffice = http://ws2.serviceobjects.net/uspo/USPatentOffice.asmx?WSDL +DOTSPhoneAppend = http://ws2.serviceobjects.net/pa/phoneappend.asmx?wsdl +DOTSShippingComparison = http://ws2.serviceobjects.net/pc/packcost.asmx?WSDL +DOTSUPC = http://ws2.serviceobjects.net/upc/UPC.asmx?WSDL +DOTSYellowPages = http://ws2.serviceobjects.net/yp/YellowPages.asmx?WSDL +Dispenser = http://www.blackstoneonline.com/webservices/dispenser.xml +DocConverterService = http://telecommerce.danet.de/axis/services/DocConverterServicePort?wsdl +FOPService = http://live.capescience.com/wsdl/FOPService.wsdl +FedRoutingDirectoryService = http://demo.soapam.com/services/FedEpayDirectory/FedEpayDirectoryService.wsdl +GMChart = http://service.graphmagic.com/GMService/GraphMagic.asmx?wsdl +GeoPlaces = http://www.codebump.com/services/placelookup.asmx?wsdl +GlobalWeather = http://live.capescience.com/wsdl/GlobalWeather.wsdl +GoogleSearch = http://api.google.com/GoogleSearch.wsdl +HPcatalogService = http://www.lixusnet.com/lixusnet/HPcatalog.jws?wsdl +HTMLeMail = http://www.framewerks.com/WebServices/HTMLeMail/HTMLeMail.asmx?WSDL +HelpfulFunctions = http://www.framewerks.com/WebServices/helpfulfunctions/helpfulfunctions.asmx?WSDL +HistoricalStockQuotes = http://glkev.webs.innerhost.com/glkev_ws/HistoricalStockQuotes.asmx?WSDL +Horoscope = http://www.swanandmokashi.com/HomePage/WebServices/Horoscope.asmx?WSDL +IACHSOAPservice = http://soap.achchex.com/exec/achsoap.dll/wsdl/IACHSOAP +IP2Geo = http://ws.cdyne.com/ip2geo/ip2geo.asmx?wsdl +ISoapFindMP3service = http://www.agnisoft.com/soap/mssoapmp3search.xml +ITeeChartservice = http://www.berneda.com/scripts/TeeChartSOAP.exe/wsdl/ITeeChart +IZPOP3service = http://www.zanetti-dev.com/scripts/zpop3ws.exe/wsdl/IZPOP3 +LookyBookService = http://www.winisp.net/cheeso/books/books.asmx?WSDL +MailLocate = http://www.maillocate.com/soap/index.php?wsdl +NavBarServer = http://ws.xara.com/navbar/navbar.wsdl +Online Messenger Service = http://www.nims.nl/soap/oms.wsdl +OnlineMessengerService = http://www.nims.nl/soap/oms2.wsdl +Option_x0020_Pricing_x0020_Calculator = http://www.indobiz.com/OptionPricing.asmx?WSDL +PersonLookup = http://www.barnaland.is/dev/personlookup.asmx?WSDL +Phonebook = http://www.barnaland.is/dev/phonebook.asmx?WSDL +PopulationWS = http://www.abundanttech.com/webservices/population/population.wsdl +QueryInterfaceService = http://www.transactionalweb.com/SOAP/globalskilocator.wsdl +QuizService = http://java.rus.uni-stuttgart.de/quiz/quiz.wsdl +QuoteOfTheDay = http://www.swanandmokashi.com/HomePage/WebServices/QuoteOfTheDay.asmx?WSDL +RateInfoClass = http://www.xeeinc.com/RateInformation/RateInfo.asmx?WSDL +RateInfoClass_1 = http://www.xeeinc.com/RateInformation/Rateinfo.asmx?WSDL +RecipeService = http://icuisine.net/webservices/RecipeService.asmx?WSDL +RenderServer3D = http://ws.xara.com/graphicrender/render3d.wsdl +RichPayments = http://www.richsolutions.com/richpayments/richpay.asmx?WSDL +SBGGetAirFareQuoteService = http://wavendon.dsdata.co.uk:8080/axis/services/SBGGetAirFareQuote?wsdl +SMS = http://www.abctext.com/webservices/SMS.asmx?WSDL +SalesRankNPrice = http://www.PerfectXML.NET/WebServices/SalesRankNPrice/BookService.asmx?WSDL +SendSMS = http://www.webservicex.net/SendSMS.asmx?WSDL +Server = http://addison.ra.cwru.edu/orc/calendar_copy/server.php?wsdl +Service = http://www.ejse.com/WeatherService/Service.asmx?WSDL +SpamKillerService = http://wavendon.dsdata.co.uk/axis/services/SpamKiller?wsdl +StockQuotes = http://www.swanandmokashi.com/HomePage/WebServices/StockQuotes.asmx?WSDL +TWSFissionDotNet = http://www.sidespace.com/ws/fission/fissiondotnet.php?wsdl +TerraService = http://terraservice.net/TerraService.asmx?WSDL +Transform = http://transform.dataconcert.com/transform.wsdl +UPSTracking = http://glkev.webs.innerhost.com/glkev_ws/UPSTracking.asmx?WSDL +URLjr_Library = http://urljr.com/soap +WeatherFetcher = http://glkev.webs.innerhost.com/glkev_ws/WeatherFetcher.asmx?WSDL +WeatherService = http://www.hkwizard.com/WeatherService.asmx?wsdl +WebServiceOfTheDay = http://www.webserviceoftheday.com/ws/soap/wsotd.asmx?wsdl +WeblogsSubscriber = http://soap.4s4c.com/weblogs/subscribe.wsdl +WhoIs = http://ws2.serviceobjects.net/whi/WhoIs.asmx?WSDL +WhoisDataService = http://wavendon.dsdata.co.uk/axis/services/WhoisData?wsdl +WolframSearchService = http://webservices.wolfram.com/services/SearchServices/WolframSearch.wsdl +XMethodsQuery = http://www.xmethods.net/wsdl/query.wsdl +XigniteEdgar = http://www.xignite.com/xEdgar.asmx?WSDL +XigniteNews = http://www.xignite.com/xnews.asmx?WSDL +XigniteOptions = http://www.xignite.com/xoptions.asmx?WSDL +XigniteQuotes = http://www.xignite.com/xquotes.asmx?WSDL +XigniteRealTime = http://www.xignite.com/xrealtime.asmx?WSDL +XigniteRetirement = http://www.xignite.com/xretirement.asmx?WSDL +XigniteSecurity = http://www.xignite.com/xsecurity.asmx?WSDL +XigniteSimulation = http://www.xignite.com/xsimulation.asmx?WSDL +XigniteStatistics = http://www.xignite.com/xstatistics.asmx?WSDL +XigniteSurvey = http://www.xignite.com/xSurvey.asmx?WSDL +XigniteWorldNews = http://www.xignite.com/xworldnews.asmx?WSDL +YourHost = http://www.esynaps.com/webservices/YourHostInfo.asmx?WSDL +Zip2Geo = http://ws.cdyne.com/ziptogeo/zip2geo.asmx?wsdl +ZipCode = http://www.ripedev.com/webservices/ZipCode.asmx?WSDL +ZipCodeResolver = http://webservices.eraserver.net/zipcoderesolver/zipcoderesolver.asmx?WSDL +ZipCodes = http://www.codebump.com/services/zipcodelookup.asmx?wsdl +ZipcodeLookupService = http://www.winisp.net/cheeso/zips/ZipService.asmx?WSDL +certServices = http://soapclient.com/xml/certService.wsdl +check = http://ws.cdyne.com/SpellChecker/check.asmx?wsdl +com.systinet.demo.freedb.FreeDBService = http://soap.systinet.net/demos/FreeDB/wsdl +com.systinet.demo.ftp.FTPService = http://soap.systinet.net/demos/FTPService/wsdl +com.systinet.demo.newsfeed.version1.NewsfeedService = http://soap.systinet.net/demos/Newsfeed/wsdl +com.systinet.demo.rpmfind.RpmService = http://soap.systinet.net/demos/RpmFinder/wsdl +com.systinet.demo.search.w3c.W3CSearchService = http://soap.systinet.net/demos/W3CSearch/wsdl +com.systinet.demo.search.zvon.ZVONSearchService = http://soap.systinet.net/demos/ZVONSearch/wsdl +dic2 = http://www.dl-me.com/webservices/dic2.asmx?WSDL +eSynapsMonitor = http://www.esynaps.com/WebServices/eSynapsMonitor.wsdl +ev = http://ws.cdyne.com/emailverify/ev.asmx?wsdl +getQuakeDataService = http://webservices.tei.or.th/getQuakeData.cfc?wsdl +getSessionReport = http://sandbox.grandcentral.com/services/reports?WSDL +pwspNoCentrbankCurRates = http://server1.pointwsp.net/ws/finance/currency.asmx?WSDL +sekeywordService = http://www.aspiringgeek.com/cfc/keyword/sekeyword.cfc?wsdl +threatService = http://www.boyzoid.com/threat.cfc?wsdl +xmethods_gcd = http://samples.bowstreet.com/bowstreet5/webengine/xmethods/gcd/Action!getWSDL + + + +########################################################################## +# SECTION [reader_errors] - +# unable to load file +########################################################################## +[reader_errors] + +BusinessFinder(UDDI)-WebService = http://www.esynaps.com/WebServices/BusinessList.asmx?WSDL +ColdFusionTip-of-the-Day = http://www.forta.com/cf/tips/syndicate.cfc?wsdl +ComputerDictionarySearch = http://dotnet.cyberthink.net/computerdictionary/computerdictionary.asmx?wsdl +DynamicChartingofXMLData = http://webservices.isitedesign.com/ws/chartWS.cfc?wsdl +EmailServices = http://soap.einsteinware.com/email/emailservices.asmx?WSDL +ExpressionEvaluator = http://www.onepercentsoftware.com/axis/services/EvaluationService?wsdl +FonttoGraphic = http://ws.cdyne.com/FontToGraphic/ftg.asmx?wsdl +HolidayInformation = http://wsdl.wsdlfeeds.com/holidays.cfc?wsdl +Html2Xml = http://www.dev1.eraserver.net/REFLECTIONIT/Html2xml.asmx?WSDL +HuZip = http://www.c6.hu/ws/huzip.wsdl +HuarananetPresstechnologynews = http://www22.brinkster.com/horaciovallejo/netpress1.asmx?wsdl +InfosVille = http://www.dotnetisp.com/webservices/dotnetisp/ville.asmx?WSDL +ItalianFiscalCode = http://www.pinellus.com/cfc/Cod_fiscale.cfc?wsdl +LinearSystemsSolver = http://www.cs.fsu.edu/~engelen/lu.wsdl +LiveScoreService = http://www.freshscore.com/service/FreshScoreLiveScores.asmx?WSDL +LogFileParser = http://www.bitounis.com/W3CParser/LogFileParser.asmx?WSDL +MP3.comMusicCharts = http://webservices.mp3.com/MP3Charts.wsdl +MachNumberWebService = http://www.cgi101.com/~msmithso/wsdl/mach.wsdl +MagicSquares = http://www.cs.fsu.edu/~engelen/magic.wsdl +MysicSearchEngine = http://mysic.com/Webservices/MysicSearchEngine.asmx?WSDL +NASCARWinstonCupStatistics = http://soap.einsteinware.com/nascar/nascardataservice.asmx?WSDL +OpenDirectoryProject = http://wsdl.wsdlfeeds.com/odp.cfc?wsdl +SchemaWebWebService = http://www.schemaweb.info/webservices/soap/SchemaWebSoap.asmx?wsdl +SlashdotNewsFeed = http://webservices.isitedesign.com/ws/slashdotnews.cfc?wsdl +SpamKiller = http://soap.prowizorka.com/spam/wsdl/ISpamCheck +SpellCheck = http://www.worldwidedesktop.com/spellcheck/spellcheckservice.asmx?wsdl +SpellChecker = http://wsdl.wsdlfeeds.com/spell.cfc?wsdl +USAZipcodeInformation = http://www.webservicex.net/uszip.asmx?WSDL +WebEvents = http://www.bitounis.com/WebEvents/events.asmx?WSDL +WebRTF2HTML = http://www.infoaccelerator.net/cfc/rtf2html.cfc?WSDL +cp2ville = http://www.dotnetisp.com/webservices/dotnetisp/codepostal.asmx?WSDL +src2html = http://www.dotnetisp.com/webservices/dotnetisp/src2html.asmx?WSDL diff --git a/wstools/tests/schema.tar.gz b/wstools/tests/schema.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..d6fe7db53491d6a69b2f7bd0d971c104c2aee698 GIT binary patch literal 68874 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 literal 0 HcmV?d00001 diff --git a/wstools/tests/test_t1.py b/wstools/tests/test_t1.py new file mode 100644 index 0000000..5c33899 --- /dev/null +++ b/wstools/tests/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/wstools/tests/test_wsdl.py b/wstools/tests/test_wsdl.py new file mode 100644 index 0000000..5f617b9 --- /dev/null +++ b/wstools/tests/test_wsdl.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python + +############################################################################ +# Joshua R. Boverhof, David W. Robertson, LBNL +# See LBNLCopyright for copyright notice! +########################################################################### + +import sys, unittest +import ConfigParser +import os +from wstools.Utility import DOM +from wstools.WSDLTools import WSDLReader +from wstools.TimeoutSocket import TimeoutError + +from wstools import tests +cwd = os.path.dirname(tests.__file__) + +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(cwd+'/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/wstools/tests/test_wstools.py b/wstools/tests/test_wstools.py new file mode 100644 index 0000000..0e0f958 --- /dev/null +++ b/wstools/tests/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/wstools/tests/test_wstools_net.py b/wstools/tests/test_wstools_net.py new file mode 100644 index 0000000..880cff3 --- /dev/null +++ b/wstools/tests/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() + + diff --git a/wstools/tests/xmethods.tar.gz b/wstools/tests/xmethods.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..4521a9ac9794a2d76c08453a62bf5af5cd359254 GIT binary patch literal 15209 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