@@ -0,0 +1,303 @@ | |||||
# 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. | |||||
from urlparse import urlparse | |||||
from ZSI import * | |||||
from ZSI.client import * | |||||
import weakref | |||||
from Utility import DOM | |||||
import WSDLTools | |||||
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, WSDLTools.SoapAddressBinding): | |||||
raise ValueError, 'Unsupported binding type.' | |||||
callinfo.location = addrbinding.location | |||||
soapbinding = binding.findBinding(WSDLTools.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(WSDLTools.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(WSDLTools.MimeMultipartRelatedBinding) | |||||
if mime is not None: | |||||
raise ValueError, 'Mime bindings are not supported.' | |||||
else: | |||||
for item in msgrole.findBindings(WSDLTools.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(WSDLTools.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: | |||||
message = messages[operation.output.message] | |||||
msgrole = opbinding.output | |||||
mime = msgrole.findBinding(WSDLTools.MimeMultipartRelatedBinding) | |||||
if mime is not None: | |||||
raise ValueError, 'Mime bindings are not supported.' | |||||
else: | |||||
for item in msgrole.findBindings(WSDLTools.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(WSDLTools.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: | |||||
callinfo.setReturnParameter( | |||||
parts[0].name, | |||||
parts[0].element or parts[0].type, | |||||
element_type = parts[0].element and 1 or 0 | |||||
) | |||||
for part in parts[1:]: | |||||
callinfo.addOutParameter( | |||||
part.name, | |||||
part.element or part.type, | |||||
element_type = part.element and 1 or 0 | |||||
) | |||||
return callinfo | |||||
class ServiceProxy: | |||||
"""A ServiceProxy provides a convenient way to call a remote web | |||||
service that is described with WSDL. The proxy exposes methods | |||||
that reflect the methods of the remote web service.""" | |||||
def __init__(self, wsdl, service=None, port=None, tracefile=None, | |||||
typesmodule=None, nsdict=None): | |||||
if not hasattr(wsdl, 'targetNamespace'): | |||||
wsdl = WSDLTools.WSDLReader().loadFromURL(wsdl) | |||||
# for item in wsdl.types.items(): | |||||
# self._serializer.loadSchema(item) | |||||
self._service = wsdl.services[service or 0] | |||||
self.__doc__ = self._service.documentation | |||||
self._port = self._service.ports[port or 0] | |||||
self._name = self._service.name | |||||
self._wsdl = wsdl | |||||
self._tracefile = tracefile | |||||
self._typesmodule = typesmodule | |||||
self._nsdict = nsdict | |||||
binding = self._port.getBinding() | |||||
portType = binding.getPortType() | |||||
for item in portType.operations: | |||||
callinfo = callInfoFromWSDL(self._port, item.name) | |||||
method = MethodProxy(self, callinfo) | |||||
setattr(self, item.name, method) | |||||
def _call(self, name, *args, **kwargs): | |||||
"""Call the named remote web service method.""" | |||||
if len(args) and len(kwargs): | |||||
raise TypeError( | |||||
'Use positional or keyword argument only.' | |||||
) | |||||
callinfo = getattr(self, name).callinfo | |||||
url = callinfo.location | |||||
(protocol, host, uri, query, fragment, identifier) = urlparse(url) | |||||
port = 80 | |||||
if host.find(':') >= 0: | |||||
host, port = host.split(':') | |||||
params = callinfo.getInParameters() | |||||
host = str(host) | |||||
port = str(port) | |||||
binding = Binding(host=host, tracefile=self._tracefile, | |||||
ssl=(protocol == 'https'), | |||||
port=port, url=uri, typesmodule=self._typesmodule, | |||||
nsdict=self._nsdict) | |||||
apply(getattr(binding, callinfo.methodName), args) | |||||
#print binding.ReceiveRaw() | |||||
return binding.Receive() | |||||
class MethodProxy: | |||||
""" """ | |||||
def __init__(self, parent, callinfo): | |||||
self.__name__ = callinfo.methodName | |||||
self.__doc__ = callinfo.documentation | |||||
self.callinfo = callinfo | |||||
self.parent = weakref.ref(parent) | |||||
def __call__(self, *args, **kwargs): | |||||
return self.parent()._call(self.__name__, *args, **kwargs) |
@@ -0,0 +1,176 @@ | |||||
"""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 <scott@chronis.pobox.com> | |||||
Lloyd Zusman <ljz@asfast.com> | |||||
Phil Mayes <pmayes@olivebr.com> | |||||
Piers Lauder <piers@cs.su.oz.au> | |||||
Radovan Garabik <garabik@melkor.dnp.fmph.uniba.sk> | |||||
""" | |||||
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 |
@@ -0,0 +1,753 @@ | |||||
# 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. | |||||
from string import join, strip, split | |||||
from UserDict import UserDict | |||||
from StringIO import StringIO | |||||
import xml.dom.minidom, weakref | |||||
import string, httplib, smtplib, urllib, socket | |||||
from TimeoutSocket import TimeoutSocket, TimeoutError | |||||
from StringIO import StringIO | |||||
from urlparse import urlparse | |||||
from httplib import HTTPConnection, HTTPSConnection | |||||
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): | |||||
if not hasattr(socket, 'ssl'): | |||||
raise ValueError( | |||||
'This Python installation does not have SSL support.' | |||||
) | |||||
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 not hasattr(socket, 'ssl'): | |||||
raise ValueError( | |||||
'This Python installation does not have SSL support.' | |||||
) | |||||
conn = TimeoutHTTPS(host, None, timeout) | |||||
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._attrs.has_key(name): | |||||
return 1 | |||||
for item in node._attrsNS.keys(): | |||||
if item[1] == name: | |||||
return 1 | |||||
return 0 | |||||
return node.attrsNS.has_key((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 getElementText(self, node, preserve_ws=None): | |||||
"""Return the text value of an xml element node. Leading and trailing | |||||
whitespace is stripped from the value unless the preserve_ws flag | |||||
is passed with a true value.""" | |||||
result = [] | |||||
for child in node.childNodes: | |||||
nodetype = child.nodeType | |||||
if nodetype == child.TEXT_NODE or \ | |||||
nodetype == child.CDATA_SECTION_NODE: | |||||
result.append(child.nodeValue) | |||||
value = join(result, '') | |||||
if preserve_ws is None: | |||||
value = strip(value) | |||||
return value | |||||
def findNamespaceURI(self, prefix, node): | |||||
"""Find a namespace uri given a prefix and a context node.""" | |||||
attrkey = (self.NS_XMLNS, prefix) | |||||
DOCUMENT_NODE = node.DOCUMENT_NODE | |||||
ELEMENT_NODE = node.ELEMENT_NODE | |||||
while 1: | |||||
if node.nodeType != ELEMENT_NODE: | |||||
node = node.parentNode | |||||
continue | |||||
result = node._attrsNS.get(attrkey, None) | |||||
if result is not None: | |||||
return result.value | |||||
if hasattr(node, '__imported__'): | |||||
raise DOMException('Value for prefix %s not found.' % prefix) | |||||
node = node.parentNode | |||||
if node.nodeType == DOCUMENT_NODE: | |||||
raise DOMException('Value for prefix %s not found.' % prefix) | |||||
def findDefaultNS(self, node): | |||||
"""Return the current default namespace uri for the given node.""" | |||||
attrkey = (self.NS_XMLNS, 'xmlns') | |||||
DOCUMENT_NODE = node.DOCUMENT_NODE | |||||
ELEMENT_NODE = node.ELEMENT_NODE | |||||
while 1: | |||||
if node.nodeType != ELEMENT_NODE: | |||||
node = node.parentNode | |||||
continue | |||||
result = node._attrsNS.get(attrkey, None) | |||||
if result is not None: | |||||
return result.value | |||||
if hasattr(node, '__imported__'): | |||||
raise DOMException('Cannot determine default namespace.') | |||||
node = node.parentNode | |||||
if node.nodeType == DOCUMENT_NODE: | |||||
raise DOMException('Cannot determine default namespace.') | |||||
def findTargetNS(self, node): | |||||
"""Return the defined target namespace uri for the given node.""" | |||||
attrget = self.getAttr | |||||
attrkey = (self.NS_XMLNS, 'xmlns') | |||||
DOCUMENT_NODE = node.DOCUMENT_NODE | |||||
ELEMENT_NODE = node.ELEMENT_NODE | |||||
while 1: | |||||
if node.nodeType != ELEMENT_NODE: | |||||
node = node.parentNode | |||||
continue | |||||
result = attrget(node, 'targetNamespace', default=None) | |||||
if result is not None: | |||||
return result | |||||
node = node.parentNode | |||||
if node.nodeType == DOCUMENT_NODE: | |||||
raise DOMException('Cannot determine target namespace.') | |||||
def getTypeRef(self, element): | |||||
"""Return (namespaceURI, name) for a type attribue of the given | |||||
element, or None if the element does not have a type attribute.""" | |||||
typeattr = self.getAttr(element, 'type', default=None) | |||||
if typeattr is None: | |||||
return None | |||||
parts = typeattr.split(':', 1) | |||||
if len(parts) == 2: | |||||
nsuri = self.findNamespaceURI(parts[0], element) | |||||
else: | |||||
nsuri = self.findDefaultNS(element) | |||||
return (nsuri, parts[1]) | |||||
def importNode(self, document, node, deep=0): | |||||
"""Implements (well enough for our purposes) DOM node import.""" | |||||
nodetype = node.nodeType | |||||
if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE): | |||||
raise DOMException('Illegal node type for importNode') | |||||
if nodetype == node.ENTITY_REFERENCE_NODE: | |||||
deep = 0 | |||||
clone = node.cloneNode(deep) | |||||
self._setOwnerDoc(document, clone) | |||||
clone.__imported__ = 1 | |||||
return clone | |||||
def _setOwnerDoc(self, document, node): | |||||
node.ownerDocument = document | |||||
for child in node.childNodes: | |||||
self._setOwnerDoc(document, child) | |||||
def nsUriMatch(self, value, wanted, strict=0, tt=type(())): | |||||
"""Return a true value if two namespace uri values match.""" | |||||
if value == wanted or (type(wanted) is tt) and value in wanted: | |||||
return 1 | |||||
if not strict: | |||||
wanted = type(wanted) is tt and wanted or (wanted,) | |||||
value = value[-1:] != '/' and value or value[:-1] | |||||
for item in wanted: | |||||
if item == value or item[:-1] == value: | |||||
return 1 | |||||
return 0 | |||||
def createDocument(self, nsuri, qname, doctype=None): | |||||
"""Create a new writable DOM document object.""" | |||||
impl = xml.dom.minidom.getDOMImplementation() | |||||
return impl.createDocument(nsuri, qname, doctype) | |||||
def loadDocument(self, data): | |||||
"""Load an xml file from a file-like object and return a DOM | |||||
document instance.""" | |||||
return xml.dom.minidom.parse(data) | |||||
def loadFromURL(self, url): | |||||
"""Load an xml file from a URL and return a DOM document.""" | |||||
file = urlopen(url) | |||||
try: result = self.loadDocument(file) | |||||
finally: file.close() | |||||
return result | |||||
class DOMException(Exception): | |||||
pass | |||||
DOM = DOM() | |||||
class Collection(UserDict): | |||||
"""Helper class for maintaining ordered named collections.""" | |||||
def __init__(self, parent): | |||||
UserDict.__init__(self) | |||||
self.parent = weakref.ref(parent) | |||||
self.list = [] | |||||
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: i.name, self.list) | |||||
def items(self): | |||||
return map(lambda i: (i.name, 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') <xml.dom.minidom.Attr instance at 0x82227c4> | |||||
# ('http://www.w3.org/2000/xmlns/', 'xmlns') <xml.dom.minidom.Attr instance at 0x8414b3c> | |||||
# | |||||
# 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) | |||||
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 = 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 = 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 |
@@ -0,0 +1,951 @@ | |||||
# 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. | |||||
from Utility import DOM, Collection | |||||
from XMLSchema import XMLSchema | |||||
from StringIO import StringIO | |||||
import urllib | |||||
class WSDLReader: | |||||
"""A WSDLReader creates WSDL instances from urls and xml data.""" | |||||
# Custom subclasses of WSDLReader may wish to implement a caching | |||||
# strategy or other optimizations. Because application needs vary | |||||
# so widely, we don't try to provide any caching by default. | |||||
def loadFromStream(self, file): | |||||
"""Return a WSDL instance loaded from a file object.""" | |||||
document = DOM.loadDocument(file) | |||||
wsdl = WSDL() | |||||
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): | |||||
self.targetNamespace = targetNamespace or 'urn:this-document.wsdl' | |||||
self.documentation = '' | |||||
self.location = None | |||||
self.document = None | |||||
self.name = None | |||||
self.services = Collection(self) | |||||
self.messages = Collection(self) | |||||
self.portTypes = Collection(self) | |||||
self.bindings = Collection(self) | |||||
self.imports = Collection(self) | |||||
self.types = Types(self) | |||||
self.extensions = [] | |||||
def __del__(self): | |||||
if self.document is not None: | |||||
self.document.unlink() | |||||
self.document = None | |||||
version = '1.1' | |||||
def addService(self, name, documentation=''): | |||||
if self.services.has_key(name): | |||||
raise WSDLError( | |||||
'Duplicate service element: %s' % name | |||||
) | |||||
item = Service(name, documentation) | |||||
self.services[name] = item | |||||
return item | |||||
def addMessage(self, name, documentation=''): | |||||
if self.messages.has_key(name): | |||||
raise WSDLError( | |||||
'Duplicate message element: %s.' % name | |||||
) | |||||
item = Message(name, documentation) | |||||
self.messages[name] = item | |||||
return item | |||||
def addPortType(self, name, documentation=''): | |||||
if self.portTypes.has_key(name): | |||||
raise WSDLError( | |||||
'Duplicate portType element: name' | |||||
) | |||||
item = PortType(name, documentation) | |||||
self.portTypes[name] = item | |||||
return item | |||||
def addBinding(self, name, type, documentation=''): | |||||
if self.bindings.has_key(name): | |||||
raise WSDLError( | |||||
'Duplicate binding element: %s' % name | |||||
) | |||||
item = Binding(name, type, documentation) | |||||
self.bindings[name] = item | |||||
return item | |||||
def addImport(self, namespace, location): | |||||
item = ImportElement(namespace, location) | |||||
self.imports[namespace] = item | |||||
return item | |||||
def load(self, document): | |||||
# We save a reference to the DOM document to ensure that elements | |||||
# saved as "extensions" will continue to have a meaningful context | |||||
# for things like namespace references. The lifetime of the DOM | |||||
# document is bound to the lifetime of the WSDL instance. | |||||
self.document = document | |||||
definitions = DOM.getElement(document, 'definitions', None, None) | |||||
if definitions is None: | |||||
raise WSDLError( | |||||
'Missing <definitions> element.' | |||||
) | |||||
self.version = DOM.WSDLUriToVersion(definitions.namespaceURI) | |||||
NS_WSDL = DOM.GetWSDLUri(self.version) | |||||
self.targetNamespace = DOM.getAttr(definitions, 'targetNamespace', | |||||
None, None) | |||||
self.name = DOM.getAttr(definitions, 'name', None, None) | |||||
self.documentation = GetDocumentation(definitions) | |||||
# Resolve (recursively) any import elements in the document. | |||||
imported = {} | |||||
while 1: | |||||
imports = [] | |||||
for element in DOM.getElements(definitions, 'import', NS_WSDL): | |||||
location = DOM.getAttr(element, 'location') | |||||
if not imported.has_key(location): | |||||
imports.append(element) | |||||
if not imports: | |||||
break | |||||
for element in imports: | |||||
self._import(document, element) | |||||
location = DOM.getAttr(element, 'location') | |||||
imported[location] = 1 | |||||
for element in DOM.getElements(definitions, None, None): | |||||
localName = element.localName | |||||
if not DOM.nsUriMatch(element.namespaceURI, NS_WSDL): | |||||
if localName == 'schema': | |||||
self.types.addSchema(XMLSchema(element)) | |||||
else: | |||||
self.extensions.append(element) | |||||
continue | |||||
elif localName == 'message': | |||||
name = DOM.getAttr(element, 'name') | |||||
docs = GetDocumentation(element) | |||||
message = self.addMessage(name, docs) | |||||
parts = DOM.getElements(element, 'part', NS_WSDL) | |||||
message.load(parts) | |||||
continue | |||||
elif localName == 'portType': | |||||
name = DOM.getAttr(element, 'name') | |||||
docs = GetDocumentation(element) | |||||
ptype = self.addPortType(name, docs) | |||||
operations = DOM.getElements(element, 'operation', NS_WSDL) | |||||
ptype.load(operations) | |||||
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 = type.split(':', 1)[-1] | |||||
docs = GetDocumentation(element) | |||||
binding = self.addBinding(name, type, docs) | |||||
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) | |||||
ports = DOM.getElements(element, 'port', NS_WSDL) | |||||
service.load(ports) | |||||
service.load_ex(GetExtensions(element)) | |||||
continue | |||||
elif localName == 'types': | |||||
self.types.documentation = GetDocumentation(element) | |||||
for item in DOM.getElements(element, None, None): | |||||
if item.localName == 'schema': | |||||
self.types.addSchema(XMLSchema(item)) | |||||
else: | |||||
self.types.addExtension(item) | |||||
continue | |||||
def _import(self, document, element): | |||||
namespace = DOM.getAttr(element, 'namespace', default=None) | |||||
location = DOM.getAttr(element, 'location', default=None) | |||||
if namespace is None or location is None: | |||||
raise WSDLError( | |||||
'Invalid import element (missing namespace or location).' | |||||
) | |||||
# Sort-of support relative locations to simplify unit testing. The | |||||
# WSDL specification actually doesn't allow relative URLs, so its | |||||
# ok that this only works with urls relative to the initial document. | |||||
location = urllib.basejoin(self.location, location) | |||||
obimport = self.addImport(namespace, location) | |||||
obimport._loaded = 1 | |||||
importdoc = DOM.loadFromURL(location) | |||||
try: | |||||
if location.find('#') > -1: | |||||
idref = location.split('#')[-1] | |||||
imported = DOM.getElementById(importdoc, idref) | |||||
else: | |||||
imported = importdoc.documentElement | |||||
if imported is None: | |||||
raise WSDLError( | |||||
'Import target element not found for: %s' % location | |||||
) | |||||
imported_tns = DOM.findTargetNS(imported) | |||||
if imported_tns != namespace: | |||||
return | |||||
if imported.localName == 'definitions': | |||||
imported_nodes = imported.childNodes | |||||
else: | |||||
imported_nodes = [imported] | |||||
parent = element.parentNode | |||||
for node in imported_nodes: | |||||
if node.nodeType != node.ELEMENT_NODE: | |||||
continue | |||||
child = DOM.importNode(document, node, 1) | |||||
parent.appendChild(child) | |||||
child.setAttribute('targetNamespace', namespace) | |||||
attrsNS = imported._attrsNS | |||||
for attrkey in attrsNS.keys(): | |||||
if attrkey[0] == DOM.NS_XMLNS: | |||||
attr = attrsNS[attrkey].cloneNode(1) | |||||
child.setAttributeNode(attr) | |||||
finally: | |||||
importdoc.unlink() | |||||
class Element: | |||||
"""A class that provides common functions for WSDL element classes.""" | |||||
def __init__(self, name=None, documentation=''): | |||||
self.name = name | |||||
self.documentation = documentation | |||||
self.extensions = [] | |||||
def addExtension(self, item): | |||||
self.extensions.append(item) | |||||
class ImportElement(Element): | |||||
def __init__(self, namespace, location): | |||||
self.namespace = namespace | |||||
self.location = location | |||||
_loaded = None | |||||
class Types(Collection): | |||||
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 keys(self): | |||||
return map(lambda i: i.targetNamespace, self.list) | |||||
def items(self): | |||||
return map(lambda i: i.targetNamespace, self.list) | |||||
def addExtension(self, item): | |||||
self.extensions.append(item) | |||||
class Message(Element): | |||||
def __init__(self, name, documentation=''): | |||||
Element.__init__(self, name, documentation) | |||||
self.parts = Collection(self) | |||||
def addPart(self, name, type=None, element=None): | |||||
if self.parts.has_key(name): | |||||
raise WSDLError( | |||||
'Duplicate message part element: %s' % name | |||||
) | |||||
if type is None and element is None: | |||||
raise WSDLError( | |||||
'Missing type or element attribute for part: %s' % name | |||||
) | |||||
item = MessagePart(name) | |||||
item.element = element | |||||
item.type = type | |||||
self.parts[name] = item | |||||
return item | |||||
def load(self, elements): | |||||
for element in elements: | |||||
name = DOM.getAttr(element, 'name') | |||||
part = MessagePart(name) | |||||
self.parts[name] = part | |||||
elemref = DOM.getAttr(element, 'element', default=None) | |||||
typeref = DOM.getAttr(element, 'type', default=None) | |||||
if typeref is None and elemref is None: | |||||
raise WSDLError( | |||||
'No type or element attribute for part: %s' % name | |||||
) | |||||
if typeref is not None: | |||||
part.type = ParseTypeRef(typeref, element) | |||||
if elemref is not None: | |||||
part.element = ParseTypeRef(elemref, element) | |||||
class MessagePart(Element): | |||||
def __init__(self, name): | |||||
Element.__init__(self, name, '') | |||||
self.element = None | |||||
self.type = None | |||||
class PortType(Element): | |||||
def __init__(self, name, documentation=''): | |||||
Element.__init__(self, name, documentation) | |||||
self.operations = Collection(self) | |||||
def getWSDL(self): | |||||
return self.parent().parent() | |||||
def addOperation(self, name, documentation='', parameterOrder=None): | |||||
item = Operation(name, documentation, parameterOrder) | |||||
self.operations[name] = item | |||||
return item | |||||
def load(self, elements): | |||||
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 = msgref.split(':', 1)[-1] | |||||
operation.setInput(message, name, docs) | |||||
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 = msgref.split(':', 1)[-1] | |||||
operation.setOutput(message, name, docs) | |||||
for item in DOM.getElements(element, 'fault', None): | |||||
name = DOM.getAttr(item, 'name') | |||||
docs = GetDocumentation(item) | |||||
msgref = DOM.getAttr(item, 'message') | |||||
message = msgref.split(':', 1)[-1] | |||||
operation.addFault(message, name, docs) | |||||
class Operation(Element): | |||||
def __init__(self, name, documentation='', parameterOrder=None): | |||||
Element.__init__(self, name, documentation) | |||||
self.parameterOrder = parameterOrder | |||||
self.faults = Collection(self) | |||||
self.input = None | |||||
self.output = None | |||||
def getPortType(self): | |||||
return self.parent().parent() | |||||
def getInputMessage(self): | |||||
if self.input is None: | |||||
return None | |||||
wsdl = self.getPortType().getWSDL() | |||||
return wsdl.messages[self.input.message] | |||||
def getOutputMessage(self): | |||||
if self.output is None: | |||||
return None | |||||
wsdl = self.getPortType().getWSDL() | |||||
return wsdl.messages[self.output.message] | |||||
def getFaultMessage(self, name): | |||||
wsdl = self.getPortType().getWSDL() | |||||
return wsdl.messages[self.faults[name].message] | |||||
def addFault(self, name, message, documentation=''): | |||||
if self.faults.has_key(name): | |||||
raise WSDLError( | |||||
'Duplicate fault element: %s' % name | |||||
) | |||||
item = MessageRole('fault', message, name, documentation) | |||||
self.faults[name] = item | |||||
return item | |||||
def setInput(self, message, name='', documentation=''): | |||||
self.input = MessageRole('input', message, name, documentation) | |||||
return self.input | |||||
def setOutput(self, message, name='', documentation=''): | |||||
self.output = MessageRole('output', message, name, documentation) | |||||
return self.output | |||||
class MessageRole(Element): | |||||
def __init__(self, type, message, name='', documentation=''): | |||||
Element.__init__(self, name, documentation) | |||||
self.message = message | |||||
self.type = type | |||||
class Binding(Element): | |||||
def __init__(self, name, type, documentation=''): | |||||
Element.__init__(self, name, documentation) | |||||
self.operations = Collection(self) | |||||
self.type = type | |||||
def getWSDL(self): | |||||
"""Return the WSDL object that contains this binding.""" | |||||
return self.parent().parent() | |||||
def getPortType(self): | |||||
"""Return the PortType object associated with this binding.""" | |||||
return self.getWSDL().portTypes[self.type] | |||||
def findBinding(self, kind): | |||||
for item in self.extensions: | |||||
if isinstance(item, kind): | |||||
return item | |||||
return None | |||||
def findBindings(self, kind): | |||||
return [ item for item in self.extensions if isinstance(item, kind) ] | |||||
def addOperationBinding(self, name, documentation=''): | |||||
item = OperationBinding(name, documentation) | |||||
self.operations[name] = item | |||||
return item | |||||
def load(self, elements): | |||||
for element in elements: | |||||
name = DOM.getAttr(element, 'name') | |||||
docs = GetDocumentation(element) | |||||
opbinding = self.addOperationBinding(name, docs) | |||||
opbinding.load_ex(GetExtensions(element)) | |||||
item = DOM.getElement(element, 'input', None, None) | |||||
if item is not None: | |||||
mbinding = MessageRoleBinding('input') | |||||
mbinding.documentation = GetDocumentation(item) | |||||
opbinding.input = mbinding | |||||
mbinding.load_ex(GetExtensions(item)) | |||||
item = DOM.getElement(element, 'output', None, None) | |||||
if item is not None: | |||||
mbinding = MessageRoleBinding('output') | |||||
mbinding.documentation = GetDocumentation(item) | |||||
opbinding.output = mbinding | |||||
mbinding.load_ex(GetExtensions(item)) | |||||
for item in DOM.getElements(element, 'fault', None): | |||||
name = DOM.getAttr(item, 'name') | |||||
mbinding = MessageRoleBinding('fault', name) | |||||
mbinding.documentation = GetDocumentation(item) | |||||
opbinding.faults[name] = mbinding | |||||
mbinding.load_ex(GetExtensions(item)) | |||||
def load_ex(self, elements): | |||||
for e in elements: | |||||
ns, name = e.namespaceURI, e.localName | |||||
if ns in DOM.NS_SOAP_BINDING_ALL and name == 'binding': | |||||
transport = DOM.getAttr(e, 'transport', default=None) | |||||
style = DOM.getAttr(e, 'style', default='document') | |||||
ob = SoapBinding(transport, style) | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'binding': | |||||
verb = DOM.getAttr(e, 'verb') | |||||
ob = HttpBinding(verb) | |||||
self.addExtension(ob) | |||||
continue | |||||
else: | |||||
self.addExtension(e) | |||||
class OperationBinding(Element): | |||||
def __init__(self, name, documentation=''): | |||||
Element.__init__(self, name, documentation) | |||||
self.input = None | |||||
self.output = None | |||||
self.faults = Collection(self) | |||||
def getBinding(self): | |||||
"""Return the parent Binding object of the operation binding.""" | |||||
return self.parent().parent() | |||||
def getOperation(self): | |||||
"""Return the abstract Operation associated with this binding.""" | |||||
return self.getBinding().getPortType().operations[self.name] | |||||
def findBinding(self, kind): | |||||
for item in self.extensions: | |||||
if isinstance(item, kind): | |||||
return item | |||||
return None | |||||
def findBindings(self, kind): | |||||
return [ item for item in self.extensions if isinstance(item, kind) ] | |||||
def addInputBinding(self, binding): | |||||
if self.input is None: | |||||
self.input = MessageRoleBinding('input') | |||||
self.input.addExtension(binding) | |||||
return binding | |||||
def addOutputBinding(self, binding): | |||||
if self.output is None: | |||||
self.output = MessageRoleBinding('output') | |||||
self.output.addExtension(binding) | |||||
return binding | |||||
def addFaultBinding(self, name, binding): | |||||
fault = self.get(name, None) | |||||
if fault is None: | |||||
fault = MessageRoleBinding('fault', name) | |||||
fault.addExtension(binding) | |||||
return binding | |||||
def load_ex(self, elements): | |||||
for e in elements: | |||||
ns, name = e.namespaceURI, e.localName | |||||
if ns in DOM.NS_SOAP_BINDING_ALL and name == 'operation': | |||||
soapaction = DOM.getAttr(e, 'soapAction', default=None) | |||||
style = DOM.getAttr(e, 'style', default=None) | |||||
ob = SoapOperationBinding(soapaction, style) | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'operation': | |||||
location = DOM.getAttr(e, 'location') | |||||
ob = HttpOperationBinding(location) | |||||
self.addExtension(ob) | |||||
continue | |||||
else: | |||||
self.addExtension(e) | |||||
class MessageRoleBinding(Element): | |||||
def __init__(self, type, name='', documentation=''): | |||||
Element.__init__(self, name, documentation) | |||||
self.type = type | |||||
def findBinding(self, kind): | |||||
for item in self.extensions: | |||||
if isinstance(item, kind): | |||||
return item | |||||
return None | |||||
def findBindings(self, kind): | |||||
return [ item for item in self.extensions if isinstance(item, kind) ] | |||||
def load_ex(self, elements): | |||||
for e in elements: | |||||
ns, name = e.namespaceURI, e.localName | |||||
if ns in DOM.NS_SOAP_BINDING_ALL and name == 'body': | |||||
encstyle = DOM.getAttr(e, 'encodingStyle', default=None) | |||||
namespace = DOM.getAttr(e, 'namespace', default=None) | |||||
parts = DOM.getAttr(e, 'parts', default=None) | |||||
use = DOM.getAttr(e, 'use', default=None) | |||||
if use is None: | |||||
raise WSDLError( | |||||
'Invalid soap:body binding element.' | |||||
) | |||||
ob = SoapBodyBinding(use, namespace, encstyle, parts) | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_SOAP_BINDING_ALL and name == 'fault': | |||||
encstyle = DOM.getAttr(e, 'encodingStyle', default=None) | |||||
namespace = DOM.getAttr(e, 'namespace', default=None) | |||||
name = DOM.getAttr(e, 'name', default=None) | |||||
use = DOM.getAttr(e, 'use', default=None) | |||||
if use is None or name is None: | |||||
raise WSDLError( | |||||
'Invalid soap:fault binding element.' | |||||
) | |||||
ob = SoapFaultBinding(name, use, namespace, encstyle) | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_SOAP_BINDING_ALL and name in ( | |||||
'header', 'headerfault' | |||||
): | |||||
encstyle = DOM.getAttr(e, 'encodingStyle', default=None) | |||||
namespace = DOM.getAttr(e, 'namespace', default=None) | |||||
message = DOM.getAttr(e, 'message') | |||||
part = DOM.getAttr(e, 'part') | |||||
use = DOM.getAttr(e, 'use') | |||||
if name == 'header': | |||||
_class = SoapHeaderBinding | |||||
else: | |||||
_class = SoapHeaderFaultBinding | |||||
ob = _class(message, part, use, namespace, encstyle) | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'urlReplacement': | |||||
ob = HttpUrlReplacementBinding() | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'urlEncoded': | |||||
ob = HttpUrlEncodedBinding() | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_MIME_BINDING_ALL and name == 'multipartRelated': | |||||
ob = MimeMultipartRelatedBinding() | |||||
self.addExtension(ob) | |||||
ob.load_ex(GetExtensions(e)) | |||||
continue | |||||
elif ns in DOM.NS_MIME_BINDING_ALL and name == 'content': | |||||
part = DOM.getAttr(e, 'part', default=None) | |||||
type = DOM.getAttr(e, 'type', default=None) | |||||
ob = MimeContentBinding(part, type) | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_MIME_BINDING_ALL and name == 'mimeXml': | |||||
part = DOM.getAttr(e, 'part', default=None) | |||||
ob = MimeXmlBinding(part) | |||||
self.addExtension(ob) | |||||
continue | |||||
else: | |||||
self.addExtension(e) | |||||
class Service(Element): | |||||
def __init__(self, name, documentation=''): | |||||
Element.__init__(self, name, documentation) | |||||
self.ports = Collection(self) | |||||
def getWSDL(self): | |||||
return self.parent().parent() | |||||
def addPort(self, name, binding, documentation=''): | |||||
item = Port(name, binding, documentation) | |||||
self.ports[name] = item | |||||
return item | |||||
def load(self, elements): | |||||
for element in elements: | |||||
name = DOM.getAttr(element, 'name', default=None) | |||||
docs = GetDocumentation(element) | |||||
binding = DOM.getAttr(element, 'binding', default=None) | |||||
if name is None or binding is None: | |||||
raise WSDLError( | |||||
'Invalid port element.' | |||||
) | |||||
binding = binding.split(':', 1)[-1] | |||||
port = self.addPort(name, binding, docs) | |||||
port.load_ex(GetExtensions(element)) | |||||
def load_ex(self, elements): | |||||
for e in elements: | |||||
self.addExtension(e) | |||||
class Port(Element): | |||||
def __init__(self, name, binding, documentation=''): | |||||
Element.__init__(self, name, documentation) | |||||
self.binding = binding | |||||
def getService(self): | |||||
"""Return the Service object associated with this port.""" | |||||
return self.parent().parent() | |||||
def getBinding(self): | |||||
"""Return the Binding object that is referenced by this port.""" | |||||
wsdl = self.getService().getWSDL() | |||||
return wsdl.bindings[self.binding] | |||||
def getPortType(self): | |||||
"""Return the PortType object that is referenced by this port.""" | |||||
wsdl = self.getService().getWSDL() | |||||
binding = wsdl.bindings[self.binding] | |||||
return wsdl.portTypes[binding.type] | |||||
def getAddressBinding(self): | |||||
"""A convenience method to obtain the extension element used | |||||
as the address binding for the port, or None if undefined.""" | |||||
for item in self.extensions: | |||||
if isinstance(item, SoapAddressBinding) or \ | |||||
isinstance(item, HttpAddressBinding): | |||||
return item | |||||
raise WSDLError( | |||||
'No address binding found in port.' | |||||
) | |||||
def load_ex(self, elements): | |||||
for e in elements: | |||||
ns, name = e.namespaceURI, e.localName | |||||
if ns in DOM.NS_SOAP_BINDING_ALL and name == 'address': | |||||
location = DOM.getAttr(e, 'location', default=None) | |||||
ob = SoapAddressBinding(location) | |||||
self.addExtension(ob) | |||||
continue | |||||
elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'address': | |||||
location = DOM.getAttr(e, 'location', default=None) | |||||
ob = HttpAddressBinding(location) | |||||
self.addExtension(ob) | |||||
continue | |||||
else: | |||||
self.addExtension(e) | |||||
class SoapBinding: | |||||
def __init__(self, transport, style='rpc'): | |||||
self.transport = transport | |||||
self.style = style | |||||
class SoapAddressBinding: | |||||
def __init__(self, location): | |||||
self.location = location | |||||
class SoapOperationBinding: | |||||
def __init__(self, soapAction=None, style=None): | |||||
self.soapAction = soapAction | |||||
self.style = style | |||||
class SoapBodyBinding: | |||||
def __init__(self, use, namespace=None, encodingStyle=None, parts=None): | |||||
if not use in ('literal', 'encoded'): | |||||
raise WSDLError( | |||||
'Invalid use attribute value: %s' % use | |||||
) | |||||
self.encodingStyle = encodingStyle | |||||
self.namespace = namespace | |||||
if type(parts) in (type(''), type(u'')): | |||||
raise WSDLError( | |||||
'The parts argument must be a sequence.' | |||||
) | |||||
self.parts = parts | |||||
self.use = use | |||||
class SoapFaultBinding: | |||||
def __init__(self, name, use, namespace=None, encodingStyle=None): | |||||
if not use in ('literal', 'encoded'): | |||||
raise WSDLError( | |||||
'Invalid use attribute value: %s' % use | |||||
) | |||||
self.encodingStyle = encodingStyle | |||||
self.namespace = namespace | |||||
self.name = name | |||||
self.use = use | |||||
class SoapHeaderBinding: | |||||
def __init__(self, message, part, use, namespace=None, encodingStyle=None): | |||||
if not use in ('literal', 'encoded'): | |||||
raise WSDLError( | |||||
'Invalid use attribute value: %s' % use | |||||
) | |||||
self.encodingStyle = encodingStyle | |||||
self.namespace = namespace | |||||
self.message = message | |||||
self.part = part | |||||
self.use = use | |||||
tagname = 'header' | |||||
class SoapHeaderFaultBinding(SoapHeaderBinding): | |||||
tagname = 'headerfault' | |||||
class HttpBinding: | |||||
def __init__(self, verb): | |||||
self.verb = verb | |||||
class HttpAddressBinding: | |||||
def __init__(self, location): | |||||
self.location = location | |||||
class HttpOperationBinding: | |||||
def __init__(self, location): | |||||
self.location = location | |||||
class HttpUrlReplacementBinding: | |||||
pass | |||||
class HttpUrlEncodedBinding: | |||||
pass | |||||
class MimeContentBinding: | |||||
def __init__(self, part=None, type=None): | |||||
self.part = part | |||||
self.type = type | |||||
class MimeXmlBinding: | |||||
def __init__(self, part=None): | |||||
self.part = part | |||||
class MimeMultipartRelatedBinding: | |||||
def __init__(self): | |||||
self.parts = [] | |||||
def load_ex(self, elements): | |||||
for e in elements: | |||||
ns, name = e.namespaceURI, e.localName | |||||
if ns in DOM.NS_MIME_BINDING_ALL and name == 'part': | |||||
self.parts.append(MimePartBinding()) | |||||
continue | |||||
class MimePartBinding: | |||||
def __init__(self): | |||||
self.items = [] | |||||
def load_ex(self, elements): | |||||
for e in elements: | |||||
ns, name = e.namespaceURI, e.localName | |||||
if ns in DOM.NS_MIME_BINDING_ALL and name == 'content': | |||||
part = DOM.getAttr(e, 'part', default=None) | |||||
type = DOM.getAttr(e, 'type', default=None) | |||||
ob = MimeContentBinding(part, type) | |||||
self.items.append(ob) | |||||
continue | |||||
elif ns in DOM.NS_MIME_BINDING_ALL and name == 'mimeXml': | |||||
part = DOM.getAttr(e, 'part', default=None) | |||||
ob = MimeXmlBinding(part) | |||||
self.items.append(ob) | |||||
continue | |||||
elif ns in DOM.NS_SOAP_BINDING_ALL and name == 'body': | |||||
encstyle = DOM.getAttr(e, 'encodingStyle', default=None) | |||||
namespace = DOM.getAttr(e, 'namespace', default=None) | |||||
parts = DOM.getAttr(e, 'parts', default=None) | |||||
use = DOM.getAttr(e, 'use', default=None) | |||||
if use is None: | |||||
raise WSDLError( | |||||
'Invalid soap:body binding element.' | |||||
) | |||||
ob = SoapBodyBinding(use, namespace, encstyle, parts) | |||||
self.items.append(ob) | |||||
continue | |||||
class WSDLError(Exception): | |||||
pass | |||||
def DeclareNSPrefix(writer, prefix, nsuri): | |||||
if writer.hasNSPrefix(nsuri): | |||||
return | |||||
writer.declareNSPrefix(prefix, nsuri) | |||||
def ParseTypeRef(value, element): | |||||
parts = value.split(':', 1) | |||||
if len(parts) == 1: | |||||
return (DOM.findTargetNS(element), value) | |||||
nsuri = DOM.findNamespaceURI(parts[0], element) | |||||
return (nsuri, parts[1]) | |||||
def ParseQName(value, element): | |||||
nameref = value.split(':', 1) | |||||
if len(nameref) == 2: | |||||
nsuri = DOM.findNamespaceURI(nameref[0], element) | |||||
name = nameref[-1] | |||||
else: | |||||
nsuri = DOM.findTargetNS(element) | |||||
name = nameref[-1] | |||||
return nsuri, name | |||||
def GetDocumentation(element): | |||||
docnode = DOM.getElement(element, 'documentation', None, None) | |||||
if docnode is not None: | |||||
return DOM.getElementText(docnode) | |||||
return '' | |||||
def GetExtensions(element): | |||||
return [ item for item in DOM.getElements(element, None, None) | |||||
if item.namespaceURI != DOM.NS_WSDL ] | |||||
def 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 |
@@ -0,0 +1,331 @@ | |||||
# 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. | |||||
import string, types, base64, re | |||||
from Utility import DOM, Collection | |||||
from StringIO import StringIO | |||||
class SchemaReader: | |||||
"""A SchemaReader creates XMLSchema objects from urls and xml data.""" | |||||
def loadFromStream(self, file): | |||||
"""Return an XMLSchema instance loaded from a file object.""" | |||||
document = DOM.loadDocument(file) | |||||
schema = XMLSchema() | |||||
schema.load(document) | |||||
return schema | |||||
def loadFromString(self, data): | |||||
"""Return an XMLSchema instance loaded from an xml string.""" | |||||
return self.loadFromStream(StringIO(data)) | |||||
def loadFromURL(self, url): | |||||
"""Return an XMLSchema instance loaded from the given url.""" | |||||
document = DOM.loadFromURL(url) | |||||
schema = XMLSchema() | |||||
schema.location = url | |||||
schema.load(document) | |||||
return schema | |||||
def loadFromFile(self, filename): | |||||
"""Return an XMLSchema instance loaded from the given file.""" | |||||
file = open(filename, 'rb') | |||||
try: schema = self.loadFromStream(file) | |||||
finally: file.close() | |||||
return schema | |||||
class XMLSchema: | |||||
# This is temporary, for the benefit of WSDL until the real thing works. | |||||
def __init__(self, element): | |||||
self.targetNamespace = DOM.getAttr(element, 'targetNamespace') | |||||
self.element = element | |||||
class realXMLSchema: | |||||
"""A schema is a collection of schema components derived from one | |||||
or more schema documents, that is, one or more <schema> element | |||||
information items. It represents the abstract notion of a schema | |||||
rather than a single schema document (or other representation).""" | |||||
def __init__(self): | |||||
self.simpleTypes = Collection(self) | |||||
self.complexTypes = Collection(self) | |||||
self.attributes = Collection(self) | |||||
self.elements = Collection(self) | |||||
self.attrGroups = Collection(self) | |||||
self.idConstraints=None | |||||
self.modelGroups = None | |||||
self.notations = None | |||||
self.extensions = [] | |||||
targetNamespace = None | |||||
attributeFormDefault = 'unqualified' | |||||
elementFormDefault = 'unqualified' | |||||
blockDefault = None | |||||
finalDefault = None | |||||
location = None | |||||
version = None | |||||
id = None | |||||
def load(self, document): | |||||
if document.nodeType == document.DOCUMENT_NODE: | |||||
schema = DOM.getElement(document, 'schema', None, None) | |||||
else: | |||||
schema = document | |||||
if schema is None: | |||||
raise SchemaError('Missing <schema> element.') | |||||
self.namespace = namespace = schema.namespaceURI | |||||
if not namespace in DOM.NS_XSD_ALL: | |||||
raise SchemaError( | |||||
'Unknown XML schema namespace: %s.' % self.namespace | |||||
) | |||||
for attrname in ( | |||||
'targetNamespace', 'attributeFormDefault', 'elementFormDefault', | |||||
'blockDefault', 'finalDefault', 'version', 'id' | |||||
): | |||||
value = DOM.getAttr(schema, attrname, None, None) | |||||
if attr is not None: | |||||
setattr(self, attrname, value) | |||||
# Resolve imports and includes here? | |||||
## imported = {} | |||||
## while 1: | |||||
## imports = [] | |||||
## for element in DOM.getElements(definitions, 'import', NS_WSDL): | |||||
## location = DOM.getAttr(element, 'location') | |||||
## if not imported.has_key(location): | |||||
## imports.append(element) | |||||
## if not imports: | |||||
## break | |||||
## for element in imports: | |||||
## self._import(document, element) | |||||
## imported[location] = 1 | |||||
for element in DOM.getElements(schema, None, None): | |||||
localName = element.localName | |||||
if not DOM.nsUriMatch(element.namespaceURI, namespace): | |||||
self.extensions.append(element) | |||||
continue | |||||
elif localName == 'message': | |||||
name = DOM.getAttr(element, 'name') | |||||
docs = GetDocumentation(element) | |||||
message = self.addMessage(name, docs) | |||||
parts = DOM.getElements(element, 'part', NS_WSDL) | |||||
message.load(parts) | |||||
continue | |||||
def _import(self, document, element): | |||||
namespace = DOM.getAttr(element, 'namespace', default=None) | |||||
location = DOM.getAttr(element, 'location', default=None) | |||||
if namespace is None or location is None: | |||||
raise WSDLError( | |||||
'Invalid import element (missing namespace or location).' | |||||
) | |||||
# Sort-of support relative locations to simplify unit testing. The | |||||
# WSDL specification actually doesn't allow relative URLs, so its | |||||
# ok that this only works with urls relative to the initial document. | |||||
location = urllib.basejoin(self.location, location) | |||||
obimport = self.addImport(namespace, location) | |||||
obimport._loaded = 1 | |||||
importdoc = DOM.loadFromURL(location) | |||||
try: | |||||
if location.find('#') > -1: | |||||
idref = location.split('#')[-1] | |||||
imported = DOM.getElementById(importdoc, idref) | |||||
else: | |||||
imported = importdoc.documentElement | |||||
if imported is None: | |||||
raise WSDLError( | |||||
'Import target element not found for: %s' % location | |||||
) | |||||
imported_tns = DOM.getAttr(imported, 'targetNamespace') | |||||
importer_tns = namespace | |||||
if imported_tns != importer_tns: | |||||
return | |||||
if imported.localName == 'definitions': | |||||
imported_nodes = imported.childNodes | |||||
else: | |||||
imported_nodes = [imported] | |||||
parent = element.parentNode | |||||
for node in imported_nodes: | |||||
if node.nodeType != node.ELEMENT_NODE: | |||||
continue | |||||
child = DOM.importNode(document, node, 1) | |||||
parent.appendChild(child) | |||||
child.setAttribute('targetNamespace', importer_tns) | |||||
attrsNS = imported._attrsNS | |||||
for attrkey in attrsNS.keys(): | |||||
if attrkey[0] == DOM.NS_XMLNS: | |||||
attr = attrsNS[attrkey].cloneNode(1) | |||||
child.setAttributeNode(attr) | |||||
finally: | |||||
importdoc.unlink() | |||||
class Element: | |||||
"""Common base class for element representation classes.""" | |||||
def __init__(self, name=None, documentation=''): | |||||
self.name = name | |||||
self.documentation = documentation | |||||
self.extensions = [] | |||||
def addExtension(self, item): | |||||
self.extensions.append(item) | |||||
class SimpleTypeDefinition: | |||||
"""Represents an xml schema simple type definition.""" | |||||
class ComplexTypeDefinition: | |||||
"""Represents an xml schema complex type definition.""" | |||||
class AttributeDeclaration: | |||||
"""Represents an xml schema attribute declaration.""" | |||||
class ElementDeclaration: | |||||
"""Represents an xml schema element declaration.""" | |||||
def __init__(self, name, type=None, targetNamespace=None): | |||||
self.name = name | |||||
targetNamespace = None | |||||
annotation = None | |||||
nillable = 0 | |||||
abstract = 0 | |||||
default = None | |||||
fixed = None | |||||
scope = 'global' | |||||
type = None | |||||
form = 0 | |||||
# Things we will not worry about for now. | |||||
id_constraint_defs = None | |||||
sub_group_exclude = None | |||||
sub_group_affils = None | |||||
disallowed_subs = None | |||||
class AttributeGroupDefinition: | |||||
"""Represents an xml schema attribute group definition.""" | |||||
class IdentityConstraintDefinition: | |||||
"""Represents an xml schema identity constraint definition.""" | |||||
class ModelGroupDefinition: | |||||
"""Represents an xml schema model group definition.""" | |||||
class NotationDeclaration: | |||||
"""Represents an xml schema notation declaration.""" | |||||
class Annotation: | |||||
"""Represents an xml schema annotation.""" | |||||
class ModelGroup: | |||||
"""Represents an xml schema model group.""" | |||||
class Particle: | |||||
"""Represents an xml schema particle.""" | |||||
class WildCard: | |||||
"""Represents an xml schema wildcard.""" | |||||
class AttributeUse: | |||||
"""Represents an xml schema attribute use.""" | |||||
class ElementComponent: | |||||
namespace = '' | |||||
name = '' | |||||
type = None | |||||
form = 'qualified | unqualified' | |||||
scope = 'global or complex def' | |||||
constraint = ('value', 'default | fixed') | |||||
nillable = 0 | |||||
id_constraint_defs = None | |||||
sub_group_affil = None | |||||
sub_group_exclusions = None | |||||
disallowed_subs = 'substitution, extension, restriction' | |||||
abstract = 0 | |||||
minOccurs = 1 | |||||
maxOccurs = 1 | |||||
ref = '' | |||||
class AttributeThing: | |||||
name = '' | |||||
namespace = '' | |||||
typeName = '' | |||||
typeUri = '' | |||||
scope = 'global | local to complex def' | |||||
constraint = ('value:default', 'value:fixed') | |||||
use = 'optional | prohibited | required' | |||||
class ElementDataType: | |||||
namespace = '' | |||||
name = '' | |||||
element_form = 'qualified | unqualified' | |||||
attr_form = None | |||||
type_name = '' | |||||
type_uri = '' | |||||
def __init__(self, name, namespace, type_name, type_uri): | |||||
self.namespace = namespace | |||||
self.name = name | |||||
# type may be anonymous... | |||||
self.type_name = type_name | |||||
self.type_uri = type_uri | |||||
def checkValue(self, value, context): | |||||
# Delegate value checking to the type of the element. | |||||
typeref = (self.type_uri, self.type_name) | |||||
handler = context.serializer.getType(typeref) | |||||
return handler.checkValue(value, context) | |||||
def serialize(self, name, namespace, value, context, **kwargs): | |||||
if context.check_values: | |||||
self.checkValue(value, context) | |||||
# Delegate serialization to the type of the element. | |||||
typeref = (self.type_uri, self.type_name) | |||||
handler = context.serializer.getType(typeref) | |||||
return handler.serialize(self.name, self.namespace, value, context) | |||||
def deserialize(self, element, context): | |||||
if element_is_null(element, context): | |||||
return None | |||||
# Delegate deserialization to the type of the element. | |||||
typeref = (self.type_uri, self.type_name) | |||||
handler = context.serializer.getType(typeref) | |||||
return handler.deserialize(element, context) | |||||
def parse_schema(data): | |||||
targetNS = '' | |||||
attributeFormDefault = 0 | |||||
elementFormDefault = 0 | |||||
blockDefault = '' | |||||
finalDefault = '' | |||||
language = None | |||||
version = None | |||||
id = '' |
@@ -0,0 +1,61 @@ | |||||
Zope Public License (ZPL) Version 2.0 | |||||
----------------------------------------------- | |||||
This software is Copyright (c) Zope Corporation (tm) and | |||||
Contributors. All rights reserved. | |||||
This license has been certified as open source. It has also | |||||
been designated as GPL compatible by the Free Software | |||||
Foundation (FSF). | |||||
Redistribution and use in source and binary forms, with or | |||||
without modification, are permitted provided that the | |||||
following conditions are met: | |||||
1. Redistributions in source code must retain the above | |||||
copyright notice, this list of conditions, and the following | |||||
disclaimer. | |||||
2. 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. | |||||
3. The name Zope Corporation (tm) must not be used to | |||||
endorse or promote products derived from this software | |||||
without prior written permission from Zope Corporation. | |||||
4. The right to distribute this software or to use it for | |||||
any purpose does not give you the right to use Servicemarks | |||||
(sm) or Trademarks (tm) of Zope Corporation. Use of them is | |||||
covered in a separate agreement (see | |||||
http://www.zope.com/Marks). | |||||
5. If any files are modified, you must cause the modified | |||||
files to carry prominent notices stating that you changed | |||||
the files and the date of any change. | |||||
Disclaimer | |||||
THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS'' | |||||
AND ANY EXPRESSED 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 ZOPE CORPORATION OR ITS 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. | |||||
This software consists of contributions made by Zope | |||||
Corporation and many individuals on behalf of Zope | |||||
Corporation. Specific attributions are listed in the | |||||
accompanying credits file. | |||||
@@ -0,0 +1,12 @@ | |||||
# 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. | |||||
"""WSDL parsing services package for Web Services for Python.""" | |||||
from ServiceProxy import ServiceProxy |