From: Forrest Voight Date: Wed, 3 Aug 2011 19:37:29 +0000 (-0400) Subject: bundled wstools-0.3 X-Git-Tag: 0.8.2~209 X-Git-Url: https://git.novaco.in/?p=p2pool.git;a=commitdiff_plain;h=a98ef1f178022ecab298d6bb08198f466d6856e5 bundled wstools-0.3 --- 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 0000000..d6fe7db Binary files /dev/null and b/wstools/tests/schema.tar.gz differ 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 0000000..4521a9a Binary files /dev/null and b/wstools/tests/xmethods.tar.gz differ