From da8ebdb64ab79f669fa226b5433d97f5f72a3066 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Thu, 30 Sep 2004 08:09:08 +0000 Subject: [PATCH] Upgraded google, which required upgrading SOAP. --- others/GoogleSOAPFacade.py | 85 + others/SOAP.py | 3977 ------------------------ others/SOAPpy/Client.py | 478 +++ others/SOAPpy/Config.py | 202 ++ others/SOAPpy/Errors.py | 79 + others/SOAPpy/GSIServer.py | 143 + others/SOAPpy/NS.py | 104 + others/SOAPpy/Parser.py | 1024 ++++++ others/SOAPpy/SOAP.py | 40 + others/SOAPpy/SOAPBuilder.py | 620 ++++ others/SOAPpy/Server.py | 706 +++++ others/SOAPpy/Types.py | 1734 +++++++++++ others/SOAPpy/URLopener.py | 23 + others/SOAPpy/Utilities.py | 178 ++ others/SOAPpy/WSDL.py | 102 + others/SOAPpy/__init__.py | 15 + others/SOAPpy/version.py | 2 + others/SOAPpy/wstools/Namespaces.py | 92 + others/SOAPpy/wstools/TimeoutSocket.py | 179 ++ others/SOAPpy/wstools/UserTuple.py | 99 + others/SOAPpy/wstools/Utility.py | 839 +++++ others/SOAPpy/wstools/WSDLTools.py | 1336 ++++++++ others/SOAPpy/wstools/XMLSchema.py | 2976 ++++++++++++++++++ others/SOAPpy/wstools/XMLname.py | 88 + others/SOAPpy/wstools/__init__.py | 36 + others/google.py | 1071 ++++--- 26 files changed, 11818 insertions(+), 4410 deletions(-) create mode 100644 others/GoogleSOAPFacade.py delete mode 100644 others/SOAP.py create mode 100644 others/SOAPpy/Client.py create mode 100644 others/SOAPpy/Config.py create mode 100644 others/SOAPpy/Errors.py create mode 100644 others/SOAPpy/GSIServer.py create mode 100644 others/SOAPpy/NS.py create mode 100644 others/SOAPpy/Parser.py create mode 100644 others/SOAPpy/SOAP.py create mode 100644 others/SOAPpy/SOAPBuilder.py create mode 100644 others/SOAPpy/Server.py create mode 100644 others/SOAPpy/Types.py create mode 100644 others/SOAPpy/URLopener.py create mode 100644 others/SOAPpy/Utilities.py create mode 100644 others/SOAPpy/WSDL.py create mode 100644 others/SOAPpy/__init__.py create mode 100644 others/SOAPpy/version.py create mode 100755 others/SOAPpy/wstools/Namespaces.py create mode 100755 others/SOAPpy/wstools/TimeoutSocket.py create mode 100755 others/SOAPpy/wstools/UserTuple.py create mode 100755 others/SOAPpy/wstools/Utility.py create mode 100755 others/SOAPpy/wstools/WSDLTools.py create mode 100755 others/SOAPpy/wstools/XMLSchema.py create mode 100755 others/SOAPpy/wstools/XMLname.py create mode 100644 others/SOAPpy/wstools/__init__.py diff --git a/others/GoogleSOAPFacade.py b/others/GoogleSOAPFacade.py new file mode 100644 index 000000000..0aab3cf40 --- /dev/null +++ b/others/GoogleSOAPFacade.py @@ -0,0 +1,85 @@ +""" +Facade that hides the differences between the SOAPpy and SOAP.py +libraries, so that google.py doesn't have to deal with them. + +@author: Brian Landers +@license: Python +@version: 0.5.4 +""" + +import warnings +from distutils.version import LooseVersion + +__author__ = "Brian Landers " +__version__ = "0.6" +__license__ = "Python" + +# +# Wrapper around the python 'warnings' facility +# +def warn( message, level=RuntimeWarning ): + warnings.warn( message, level, stacklevel=3 ) + +# We can't use older version of SOAPpy, due to bugs that break the Google API +minSOAPpyVersion = "0.11.3" + +# +# Try loading SOAPpy first. If that fails, fall back to the old SOAP.py +# +SOAPpy = None +try: + import SOAPpy + from SOAPpy import SOAPProxy, Types + + if LooseVersion( minSOAPpyVersion ) > \ + LooseVersion( SOAPpy.version.__version__ ): + + warn( "Versions of SOAPpy before %s have known bugs that prevent " + + "PyGoogle from functioning." % minSOAPpyVersion ) + raise ImportError + +except ImportError: + warn( "SOAPpy not imported. Trying legacy SOAP.py.", + DeprecationWarning ) + try: + import SOAP + except ImportError: + raise RuntimeError( "Unable to find SOAPpy or SOAP. Can't continue.\n" ) + +# +# Constants that differ between the modules +# +if SOAPpy: + false = Types.booleanType(0) + true = Types.booleanType(1) + structType = Types.structType + faultType = Types.faultType +else: + false = SOAP.booleanType(0) + true = SOAP.booleanType(1) + structType = SOAP.structType + faultType = SOAP.faultType + +# +# Get a SOAP Proxy object in the correct way for the module we're using +# +def getProxy( url, namespace, http_proxy ): + if SOAPpy: + return SOAPProxy( url, + namespace = namespace, + http_proxy = http_proxy ) + + else: + return SOAP.SOAPProxy( url, + namespace = namespace, + http_proxy = http_proxy ) + +# +# Convert an object to a dictionary in the proper way for the module +# we're using for SOAP +# +def toDict( obj ): + if SOAPpy: + return obj._asdict() + else: + return obj._asdict diff --git a/others/SOAP.py b/others/SOAP.py deleted file mode 100644 index b1e9c4ec1..000000000 --- a/others/SOAP.py +++ /dev/null @@ -1,3977 +0,0 @@ -################################################################################ -# -# SOAP.py 0.9.7 - Cayce Ullman (cayce@actzero.com) -# Brian Matthews (blm@actzero.com) -# -# INCLUDED: -# - General SOAP Parser based on sax.xml (requires Python 2.0) -# - General SOAP Builder -# - SOAP Proxy for RPC client code -# - SOAP Server framework for RPC server code -# -# FEATURES: -# - Handles all of the types in the BDG -# - Handles faults -# - Allows namespace specification -# - Allows SOAPAction specification -# - Homogeneous typed arrays -# - Supports multiple schemas -# - Header support (mustUnderstand and actor) -# - XML attribute support -# - Multi-referencing support (Parser/Builder) -# - Understands SOAP-ENC:root attribute -# - Good interop, passes all client tests for Frontier, SOAP::LITE, SOAPRMI -# - Encodings -# - SSL clients (with OpenSSL configured in to Python) -# - SSL servers (with OpenSSL configured in to Python and M2Crypto installed) -# -# TODO: -# - Timeout on method calls - MCU -# - Arrays (sparse, multidimensional and partial) - BLM -# - Clean up data types - BLM -# - Type coercion system (Builder) - MCU -# - Early WSDL Support - MCU -# - Attachments - BLM -# - setup.py - MCU -# - mod_python example - MCU -# - medusa example - MCU -# - Documentation - JAG -# - Look at performance -# -################################################################################ -# -# Copyright (c) 2001, Cayce Ullman. -# Copyright (c) 2001, Brian Matthews. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# Neither the name of actzero, inc. nor the names of its contributors may -# be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -################################################################################ -# -# Additional changes: -# 0.9.7.3 - 4/18/2002 - Mark Pilgrim (f8dy@diveintomark.org) -# added dump_dict as alias for dump_dictionary for Python 2.2 compatibility -# 0.9.7.2 - 4/12/2002 - Mark Pilgrim (f8dy@diveintomark.org) -# fixed logic to unmarshal the value of "null" attributes ("true" or "1" -# means true, others false) -# 0.9.7.1 - 4/11/2002 - Mark Pilgrim (f8dy@diveintomark.org) -# added "dump_str" as alias for "dump_string" for Python 2.2 compatibility -# Between 2.1 and 2.2, type("").__name__ changed from "string" to "str" -################################################################################ - -import xml.sax -import UserList -import base64 -import cgi -import urllib -import exceptions -import copy -import re -import socket -import string -import sys -import time -import SocketServer -from types import * - -try: from M2Crypto import SSL -except: pass - -ident = '$Id$' - -__version__ = "0.9.7.3" - -# Platform hackery - -# Check float support -try: - float("NaN") - float("INF") - float("-INF") - good_float = 1 -except: - good_float = 0 - -################################################################################ -# Exceptions -################################################################################ -class Error(exceptions.Exception): - def __init__(self, msg): - self.msg = msg - def __str__(self): - return "" % self.msg - __repr__ = __str__ - -class RecursionError(Error): - pass - -class UnknownTypeError(Error): - pass - -class HTTPError(Error): - # indicates an HTTP protocol error - def __init__(self, code, msg): - self.code = code - self.msg = msg - def __str__(self): - return "" % (self.code, self.msg) - __repr__ = __str__ - -############################################################################## -# Namespace Class -################################################################################ -def invertDict(dict): - d = {} - - for k, v in dict.items(): - d[v] = k - - return d - -class NS: - XML = "http://www.w3.org/XML/1998/namespace" - - ENV = "http://schemas.xmlsoap.org/soap/envelope/" - ENC = "http://schemas.xmlsoap.org/soap/encoding/" - - XSD = "http://www.w3.org/1999/XMLSchema" - XSD2 = "http://www.w3.org/2000/10/XMLSchema" - XSD3 = "http://www.w3.org/2001/XMLSchema" - - XSD_L = [XSD, XSD2, XSD3] - EXSD_L= [ENC, XSD, XSD2, XSD3] - - XSI = "http://www.w3.org/1999/XMLSchema-instance" - XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" - XSI3 = "http://www.w3.org/2001/XMLSchema-instance" - XSI_L = [XSI, XSI2, XSI3] - - URN = "http://soapinterop.org/xsd" - - # For generated messages - XML_T = "xml" - ENV_T = "SOAP-ENV" - ENC_T = "SOAP-ENC" - XSD_T = "xsd" - XSD2_T= "xsd2" - XSD3_T= "xsd3" - XSI_T = "xsi" - XSI2_T= "xsi2" - XSI3_T= "xsi3" - URN_T = "urn" - - NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2, - XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3, - URN_T: URN} - NSMAP_R = invertDict(NSMAP) - - STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T), - '2001': (XSD3_T, XSI3_T)} - STMAP_R = invertDict(STMAP) - - def __init__(self): - raise Error, "Don't instantiate this" - -################################################################################ -# Configuration class -################################################################################ - -class SOAPConfig: - __readonly = ('SSLserver', 'SSLclient') - - def __init__(self, config = None, **kw): - d = self.__dict__ - - if config: - if not isinstance(config, SOAPConfig): - raise AttributeError, \ - "initializer must be SOAPConfig instance" - - s = config.__dict__ - - for k, v in s.items(): - if k[0] != '_': - d[k] = v - else: - # Setting debug also sets returnFaultInfo, dumpFaultInfo, - # dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut - self.debug = 0 - # Setting namespaceStyle sets typesNamespace, typesNamespaceURI, - # schemaNamespace, and schemaNamespaceURI - self.namespaceStyle = '1999' - self.strictNamespaces = 0 - self.typed = 1 - self.buildWithNamespacePrefix = 1 - self.returnAllAttrs = 0 - - try: SSL; d['SSLserver'] = 1 - except: d['SSLserver'] = 0 - - try: socket.ssl; d['SSLclient'] = 1 - except: d['SSLclient'] = 0 - - for k, v in kw.items(): - if k[0] != '_': - setattr(self, k, v) - - def __setattr__(self, name, value): - if name in self.__readonly: - raise AttributeError, "readonly configuration setting" - - d = self.__dict__ - - if name in ('typesNamespace', 'typesNamespaceURI', - 'schemaNamespace', 'schemaNamespaceURI'): - - if name[-3:] == 'URI': - base, uri = name[:-3], 1 - else: - base, uri = name, 0 - - if type(value) == StringType: - if NS.NSMAP.has_key(value): - n = (value, NS.NSMAP[value]) - elif NS.NSMAP_R.has_key(value): - n = (NS.NSMAP_R[value], value) - else: - raise AttributeError, "unknown namespace" - elif type(value) in (ListType, TupleType): - if uri: - n = (value[1], value[0]) - else: - n = (value[0], value[1]) - else: - raise AttributeError, "unknown namespace type" - - d[base], d[base + 'URI'] = n - - try: - d['namespaceStyle'] = \ - NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])] - except: - d['namespaceStyle'] = '' - - elif name == 'namespaceStyle': - value = str(value) - - if not NS.STMAP.has_key(value): - raise AttributeError, "unknown namespace style" - - d[name] = value - n = d['typesNamespace'] = NS.STMAP[value][0] - d['typesNamespaceURI'] = NS.NSMAP[n] - n = d['schemaNamespace'] = NS.STMAP[value][1] - d['schemaNamespaceURI'] = NS.NSMAP[n] - - elif name == 'debug': - d[name] = \ - d['returnFaultInfo'] = \ - d['dumpFaultInfo'] = \ - d['dumpHeadersIn'] = \ - d['dumpHeadersOut'] = \ - d['dumpSOAPIn'] = \ - d['dumpSOAPOut'] = value - - else: - d[name] = value - -Config = SOAPConfig() - -################################################################################ -# Types and Wrappers -################################################################################ - -class anyType: - _validURIs = (NS.XSD, NS.XSD2, NS.XSD3, NS.ENC) - - def __init__(self, data = None, name = None, typed = 1, attrs = None): - if self.__class__ == anyType: - raise Error, "anyType can't be instantiated directly" - - if type(name) in (ListType, TupleType): - self._ns, self._name = name - else: - self._ns, self._name = self._validURIs[0], name - self._typed = typed - self._attrs = {} - - self._cache = None - self._type = self._typeName() - - self._data = self._checkValueSpace(data) - - if attrs != None: - self._setAttrs(attrs) - - def __str__(self): - if self._name: - return "<%s %s at %d>" % (self.__class__, self._name, id(self)) - return "<%s at %d>" % (self.__class__, id(self)) - - __repr__ = __str__ - - def _checkValueSpace(self, data): - return data - - def _marshalData(self): - return str(self._data) - - def _marshalAttrs(self, ns_map, builder): - a = '' - - for attr, value in self._attrs.items(): - ns, n = builder.genns(ns_map, attr[0]) - a += n + ' %s%s="%s"' % \ - (ns, attr[1], cgi.escape(str(value), 1)) - - return a - - def _fixAttr(self, attr): - if type(attr) in (StringType, UnicodeType): - attr = (None, attr) - elif type(attr) == ListType: - attr = tuple(attr) - elif type(attr) != TupleType: - raise AttributeError, "invalid attribute type" - - if len(attr) != 2: - raise AttributeError, "invalid attribute length" - - if type(attr[0]) not in (NoneType, StringType, UnicodeType): - raise AttributeError, "invalid attribute namespace URI type" - - return attr - - def _getAttr(self, attr): - attr = self._fixAttr(attr) - - try: - return self._attrs[attr] - except: - return None - - def _setAttr(self, attr, value): - attr = self._fixAttr(attr) - - self._attrs[attr] = str(value) - - def _setAttrs(self, attrs): - if type(attrs) in (ListType, TupleType): - for i in range(0, len(attrs), 2): - self._setAttr(attrs[i], attrs[i + 1]) - - return - - if type(attrs) == DictType: - d = attrs - elif isinstance(attrs, anyType): - d = attrs._attrs - else: - raise AttributeError, "invalid attribute type" - - for attr, value in d.items(): - self._setAttr(attr, value) - - def _setMustUnderstand(self, val): - self._setAttr((NS.ENV, "mustUnderstand"), val) - - def _getMustUnderstand(self): - return self._getAttr((NS.ENV, "mustUnderstand")) - - def _setActor(self, val): - self._setAttr((NS.ENV, "actor"), val) - - def _getActor(self): - return self._getAttr((NS.ENV, "actor")) - - def _typeName(self): - return self.__class__.__name__[:-4] - - def _validNamespaceURI(self, URI, strict): - if not self._typed: - return None - if URI in self._validURIs: - return URI - if not strict: - return self._ns - raise AttributeError, \ - "not a valid namespace for type %s" % self._type - -class voidType(anyType): - pass - -class stringType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - -class untypedType(stringType): - def __init__(self, data = None, name = None, attrs = None): - stringType.__init__(self, data, name, 0, attrs) - -class IDType(stringType): pass -class NCNameType(stringType): pass -class NameType(stringType): pass -class ENTITYType(stringType): pass -class IDREFType(stringType): pass -class languageType(stringType): pass -class NMTOKENType(stringType): pass -class QNameType(stringType): pass - -class tokenType(anyType): - _validURIs = (NS.XSD2, NS.XSD3) - __invalidre = '[\n\t]|^ | $| ' - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - if type(self.__invalidre) == StringType: - self.__invalidre = re.compile(self.__invalidre) - - if self.__invalidre.search(data): - raise ValueError, "invalid %s value" % self._type - - return data - -class normalizedStringType(anyType): - _validURIs = (NS.XSD3,) - __invalidre = '[\n\r\t]' - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - if type(self.__invalidre) == StringType: - self.__invalidre = re.compile(self.__invalidre) - - if self.__invalidre.search(data): - raise ValueError, "invalid %s value" % self._type - - return data - -class CDATAType(normalizedStringType): - _validURIs = (NS.XSD2,) - -class booleanType(anyType): - def __int__(self): - return self._data - - __nonzero__ = __int__ - - def _marshalData(self): - return ['false', 'true'][self._data] - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if data in (0, '0', 'false', ''): - return 0 - if data in (1, '1', 'true'): - return 1 - raise ValueError, "invalid %s value" % self._type - -class decimalType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType, FloatType): - raise Error, "invalid %s value" % self._type - - return data - -class floatType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType, FloatType) or \ - data < -3.4028234663852886E+38 or \ - data > 3.4028234663852886E+38: - raise ValueError, "invalid %s value" % self._type - - return data - - def _marshalData(self): - return "%.18g" % self._data # More precision - -class doubleType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType, FloatType) or \ - data < -1.7976931348623158E+308 or \ - data > 1.7976931348623157E+308: - raise ValueError, "invalid %s value" % self._type - - return data - - def _marshalData(self): - return "%.18g" % self._data # More precision - -class durationType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - try: - # A tuple or a scalar is OK, but make them into a list - - if type(data) == TupleType: - data = list(data) - elif type(data) != ListType: - data = [data] - - if len(data) > 6: - raise Exception, "too many values" - - # Now check the types of all the components, and find - # the first nonzero element along the way. - - f = -1 - - for i in range(len(data)): - if data[i] == None: - data[i] = 0 - continue - - if type(data[i]) not in \ - (IntType, LongType, FloatType): - raise Exception, "element %d a bad type" % i - - if data[i] and f == -1: - f = i - - # If they're all 0, just use zero seconds. - - if f == -1: - self._cache = 'PT0S' - - return (0,) * 6 - - # Make sure only the last nonzero element has a decimal fraction - # and only the first element is negative. - - d = -1 - - for i in range(f, len(data)): - if data[i]: - if d != -1: - raise Exception, \ - "all except the last nonzero element must be " \ - "integers" - if data[i] < 0 and i > f: - raise Exception, \ - "only the first nonzero element can be negative" - elif data[i] != long(data[i]): - d = i - - # Pad the list on the left if necessary. - - if len(data) < 6: - n = 6 - len(data) - f += n - d += n - data = [0] * n + data - - # Save index of the first nonzero element and the decimal - # element for _marshalData. - - self.__firstnonzero = f - self.__decimal = d - - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - t = 0 - - if d[self.__firstnonzero] < 0: - s = '-P' - else: - s = 'P' - - t = 0 - - for i in range(self.__firstnonzero, len(d)): - if d[i]: - if i > 2 and not t: - s += 'T' - t = 1 - if self.__decimal == i: - s += "%g" % abs(d[i]) - else: - s += "%d" % long(abs(d[i])) - s += ['Y', 'M', 'D', 'H', 'M', 'S'][i] - - self._cache = s - - return self._cache - -class timeDurationType(durationType): - _validURIs = (NS.XSD, NS.XSD2, NS.ENC) - -class dateTimeType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.time() - - if (type(data) in (IntType, LongType)): - data = list(time.gmtime(data)[:6]) - elif (type(data) == FloatType): - f = data - int(data) - data = list(time.gmtime(int(data))[:6]) - data[5] += f - elif type(data) in (ListType, TupleType): - if len(data) < 6: - raise Exception, "not enough values" - if len(data) > 9: - raise Exception, "too many values" - - data = list(data[:6]) - - cleanDate(data) - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%04d-%02d-%02dT%02d:%02d:%02d" % ((abs(d[0]),) + d[1:]) - if d[0] < 0: - s = '-' + s - f = d[5] - int(d[5]) - if f != 0: - s += ("%g" % f)[1:] - s += 'Z' - - self._cache = s - - return self._cache - -class recurringInstantType(anyType): - _validURIs = (NS.XSD,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = list(time.gmtime(time.time())[:6]) - if (type(data) in (IntType, LongType)): - data = list(time.gmtime(data)[:6]) - elif (type(data) == FloatType): - f = data - int(data) - data = list(time.gmtime(int(data))[:6]) - data[5] += f - elif type(data) in (ListType, TupleType): - if len(data) < 1: - raise Exception, "not enough values" - if len(data) > 9: - raise Exception, "too many values" - - data = list(data[:6]) - - if len(data) < 6: - data += [0] * (6 - len(data)) - - f = len(data) - - for i in range(f): - if data[i] == None: - if f < i: - raise Exception, \ - "only leftmost elements can be none" - else: - f = i - break - - cleanDate(data, f) - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - e = list(d) - neg = '' - - if e[0] < 0: - neg = '-' - e[0] = abs(e[0]) - - if not e[0]: - e[0] = '--' - elif e[0] < 100: - e[0] = '-' + "%02d" % e[0] - else: - e[0] = "%04d" % e[0] - - for i in range(1, len(e)): - if e[i] == None or (i < 3 and e[i] == 0): - e[i] = '-' - else: - if e[i] < 0: - neg = '-' - e[i] = abs(e[i]) - - e[i] = "%02d" % e[i] - - if d[5]: - f = abs(d[5] - int(d[5])) - - if f: - e[5] += ("%g" % f)[1:] - - s = "%s%s-%s-%sT%s:%s:%sZ" % ((neg,) + tuple(e)) - - self._cache = s - - return self._cache - -class timeInstantType(dateTimeType): - _validURIs = (NS.XSD, NS.XSD2, NS.ENC) - -class timePeriodType(dateTimeType): - _validURIs = (NS.XSD2, NS.ENC) - -class timeType(anyType): - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[3:6] - elif (type(data) == FloatType): - f = data - int(data) - data = list(time.gmtime(int(data))[3:6]) - data[2] += f - elif type(data) in (IntType, LongType): - data = time.gmtime(data)[3:6] - elif type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[3:6] - elif len(data) > 3: - raise Exception, "too many values" - - data = [None, None, None] + list(data) - - if len(data) < 6: - data += [0] * (6 - len(data)) - - cleanDate(data, 3) - - data = data[3:] - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - s = '' - - s = time.strftime("%H:%M:%S", (0, 0, 0) + d + (0, 0, -1)) - f = d[2] - int(d[2]) - if f != 0: - s += ("%g" % f)[1:] - s += 'Z' - - self._cache = s - - return self._cache - -class dateType(anyType): - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[0:3] - elif type(data) in (IntType, LongType, FloatType): - data = time.gmtime(data)[0:3] - elif type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:3] - elif len(data) > 3: - raise Exception, "too many values" - - data = list(data) - - if len(data) < 3: - data += [1, 1, 1][len(data):] - - data += [0, 0, 0] - - cleanDate(data) - - data = data[:3] - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%04d-%02d-%02dZ" % ((abs(d[0]),) + d[1:]) - if d[0] < 0: - s = '-' + s - - self._cache = s - - return self._cache - -class gYearMonthType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[0:2] - elif type(data) in (IntType, LongType, FloatType): - data = time.gmtime(data)[0:2] - elif type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:2] - elif len(data) > 2: - raise Exception, "too many values" - - data = list(data) - - if len(data) < 2: - data += [1, 1][len(data):] - - data += [1, 0, 0, 0] - - cleanDate(data) - - data = data[:2] - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%04d-%02dZ" % ((abs(d[0]),) + d[1:]) - if d[0] < 0: - s = '-' + s - - self._cache = s - - return self._cache - -class gYearType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[0:1] - elif type(data) in (IntType, LongType, FloatType): - data = [data] - - if type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:1] - elif len(data) < 1: - raise Exception, "too few values" - elif len(data) > 1: - raise Exception, "too many values" - - if type(data[0]) == FloatType: - try: s = int(data[0]) - except: s = long(data[0]) - - if s != data[0]: - raise Exception, "not integral" - - data = [s] - elif type(data[0]) not in (IntType, LongType): - raise Exception, "bad type" - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return data[0] - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%04dZ" % abs(d) - if d < 0: - s = '-' + s - - self._cache = s - - return self._cache - -class centuryType(anyType): - _validURIs = (NS.XSD2, NS.ENC) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[0:1] / 100 - elif type(data) in (IntType, LongType, FloatType): - data = [data] - - if type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:1] / 100 - elif len(data) < 1: - raise Exception, "too few values" - elif len(data) > 1: - raise Exception, "too many values" - - if type(data[0]) == FloatType: - try: s = int(data[0]) - except: s = long(data[0]) - - if s != data[0]: - raise Exception, "not integral" - - data = [s] - elif type(data[0]) not in (IntType, LongType): - raise Exception, "bad type" - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return data[0] - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%02dZ" % abs(d) - if d < 0: - s = '-' + s - - self._cache = s - - return self._cache - -class yearType(gYearType): - _validURIs = (NS.XSD2, NS.ENC) - -class gMonthDayType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[1:3] - elif type(data) in (IntType, LongType, FloatType): - data = time.gmtime(data)[1:3] - elif type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:2] - elif len(data) > 2: - raise Exception, "too many values" - - data = list(data) - - if len(data) < 2: - data += [1, 1][len(data):] - - data = [0] + data + [0, 0, 0] - - cleanDate(data, 1) - - data = data[1:3] - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - self._cache = "--%02d-%02dZ" % self._data - - return self._cache - -class recurringDateType(gMonthDayType): - _validURIs = (NS.XSD2, NS.ENC) - -class gMonthType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[1:2] - elif type(data) in (IntType, LongType, FloatType): - data = [data] - - if type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[1:2] - elif len(data) < 1: - raise Exception, "too few values" - elif len(data) > 1: - raise Exception, "too many values" - - if type(data[0]) == FloatType: - try: s = int(data[0]) - except: s = long(data[0]) - - if s != data[0]: - raise Exception, "not integral" - - data = [s] - elif type(data[0]) not in (IntType, LongType): - raise Exception, "bad type" - - if data[0] < 1 or data[0] > 12: - raise Exception, "bad value" - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return data[0] - - def _marshalData(self): - if self._cache == None: - self._cache = "--%02d--Z" % self._data - - return self._cache - -class monthType(gMonthType): - _validURIs = (NS.XSD2, NS.ENC) - -class gDayType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[2:3] - elif type(data) in (IntType, LongType, FloatType): - data = [data] - - if type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[2:3] - elif len(data) < 1: - raise Exception, "too few values" - elif len(data) > 1: - raise Exception, "too many values" - - if type(data[0]) == FloatType: - try: s = int(data[0]) - except: s = long(data[0]) - - if s != data[0]: - raise Exception, "not integral" - - data = [s] - elif type(data[0]) not in (IntType, LongType): - raise Exception, "bad type" - - if data[0] < 1 or data[0] > 31: - raise Exception, "bad value" - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return data[0] - - def _marshalData(self): - if self._cache == None: - self._cache = "---%02dZ" % self._data - - return self._cache - -class recurringDayType(gDayType): - _validURIs = (NS.XSD2, NS.ENC) - -class hexBinaryType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _marshalData(self): - if self._cache == None: - self._cache = encodeHexString(self._data) - - return self._cache - -class base64BinaryType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _marshalData(self): - if self._cache == None: - self._cache = base64.encodestring(self._data) - - return self._cache - -class base64Type(base64BinaryType): - _validURIs = (NS.ENC,) - -class binaryType(anyType): - _validURIs = (NS.XSD, NS.ENC) - - def __init__(self, data, name = None, typed = 1, encoding = 'base64', - attrs = None): - - anyType.__init__(self, data, name, typed, attrs) - - self._setAttr('encoding', encoding) - - def _marshalData(self): - if self._cache == None: - if self._getAttr((None, 'encoding')) == 'base64': - self._cache = base64.encodestring(self._data) - else: - self._cache = encodeHexString(self._data) - - return self._cache - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _setAttr(self, attr, value): - attr = self._fixAttr(attr) - - if attr[1] == 'encoding': - if attr[0] != None or value not in ('base64', 'hex'): - raise AttributeError, "invalid encoding" - - self._cache = None - - anyType._setAttr(self, attr, value) - - -class anyURIType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _marshalData(self): - if self._cache == None: - self._cache = urllib.quote(self._data) - - return self._cache - -class uriType(anyURIType): - _validURIs = (NS.XSD,) - -class uriReferenceType(anyURIType): - _validURIs = (NS.XSD2,) - -class NOTATIONType(anyType): - def __init__(self, data, name = None, typed = 1, attrs = None): - - if self.__class__ == NOTATIONType: - raise Error, "a NOTATION can't be instantiated directly" - - anyType.__init__(self, data, name, typed, attrs) - -class ENTITIESType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) in (StringType, UnicodeType): - return (data,) - - if type(data) not in (ListType, TupleType) or \ - filter (lambda x: type(x) not in (StringType, UnicodeType), data): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _marshalData(self): - return ' '.join(self._data) - -class IDREFSType(ENTITIESType): pass -class NMTOKENSType(ENTITIESType): pass - -class integerType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType): - raise ValueError, "invalid %s value" % self._type - - return data - -class nonPositiveIntegerType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or data > 0: - raise ValueError, "invalid %s value" % self._type - - return data - -class non_Positive_IntegerType(nonPositiveIntegerType): - _validURIs = (NS.XSD,) - - def _typeName(self): - return 'non-positive-integer' - -class negativeIntegerType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or data >= 0: - raise ValueError, "invalid %s value" % self._type - - return data - -class negative_IntegerType(negativeIntegerType): - _validURIs = (NS.XSD,) - - def _typeName(self): - return 'negative-integer' - -class longType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < -9223372036854775808L or \ - data > 9223372036854775807L: - raise ValueError, "invalid %s value" % self._type - - return data - -class intType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < -2147483648L or \ - data > 2147483647: - raise ValueError, "invalid %s value" % self._type - - return data - -class shortType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < -32768 or \ - data > 32767: - raise ValueError, "invalid %s value" % self._type - - return data - -class byteType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < -128 or \ - data > 127: - raise ValueError, "invalid %s value" % self._type - - return data - -class nonNegativeIntegerType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or data < 0: - raise ValueError, "invalid %s value" % self._type - - return data - -class non_Negative_IntegerType(nonNegativeIntegerType): - _validURIs = (NS.XSD,) - - def _typeName(self): - return 'non-negative-integer' - -class unsignedLongType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < 0 or \ - data > 18446744073709551615L: - raise ValueError, "invalid %s value" % self._type - - return data - -class unsignedIntType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < 0 or \ - data > 4294967295L: - raise ValueError, "invalid %s value" % self._type - - return data - -class unsignedShortType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < 0 or \ - data > 65535: - raise ValueError, "invalid %s value" % self._type - - return data - -class unsignedByteType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < 0 or \ - data > 255: - raise ValueError, "invalid %s value" % self._type - - return data - -class positiveIntegerType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or data <= 0: - raise ValueError, "invalid %s value" % self._type - - return data - -class positive_IntegerType(positiveIntegerType): - _validURIs = (NS.XSD,) - - def _typeName(self): - return 'positive-integer' - -# Now compound types - -class compoundType(anyType): - def __init__(self, data = None, name = None, typed = 1, attrs = None): - if self.__class__ == compoundType: - raise Error, "a compound can't be instantiated directly" - - anyType.__init__(self, data, name, typed, attrs) - self._aslist = [] - self._asdict = {} - self._keyord = [] - - if type(data) == DictType: - self.__dict__.update(data) - - def __getitem__(self, item): - if type(item) == IntType: - return self._aslist[item] - return getattr(self, item) - - def __len__(self): - return len(self._aslist) - - def __nonzero__(self): - return 1 - - def _keys(self): - return filter(lambda x: x[0] != '_', self.__dict__.keys()) - - def _addItem(self, name, value, attrs = None): - d = self._asdict - - if d.has_key(name): - if type(d[name]) != ListType: - d[name] = [d[name]] - d[name].append(value) - else: - d[name] = value - - self._keyord.append(name) - self._aslist.append(value) - self.__dict__[name] = d[name] - - def _placeItem(self, name, value, pos, subpos = 0, attrs = None): - d = self._asdict - - if subpos == 0 and type(d[name]) != ListType: - d[name] = value - else: - d[name][subpos] = value - - self._keyord[pos] = name - self._aslist[pos] = value - self.__dict__[name] = d[name] - - def _getItemAsList(self, name, default = []): - try: - d = self.__dict__[name] - except: - return default - - if type(d) == ListType: - return d - return [d] - -class structType(compoundType): - pass - -class headerType(structType): - _validURIs = (NS.ENV,) - - def __init__(self, data = None, typed = 1, attrs = None): - structType.__init__(self, data, "Header", typed, attrs) - -class bodyType(structType): - _validURIs = (NS.ENV,) - - def __init__(self, data = None, typed = 1, attrs = None): - structType.__init__(self, data, "Body", typed, attrs) - -class arrayType(UserList.UserList, compoundType): - def __init__(self, data = None, name = None, attrs = None, - offset = 0, rank = None, asize = 0, elemsname = None): - - if data: - if type(data) not in (ListType, TupleType): - raise Error, "Data must be a sequence" - - UserList.UserList.__init__(self, data) - compoundType.__init__(self, data, name, 0, attrs) - - self._elemsname = elemsname or "item" - - if data == None: - self._rank = rank - - # According to 5.4.2.2 in the SOAP spec, each element in a - # sparse array must have a position. _posstate keeps track of - # whether we've seen a position or not. It's possible values - # are: - # -1 No elements have been added, so the state is indeterminate - # 0 An element without a position has been added, so no - # elements can have positions - # 1 An element with a position has been added, so all elements - # must have positions - - self._posstate = -1 - - self._full = 0 - - if asize in ('', None): - asize = '0' - - self._dims = map (lambda x: int(x), str(asize).split(',')) - self._dims.reverse() # It's easier to work with this way - self._poss = [0] * len(self._dims) # This will end up - # reversed too - - for i in range(len(self._dims)): - if self._dims[i] < 0 or \ - self._dims[i] == 0 and len(self._dims) > 1: - raise TypeError, "invalid Array dimensions" - - if offset > 0: - self._poss[i] = offset % self._dims[i] - offset = int(offset / self._dims[i]) - - # Don't break out of the loop if offset is 0 so we test all the - # dimensions for > 0. - if offset: - raise AttributeError, "invalid Array offset" - - a = [None] * self._dims[0] - - for i in range(1, len(self._dims)): - b = [] - - for j in range(self._dims[i]): - b.append(copy.deepcopy(a)) - - a = b - - self.data = a - - def _addItem(self, name, value, attrs): - if self._full: - raise ValueError, "Array is full" - - pos = attrs.get((NS.ENC, 'position')) - - if pos != None: - if self._posstate == 0: - raise AttributeError, \ - "all elements in a sparse Array must have a " \ - "position attribute" - - self._posstate = 1 - - try: - if pos[0] == '[' and pos[-1] == ']': - pos = map (lambda x: int(x), pos[1:-1].split(',')) - pos.reverse() - - if len(pos) == 1: - pos = pos[0] - - curpos = [0] * len(self._dims) - - for i in range(len(self._dims)): - curpos[i] = pos % self._dims[i] - pos = int(pos / self._dims[i]) - - if pos == 0: - break - - if pos: - raise Exception - elif len(pos) != len(self._dims): - raise Exception - else: - for i in range(len(self._dims)): - if pos[i] >= self._dims[i]: - raise Exception - - curpos = pos - else: - raise Exception - except: - raise AttributeError, \ - "invalid Array element position %s" % str(pos) - else: - if self._posstate == 1: - raise AttributeError, \ - "only elements in a sparse Array may have a " \ - "position attribute" - - self._posstate = 0 - - curpos = self._poss - - a = self.data - - for i in range(len(self._dims) - 1, 0, -1): - a = a[curpos[i]] - - if curpos[0] >= len(a): - a += [None] * (len(a) - curpos[0] + 1) - - a[curpos[0]] = value - - if pos == None: - self._poss[0] += 1 - - for i in range(len(self._dims) - 1): - if self._poss[i] < self._dims[i]: - break - - self._poss[i] = 0 - self._poss[i + 1] += 1 - - if self._dims[-1] and self._poss[-1] >= self._dims[-1]: - self._full = 1 - - def _placeItem(self, name, value, pos, subpos, attrs = None): - curpos = [0] * len(self._dims) - - for i in range(len(self._dims)): - if self._dims[i] == 0: - curpos[0] = pos - break - - curpos[i] = pos % self._dims[i] - pos = int(pos / self._dims[i]) - - if pos == 0: - break - - if self._dims[i] != 0 and pos: - raise Error, "array index out of range" - - a = self.data - - for i in range(len(self._dims) - 1, 0, -1): - a = a[curpos[i]] - - if curpos[0] >= len(a): - a += [None] * (len(a) - curpos[0] + 1) - - a[curpos[0]] = value - -class typedArrayType(arrayType): - def __init__(self, data = None, name = None, typed = None, attrs = None, - offset = 0, rank = None, asize = 0, elemsname = None): - - arrayType.__init__(self, data, name, attrs, offset, rank, asize, - elemsname) - - self._type = typed - -class faultType(structType, Error): - def __init__(self, faultcode = "", faultstring = "", detail = None): - self.faultcode = faultcode - self.faultstring = faultstring - if detail != None: - self.detail = detail - - structType.__init__(self, None, 0) - - def _setDetail(self, detail = None): - if detail != None: - self.detail = detail - else: - try: del self.detail - except AttributeError: pass - - def __repr__(self): - return "" % (self.faultcode, self.faultstring) - - __str__ = __repr__ - -################################################################################ -class RefHolder: - def __init__(self, name, frame): - self.name = name - self.parent = frame - self.pos = len(frame) - self.subpos = frame.namecounts.get(name, 0) - - def __repr__(self): - return "<%s %s at %d>" % (self.__class__, self.name, id(self)) - -################################################################################ -# Utility infielders -################################################################################ -def collapseWhiteSpace(s): - return re.sub('\s+', ' ', s).strip() - -def decodeHexString(data): - conv = {'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4, - '5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9, 'a': 0xa, - 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe, 'f': 0xf, 'A': 0xa, - 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe, 'F': 0xf,} - ws = string.whitespace - - bin = '' - - i = 0 - - while i < len(data): - if data[i] not in ws: - break - i += 1 - - low = 0 - - while i < len(data): - c = data[i] - - if c in string.whitespace: - break - - try: - c = conv[c] - except KeyError: - raise ValueError, \ - "invalid hex string character `%s'" % c - - if low: - bin += chr(high * 16 + c) - low = 0 - else: - high = c - low = 1 - - i += 1 - - if low: - raise ValueError, "invalid hex string length" - - while i < len(data): - if data[i] not in string.whitespace: - raise ValueError, \ - "invalid hex string character `%s'" % c - - i += 1 - - return bin - -def encodeHexString(data): - h = '' - - for i in data: - h += "%02X" % ord(i) - - return h - -def leapMonth(year, month): - return month == 2 and \ - year % 4 == 0 and \ - (year % 100 != 0 or year % 400 == 0) - -def cleanDate(d, first = 0): - ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61)) - months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) - names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds') - - if len(d) != 6: - raise ValueError, "date must have 6 elements" - - for i in range(first, 6): - s = d[i] - - if type(s) == FloatType: - if i < 5: - try: - s = int(s) - except OverflowError: - if i > 0: - raise - s = long(s) - - if s != d[i]: - raise ValueError, "%s must be integral" % names[i] - - d[i] = s - elif type(s) == LongType: - try: s = int(s) - except: pass - elif type(s) != IntType: - raise TypeError, "%s isn't a valid type" % names[i] - - if i == first and s < 0: - continue - - if ranges[i] != None and \ - (s < ranges[i][0] or ranges[i][1] < s): - raise ValueError, "%s out of range" % names[i] - - if first < 6 and d[5] >= 61: - raise ValueError, "seconds out of range" - - if first < 2: - leap = first < 1 and leapMonth(d[0], d[1]) - - if d[2] > months[d[1]] + leap: - raise ValueError, "day out of range" - -class UnderflowError(exceptions.ArithmeticError): - pass - -def debugHeader(title): - s = '*** ' + title + ' ' - print s + ('*' * (72 - len(s))) - -def debugFooter(title): - print '*' * 72 - sys.stdout.flush() - -################################################################################ -# SOAP Parser -################################################################################ -class SOAPParser(xml.sax.handler.ContentHandler): - class Frame: - def __init__(self, name, kind = None, attrs = {}, rules = {}): - self.name = name - self.kind = kind - self.attrs = attrs - self.rules = rules - - self.contents = [] - self.names = [] - self.namecounts = {} - self.subattrs = [] - - def append(self, name, data, attrs): - self.names.append(name) - self.contents.append(data) - self.subattrs.append(attrs) - - if self.namecounts.has_key(name): - self.namecounts[name] += 1 - else: - self.namecounts[name] = 1 - - def _placeItem(self, name, value, pos, subpos = 0, attrs = None): - self.contents[pos] = value - - if attrs: - self.attrs.update(attrs) - - def __len__(self): - return len(self.contents) - - def __repr__(self): - return "<%s %s at %d>" % (self.__class__, self.name, id(self)) - - def __init__(self, rules = None): - xml.sax.handler.ContentHandler.__init__(self) - self.body = None - self.header = None - self.attrs = {} - self._data = None - self._next = "E" # Keeping state for message validity - self._stack = [self.Frame('SOAP')] - - # Make two dictionaries to store the prefix <-> URI mappings, and - # initialize them with the default - self._prem = {NS.XML_T: NS.XML} - self._prem_r = {NS.XML: NS.XML_T} - self._ids = {} - self._refs = {} - self._rules = rules - - def startElementNS(self, name, qname, attrs): - # Workaround two sax bugs - if name[0] == None and name[1][0] == ' ': - name = (None, name[1][1:]) - else: - name = tuple(name) - - # First some checking of the layout of the message - - if self._next == "E": - if name[1] != 'Envelope': - raise Error, "expected `SOAP-ENV:Envelope', got `%s:%s'" % \ - (self._prem_r[name[0]], name[1]) - if name[0] != NS.ENV: - raise faultType, ("%s:VersionMismatch" % NS.ENV_T, - "Don't understand version `%s' Envelope" % name[0]) - else: - self._next = "HorB" - elif self._next == "HorB": - if name[0] == NS.ENV and name[1] in ("Header", "Body"): - self._next = None - else: - raise Error, \ - "expected `SOAP-ENV:Header' or `SOAP-ENV:Body', " \ - "got `%s'" % self._prem_r[name[0]] + ':' + name[1] - elif self._next == "B": - if name == (NS.ENV, "Body"): - self._next = None - else: - raise Error, "expected `SOAP-ENV:Body', got `%s'" % \ - self._prem_r[name[0]] + ':' + name[1] - elif self._next == "": - raise Error, "expected nothing, got `%s'" % \ - self._prem_r[name[0]] + ':' + name[1] - - if len(self._stack) == 2: - rules = self._rules - else: - try: - rules = self._stack[-1].rules[name[1]] - except: - rules = None - - if type(rules) not in (NoneType, DictType): - kind = rules - else: - kind = attrs.get((NS.ENC, 'arrayType')) - - if kind != None: - del attrs._attrs[(NS.ENC, 'arrayType')] - - i = kind.find(':') - if i >= 0: - kind = (self._prem[kind[:i]], kind[i + 1:]) - else: - kind = None - - self.pushFrame(self.Frame(name[1], kind, attrs._attrs, rules)) - - self._data = '' # Start accumulating - - def pushFrame(self, frame): - self._stack.append(frame) - - def popFrame(self): - return self._stack.pop() - - def endElementNS(self, name, qname): - # Workaround two sax bugs - if name[0] == None and name[1][0] == ' ': - ns, name = None, name[1][1:] - else: - ns, name = tuple(name) - - if self._next == "E": - raise Error, "didn't get SOAP-ENV:Envelope" - if self._next in ("HorB", "B"): - raise Error, "didn't get SOAP-ENV:Body" - - cur = self.popFrame() - attrs = cur.attrs - - idval = None - - if attrs.has_key((None, 'id')): - idval = attrs[(None, 'id')] - - if self._ids.has_key(idval): - raise Error, "duplicate id `%s'" % idval - - del attrs[(None, 'id')] - - root = 1 - - if len(self._stack) == 3: - if attrs.has_key((NS.ENC, 'root')): - root = int(attrs[(NS.ENC, 'root')]) - - # Do some preliminary checks. First, if root="0" is present, - # the element must have an id. Next, if root="n" is present, - # n something other than 0 or 1, raise an exception. - - if root == 0: - if idval == None: - raise Error, "non-root element must have an id" - elif root != 1: - raise Error, "SOAP-ENC:root must be `0' or `1'" - - del attrs[(NS.ENC, 'root')] - - while 1: - href = attrs.get((None, 'href')) - if href: - if href[0] != '#': - raise Error, "only do local hrefs right now" - if self._data != None and self._data.strip() != '': - raise Error, "hrefs can't have data" - - href = href[1:] - - if self._ids.has_key(href): - data = self._ids[href] - else: - data = RefHolder(name, self._stack[-1]) - - if self._refs.has_key(href): - self._refs[href].append(data) - else: - self._refs[href] = [data] - - del attrs[(None, 'href')] - - break - - kind = None - - if attrs: - for i in NS.XSI_L: - if attrs.has_key((i, 'type')): - kind = attrs[(i, 'type')] - del attrs[(i, 'type')] - - if kind != None: - i = kind.find(':') - if i >= 0: - kind = (self._prem[kind[:i]], kind[i + 1:]) - else: -# XXX What to do here? (None, kind) is just going to fail in convertType - kind = (None, kind) - - null = 0 - - if attrs: - for i in (NS.XSI, NS.XSI2): - if attrs.has_key((i, 'null')): - null = attrs[(i, 'null')] - del attrs[(i, 'null')] - - if attrs.has_key((NS.XSI3, 'nil')): - null = attrs[(NS.XSI3, 'nil')] - del attrs[(NS.XSI3, 'nil')] - - #MAP 4/12/2002 - must also support "true" - #null = int(null) - null = (str(null).lower() in ['true', '1']) - - if null: - if len(cur) or \ - (self._data != None and self._data.strip() != ''): - raise Error, "nils can't have data" - - data = None - - break - - if len(self._stack) == 2: - if (ns, name) == (NS.ENV, "Header"): - self.header = data = headerType(attrs = attrs) - self._next = "B" - break - elif (ns, name) == (NS.ENV, "Body"): - self.body = data = bodyType(attrs = attrs) - self._next = "" - break - elif len(self._stack) == 3 and self._next == None: - if (ns, name) == (NS.ENV, "Fault"): - data = faultType() - self._next = "" - break - - if cur.rules != None: - rule = cur.rules - - if type(rule) in (StringType, UnicodeType): -# XXX Need a namespace here - rule = (None, rule) - elif type(rule) == ListType: - rule = tuple(rule) - -# XXX What if rule != kind? - if callable(rule): - data = rule(self._data) - elif type(rule) == DictType: - data = structType(name = (ns, name), attrs = attrs) - else: - data = self.convertType(self._data, rule, attrs) - - break - - if (kind == None and cur.kind != None) or \ - (kind == (NS.ENC, 'Array')): - kind = cur.kind - - if kind == None: - kind = 'ur-type[%d]' % len(cur) - else: - kind = kind[1] - - if len(cur.namecounts) == 1: - elemsname = cur.names[0] - else: - elemsname = None - - data = self.startArray((ns, name), kind, attrs, elemsname) - - break - - if len(self._stack) == 3 and kind == None and \ - len(cur) == 0 and \ - (self._data == None or self._data.strip() == ''): - data = structType(name = (ns, name), attrs = attrs) - break - - if len(cur) == 0 and ns != NS.URN: - # Nothing's been added to the current frame so it must be a - # simple type. - - if kind == None: - # If the current item's container is an array, it will - # have a kind. If so, get the bit before the first [, - # which is the type of the array, therefore the type of - # the current item. - - kind = self._stack[-1].kind - - if kind != None: - i = kind[1].find('[') - if i >= 0: - kind = (kind[0], kind[1][:i]) - elif ns != None: - kind = (ns, name) - - if kind != None: - try: - data = self.convertType(self._data, kind, attrs) - except UnknownTypeError: - data = None - else: - data = None - - if data == None: - data = self._data or '' - - if len(attrs) == 0: - try: data = str(data) - except: pass - - break - - data = structType(name = (ns, name), attrs = attrs) - - break - - if isinstance(data, compoundType): - for i in range(len(cur)): - v = cur.contents[i] - data._addItem(cur.names[i], v, cur.subattrs[i]) - - if isinstance(v, RefHolder): - v.parent = data - - if root: - self._stack[-1].append(name, data, attrs) - - if idval != None: - self._ids[idval] = data - - if self._refs.has_key(idval): - for i in self._refs[idval]: - i.parent._placeItem(i.name, data, i.pos, i.subpos, attrs) - - del self._refs[idval] - - self.attrs[id(data)] = attrs - - if isinstance(data, anyType): - data._setAttrs(attrs) - - self._data = None # Stop accumulating - - def endDocument(self): - if len(self._refs) == 1: - raise Error, \ - "unresolved reference " + self._refs.keys()[0] - elif len(self._refs) > 1: - raise Error, \ - "unresolved references " + ', '.join(self._refs.keys()) - - def startPrefixMapping(self, prefix, uri): - self._prem[prefix] = uri - self._prem_r[uri] = prefix - - def endPrefixMapping(self, prefix): - try: - del self._prem_r[self._prem[prefix]] - del self._prem[prefix] - except: - pass - - def characters(self, c): - if self._data != None: - self._data += c - - arrayre = '^(?:(?P[^:]*):)?' \ - '(?P[^[]+)' \ - '(?:\[(?P,*)\])?' \ - '(?:\[(?P\d+(?:,\d+)*)?\])$' - - def startArray(self, name, kind, attrs, elemsname): - if type(self.arrayre) == StringType: - self.arrayre = re.compile (self.arrayre) - - offset = attrs.get((NS.ENC, "offset")) - - if offset != None: - del attrs[(NS.ENC, "offset")] - - try: - if offset[0] == '[' and offset[-1] == ']': - offset = int(offset[1:-1]) - if offset < 0: - raise Exception - else: - raise Exception - except: - raise AttributeError, "invalid Array offset" - else: - offset = 0 - - try: - m = self.arrayre.search(kind) - - if m == None: - raise Exception - - t = m.group('type') - - if t == 'ur-type': - return arrayType(None, name, attrs, offset, m.group('rank'), - m.group('asize'), elemsname) - elif m.group('ns') != None: - return typedArrayType(None, name, - (self._prem[m.group('ns')], t), attrs, offset, - m.group('rank'), m.group('asize'), elemsname) - else: - return typedArrayType(None, name, (None, t), attrs, offset, - m.group('rank'), m.group('asize'), elemsname) - except: - raise AttributeError, "invalid Array type `%s'" % kind - - # Conversion - - class DATETIMECONSTS: - SIGNre = '(?P-?)' - CENTURYre = '(?P\d{2,})' - YEARre = '(?P\d{2})' - MONTHre = '(?P\d{2})' - DAYre = '(?P\d{2})' - HOURre = '(?P\d{2})' - MINUTEre = '(?P\d{2})' - SECONDre = '(?P\d{2}(?:\.\d*)?)' - TIMEZONEre = '(?PZ)|(?P[-+])(?P\d{2}):' \ - '(?P\d{2})' - BOSre = '^\s*' - EOSre = '\s*$' - - __allres = {'sign': SIGNre, 'century': CENTURYre, 'year': YEARre, - 'month': MONTHre, 'day': DAYre, 'hour': HOURre, - 'minute': MINUTEre, 'second': SECONDre, 'timezone': TIMEZONEre, - 'b': BOSre, 'e': EOSre} - - dateTime = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)sT' \ - '%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % __allres - timeInstant = dateTime - timePeriod = dateTime - time = '%(b)s%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % \ - __allres - date = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)s' \ - '(%(timezone)s)?%(e)s' % __allres - century = '%(b)s%(sign)s%(century)s(%(timezone)s)?%(e)s' % __allres - gYearMonth = '%(b)s%(sign)s%(century)s%(year)s-%(month)s' \ - '(%(timezone)s)?%(e)s' % __allres - gYear = '%(b)s%(sign)s%(century)s%(year)s(%(timezone)s)?%(e)s' % \ - __allres - year = gYear - gMonthDay = '%(b)s--%(month)s-%(day)s(%(timezone)s)?%(e)s' % __allres - recurringDate = gMonthDay - gDay = '%(b)s---%(day)s(%(timezone)s)?%(e)s' % __allres - recurringDay = gDay - gMonth = '%(b)s--%(month)s--(%(timezone)s)?%(e)s' % __allres - month = gMonth - - recurringInstant = '%(b)s%(sign)s(%(century)s|-)(%(year)s|-)-' \ - '(%(month)s|-)-(%(day)s|-)T' \ - '(%(hour)s|-):(%(minute)s|-):(%(second)s|-)' \ - '(%(timezone)s)?%(e)s' % __allres - - duration = '%(b)s%(sign)sP' \ - '((?P\d+)Y)?' \ - '((?P\d+)M)?' \ - '((?P\d+)D)?' \ - '((?PT)' \ - '((?P\d+)H)?' \ - '((?P\d+)M)?' \ - '((?P\d*(?:\.\d*)?)S)?)?%(e)s' % \ - __allres - - timeDuration = duration - - # The extra 31 on the front is: - # - so the tuple is 1-based - # - so months[month-1] is December's days if month is 1 - - months = (31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) - - def convertDateTime(self, value, kind): - def getZoneOffset(d): - zoffs = 0 - - try: - if d['zulu'] == None: - zoffs = 60 * int(d['tzhour']) + int(d['tzminute']) - if d['tzsign'] != '-': - zoffs = -zoffs - except TypeError: - pass - - return zoffs - - def applyZoneOffset(months, zoffs, date, minfield, posday = 1): - if zoffs == 0 and (minfield > 4 or 0 <= date[5] < 60): - return date - - if minfield > 5: date[5] = 0 - if minfield > 4: date[4] = 0 - - if date[5] < 0: - date[4] += int(date[5]) / 60 - date[5] %= 60 - - date[4] += zoffs - - if minfield > 3 or 0 <= date[4] < 60: return date - - date[3] += date[4] / 60 - date[4] %= 60 - - if minfield > 2 or 0 <= date[3] < 24: return date - - date[2] += date[3] / 24 - date[3] %= 24 - - if minfield > 1: - if posday and date[2] <= 0: - date[2] += 31 # zoffs is at most 99:59, so the - # day will never be less than -3 - return date - - while 1: - # The date[1] == 3 (instead of == 2) is because we're - # going back a month, so we need to know if the previous - # month is February, so we test if this month is March. - - leap = minfield == 0 and date[1] == 3 and \ - date[0] % 4 == 0 and \ - (date[0] % 100 != 0 or date[0] % 400 == 0) - - if 0 < date[2] <= months[date[1]] + leap: break - - date[2] += months[date[1] - 1] + leap - - date[1] -= 1 - - if date[1] > 0: break - - date[1] = 12 - - if minfield > 0: break - - date[0] -= 1 - - return date - - try: - exp = getattr(self.DATETIMECONSTS, kind) - except AttributeError: - return None - - if type(exp) == StringType: - exp = re.compile(exp) - setattr (self.DATETIMECONSTS, kind, exp) - - m = exp.search(value) - - try: - if m == None: - raise Exception - - d = m.groupdict() - f = ('century', 'year', 'month', 'day', - 'hour', 'minute', 'second') - fn = len(f) # Index of first non-None value - r = [] - - if kind in ('duration', 'timeDuration'): - if d['sep'] != None and d['hour'] == None and \ - d['minute'] == None and d['second'] == None: - raise Exception - - f = f[1:] - - for i in range(len(f)): - s = d[f[i]] - - if s != None: - if f[i] == 'second': - s = float(s) - else: - try: s = int(s) - except ValueError: s = long(s) - - if i < fn: fn = i - - r.append(s) - - if fn > len(r): # Any non-Nones? - raise Exception - - if d['sign'] == '-': - r[fn] = -r[fn] - - return tuple(r) - - if kind == 'recurringInstant': - for i in range(len(f)): - s = d[f[i]] - - if s == None or s == '-': - if i > fn: - raise Exception - s = None - else: - if i < fn: - fn = i - - if f[i] == 'second': - s = float(s) - else: - try: - s = int(s) - except ValueError: - s = long(s) - - r.append(s) - - s = r.pop(0) - - if fn == 0: - r[0] += s * 100 - else: - fn -= 1 - - if fn < len(r) and d['sign'] == '-': - r[fn] = -r[fn] - - cleanDate(r, fn) - - return tuple(applyZoneOffset(self.DATETIMECONSTS.months, - getZoneOffset(d), r, fn, 0)) - - r = [0, 0, 1, 1, 0, 0, 0] - - for i in range(len(f)): - field = f[i] - - s = d.get(field) - - if s != None: - if field == 'second': - s = float(s) - else: - try: - s = int(s) - except ValueError: - s = long(s) - - if i < fn: - fn = i - - r[i] = s - - if fn > len(r): # Any non-Nones? - raise Exception - - s = r.pop(0) - - if fn == 0: - r[0] += s * 100 - else: - fn -= 1 - - if d.get('sign') == '-': - r[fn] = -r[fn] - - cleanDate(r, fn) - - zoffs = getZoneOffset(d) - - if zoffs: - r = applyZoneOffset(self.DATETIMECONSTS.months, zoffs, r, fn) - - if kind == 'century': - return r[0] / 100 - - s = [] - - for i in range(1, len(f)): - if d.has_key(f[i]): - s.append(r[i - 1]) - - if len(s) == 1: - return s[0] - return tuple(s) - except Exception, e: - raise Error, "invalid %s value `%s' - %s" % (kind, value, e) - - intlimits = \ - { - 'nonPositiveInteger': (0, None, 0), - 'non-positive-integer': (0, None, 0), - 'negativeInteger': (0, None, -1), - 'negative-integer': (0, None, -1), - 'long': (1, -9223372036854775808L, - 9223372036854775807L), - 'int': (0, -2147483648L, 2147483647), - 'short': (0, -32768, 32767), - 'byte': (0, -128, 127), - 'nonNegativeInteger': (0, 0, None), - 'non-negative-integer': (0, 0, None), - 'positiveInteger': (0, 1, None), - 'positive-integer': (0, 1, None), - 'unsignedLong': (1, 0, 18446744073709551615L), - 'unsignedInt': (0, 0, 4294967295L), - 'unsignedShort': (0, 0, 65535), - 'unsignedByte': (0, 0, 255), - } - floatlimits = \ - { - 'float': (7.0064923216240861E-46, -3.4028234663852886E+38, - 3.4028234663852886E+38), - 'double': (2.4703282292062327E-324, -1.7976931348623158E+308, - 1.7976931348623157E+308), - } - zerofloatre = '[1-9]' - - def convertType(self, d, t, attrs): - dnn = d or '' - - if t[0] in NS.EXSD_L: - if t[1] == "integer": - try: - d = int(d) - if len(attrs): - d = long(d) - except: - d = long(d) - return d - if self.intlimits.has_key (t[1]): - l = self.intlimits[t[1]] - try: d = int(d) - except: d = long(d) - - if l[1] != None and d < l[1]: - raise UnderflowError, "%s too small" % d - if l[2] != None and d > l[2]: - raise OverflowError, "%s too large" % d - - if l[0] or len(attrs): - return long(d) - return d - if t[1] == "string": - if len(attrs): - return unicode(dnn) - try: - return str(dnn) - except: - return dnn - if t[1] == "boolean": - d = d.strip().lower() - if d in ('0', 'false'): - return 0 - if d in ('1', 'true'): - return 1 - raise AttributeError, "invalid boolean value" - if self.floatlimits.has_key (t[1]): - l = self.floatlimits[t[1]] - s = d.strip().lower() - try: - d = float(s) - except: - # Some platforms don't implement the float stuff. This - # is close, but NaN won't be > "INF" as required by the - # standard. - - if s in ("nan", "inf"): - return 1e300**2 - if s == "-inf": - return -1e300**2 - - raise - - if str (d) == 'nan': - if s != 'nan': - raise ValueError, "invalid %s" % t[1] - elif str (d) == '-inf': - if s != '-inf': - raise UnderflowError, "%s too small" % t[1] - elif str (d) == 'inf': - if s != 'inf': - raise OverflowError, "%s too large" % t[1] - elif d < 0: - if d < l[1]: - raise UnderflowError, "%s too small" % t[1] - elif d > 0: - if d < l[0] or d > l[2]: - raise OverflowError, "%s too large" % t[1] - elif d == 0: - if type(self.zerofloatre) == StringType: - self.zerofloatre = re.compile(self.zerofloatre) - - if self.zerofloatre.search(s): - raise UnderflowError, "invalid %s" % t[1] - - return d - if t[1] in ("dateTime", "date", "timeInstant", "time"): - return self.convertDateTime(d, t[1]) - if t[1] == "decimal": - return float(d) - if t[1] in ("language", "QName", "NOTATION", "NMTOKEN", "Name", - "NCName", "ID", "IDREF", "ENTITY"): - return collapseWhiteSpace(d) - if t[1] in ("IDREFS", "ENTITIES", "NMTOKENS"): - d = collapseWhiteSpace(d) - return d.split() - if t[0] in NS.XSD_L: - if t[1] in ("base64", "base64Binary"): - return base64.decodestring(d) - if t[1] == "hexBinary": - return decodeHexString(d) - if t[1] == "anyURI": - return urllib.unquote(collapseWhiteSpace(d)) - if t[1] in ("normalizedString", "token"): - return collapseWhiteSpace(d) - if t[0] == NS.ENC: - if t[1] == "base64": - return base64.decodestring(d) - if t[0] == NS.XSD: - if t[1] == "binary": - try: - e = attrs[(None, 'encoding')] - - if e == 'hex': - return decodeHexString(d) - elif e == 'base64': - return base64.decodestring(d) - except: - pass - - raise Error, "unknown or missing binary encoding" - if t[1] == "uri": - return urllib.unquote(collapseWhiteSpace(d)) - if t[1] == "recurringInstant": - return self.convertDateTime(d, t[1]) - if t[0] in (NS.XSD2, NS.ENC): - if t[1] == "uriReference": - return urllib.unquote(collapseWhiteSpace(d)) - if t[1] == "timePeriod": - return self.convertDateTime(d, t[1]) - if t[1] in ("century", "year"): - return self.convertDateTime(d, t[1]) - if t[0] in (NS.XSD, NS.XSD2, NS.ENC): - if t[1] == "timeDuration": - return self.convertDateTime(d, t[1]) - if t[0] == NS.XSD3: - if t[1] == "anyURI": - return urllib.unquote(collapseWhiteSpace(d)) - if t[1] in ("gYearMonth", "gMonthDay"): - return self.convertDateTime(d, t[1]) - if t[1] == "gYear": - return self.convertDateTime(d, t[1]) - if t[1] == "gMonth": - return self.convertDateTime(d, t[1]) - if t[1] == "gDay": - return self.convertDateTime(d, t[1]) - if t[1] == "duration": - return self.convertDateTime(d, t[1]) - if t[0] in (NS.XSD2, NS.XSD3): - if t[1] == "token": - return collapseWhiteSpace(d) - if t[1] == "recurringDate": - return self.convertDateTime(d, t[1]) - if t[1] == "month": - return self.convertDateTime(d, t[1]) - if t[1] == "recurringDay": - return self.convertDateTime(d, t[1]) - if t[0] == NS.XSD2: - if t[1] == "CDATA": - return collapseWhiteSpace(d) - - raise UnknownTypeError, "unknown type `%s'" % (t[0] + ':' + t[1]) - -################################################################################ -# call to SOAPParser that keeps all of the info -################################################################################ -def _parseSOAP(xml_str, rules = None): - try: - from cStringIO import StringIO - except ImportError: - from StringIO import StringIO - - parser = xml.sax.make_parser() - t = SOAPParser(rules = rules) - parser.setContentHandler(t) - e = xml.sax.handler.ErrorHandler() - parser.setErrorHandler(e) - - inpsrc = xml.sax.xmlreader.InputSource() - inpsrc.setByteStream(StringIO(xml_str)) - - # turn on namespace mangeling - parser.setFeature(xml.sax.handler.feature_namespaces,1) - - parser.parse(inpsrc) - - return t - -################################################################################ -# SOAPParser's more public interface -################################################################################ -def parseSOAP(xml_str, attrs = 0): - t = _parseSOAP(xml_str) - - if attrs: - return t.body, t.attrs - return t.body - - -def parseSOAPRPC(xml_str, header = 0, body = 0, attrs = 0, rules = None): - t = _parseSOAP(xml_str, rules = rules) - p = t.body._aslist[0] - - # Empty string, for RPC this translates into a void - if type(p) in (type(''), type(u'')) and p in ('', u''): - name = "Response" - for k in t.body.__dict__.keys(): - if k[0] != "_": - name = k - p = structType(name) - - if header or body or attrs: - ret = (p,) - if header : ret += (t.header,) - if body: ret += (t.body,) - if attrs: ret += (t.attrs,) - return ret - else: - return p - - -################################################################################ -# SOAP Builder -################################################################################ -class SOAPBuilder: - _xml_top = '\n' - _xml_enc_top = '\n' - _env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \ - NS.__dict__ - _env_bot = '\n' % NS.__dict__ - - # Namespaces potentially defined in the Envelope tag. - - _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T, - NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T, - NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T} - - def __init__(self, args = (), kw = {}, method = None, namespace = None, - header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8', - use_refs = 0, config = Config): - - # Test the encoding, raising an exception if it's not known - if encoding != None: - ''.encode(encoding) - - self.args = args - self.kw = kw - self.envelope = envelope - self.encoding = encoding - self.method = method - self.namespace = namespace - self.header = header - self.methodattrs= methodattrs - self.use_refs = use_refs - self.config = config - self.out = '' - self.tcounter = 0 - self.ncounter = 1 - self.icounter = 1 - self.envns = {} - self.ids = {} - self.depth = 0 - self.multirefs = [] - self.multis = 0 - self.body = not isinstance(args, bodyType) - - def build(self): - ns_map = {} - - # Cache whether typing is on or not - typed = self.config.typed - - if self.header: - # Create a header. - self.dump(self.header, "Header", typed = typed) - self.header = None # Wipe it out so no one is using it. - if self.body: - # Call genns to record that we've used SOAP-ENV. - self.depth += 1 - body_ns = self.genns(ns_map, NS.ENV)[0] - self.out += "<%sBody>\n" % body_ns - - if self.method: - self.depth += 1 - a = '' - if self.methodattrs: - for (k, v) in self.methodattrs.items(): - a += ' %s="%s"' % (k, v) - - if self.namespace: # Use the namespace info handed to us - methodns, n = self.genns(ns_map, self.namespace) - else: - methodns, n = '', '' - - self.out += '<%s%s%s%s%s>\n' % \ - (methodns, self.method, n, a, self.genroot(ns_map)) - - try: - if type(self.args) != TupleType: - args = (self.args,) - else: - args = self.args - - for i in args: - self.dump(i, typed = typed, ns_map = ns_map) - - for (k, v) in self.kw.items(): - self.dump(v, k, typed = typed, ns_map = ns_map) - except RecursionError: - if self.use_refs == 0: - # restart - b = SOAPBuilder(args = self.args, kw = self.kw, - method = self.method, namespace = self.namespace, - header = self.header, methodattrs = self.methodattrs, - envelope = self.envelope, encoding = self.encoding, - use_refs = 1, config = self.config) - return b.build() - raise - - if self.method: - self.out += "\n" % (methodns, self.method) - self.depth -= 1 - - if self.body: - # dump may add to self.multirefs, but the for loop will keep - # going until it has used all of self.multirefs, even those - # entries added while in the loop. - - self.multis = 1 - - for obj, tag in self.multirefs: - self.dump(obj, tag, typed = typed, ns_map = ns_map) - - self.out += "\n" % body_ns - self.depth -= 1 - - if self.envelope: - e = map (lambda ns: 'xmlns:%s="%s"' % (ns[1], ns[0]), - self.envns.items()) - - self.out = '<' + self._env_top + ' '.join([''] + e) + '>\n' + \ - self.out + \ - self._env_bot - - if self.encoding != None: - self.out = self._xml_enc_top % self.encoding + self.out - - return self.out.encode(self.encoding) - - return self._xml_top + self.out - - def gentag(self): - self.tcounter += 1 - return "v%d" % self.tcounter - - def genns(self, ns_map, nsURI): - if nsURI == None: - return ('', '') - - if type(nsURI) == TupleType: # already a tuple - if len(nsURI) == 2: - ns, nsURI = nsURI - else: - ns, nsURI = None, nsURI[0] - else: - ns = None - - if ns_map.has_key(nsURI): - return (ns_map[nsURI] + ':', '') - - if self._env_ns.has_key(nsURI): - ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI] - return (ns + ':', '') - - if not ns: - ns = "ns%d" % self.ncounter - self.ncounter += 1 - ns_map[nsURI] = ns - if self.config.buildWithNamespacePrefix: - return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI)) - else: - return ('', ' xmlns="%s"' % (nsURI)) - - def genroot(self, ns_map): - if self.depth != 2: - return '' - - ns, n = self.genns(ns_map, NS.ENC) - return ' %sroot="%d"%s' % (ns, not self.multis, n) - - # checkref checks an element to see if it needs to be encoded as a - # multi-reference element or not. If it returns None, the element has - # been handled and the caller can continue with subsequent elements. - # If it returns a string, the string should be included in the opening - # tag of the marshaled element. - - def checkref(self, obj, tag, ns_map): - if self.depth < 2: - return '' - - if not self.ids.has_key(id(obj)): - n = self.ids[id(obj)] = self.icounter - self.icounter = n + 1 - - if self.use_refs == 0: - return '' - - if self.depth == 2: - return ' id="i%d"' % n - - self.multirefs.append((obj, tag)) - else: - if self.use_refs == 0: - raise RecursionError, "Cannot serialize recursive object" - - n = self.ids[id(obj)] - - if self.multis and self.depth == 2: - return ' id="i%d"' % n - - self.out += '<%s href="#i%d"%s/>\n' % (tag, n, self.genroot(ns_map)) - return None - - # dumpers - - def dump(self, obj, tag = None, typed = 1, ns_map = {}): - ns_map = ns_map.copy() - self.depth += 1 - - if type(tag) not in (NoneType, StringType, UnicodeType): - raise KeyError, "tag must be a string or None" - - try: - meth = getattr(self, "dump_" + type(obj).__name__) - meth(obj, tag, typed, ns_map) - except AttributeError: - if type(obj) == LongType: - obj_type = "integer" - else: - obj_type = type(obj).__name__ - - self.out += self.dumper(None, obj_type, obj, tag, typed, - ns_map, self.genroot(ns_map)) - - self.depth -= 1 - - # generic dumper - def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {}, - rootattr = '', id = '', - xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s\n'): - - if nsURI == None: - nsURI = self.config.typesNamespaceURI - - tag = tag or self.gentag() - - a = n = t = '' - if typed and obj_type: - ns, n = self.genns(ns_map, nsURI) - ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0] - t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n) - - try: a = obj._marshalAttrs(ns_map, self) - except: pass - - try: data = obj._marshalData() - except: data = obj - - return xml % {"tag": tag, "type": t, "data": data, "root": rootattr, - "id": id, "attrs": a} - - def dump_float(self, obj, tag, typed = 1, ns_map = {}): - # Terrible windows hack - if not good_float: - if obj == float(1e300**2): - obj = "INF" - elif obj == float(-1e300**2): - obj = "-INF" - - obj = str(obj) - if obj in ('inf', '-inf'): - obj = str(obj).upper() - elif obj == 'nan': - obj = 'NaN' - self.out += self.dumper(None, "float", obj, tag, typed, ns_map, - self.genroot(ns_map)) - - def dump_string(self, obj, tag, typed = 0, ns_map = {}): - tag = tag or self.gentag() - - id = self.checkref(obj, tag, ns_map) - if id == None: - return - - try: data = obj._marshalData() - except: data = obj - - self.out += self.dumper(None, "string", cgi.escape(data), tag, - typed, ns_map, self.genroot(ns_map), id) - - dump_unicode = dump_string - dump_str = dump_string # 4/12/2002 - MAP - for Python 2.2 - - def dump_None(self, obj, tag, typed = 0, ns_map = {}): - tag = tag or self.gentag() - ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0] - - self.out += '<%s %snull="1"%s/>\n' % (tag, ns, self.genroot(ns_map)) - - def dump_list(self, obj, tag, typed = 1, ns_map = {}): - if type(obj) == InstanceType: - data = obj.data - else: - data = obj - - tag = tag or self.gentag() - - id = self.checkref(obj, tag, ns_map) - if id == None: - return - - try: - sample = data[0] - empty = 0 - except: - sample = structType() - empty = 1 - - # First scan list to see if all are the same type - same_type = 1 - - if not empty: - for i in data[1:]: - if type(sample) != type(i) or \ - (type(sample) == InstanceType and \ - sample.__class__ != i.__class__): - same_type = 0 - break - - ndecl = '' - if same_type: - if (isinstance(sample, structType)) or \ - type(sample) == DictType: # force to urn struct - - try: - tns = obj._ns or NS.URN - except: - tns = NS.URN - - ns, ndecl = self.genns(ns_map, tns) - - try: - typename = last._typename - except: - typename = "SOAPStruct" - - t = ns + typename - - elif isinstance(sample, anyType): - ns = sample._validNamespaceURI(self.config.typesNamespaceURI, - self.config.strictNamespaces) - if ns: - ns, ndecl = self.genns(ns_map, ns) - t = ns + sample._type - else: - t = 'ur-type' - else: - t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ - type(sample).__name__ - else: - t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ - "ur-type" - - try: a = obj._marshalAttrs(ns_map, self) - except: a = '' - - ens, edecl = self.genns(ns_map, NS.ENC) - ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI) - - self.out += \ - '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %\ - (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl, - self.genroot(ns_map), id, a) - - typed = not same_type - - try: elemsname = obj._elemsname - except: elemsname = "item" - - for i in data: - self.dump(i, elemsname, typed, ns_map) - - self.out += '\n' % tag - - dump_tuple = dump_list - - def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}): - tag = tag or self.gentag() - - id = self.checkref(obj, tag, ns_map) - if id == None: - return - - try: a = obj._marshalAttrs(ns_map, self) - except: a = '' - - self.out += '<%s%s%s%s>\n' % \ - (tag, id, a, self.genroot(ns_map)) - - for (k, v) in obj.items(): - if k[0] != "_": - self.dump(v, k, 1, ns_map) - - self.out += '\n' % tag - dump_dict = dump_dictionary # 4/18/2002 - MAP - for Python 2.2 - - def dump_instance(self, obj, tag, typed = 1, ns_map = {}): - if not tag: - # If it has a name use it. - if isinstance(obj, anyType) and obj._name: - tag = obj._name - else: - tag = self.gentag() - - if isinstance(obj, arrayType): # Array - self.dump_list(obj, tag, typed, ns_map) - return - - if isinstance(obj, faultType): # Fault - cns, cdecl = self.genns(ns_map, NS.ENC) - vns, vdecl = self.genns(ns_map, NS.ENV) - self.out += '''<%sFault %sroot="1"%s%s> -%s -%s -''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring) - if hasattr(obj, "detail"): - self.dump(obj.detail, "detail", typed, ns_map) - self.out += "\n" % vns - return - - r = self.genroot(ns_map) - - try: a = obj._marshalAttrs(ns_map, self) - except: a = '' - - if isinstance(obj, voidType): # void - self.out += "<%s%s%s>\n" % (tag, a, r, tag) - return - - id = self.checkref(obj, tag, ns_map) - if id == None: - return - - if isinstance(obj, structType): - # Check for namespace - ndecl = '' - ns = obj._validNamespaceURI(self.config.typesNamespaceURI, - self.config.strictNamespaces) - if ns: - ns, ndecl = self.genns(ns_map, ns) - tag = ns + tag - self.out += "<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r) - - # If we have order use it. - order = 1 - - for i in obj._keys(): - if i not in obj._keyord: - order = 0 - break - if order: - for i in range(len(obj._keyord)): - self.dump(obj._aslist[i], obj._keyord[i], 1, ns_map) - else: - # don't have pristine order information, just build it. - for (k, v) in obj.__dict__.items(): - if k[0] != "_": - self.dump(v, k, 1, ns_map) - - if isinstance(obj, bodyType): - self.multis = 1 - - for v, k in self.multirefs: - self.dump(v, k, typed = typed, ns_map = ns_map) - - self.out += '\n' % tag - - elif isinstance(obj, anyType): - t = '' - - if typed: - ns = obj._validNamespaceURI(self.config.typesNamespaceURI, - self.config.strictNamespaces) - if ns: - ons, ondecl = self.genns(ns_map, ns) - ins, indecl = self.genns(ns_map, - self.config.schemaNamespaceURI) - t = ' %stype="%s%s"%s%s' % \ - (ins, ons, obj._type, ondecl, indecl) - - self.out += '<%s%s%s%s%s>%s\n' % \ - (tag, t, id, a, r, obj._marshalData(), tag) - - else: # Some Class - self.out += '<%s%s%s>\n' % (tag, id, r) - - for (k, v) in obj.__dict__.items(): - if k[0] != "_": - self.dump(v, k, 1, ns_map) - - self.out += '\n' % tag - - -################################################################################ -# SOAPBuilder's more public interface -################################################################################ -def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None, - methodattrs=None,envelope=1,encoding='UTF-8',config=Config): - t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace, - header=header, methodattrs=methodattrs,envelope=envelope, - encoding=encoding, config=config) - return t.build() - -################################################################################ -# RPC -################################################################################ - -def SOAPUserAgent(): - return "SOAP.py " + __version__ + " (actzero.com)" - -################################################################################ -# Client -################################################################################ -class SOAPAddress: - def __init__(self, url, config = Config): - proto, uri = urllib.splittype(url) - - # apply some defaults - if uri[0:2] != '//': - if proto != None: - uri = proto + ':' + uri - - uri = '//' + uri - proto = 'http' - - host, path = urllib.splithost(uri) - - try: - int(host) - host = 'localhost:' + host - except: - pass - - if not path: - path = '/' - - if proto not in ('http', 'https'): - raise IOError, "unsupported SOAP protocol" - if proto == 'https' and not config.SSLclient: - raise AttributeError, \ - "SSL client not supported by this Python installation" - - self.proto = proto - self.host = host - self.path = path - - def __str__(self): - return "%(proto)s://%(host)s%(path)s" % self.__dict__ - - __repr__ = __str__ - - -class HTTPTransport: - # Need a Timeout someday? - def call(self, addr, data, soapaction = '', encoding = None, - http_proxy = None, config = Config): - - import httplib - - if not isinstance(addr, SOAPAddress): - addr = SOAPAddress(addr, config) - - # Build a request - if http_proxy: - real_addr = http_proxy - real_path = addr.proto + "://" + addr.host + addr.path - else: - real_addr = addr.host - real_path = addr.path - - if addr.proto == 'https': - r = httplib.HTTPS(real_addr) - else: - r = httplib.HTTP(real_addr) - - r.putrequest("POST", real_path) - - r.putheader("Host", addr.host) - r.putheader("User-agent", SOAPUserAgent()) - t = 'text/xml'; - if encoding != None: - t += '; charset="%s"' % encoding - r.putheader("Content-type", t) - r.putheader("Content-length", str(len(data))) - r.putheader("SOAPAction", '"%s"' % soapaction) - - if config.dumpHeadersOut: - s = 'Outgoing HTTP headers' - debugHeader(s) - print "POST %s %s" % (real_path, r._http_vsn_str) - print "Host:", addr.host - print "User-agent: SOAP.py " + __version__ + " (actzero.com)" - print "Content-type:", t - print "Content-length:", len(data) - print 'SOAPAction: "%s"' % soapaction - debugFooter(s) - - r.endheaders() - - if config.dumpSOAPOut: - s = 'Outgoing SOAP' - debugHeader(s) - print data, - if data[-1] != '\n': - print - debugFooter(s) - - # send the payload - r.send(data) - - # read response line - code, msg, headers = r.getreply() - - if config.dumpHeadersIn: - s = 'Incoming HTTP headers' - debugHeader(s) - if headers.headers: - print "HTTP/1.? %d %s" % (code, msg) - print "\n".join(map (lambda x: x.strip(), headers.headers)) - else: - print "HTTP/0.9 %d %s" % (code, msg) - debugFooter(s) - - if config.dumpSOAPIn: - data = r.getfile().read() - - s = 'Incoming SOAP' - debugHeader(s) - print data, - if data[-1] != '\n': - print - debugFooter(s) - - if code not in (200, 500): - raise HTTPError(code, msg) - - if not config.dumpSOAPIn: - data = r.getfile().read() - - # return response payload - return data - -################################################################################ -# SOAP Proxy -################################################################################ -class SOAPProxy: - def __init__(self, proxy, namespace = None, soapaction = '', - header = None, methodattrs = None, transport = HTTPTransport, - encoding = 'UTF-8', throw_faults = 1, unwrap_results = 1, - http_proxy=None, config = Config): - - # Test the encoding, raising an exception if it's not known - if encoding != None: - ''.encode(encoding) - - self.proxy = SOAPAddress(proxy, config) - self.namespace = namespace - self.soapaction = soapaction - self.header = header - self.methodattrs = methodattrs - self.transport = transport() - self.encoding = encoding - self.throw_faults = throw_faults - self.unwrap_results = unwrap_results - self.http_proxy = http_proxy - self.config = config - - - def __call(self, name, args, kw, ns = None, sa = None, hd = None, - ma = None): - - ns = ns or self.namespace - ma = ma or self.methodattrs - - if sa: # Get soapaction - if type(sa) == TupleType: sa = sa[0] - else: - sa = self.soapaction - - if hd: # Get header - if type(hd) == TupleType: - hd = hd[0] - else: - hd = self.header - - hd = hd or self.header - - if ma: # Get methodattrs - if type(ma) == TupleType: ma = ma[0] - else: - ma = self.methodattrs - ma = ma or self.methodattrs - - m = buildSOAP(args = args, kw = kw, method = name, namespace = ns, - header = hd, methodattrs = ma, encoding = self.encoding, - config = self.config) - - r = self.transport.call(self.proxy, m, sa, encoding = self.encoding, - http_proxy = self.http_proxy, - config = self.config) - - p, attrs = parseSOAPRPC(r, attrs = 1) - - try: - throw_struct = self.throw_faults and \ - isinstance (p, faultType) - except: - throw_struct = 0 - - if throw_struct: - raise p - - # Bubble a regular result up, if there is only element in the - # struct, assume that is the result and return it. - # Otherwise it will return the struct with all the elements - # as attributes. - if self.unwrap_results: - try: - count = 0 - for i in p.__dict__.keys(): - if i[0] != "_": # don't move the private stuff - count += 1 - t = getattr(p, i) - if count == 1: p = t # Only one piece of data, bubble it up - except: - pass - - if self.config.returnAllAttrs: - return p, attrs - return p - - def _callWithBody(self, body): - return self.__call(None, body, {}) - - def __getattr__(self, name): # hook to catch method calls - if name == '__del__': - raise AttributeError, name - else: - return self.__Method(self.__call, name, config = self.config) - - # To handle attribute wierdness - class __Method: - # Some magic to bind a SOAP method to an RPC server. - # Supports "nested" methods (e.g. examples.getStateName) -- concept - # borrowed from xmlrpc/soaplib -- www.pythonware.com - # Altered (improved?) to let you inline namespaces on a per call - # basis ala SOAP::LITE -- www.soaplite.com - - def __init__(self, call, name, ns = None, sa = None, hd = None, - ma = None, config = Config): - - self.__call = call - self.__name = name - self.__ns = ns - self.__sa = sa - self.__hd = hd - self.__ma = ma - self.__config = config - if self.__name[0] == "_": - if self.__name in ["__repr__","__str__"]: - self.__call__ = self.__repr__ - else: - self.__call__ = self.__f_call - else: - self.__call__ = self.__r_call - - def __getattr__(self, name): - if name == '__del__': - raise AttributeError, name - if self.__name[0] == "_": - # Don't nest method if it is a directive - return self.__class__(self.__call, name, self.__ns, - self.__sa, self.__hd, self.__ma) - - return self.__class__(self.__call, "%s.%s" % (self.__name, name), - self.__ns, self.__sa, self.__hd, self.__ma) - - def __f_call(self, *args, **kw): - if self.__name == "_ns": self.__ns = args - elif self.__name == "_sa": self.__sa = args - elif self.__name == "_hd": self.__hd = args - elif self.__name == "_ma": self.__ma = args - return self - - def __r_call(self, *args, **kw): - return self.__call(self.__name, args, kw, self.__ns, self.__sa, - self.__hd, self.__ma) - - def __repr__(self): - return "<%s at %d>" % (self.__class__, id(self)) - -################################################################################ -# Server -################################################################################ - -# Method Signature class for adding extra info to registered funcs, right now -# used just to indicate it should be called with keywords, instead of ordered -# params. -class MethodSig: - def __init__(self, func, keywords=0, context=0): - self.func = func - self.keywords = keywords - self.context = context - self.__name__ = func.__name__ - - def __call__(self, *args, **kw): - return apply(self.func,args,kw) - -class SOAPContext: - def __init__(self, header, body, attrs, xmldata, connection, httpheaders, - soapaction): - - self.header = header - self.body = body - self.attrs = attrs - self.xmldata = xmldata - self.connection = connection - self.httpheaders= httpheaders - self.soapaction = soapaction - -# A class to describe how header messages are handled -class HeaderHandler: - # Initially fail out if there are any problems. - def __init__(self, header, attrs): - for i in header.__dict__.keys(): - if i[0] == "_": - continue - - d = getattr(header, i) - - try: - fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')]) - except: - fault = 0 - - if fault: - raise faultType, ("%s:MustUnderstand" % NS.ENV_T, - "Don't understand `%s' header element but " - "mustUnderstand attribute is set." % i) - - -################################################################################ -# SOAP Server -################################################################################ -class SOAPServer(SocketServer.TCPServer): - import BaseHTTPServer - - class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - def version_string(self): - return '' + \ - 'SOAP.py ' + __version__ + ' (Python ' + \ - sys.version.split()[0] + ')' - - def date_time_string(self): - self.__last_date_time_string = \ - SOAPServer.BaseHTTPServer.BaseHTTPRequestHandler.\ - date_time_string(self) - - return self.__last_date_time_string - - def do_POST(self): - try: - if self.server.config.dumpHeadersIn: - s = 'Incoming HTTP headers' - debugHeader(s) - print self.raw_requestline.strip() - print "\n".join(map (lambda x: x.strip(), - self.headers.headers)) - debugFooter(s) - - data = self.rfile.read(int(self.headers["content-length"])) - - if self.server.config.dumpSOAPIn: - s = 'Incoming SOAP' - debugHeader(s) - print data, - if data[-1] != '\n': - print - debugFooter(s) - - (r, header, body, attrs) = \ - parseSOAPRPC(data, header = 1, body = 1, attrs = 1) - - method = r._name - args = r._aslist - kw = r._asdict - - ns = r._ns - resp = "" - # For fault messages - if ns: - nsmethod = "%s:%s" % (ns, method) - else: - nsmethod = method - - try: - # First look for registered functions - if self.server.funcmap.has_key(ns) and \ - self.server.funcmap[ns].has_key(method): - f = self.server.funcmap[ns][method] - else: # Now look at registered objects - # Check for nested attributes. This works even if - # there are none, because the split will return - # [method] - f = self.server.objmap[ns] - l = method.split(".") - for i in l: - f = getattr(f, i) - except: - resp = buildSOAP(faultType("%s:Client" % NS.ENV_T, - "No method %s found" % nsmethod, - "%s %s" % tuple(sys.exc_info()[0:2])), - encoding = self.server.encoding, - config = self.server.config) - status = 500 - else: - try: - if header: - x = HeaderHandler(header, attrs) - - # If it's wrapped, some special action may be needed - - if isinstance(f, MethodSig): - c = None - - if f.context: # Build context object - c = SOAPContext(header, body, attrs, data, - self.connection, self.headers, - self.headers["soapaction"]) - - if f.keywords: - # This is lame, but have to de-unicode - # keywords - - strkw = {} - - for (k, v) in kw.items(): - strkw[str(k)] = v - if c: - strkw["_SOAPContext"] = c - fr = apply(f, (), strkw) - elif c: - fr = apply(f, args, {'_SOAPContext':c}) - else: - fr = apply(f, args, {}) - else: - fr = apply(f, args, {}) - - if type(fr) == type(self) and \ - isinstance(fr, voidType): - resp = buildSOAP(kw = {'%sResponse' % method: fr}, - encoding = self.server.encoding, - config = self.server.config) - else: - resp = buildSOAP(kw = - {'%sResponse' % method: {'Result': fr}}, - encoding = self.server.encoding, - config = self.server.config) - except Exception, e: - import traceback - info = sys.exc_info() - - if self.server.config.dumpFaultInfo: - s = 'Method %s exception' % nsmethod - debugHeader(s) - traceback.print_exception(info[0], info[1], - info[2]) - debugFooter(s) - - if isinstance(e, faultType): - f = e - else: - f = faultType("%s:Server" % NS.ENV_T, - "Method %s failed." % nsmethod) - - if self.server.config.returnFaultInfo: - f._setDetail("".join(traceback.format_exception( - info[0], info[1], info[2]))) - elif not hasattr(f, 'detail'): - f._setDetail("%s %s" % (info[0], info[1])) - - resp = buildSOAP(f, encoding = self.server.encoding, - config = self.server.config) - status = 500 - else: - status = 200 - except faultType, e: - import traceback - info = sys.exc_info() - - if self.server.config.dumpFaultInfo: - s = 'Received fault exception' - debugHeader(s) - traceback.print_exception(info[0], info[1], - info[2]) - debugFooter(s) - - if self.server.config.returnFaultInfo: - e._setDetail("".join(traceback.format_exception( - info[0], info[1], info[2]))) - elif not hasattr(e, 'detail'): - e._setDetail("%s %s" % (info[0], info[1])) - - resp = buildSOAP(e, encoding = self.server.encoding, - config = self.server.config) - status = 500 - except: - # internal error, report as HTTP server error - if self.server.config.dumpFaultInfo: - import traceback - s = 'Internal exception' - debugHeader(s) - traceback.print_exc () - debugFooter(s) - self.send_response(500) - self.end_headers() - - if self.server.config.dumpHeadersOut and \ - self.request_version != 'HTTP/0.9': - s = 'Outgoing HTTP headers' - debugHeader(s) - if self.responses.has_key(status): - s = ' ' + self.responses[status][0] - else: - s = '' - print "%s %d%s" % (self.protocol_version, 500, s) - print "Server:", self.version_string() - print "Date:", self.__last_date_time_string - debugFooter(s) - else: - # got a valid SOAP response - self.send_response(status) - - t = 'text/xml'; - if self.server.encoding != None: - t += '; charset="%s"' % self.server.encoding - self.send_header("Content-type", t) - self.send_header("Content-length", str(len(resp))) - self.end_headers() - - if self.server.config.dumpHeadersOut and \ - self.request_version != 'HTTP/0.9': - s = 'Outgoing HTTP headers' - debugHeader(s) - if self.responses.has_key(status): - s = ' ' + self.responses[status][0] - else: - s = '' - print "%s %d%s" % (self.protocol_version, status, s) - print "Server:", self.version_string() - print "Date:", self.__last_date_time_string - print "Content-type:", t - print "Content-length:", len(resp) - debugFooter(s) - - if self.server.config.dumpSOAPOut: - s = 'Outgoing SOAP' - debugHeader(s) - print resp, - if resp[-1] != '\n': - print - debugFooter(s) - - self.wfile.write(resp) - self.wfile.flush() - - # We should be able to shut down both a regular and an SSL - # connection, but under Python 2.1, calling shutdown on an - # SSL connections drops the output, so this work-around. - # This should be investigated more someday. - - if self.server.config.SSLserver and \ - isinstance(self.connection, SSL.Connection): - self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN | - SSL.SSL_RECEIVED_SHUTDOWN) - else: - self.connection.shutdown(1) - - def log_message(self, format, *args): - if self.server.log: - SOAPServer.BaseHTTPServer.BaseHTTPRequestHandler.\ - log_message (self, format, *args) - - def __init__(self, addr = ('localhost', 8000), - RequestHandler = SOAPRequestHandler, log = 1, encoding = 'UTF-8', - config = Config, namespace = None, ssl_context = None): - - # Test the encoding, raising an exception if it's not known - if encoding != None: - ''.encode(encoding) - - if ssl_context != None and not config.SSLserver: - raise AttributeError, \ - "SSL server not supported by this Python installation" - - self.namespace = namespace - self.objmap = {} - self.funcmap = {} - self.ssl_context = ssl_context - self.encoding = encoding - self.config = config - self.log = log - - self.allow_reuse_address= 1 - - SocketServer.TCPServer.__init__(self, addr, RequestHandler) - - def get_request(self): - sock, addr = SocketServer.TCPServer.get_request(self) - - if self.ssl_context: - sock = SSL.Connection(self.ssl_context, sock) - sock._setup_ssl(addr) - if sock.accept_ssl() != 1: - raise socket.error, "Couldn't accept SSL connection" - - return sock, addr - - def registerObject(self, object, namespace = ''): - if namespace == '': namespace = self.namespace - self.objmap[namespace] = object - - def registerFunction(self, function, namespace = '', funcName = None): - if not funcName : funcName = function.__name__ - if namespace == '': namespace = self.namespace - if self.funcmap.has_key(namespace): - self.funcmap[namespace][funcName] = function - else: - self.funcmap[namespace] = {funcName : function} - - def registerKWObject(self, object, namespace = ''): - if namespace == '': namespace = self.namespace - for i in dir(object.__class__): - if i[0] != "_" and callable(getattr(object, i)): - self.registerKWFunction(getattr(object,i), namespace) - - # convenience - wraps your func for you. - def registerKWFunction(self, function, namespace = '', funcName = None): - self.registerFunction(MethodSig(function,keywords=1), namespace, - funcName) -# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/others/SOAPpy/Client.py b/others/SOAPpy/Client.py new file mode 100644 index 000000000..cfea9c3e3 --- /dev/null +++ b/others/SOAPpy/Client.py @@ -0,0 +1,478 @@ +""" +################################################################################ +# +# SOAPpy - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# Gregory Warnes (gregory_r_warnes@groton.pfizer.com) +# Christopher Blunck (blunck@gst.com) +# +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id$' +from version import __version__ + +from __future__ import nested_scopes + +#import xml.sax +import urllib +from types import * +import re +import base64 + +# SOAPpy modules +from Errors import * +from Config import Config +from Parser import parseSOAPRPC +from SOAPBuilder import buildSOAP +from Utilities import * +from Types import faultType, simplify + +################################################################################ +# Client +################################################################################ + + +def SOAPUserAgent(): + return "SOAPpy " + __version__ + " (pywebsvcs.sf.net)" + + +class SOAPAddress: + def __init__(self, url, config = Config): + proto, uri = urllib.splittype(url) + + # apply some defaults + if uri[0:2] != '//': + if proto != None: + uri = proto + ':' + uri + + uri = '//' + uri + proto = 'http' + + host, path = urllib.splithost(uri) + + try: + int(host) + host = 'localhost:' + host + except: + pass + + if not path: + path = '/' + + if proto not in ('http', 'https', 'httpg'): + raise IOError, "unsupported SOAP protocol" + if proto == 'httpg' and not config.GSIclient: + raise AttributeError, \ + "GSI client not supported by this Python installation" + if proto == 'https' and not config.SSLclient: + raise AttributeError, \ + "SSL client not supported by this Python installation" + + self.user,host = urllib.splituser(host) + self.proto = proto + self.host = host + self.path = path + + def __str__(self): + return "%(proto)s://%(host)s%(path)s" % self.__dict__ + + __repr__ = __str__ + + +class HTTPTransport: + def getNS(self, original_namespace, data): + """Extract the (possibly extended) namespace from the returned + SOAP message.""" + + if type(original_namespace) == StringType: + pattern="xmlns:\w+=['\"](" + original_namespace + "[^'\"]*)['\"]" + match = re.search(pattern, data) + if match: + return match.group(1) + else: + return original_namespace + else: + return original_namespace + + # Need a Timeout someday? + def call(self, addr, data, namespace, soapaction = None, encoding = None, + http_proxy = None, config = Config): + + import httplib + + if not isinstance(addr, SOAPAddress): + addr = SOAPAddress(addr, config) + + # Build a request + if http_proxy: + real_addr = http_proxy + real_path = addr.proto + "://" + addr.host + addr.path + else: + real_addr = addr.host + real_path = addr.path + + if addr.proto == 'httpg': + from pyGlobus.io import GSIHTTP + r = GSIHTTP(real_addr, tcpAttr = config.tcpAttr) + elif addr.proto == 'https': + r = httplib.HTTPS(real_addr) + else: + r = httplib.HTTP(real_addr) + + r.putrequest("POST", real_path) + + r.putheader("Host", addr.host) + r.putheader("User-agent", SOAPUserAgent()) + t = 'text/xml'; + if encoding != None: + t += '; charset="%s"' % encoding + r.putheader("Content-type", t) + r.putheader("Content-length", str(len(data))) + + # if user is not a user:passwd format + # we'll receive a failure from the server. . .I guess (??) + if addr.user != None: + val = base64.encodestring(addr.user) + r.putheader('Authorization','Basic ' + val.replace('\012','')) + + # This fixes sending either "" or "None" + if soapaction == None or len(soapaction) == 0: + r.putheader("SOAPAction", "") + else: + r.putheader("SOAPAction", '"%s"' % soapaction) + + if config.dumpHeadersOut: + s = 'Outgoing HTTP headers' + debugHeader(s) + print "POST %s %s" % (real_path, r._http_vsn_str) + print "Host:", addr.host + print "User-agent: SOAPpy " + __version__ + " (http://pywebsvcs.sf.net)" + print "Content-type:", t + print "Content-length:", len(data) + print 'SOAPAction: "%s"' % soapaction + debugFooter(s) + + r.endheaders() + + if config.dumpSOAPOut: + s = 'Outgoing SOAP' + debugHeader(s) + print data, + if data[-1] != '\n': + print + debugFooter(s) + + # send the payload + r.send(data) + + # read response line + code, msg, headers = r.getreply() + + content_type = headers.get("content-type","text/xml") + content_length = headers.get("Content-length") + if content_length == None: + # No Content-Length provided; just read the whole socket + # This won't work with HTTP/1.1 chunked encoding + data = r.getfile().read() + message_len = len(data) + else: + message_len = int(content_length) + data = r.getfile().read(message_len) + + if(config.debug): + print "code=",code + print "msg=", msg + print "headers=", headers + print "content-type=", content_type + print "data=", data + + if config.dumpHeadersIn: + s = 'Incoming HTTP headers' + debugHeader(s) + if headers.headers: + print "HTTP/1.? %d %s" % (code, msg) + print "\n".join(map (lambda x: x.strip(), headers.headers)) + else: + print "HTTP/0.9 %d %s" % (code, msg) + debugFooter(s) + + def startswith(string, val): + return string[0:len(val)] == val + + if code == 500 and not \ + ( startswith(content_type, "text/xml") and message_len > 0 ): + raise HTTPError(code, msg) + + if config.dumpSOAPIn: + s = 'Incoming SOAP' + debugHeader(s) + print data, + if (len(data)>0) and (data[-1] != '\n'): + print + debugFooter(s) + + if code not in (200, 500): + raise HTTPError(code, msg) + + + # get the new namespace + if namespace is None: + new_ns = None + else: + new_ns = self.getNS(namespace, data) + + # return response payload + return data, new_ns + +################################################################################ +# SOAP Proxy +################################################################################ +class SOAPProxy: + def __init__(self, proxy, namespace = None, soapaction = None, + header = None, methodattrs = None, transport = HTTPTransport, + encoding = 'UTF-8', throw_faults = 1, unwrap_results = None, + http_proxy=None, config = Config, noroot = 0, + simplify_objects=None): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + # get default values for unwrap_results and simplify_objects + # from config + if unwrap_results is None: + self.unwrap_results=config.unwrap_results + else: + self.unwrap_results=unwrap_results + + if simplify_objects is None: + self.simplify_objects=config.simplify_objects + else: + self.simplify_objects=simplify_objects + + self.proxy = SOAPAddress(proxy, config) + self.namespace = namespace + self.soapaction = soapaction + self.header = header + self.methodattrs = methodattrs + self.transport = transport() + self.encoding = encoding + self.throw_faults = throw_faults + self.http_proxy = http_proxy + self.config = config + self.noroot = noroot + + # GSI Additions + if hasattr(config, "channel_mode") and \ + hasattr(config, "delegation_mode"): + self.channel_mode = config.channel_mode + self.delegation_mode = config.delegation_mode + #end GSI Additions + + def invoke(self, method, args): + return self.__call(method, args, {}) + + def __call(self, name, args, kw, ns = None, sa = None, hd = None, + ma = None): + + ns = ns or self.namespace + ma = ma or self.methodattrs + + if sa: # Get soapaction + if type(sa) == TupleType: + sa = sa[0] + else: + if self.soapaction: + sa = self.soapaction + else: + sa = name + + if hd: # Get header + if type(hd) == TupleType: + hd = hd[0] + else: + hd = self.header + + hd = hd or self.header + + if ma: # Get methodattrs + if type(ma) == TupleType: ma = ma[0] + else: + ma = self.methodattrs + ma = ma or self.methodattrs + + m = buildSOAP(args = args, kw = kw, method = name, namespace = ns, + header = hd, methodattrs = ma, encoding = self.encoding, + config = self.config, noroot = self.noroot) + + + call_retry = 0 + try: + + r, self.namespace = self.transport.call(self.proxy, m, ns, sa, + encoding = self.encoding, + http_proxy = self.http_proxy, + config = self.config) + + except Exception, ex: + # + # Call failed. + # + # See if we have a fault handling vector installed in our + # config. If we do, invoke it. If it returns a true value, + # retry the call. + # + # In any circumstance other than the fault handler returning + # true, reraise the exception. This keeps the semantics of this + # code the same as without the faultHandler code. + # + + if hasattr(self.config, "faultHandler"): + if callable(self.config.faultHandler): + call_retry = self.config.faultHandler(self.proxy, ex) + if not call_retry: + raise + else: + raise + else: + raise + + if call_retry: + r, self.namespace = self.transport.call(self.proxy, m, ns, sa, + encoding = self.encoding, + http_proxy = self.http_proxy, + config = self.config) + + + p, attrs = parseSOAPRPC(r, attrs = 1) + + try: + throw_struct = self.throw_faults and \ + isinstance (p, faultType) + except: + throw_struct = 0 + + if throw_struct: + print p + raise p + + # If unwrap_results=1 and there is only element in the struct, + # SOAPProxy will assume that this element is the result + # and return it rather than the struct containing it. + # Otherwise SOAPproxy will return the struct with all the + # elements as attributes. + if self.unwrap_results: + try: + count = 0 + for i in p.__dict__.keys(): + if i[0] != "_": # don't count the private stuff + count += 1 + t = getattr(p, i) + if count == 1: # Only one piece of data, bubble it up + p = t + except: + pass + + # Automatically simplfy SOAP complex types into the + # corresponding python types. (structType --> dict, + # arrayType --> array, etc.) + if self.simplify_objects: + p = simplify(p) + + if self.config.returnAllAttrs: + return p, attrs + return p + + def _callWithBody(self, body): + return self.__call(None, body, {}) + + def __getattr__(self, name): # hook to catch method calls + if name == '__del__': + raise AttributeError, name + return self.__Method(self.__call, name, config = self.config) + + # To handle attribute wierdness + class __Method: + # Some magic to bind a SOAP method to an RPC server. + # Supports "nested" methods (e.g. examples.getStateName) -- concept + # borrowed from xmlrpc/soaplib -- www.pythonware.com + # Altered (improved?) to let you inline namespaces on a per call + # basis ala SOAP::LITE -- www.soaplite.com + + def __init__(self, call, name, ns = None, sa = None, hd = None, + ma = None, config = Config): + + self.__call = call + self.__name = name + self.__ns = ns + self.__sa = sa + self.__hd = hd + self.__ma = ma + self.__config = config + return + + def __call__(self, *args, **kw): + if self.__name[0] == "_": + if self.__name in ["__repr__","__str__"]: + return self.__repr__() + else: + return self.__f_call(*args, **kw) + else: + return self.__r_call(*args, **kw) + + def __getattr__(self, name): + if name == '__del__': + raise AttributeError, name + if self.__name[0] == "_": + # Don't nest method if it is a directive + return self.__class__(self.__call, name, self.__ns, + self.__sa, self.__hd, self.__ma) + + return self.__class__(self.__call, "%s.%s" % (self.__name, name), + self.__ns, self.__sa, self.__hd, self.__ma) + + def __f_call(self, *args, **kw): + if self.__name == "_ns": self.__ns = args + elif self.__name == "_sa": self.__sa = args + elif self.__name == "_hd": self.__hd = args + elif self.__name == "_ma": self.__ma = args + return self + + def __r_call(self, *args, **kw): + return self.__call(self.__name, args, kw, self.__ns, self.__sa, + self.__hd, self.__ma) + + def __repr__(self): + return "<%s at %d>" % (self.__class__, id(self)) diff --git a/others/SOAPpy/Config.py b/others/SOAPpy/Config.py new file mode 100644 index 000000000..1d82e8412 --- /dev/null +++ b/others/SOAPpy/Config.py @@ -0,0 +1,202 @@ +""" +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id$' +from version import __version__ + +import copy, socket +from types import * + +from NS import NS + +################################################################################ +# Configuration class +################################################################################ + +class SOAPConfig: + __readonly = ('SSLserver', 'SSLclient', 'GSIserver', 'GSIclient') + + def __init__(self, config = None, **kw): + d = self.__dict__ + + if config: + if not isinstance(config, SOAPConfig): + raise AttributeError, \ + "initializer must be SOAPConfig instance" + + s = config.__dict__ + + for k, v in s.items(): + if k[0] != '_': + d[k] = v + else: + # Setting debug also sets returnFaultInfo, + # dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut + self.debug = 0 + self.dumpFaultInfo = 1 + # Setting namespaceStyle sets typesNamespace, typesNamespaceURI, + # schemaNamespace, and schemaNamespaceURI + self.namespaceStyle = '1999' + self.strictNamespaces = 0 + self.typed = 1 + self.buildWithNamespacePrefix = 1 + self.returnAllAttrs = 0 + + # Strict checking of range for floats and doubles + self.strict_range = 0 + + # Default encoding for dictionary keys + self.dict_encoding = 'ascii' + + # New argument name handling mechanism. See + # README.MethodParameterNaming for details + self.specialArgs = 1 + + # If unwrap_results=1 and there is only element in the struct, + # SOAPProxy will assume that this element is the result + # and return it rather than the struct containing it. + # Otherwise SOAPproxy will return the struct with all the + # elements as attributes. + self.unwrap_results = 1 + + # Automatically convert SOAP complex types, and + # (recursively) public contents into the corresponding + # python types. (Private subobjects have names that start + # with '_'.) + # + # Conversions: + # - faultType --> raise python exception + # - arrayType --> array + # - compoundType --> dictionary + # + self.simplify_objects = 0 + + # Per-class authorization method. If this is set, before + # calling a any class method, the specified authorization + # method will be called. If it returns 1, the method call + # will proceed, otherwise the call will throw with an + # authorization error. + self.authMethod = None + + # Globus Support if pyGlobus.io available + try: + from pyGlobus import io; + d['GSIserver'] = 1 + d['GSIclient'] = 1 + except: + d['GSIserver'] = 0 + d['GSIclient'] = 0 + + + # Server SSL support if M2Crypto.SSL available + try: + from M2Crypto import SSL + d['SSLserver'] = 1 + except: + d['SSLserver'] = 0 + + # Client SSL support if socket.ssl available + try: + from socket import ssl + d['SSLclient'] = 1 + except: + d['SSLclient'] = 0 + + for k, v in kw.items(): + if k[0] != '_': + setattr(self, k, v) + + def __setattr__(self, name, value): + if name in self.__readonly: + raise AttributeError, "readonly configuration setting" + + d = self.__dict__ + + if name in ('typesNamespace', 'typesNamespaceURI', + 'schemaNamespace', 'schemaNamespaceURI'): + + if name[-3:] == 'URI': + base, uri = name[:-3], 1 + else: + base, uri = name, 0 + + if type(value) == StringType: + if NS.NSMAP.has_key(value): + n = (value, NS.NSMAP[value]) + elif NS.NSMAP_R.has_key(value): + n = (NS.NSMAP_R[value], value) + else: + raise AttributeError, "unknown namespace" + elif type(value) in (ListType, TupleType): + if uri: + n = (value[1], value[0]) + else: + n = (value[0], value[1]) + else: + raise AttributeError, "unknown namespace type" + + d[base], d[base + 'URI'] = n + + try: + d['namespaceStyle'] = \ + NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])] + except: + d['namespaceStyle'] = '' + + elif name == 'namespaceStyle': + value = str(value) + + if not NS.STMAP.has_key(value): + raise AttributeError, "unknown namespace style" + + d[name] = value + n = d['typesNamespace'] = NS.STMAP[value][0] + d['typesNamespaceURI'] = NS.NSMAP[n] + n = d['schemaNamespace'] = NS.STMAP[value][1] + d['schemaNamespaceURI'] = NS.NSMAP[n] + + elif name == 'debug': + d[name] = \ + d['returnFaultInfo'] = \ + d['dumpHeadersIn'] = \ + d['dumpHeadersOut'] = \ + d['dumpSOAPIn'] = \ + d['dumpSOAPOut'] = value + + else: + d[name] = value + + +Config = SOAPConfig() diff --git a/others/SOAPpy/Errors.py b/others/SOAPpy/Errors.py new file mode 100644 index 000000000..9534427d0 --- /dev/null +++ b/others/SOAPpy/Errors.py @@ -0,0 +1,79 @@ +""" +################################################################################ +# +# SOAPpy - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# Gregory Warnes (gregory_r_warnes@groton.pfizer.com) +# Christopher Blunck (blunck@gst.com) +# +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id$' +from version import __version__ + +import exceptions + +################################################################################ +# Exceptions +################################################################################ +class Error(exceptions.Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return "" % self.msg + __repr__ = __str__ + def __call__(self): + return (msg,) + +class RecursionError(Error): + pass + +class UnknownTypeError(Error): + pass + +class HTTPError(Error): + # indicates an HTTP protocol error + def __init__(self, code, msg): + self.code = code + self.msg = msg + def __str__(self): + return "" % (self.code, self.msg) + __repr__ = __str__ + def __call___(self): + return (self.code, self.msg, ) + +class UnderflowError(exceptions.ArithmeticError): + pass + diff --git a/others/SOAPpy/GSIServer.py b/others/SOAPpy/GSIServer.py new file mode 100644 index 000000000..e66ee2600 --- /dev/null +++ b/others/SOAPpy/GSIServer.py @@ -0,0 +1,143 @@ +""" +GSIServer - Contributed by Ivan R. Judson + + +################################################################################ +# +# SOAPpy - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# Gregory Warnes (gregory_r_warnes@groton.pfizer.com) +# Christopher Blunck (blunck@gst.com) +# +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id$' +from version import __version__ + +from __future__ import nested_scopes + +#import xml.sax +import re +import socket +import sys +import SocketServer +from types import * +import BaseHTTPServer + +# SOAPpy modules +from Parser import parseSOAPRPC +from Config import SOAPConfig +from Types import faultType, voidType, simplify +from NS import NS +from SOAPBuilder import buildSOAP +from Utilities import debugHeader, debugFooter + +try: from M2Crypto import SSL +except: pass + +##### + +from Server import * + +from pyGlobus.io import GSITCPSocketServer, ThreadingGSITCPSocketServer +from pyGlobus import ioc + +def GSIConfig(): + config = SOAPConfig() + config.channel_mode = ioc.GLOBUS_IO_SECURE_CHANNEL_MODE_GSI_WRAP + config.delegation_mode = ioc.GLOBUS_IO_SECURE_DELEGATION_MODE_FULL_PROXY + config.tcpAttr = None + config.authMethod = "_authorize" + return config + +Config = GSIConfig() + +class GSISOAPServer(GSITCPSocketServer, SOAPServerBase): + def __init__(self, addr = ('localhost', 8000), + RequestHandler = SOAPRequestHandler, log = 0, + encoding = 'UTF-8', config = Config, namespace = None): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + self.namespace = namespace + self.objmap = {} + self.funcmap = {} + self.encoding = encoding + self.config = config + self.log = log + + self.allow_reuse_address= 1 + + GSITCPSocketServer.__init__(self, addr, RequestHandler, + self.config.channel_mode, + self.config.delegation_mode, + tcpAttr = self.config.tcpAttr) + + def get_request(self): + sock, addr = GSITCPSocketServer.get_request(self) + + return sock, addr + +class ThreadingGSISOAPServer(ThreadingGSITCPSocketServer, SOAPServerBase): + + def __init__(self, addr = ('localhost', 8000), + RequestHandler = SOAPRequestHandler, log = 0, + encoding = 'UTF-8', config = Config, namespace = None): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + self.namespace = namespace + self.objmap = {} + self.funcmap = {} + self.encoding = encoding + self.config = config + self.log = log + + self.allow_reuse_address= 1 + + ThreadingGSITCPSocketServer.__init__(self, addr, RequestHandler, + self.config.channel_mode, + self.config.delegation_mode, + tcpAttr = self.config.tcpAttr) + + def get_request(self): + sock, addr = ThreadingGSITCPSocketServer.get_request(self) + + return sock, addr + diff --git a/others/SOAPpy/NS.py b/others/SOAPpy/NS.py new file mode 100644 index 000000000..443e027a3 --- /dev/null +++ b/others/SOAPpy/NS.py @@ -0,0 +1,104 @@ +""" +################################################################################ +# +# SOAPpy - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# Gregory Warnes (gregory_r_warnes@groton.pfizer.com) +# Christopher Blunck (blunck@gst.com) +# +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +from __future__ import nested_scopes + +ident = '$Id$' +from version import __version__ + +############################################################################## +# Namespace Class +################################################################################ +def invertDict(dict): + d = {} + + for k, v in dict.items(): + d[v] = k + + return d + +class NS: + XML = "http://www.w3.org/XML/1998/namespace" + + ENV = "http://schemas.xmlsoap.org/soap/envelope/" + ENC = "http://schemas.xmlsoap.org/soap/encoding/" + + XSD = "http://www.w3.org/1999/XMLSchema" + XSD2 = "http://www.w3.org/2000/10/XMLSchema" + XSD3 = "http://www.w3.org/2001/XMLSchema" + + XSD_L = [XSD, XSD2, XSD3] + EXSD_L= [ENC, XSD, XSD2, XSD3] + + XSI = "http://www.w3.org/1999/XMLSchema-instance" + XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" + XSI3 = "http://www.w3.org/2001/XMLSchema-instance" + XSI_L = [XSI, XSI2, XSI3] + + URN = "http://soapinterop.org/xsd" + + # For generated messages + XML_T = "xml" + ENV_T = "SOAP-ENV" + ENC_T = "SOAP-ENC" + XSD_T = "xsd" + XSD2_T= "xsd2" + XSD3_T= "xsd3" + XSI_T = "xsi" + XSI2_T= "xsi2" + XSI3_T= "xsi3" + URN_T = "urn" + + NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2, + XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3, + URN_T: URN} + NSMAP_R = invertDict(NSMAP) + + STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T), + '2001': (XSD3_T, XSI3_T)} + STMAP_R = invertDict(STMAP) + + def __init__(self): + raise Error, "Don't instantiate this" + + + diff --git a/others/SOAPpy/Parser.py b/others/SOAPpy/Parser.py new file mode 100644 index 000000000..7779e1e08 --- /dev/null +++ b/others/SOAPpy/Parser.py @@ -0,0 +1,1024 @@ +# SOAPpy modules +from Config import Config +from Types import * +from NS import NS +from Utilities import * + +import string +import fpconst +import xml.sax +from wstools.XMLname import fromXMLname + +try: from M2Crypto import SSL +except: pass + +ident = '$Id$' +from version import __version__ + + +################################################################################ +# SOAP Parser +################################################################################ +class RefHolder: + def __init__(self, name, frame): + self.name = name + self.parent = frame + self.pos = len(frame) + self.subpos = frame.namecounts.get(name, 0) + + def __repr__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + + def __str__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + +class SOAPParser(xml.sax.handler.ContentHandler): + class Frame: + def __init__(self, name, kind = None, attrs = {}, rules = {}): + self.name = name + self.kind = kind + self.attrs = attrs + self.rules = rules + + self.contents = [] + self.names = [] + self.namecounts = {} + self.subattrs = [] + + def append(self, name, data, attrs): + self.names.append(name) + self.contents.append(data) + self.subattrs.append(attrs) + + if self.namecounts.has_key(name): + self.namecounts[name] += 1 + else: + self.namecounts[name] = 1 + + def _placeItem(self, name, value, pos, subpos = 0, attrs = None): + self.contents[pos] = value + + if attrs: + self.attrs.update(attrs) + + def __len__(self): + return len(self.contents) + + def __repr__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + + def __init__(self, rules = None): + xml.sax.handler.ContentHandler.__init__(self) + self.body = None + self.header = None + self.attrs = {} + self._data = None + self._next = "E" # Keeping state for message validity + self._stack = [self.Frame('SOAP')] + + # Make two dictionaries to store the prefix <-> URI mappings, and + # initialize them with the default + self._prem = {NS.XML_T: NS.XML} + self._prem_r = {NS.XML: NS.XML_T} + self._ids = {} + self._refs = {} + self._rules = rules + + def startElementNS(self, name, qname, attrs): + # Workaround two sax bugs + if name[0] == None and name[1][0] == ' ': + name = (None, name[1][1:]) + else: + name = tuple(name) + + # First some checking of the layout of the message + + if self._next == "E": + if name[1] != 'Envelope': + raise Error, "expected `SOAP-ENV:Envelope', gto `%s:%s'" % \ + (self._prem_r[name[0]], name[1]) + if name[0] != NS.ENV: + raise faultType, ("%s:VersionMismatch" % NS.ENV_T, + "Don't understand version `%s' Envelope" % name[0]) + else: + self._next = "HorB" + elif self._next == "HorB": + if name[0] == NS.ENV and name[1] in ("Header", "Body"): + self._next = None + else: + raise Error, \ + "expected `SOAP-ENV:Header' or `SOAP-ENV:Body', " \ + "got `%s'" % self._prem_r[name[0]] + ':' + name[1] + elif self._next == "B": + if name == (NS.ENV, "Body"): + self._next = None + else: + raise Error, "expected `SOAP-ENV:Body', got `%s'" % \ + self._prem_r[name[0]] + ':' + name[1] + elif self._next == "": + raise Error, "expected nothing, got `%s'" % \ + self._prem_r[name[0]] + ':' + name[1] + + if len(self._stack) == 2: + rules = self._rules + else: + try: + rules = self._stack[-1].rules[name[1]] + except: + rules = None + + if type(rules) not in (NoneType, DictType): + kind = rules + else: + kind = attrs.get((NS.ENC, 'arrayType')) + + if kind != None: + del attrs._attrs[(NS.ENC, 'arrayType')] + + i = kind.find(':') + if i >= 0: + kind = (self._prem[kind[:i]], kind[i + 1:]) + else: + kind = None + + self.pushFrame(self.Frame(name[1], kind, attrs._attrs, rules)) + + self._data = [] # Start accumulating + + def pushFrame(self, frame): + self._stack.append(frame) + + def popFrame(self): + return self._stack.pop() + + def endElementNS(self, name, qname): + # Workaround two sax bugs + if name[0] == None and name[1][0] == ' ': + ns, name = None, name[1][1:] + else: + ns, name = tuple(name) + + name = fromXMLname(name) # convert to SOAP 1.2 XML name encoding + + if self._next == "E": + raise Error, "didn't get SOAP-ENV:Envelope" + if self._next in ("HorB", "B"): + raise Error, "didn't get SOAP-ENV:Body" + + cur = self.popFrame() + attrs = cur.attrs + + idval = None + + if attrs.has_key((None, 'id')): + idval = attrs[(None, 'id')] + + if self._ids.has_key(idval): + raise Error, "duplicate id `%s'" % idval + + del attrs[(None, 'id')] + + root = 1 + + if len(self._stack) == 3: + if attrs.has_key((NS.ENC, 'root')): + root = int(attrs[(NS.ENC, 'root')]) + + # Do some preliminary checks. First, if root="0" is present, + # the element must have an id. Next, if root="n" is present, + # n something other than 0 or 1, raise an exception. + + if root == 0: + if idval == None: + raise Error, "non-root element must have an id" + elif root != 1: + raise Error, "SOAP-ENC:root must be `0' or `1'" + + del attrs[(NS.ENC, 'root')] + + while 1: + href = attrs.get((None, 'href')) + if href: + if href[0] != '#': + raise Error, "Non-local hrefs are not yet suppported." + if self._data != None and string.join(self._data, "").strip() != '': + raise Error, "hrefs can't have data" + + href = href[1:] + + if self._ids.has_key(href): + data = self._ids[href] + else: + data = RefHolder(name, self._stack[-1]) + + if self._refs.has_key(href): + self._refs[href].append(data) + else: + self._refs[href] = [data] + + del attrs[(None, 'href')] + + break + + kind = None + + if attrs: + for i in NS.XSI_L: + if attrs.has_key((i, 'type')): + kind = attrs[(i, 'type')] + del attrs[(i, 'type')] + + if kind != None: + i = kind.find(':') + if i >= 0: + kind = (self._prem[kind[:i]], kind[i + 1:]) + else: +# XXX What to do here? (None, kind) is just going to fail in convertType + kind = (None, kind) + + null = 0 + + if attrs: + for i in (NS.XSI, NS.XSI2): + if attrs.has_key((i, 'null')): + null = attrs[(i, 'null')] + del attrs[(i, 'null')] + + if attrs.has_key((NS.XSI3, 'nil')): + null = attrs[(NS.XSI3, 'nil')] + del attrs[(NS.XSI3, 'nil')] + + + ## Check for nil + + # check for nil='true' + if type(null) in (StringType, UnicodeType): + if null.lower() == 'true': + null = 1 + + # check for nil=1, but watch out for string values + try: + null = int(null) + except ValueError, e: + if not e[0].startswith("invalid literal for int()"): + raise e + null = 0 + + if null: + if len(cur) or \ + (self._data != None and string.join(self._data, "").strip() != ''): + raise Error, "nils can't have data" + + data = None + + break + + if len(self._stack) == 2: + if (ns, name) == (NS.ENV, "Header"): + self.header = data = headerType(attrs = attrs) + self._next = "B" + break + elif (ns, name) == (NS.ENV, "Body"): + self.body = data = bodyType(attrs = attrs) + self._next = "" + break + elif len(self._stack) == 3 and self._next == None: + if (ns, name) == (NS.ENV, "Fault"): + data = faultType() + self._next = "" + break + + if cur.rules != None: + rule = cur.rules + + if type(rule) in (StringType, UnicodeType): +# XXX Need a namespace here + rule = (None, rule) + elif type(rule) == ListType: + rule = tuple(rule) + +# XXX What if rule != kind? + if callable(rule): + data = rule(string.join(self._data, "")) + elif type(rule) == DictType: + data = structType(name = (ns, name), attrs = attrs) + else: + data = self.convertType(string.join(self._data, ""), + rule, attrs) + + break + + if (kind == None and cur.kind != None) or \ + (kind == (NS.ENC, 'Array')): + kind = cur.kind + + if kind == None: + kind = 'ur-type[%d]' % len(cur) + else: + kind = kind[1] + + if len(cur.namecounts) == 1: + elemsname = cur.names[0] + else: + elemsname = None + + data = self.startArray((ns, name), kind, attrs, elemsname) + + break + + if len(self._stack) == 3 and kind == None and \ + len(cur) == 0 and \ + (self._data == None or string.join(self._data, "").strip() == ''): + data = structType(name = (ns, name), attrs = attrs) + break + + if len(cur) == 0 and ns != NS.URN: + # Nothing's been added to the current frame so it must be a + # simple type. + + if kind == None: + # If the current item's container is an array, it will + # have a kind. If so, get the bit before the first [, + # which is the type of the array, therefore the type of + # the current item. + + kind = self._stack[-1].kind + + if kind != None: + i = kind[1].find('[') + if i >= 0: + kind = (kind[0], kind[1][:i]) + elif ns != None: + kind = (ns, name) + + if kind != None: + try: + data = self.convertType(string.join(self._data, ""), + kind, attrs) + except UnknownTypeError: + data = None + else: + data = None + + if data == None: + if self._data == None: + data = '' + else: + data = string.join(self._data, "") + + if len(attrs) == 0: + try: data = str(data) + except: pass + + break + + data = structType(name = (ns, name), attrs = attrs) + + break + + if isinstance(data, compoundType): + for i in range(len(cur)): + v = cur.contents[i] + data._addItem(cur.names[i], v, cur.subattrs[i]) + + if isinstance(v, RefHolder): + v.parent = data + + if root: + self._stack[-1].append(name, data, attrs) + + if idval != None: + self._ids[idval] = data + + if self._refs.has_key(idval): + for i in self._refs[idval]: + i.parent._placeItem(i.name, data, i.pos, i.subpos, attrs) + + del self._refs[idval] + + self.attrs[id(data)] = attrs + + if isinstance(data, anyType): + data._setAttrs(attrs) + + self._data = None # Stop accumulating + + def endDocument(self): + if len(self._refs) == 1: + raise Error, \ + "unresolved reference " + self._refs.keys()[0] + elif len(self._refs) > 1: + raise Error, \ + "unresolved references " + ', '.join(self._refs.keys()) + + def startPrefixMapping(self, prefix, uri): + self._prem[prefix] = uri + self._prem_r[uri] = prefix + + def endPrefixMapping(self, prefix): + try: + del self._prem_r[self._prem[prefix]] + del self._prem[prefix] + except: + pass + + def characters(self, c): + if self._data != None: + self._data.append(c) + + arrayre = '^(?:(?P[^:]*):)?' \ + '(?P[^[]+)' \ + '(?:\[(?P,*)\])?' \ + '(?:\[(?P\d+(?:,\d+)*)?\])$' + + def startArray(self, name, kind, attrs, elemsname): + if type(self.arrayre) == StringType: + self.arrayre = re.compile (self.arrayre) + + offset = attrs.get((NS.ENC, "offset")) + + if offset != None: + del attrs[(NS.ENC, "offset")] + + try: + if offset[0] == '[' and offset[-1] == ']': + offset = int(offset[1:-1]) + if offset < 0: + raise Exception + else: + raise Exception + except: + raise AttributeError, "invalid Array offset" + else: + offset = 0 + + try: + m = self.arrayre.search(kind) + + if m == None: + raise Exception + + t = m.group('type') + + if t == 'ur-type': + return arrayType(None, name, attrs, offset, m.group('rank'), + m.group('asize'), elemsname) + elif m.group('ns') != None: + return typedArrayType(None, name, + (self._prem[m.group('ns')], t), attrs, offset, + m.group('rank'), m.group('asize'), elemsname) + else: + return typedArrayType(None, name, (None, t), attrs, offset, + m.group('rank'), m.group('asize'), elemsname) + except: + raise AttributeError, "invalid Array type `%s'" % kind + + # Conversion + + class DATETIMECONSTS: + SIGNre = '(?P-?)' + CENTURYre = '(?P\d{2,})' + YEARre = '(?P\d{2})' + MONTHre = '(?P\d{2})' + DAYre = '(?P\d{2})' + HOURre = '(?P\d{2})' + MINUTEre = '(?P\d{2})' + SECONDre = '(?P\d{2}(?:\.\d*)?)' + TIMEZONEre = '(?PZ)|(?P[-+])(?P\d{2}):' \ + '(?P\d{2})' + BOSre = '^\s*' + EOSre = '\s*$' + + __allres = {'sign': SIGNre, 'century': CENTURYre, 'year': YEARre, + 'month': MONTHre, 'day': DAYre, 'hour': HOURre, + 'minute': MINUTEre, 'second': SECONDre, 'timezone': TIMEZONEre, + 'b': BOSre, 'e': EOSre} + + dateTime = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)sT' \ + '%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % __allres + timeInstant = dateTime + timePeriod = dateTime + time = '%(b)s%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % \ + __allres + date = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)s' \ + '(%(timezone)s)?%(e)s' % __allres + century = '%(b)s%(sign)s%(century)s(%(timezone)s)?%(e)s' % __allres + gYearMonth = '%(b)s%(sign)s%(century)s%(year)s-%(month)s' \ + '(%(timezone)s)?%(e)s' % __allres + gYear = '%(b)s%(sign)s%(century)s%(year)s(%(timezone)s)?%(e)s' % \ + __allres + year = gYear + gMonthDay = '%(b)s--%(month)s-%(day)s(%(timezone)s)?%(e)s' % __allres + recurringDate = gMonthDay + gDay = '%(b)s---%(day)s(%(timezone)s)?%(e)s' % __allres + recurringDay = gDay + gMonth = '%(b)s--%(month)s--(%(timezone)s)?%(e)s' % __allres + month = gMonth + + recurringInstant = '%(b)s%(sign)s(%(century)s|-)(%(year)s|-)-' \ + '(%(month)s|-)-(%(day)s|-)T' \ + '(%(hour)s|-):(%(minute)s|-):(%(second)s|-)' \ + '(%(timezone)s)?%(e)s' % __allres + + duration = '%(b)s%(sign)sP' \ + '((?P\d+)Y)?' \ + '((?P\d+)M)?' \ + '((?P\d+)D)?' \ + '((?PT)' \ + '((?P\d+)H)?' \ + '((?P\d+)M)?' \ + '((?P\d*(?:\.\d*)?)S)?)?%(e)s' % \ + __allres + + timeDuration = duration + + # The extra 31 on the front is: + # - so the tuple is 1-based + # - so months[month-1] is December's days if month is 1 + + months = (31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + + def convertDateTime(self, value, kind): + def getZoneOffset(d): + zoffs = 0 + + try: + if d['zulu'] == None: + zoffs = 60 * int(d['tzhour']) + int(d['tzminute']) + if d['tzsign'] != '-': + zoffs = -zoffs + except TypeError: + pass + + return zoffs + + def applyZoneOffset(months, zoffs, date, minfield, posday = 1): + if zoffs == 0 and (minfield > 4 or 0 <= date[5] < 60): + return date + + if minfield > 5: date[5] = 0 + if minfield > 4: date[4] = 0 + + if date[5] < 0: + date[4] += int(date[5]) / 60 + date[5] %= 60 + + date[4] += zoffs + + if minfield > 3 or 0 <= date[4] < 60: return date + + date[3] += date[4] / 60 + date[4] %= 60 + + if minfield > 2 or 0 <= date[3] < 24: return date + + date[2] += date[3] / 24 + date[3] %= 24 + + if minfield > 1: + if posday and date[2] <= 0: + date[2] += 31 # zoffs is at most 99:59, so the + # day will never be less than -3 + return date + + while 1: + # The date[1] == 3 (instead of == 2) is because we're + # going back a month, so we need to know if the previous + # month is February, so we test if this month is March. + + leap = minfield == 0 and date[1] == 3 and \ + date[0] % 4 == 0 and \ + (date[0] % 100 != 0 or date[0] % 400 == 0) + + if 0 < date[2] <= months[date[1]] + leap: break + + date[2] += months[date[1] - 1] + leap + + date[1] -= 1 + + if date[1] > 0: break + + date[1] = 12 + + if minfield > 0: break + + date[0] -= 1 + + return date + + try: + exp = getattr(self.DATETIMECONSTS, kind) + except AttributeError: + return None + + if type(exp) == StringType: + exp = re.compile(exp) + setattr (self.DATETIMECONSTS, kind, exp) + + m = exp.search(value) + + try: + if m == None: + raise Exception + + d = m.groupdict() + f = ('century', 'year', 'month', 'day', + 'hour', 'minute', 'second') + fn = len(f) # Index of first non-None value + r = [] + + if kind in ('duration', 'timeDuration'): + if d['sep'] != None and d['hour'] == None and \ + d['minute'] == None and d['second'] == None: + raise Exception + + f = f[1:] + + for i in range(len(f)): + s = d[f[i]] + + if s != None: + if f[i] == 'second': + s = float(s) + else: + try: s = int(s) + except ValueError: s = long(s) + + if i < fn: fn = i + + r.append(s) + + if fn > len(r): # Any non-Nones? + raise Exception + + if d['sign'] == '-': + r[fn] = -r[fn] + + return tuple(r) + + if kind == 'recurringInstant': + for i in range(len(f)): + s = d[f[i]] + + if s == None or s == '-': + if i > fn: + raise Exception + s = None + else: + if i < fn: + fn = i + + if f[i] == 'second': + s = float(s) + else: + try: + s = int(s) + except ValueError: + s = long(s) + + r.append(s) + + s = r.pop(0) + + if fn == 0: + r[0] += s * 100 + else: + fn -= 1 + + if fn < len(r) and d['sign'] == '-': + r[fn] = -r[fn] + + cleanDate(r, fn) + + return tuple(applyZoneOffset(self.DATETIMECONSTS.months, + getZoneOffset(d), r, fn, 0)) + + r = [0, 0, 1, 1, 0, 0, 0] + + for i in range(len(f)): + field = f[i] + + s = d.get(field) + + if s != None: + if field == 'second': + s = float(s) + else: + try: + s = int(s) + except ValueError: + s = long(s) + + if i < fn: + fn = i + + r[i] = s + + if fn > len(r): # Any non-Nones? + raise Exception + + s = r.pop(0) + + if fn == 0: + r[0] += s * 100 + else: + fn -= 1 + + if d.get('sign') == '-': + r[fn] = -r[fn] + + cleanDate(r, fn) + + zoffs = getZoneOffset(d) + + if zoffs: + r = applyZoneOffset(self.DATETIMECONSTS.months, zoffs, r, fn) + + if kind == 'century': + return r[0] / 100 + + s = [] + + for i in range(1, len(f)): + if d.has_key(f[i]): + s.append(r[i - 1]) + + if len(s) == 1: + return s[0] + return tuple(s) + except Exception, e: + raise Error, "invalid %s value `%s' - %s" % (kind, value, e) + + intlimits = \ + { + 'nonPositiveInteger': (0, None, 0), + 'non-positive-integer': (0, None, 0), + 'negativeInteger': (0, None, -1), + 'negative-integer': (0, None, -1), + 'long': (1, -9223372036854775808L, + 9223372036854775807L), + 'int': (0, -2147483648L, 2147483647), + 'short': (0, -32768, 32767), + 'byte': (0, -128, 127), + 'nonNegativeInteger': (0, 0, None), + 'non-negative-integer': (0, 0, None), + 'positiveInteger': (0, 1, None), + 'positive-integer': (0, 1, None), + 'unsignedLong': (1, 0, 18446744073709551615L), + 'unsignedInt': (0, 0, 4294967295L), + 'unsignedShort': (0, 0, 65535), + 'unsignedByte': (0, 0, 255), + } + floatlimits = \ + { + 'float': (7.0064923216240861E-46, -3.4028234663852886E+38, + 3.4028234663852886E+38), + 'double': (2.4703282292062327E-324, -1.7976931348623158E+308, + 1.7976931348623157E+308), + } + zerofloatre = '[1-9]' + + + + + + def convertType(self, d, t, attrs, config=Config): + return self.convertToBasicTypes(d, t, attrs, config) + + + def convertToSOAPpyTypes(self, d, t, attrs, config=Config): + pass + + + def convertToBasicTypes(self, d, t, attrs, config=Config): + dnn = d or '' + + if t[0] in NS.EXSD_L: + if t[1] == "integer": + try: + d = int(d) + if len(attrs): + d = long(d) + except: + d = long(d) + return d + if self.intlimits.has_key (t[1]): # integer types + l = self.intlimits[t[1]] + try: d = int(d) + except: d = long(d) + + if l[1] != None and d < l[1]: + raise UnderflowError, "%s too small" % d + if l[2] != None and d > l[2]: + raise OverflowError, "%s too large" % d + + if l[0] or len(attrs): + return long(d) + return d + if t[1] == "string": + if len(attrs): + return unicode(dnn) + try: + return str(dnn) + except: + return dnn + if t[1] == "boolean": + d = d.strip().lower() + if d in ('0', 'false'): + return 0 + if d in ('1', 'true'): + return 1 + raise AttributeError, "invalid boolean value" + if t[1] in ('double','float'): + l = self.floatlimits[t[1]] + s = d.strip().lower() + + d = float(s) + + if config.strict_range: + if d < l[1]: raise UnderflowError + if d > l[2]: raise OverflowError + else: + # some older SOAP impementations (notably SOAP4J, + # Apache SOAP) return "infinity" instead of "INF" + # so check the first 3 characters for a match. + if s == "nan": + return fpconst.NaN + elif s[0:3] in ("inf", "+inf"): + return fpconst.PosInf + elif s[0:3] == "-inf": + return fpconst.NegInf + + if fpconst.isNaN(d): + if s != 'nan': + raise ValueError, "invalid %s: %s" % (t[1], s) + elif fpconst.isNegInf(d): + if s != '-inf': + raise UnderflowError, "%s too small: %s" % (t[1], s) + elif fpconst.isPosInf(d): + if s != 'inf': + raise OverflowError, "%s too large: %s" % (t[1], s) + elif d < 0 and d < l[1]: + raise UnderflowError, "%s too small: %s" % (t[1], s) + elif d > 0 and ( d < l[0] or d > l[2] ): + raise OverflowError, "%s too large: %s" % (t[1], s) + elif d == 0: + if type(self.zerofloatre) == StringType: + self.zerofloatre = re.compile(self.zerofloatre) + + if self.zerofloatre.search(s): + raise UnderflowError, "invalid %s: %s" % (t[1], s) + + return d + if t[1] in ("dateTime", "date", "timeInstant", "time"): + return self.convertDateTime(d, t[1]) + if t[1] == "decimal": + return float(d) + if t[1] in ("language", "QName", "NOTATION", "NMTOKEN", "Name", + "NCName", "ID", "IDREF", "ENTITY"): + return collapseWhiteSpace(d) + if t[1] in ("IDREFS", "ENTITIES", "NMTOKENS"): + d = collapseWhiteSpace(d) + return d.split() + if t[0] in NS.XSD_L: + if t[1] in ("base64", "base64Binary"): + if d: + return base64.decodestring(d) + else: + return '' + if t[1] == "hexBinary": + if d: + return decodeHexString(d) + else: + return + if t[1] == "anyURI": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] in ("normalizedString", "token"): + return collapseWhiteSpace(d) + if t[0] == NS.ENC: + if t[1] == "base64": + if d: + return base64.decodestring(d) + else: + return '' + if t[0] == NS.XSD: + if t[1] == "binary": + try: + e = attrs[(None, 'encoding')] + + if d: + if e == 'hex': + return decodeHexString(d) + elif e == 'base64': + return base64.decodestring(d) + else: + return '' + except: + pass + + raise Error, "unknown or missing binary encoding" + if t[1] == "uri": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] == "recurringInstant": + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD2, NS.ENC): + if t[1] == "uriReference": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] == "timePeriod": + return self.convertDateTime(d, t[1]) + if t[1] in ("century", "year"): + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD, NS.XSD2, NS.ENC): + if t[1] == "timeDuration": + return self.convertDateTime(d, t[1]) + if t[0] == NS.XSD3: + if t[1] == "anyURI": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] in ("gYearMonth", "gMonthDay"): + return self.convertDateTime(d, t[1]) + if t[1] == "gYear": + return self.convertDateTime(d, t[1]) + if t[1] == "gMonth": + return self.convertDateTime(d, t[1]) + if t[1] == "gDay": + return self.convertDateTime(d, t[1]) + if t[1] == "duration": + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD2, NS.XSD3): + if t[1] == "token": + return collapseWhiteSpace(d) + if t[1] == "recurringDate": + return self.convertDateTime(d, t[1]) + if t[1] == "month": + return self.convertDateTime(d, t[1]) + if t[1] == "recurringDay": + return self.convertDateTime(d, t[1]) + if t[0] == NS.XSD2: + if t[1] == "CDATA": + return collapseWhiteSpace(d) + + raise UnknownTypeError, "unknown type `%s'" % (t[0] + ':' + t[1]) + + +################################################################################ +# call to SOAPParser that keeps all of the info +################################################################################ +def _parseSOAP(xml_str, rules = None): + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + + parser = xml.sax.make_parser() + t = SOAPParser(rules = rules) + parser.setContentHandler(t) + e = xml.sax.handler.ErrorHandler() + parser.setErrorHandler(e) + + inpsrc = xml.sax.xmlreader.InputSource() + inpsrc.setByteStream(StringIO(xml_str)) + + # turn on namespace mangeling + parser.setFeature(xml.sax.handler.feature_namespaces,1) + + try: + parser.parse(inpsrc) + except xml.sax.SAXParseException, e: + parser._parser = None + raise e + + return t + +################################################################################ +# SOAPParser's more public interface +################################################################################ +def parseSOAP(xml_str, attrs = 0): + t = _parseSOAP(xml_str) + + if attrs: + return t.body, t.attrs + return t.body + + +def parseSOAPRPC(xml_str, header = 0, body = 0, attrs = 0, rules = None): + #config=Config, unwrap_outer=1): + + t = _parseSOAP(xml_str, rules = rules) + p = t.body[0] + + # Empty string, for RPC this translates into a void + if type(p) in (type(''), type(u'')) and p in ('', u''): + name = "Response" + for k in t.body.__dict__.keys(): + if k[0] != "_": + name = k + p = structType(name) + + if header or body or attrs: + ret = (p,) + if header : ret += (t.header,) + if body: ret += (t.body,) + if attrs: ret += (t.attrs,) + return ret + else: + return p diff --git a/others/SOAPpy/SOAP.py b/others/SOAPpy/SOAP.py new file mode 100644 index 000000000..eb09c3b06 --- /dev/null +++ b/others/SOAPpy/SOAP.py @@ -0,0 +1,40 @@ +"""This file is here for backward compatibility with versions <= 0.9.9 + +Delete when 1.0.0 is released! +""" + +ident = '$Id$' +from version import __version__ + +from Client import * +from Config import * +from Errors import * +from NS import * +from Parser import * +from SOAPBuilder import * +from Server import * +from Types import * +from Utilities import * +import wstools +import WSDL + +from warnings import warn + +warn(""" + +The sub-module SOAPpy.SOAP is deprecated and is only +provided for short-term backward compatibility. Objects are now +available directly within the SOAPpy module. Thus, instead of + + from SOAPpy import SOAP + ... + SOAP.SOAPProxy(...) + +use + + from SOAPpy import SOAPProxy + ... + SOAPProxy(...) + +instead. +""", DeprecationWarning) diff --git a/others/SOAPpy/SOAPBuilder.py b/others/SOAPpy/SOAPBuilder.py new file mode 100644 index 000000000..65ad89808 --- /dev/null +++ b/others/SOAPpy/SOAPBuilder.py @@ -0,0 +1,620 @@ +""" +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id$' +from version import __version__ + +import cgi +import copy +from wstools.XMLname import toXMLname, fromXMLname +import fpconst + +# SOAPpy modules +from Config import Config +from NS import NS +from Types import * + +# Test whether this Python version has Types.BooleanType +# If it doesn't have it, then False and True are serialized as integers +try: + BooleanType + pythonHasBooleanType = 1 +except NameError: + pythonHasBooleanType = 0 + +################################################################################ +# SOAP Builder +################################################################################ +class SOAPBuilder: + _xml_top = '\n' + _xml_enc_top = '\n' + _env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \ + NS.__dict__ + _env_bot = '\n' % NS.__dict__ + + # Namespaces potentially defined in the Envelope tag. + + _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T, + NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T, + NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T} + + def __init__(self, args = (), kw = {}, method = None, namespace = None, + header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8', + use_refs = 0, config = Config, noroot = 0): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + self.args = args + self.kw = kw + self.envelope = envelope + self.encoding = encoding + self.method = method + self.namespace = namespace + self.header = header + self.methodattrs= methodattrs + self.use_refs = use_refs + self.config = config + self.out = [] + self.tcounter = 0 + self.ncounter = 1 + self.icounter = 1 + self.envns = {} + self.ids = {} + self.depth = 0 + self.multirefs = [] + self.multis = 0 + self.body = not isinstance(args, bodyType) + self.noroot = noroot + + def build(self): + if Config.debug: print "In build." + ns_map = {} + + # Cache whether typing is on or not + typed = self.config.typed + + if self.header: + # Create a header. + self.dump(self.header, "Header", typed = typed) + self.header = None # Wipe it out so no one is using it. + + if self.body: + # Call genns to record that we've used SOAP-ENV. + self.depth += 1 + body_ns = self.genns(ns_map, NS.ENV)[0] + self.out.append("<%sBody>\n" % body_ns) + + if self.method: + self.depth += 1 + a = '' + if self.methodattrs: + for (k, v) in self.methodattrs.items(): + a += ' %s="%s"' % (k, v) + + if self.namespace: # Use the namespace info handed to us + methodns, n = self.genns(ns_map, self.namespace) + else: + methodns, n = '', '' + + self.out.append('<%s%s%s%s%s>\n' % ( + methodns, self.method, n, a, self.genroot(ns_map))) + + try: + if type(self.args) != TupleType: + args = (self.args,) + else: + args = self.args + + for i in args: + self.dump(i, typed = typed, ns_map = ns_map) + + if hasattr(self.config, "argsOrdering") and self.config.argsOrdering.has_key(self.method): + for k in self.config.argsOrdering.get(self.method): + self.dump(self.kw.get(k), k, typed = typed, ns_map = ns_map) + else: + for (k, v) in self.kw.items(): + self.dump(v, k, typed = typed, ns_map = ns_map) + + except RecursionError: + if self.use_refs == 0: + # restart + b = SOAPBuilder(args = self.args, kw = self.kw, + method = self.method, namespace = self.namespace, + header = self.header, methodattrs = self.methodattrs, + envelope = self.envelope, encoding = self.encoding, + use_refs = 1, config = self.config) + return b.build() + raise + + if self.method: + self.out.append("\n" % (methodns, self.method)) + self.depth -= 1 + + if self.body: + # dump may add to self.multirefs, but the for loop will keep + # going until it has used all of self.multirefs, even those + # entries added while in the loop. + + self.multis = 1 + + for obj, tag in self.multirefs: + self.dump(obj, tag, typed = typed, ns_map = ns_map) + + self.out.append("\n" % body_ns) + self.depth -= 1 + + if self.envelope: + e = map (lambda ns: ' xmlns:%s="%s"' % (ns[1], ns[0]), + self.envns.items()) + + self.out = ['<', self._env_top] + e + ['>\n'] + \ + self.out + \ + [self._env_bot] + + if self.encoding != None: + self.out.insert(0, self._xml_enc_top % self.encoding) + return ''.join(self.out).encode(self.encoding) + + self.out.insert(0, self._xml_top) + return ''.join(self.out) + + def gentag(self): + if Config.debug: print "In gentag." + self.tcounter += 1 + return "v%d" % self.tcounter + + def genns(self, ns_map, nsURI): + if nsURI == None: + return ('', '') + + if type(nsURI) == TupleType: # already a tuple + if len(nsURI) == 2: + ns, nsURI = nsURI + else: + ns, nsURI = None, nsURI[0] + else: + ns = None + + if ns_map.has_key(nsURI): + return (ns_map[nsURI] + ':', '') + + if self._env_ns.has_key(nsURI): + ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI] + return (ns + ':', '') + + if not ns: + ns = "ns%d" % self.ncounter + self.ncounter += 1 + ns_map[nsURI] = ns + if self.config.buildWithNamespacePrefix: + return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI)) + else: + return ('', ' xmlns="%s"' % (nsURI)) + + def genroot(self, ns_map): + if self.noroot: + return '' + + if self.depth != 2: + return '' + + ns, n = self.genns(ns_map, NS.ENC) + return ' %sroot="%d"%s' % (ns, not self.multis, n) + + # checkref checks an element to see if it needs to be encoded as a + # multi-reference element or not. If it returns None, the element has + # been handled and the caller can continue with subsequent elements. + # If it returns a string, the string should be included in the opening + # tag of the marshaled element. + + def checkref(self, obj, tag, ns_map): + if self.depth < 2: + return '' + + if not self.ids.has_key(id(obj)): + n = self.ids[id(obj)] = self.icounter + self.icounter = n + 1 + + if self.use_refs == 0: + return '' + + if self.depth == 2: + return ' id="i%d"' % n + + self.multirefs.append((obj, tag)) + else: + if self.use_refs == 0: + raise RecursionError, "Cannot serialize recursive object" + + n = self.ids[id(obj)] + + if self.multis and self.depth == 2: + return ' id="i%d"' % n + + self.out.append('<%s href="#i%d"%s/>\n' % + (tag, n, self.genroot(ns_map))) + return None + + # dumpers + + def dump(self, obj, tag = None, typed = 1, ns_map = {}): + if Config.debug: print "In dump.", "obj=", obj + ns_map = ns_map.copy() + self.depth += 1 + + if type(tag) not in (NoneType, StringType, UnicodeType): + raise KeyError, "tag must be a string or None" + + try: + meth = getattr(self, "dump_" + type(obj).__name__) + except AttributeError: + if type(obj) == LongType: + obj_type = "integer" + elif pythonHasBooleanType and type(obj) == BooleanType: + obj_type = "boolean" + else: + obj_type = type(obj).__name__ + + self.out.append(self.dumper(None, obj_type, obj, tag, typed, + ns_map, self.genroot(ns_map))) + else: + meth(obj, tag, typed, ns_map) + + + self.depth -= 1 + + # generic dumper + def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {}, + rootattr = '', id = '', + xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s\n'): + if Config.debug: print "In dumper." + + if nsURI == None: + nsURI = self.config.typesNamespaceURI + + tag = tag or self.gentag() + + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + a = n = t = '' + if typed and obj_type: + ns, n = self.genns(ns_map, nsURI) + ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0] + t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n) + + try: a = obj._marshalAttrs(ns_map, self) + except: pass + + try: data = obj._marshalData() + except: + if (obj_type != "string"): # strings are already encoded + data = cgi.escape(str(obj)) + else: + data = obj + + + return xml % {"tag": tag, "type": t, "data": data, "root": rootattr, + "id": id, "attrs": a} + + def dump_float(self, obj, tag, typed = 1, ns_map = {}): + if Config.debug: print "In dump_float." + tag = tag or self.gentag() + + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + if Config.strict_range: + doubleType(obj) + + if fpconst.isPosInf(obj): + obj = "INF" + elif fpconst.isNegInf(obj): + obj = "-INF" + elif fpconst.isNaN(obj): + obj = "NaN" + else: + obj = str(obj) + + # Note: python 'float' is actually a SOAP 'double'. + self.out.append(self.dumper(None, "double", obj, tag, typed, ns_map, + self.genroot(ns_map))) + + def dump_string(self, obj, tag, typed = 0, ns_map = {}): + if Config.debug: print "In dump_string." + tag = tag or self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: data = obj._marshalData() + except: data = obj + + self.out.append(self.dumper(None, "string", cgi.escape(data), tag, + typed, ns_map, self.genroot(ns_map), id)) + + dump_str = dump_string # For Python 2.2+ + dump_unicode = dump_string + + def dump_None(self, obj, tag, typed = 0, ns_map = {}): + if Config.debug: print "In dump_None." + tag = tag or self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0] + + self.out.append('<%s %snull="1"%s/>\n' % + (tag, ns, self.genroot(ns_map))) + + dump_NoneType = dump_None # For Python 2.2+ + + def dump_list(self, obj, tag, typed = 1, ns_map = {}): + if Config.debug: print "In dump_list.", "obj=", obj + tag = tag or self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + if type(obj) == InstanceType: + data = obj.data + else: + data = obj + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: + sample = data[0] + empty = 0 + except: + # preserve type if present + if getattr(obj,"_typed",None) and getattr(obj,"_type",None): + if getattr(obj, "_complexType", None): + sample = typedArrayType(typed=obj._type, + complexType = obj._complexType) + sample._typename = obj._type + obj._ns = NS.URN + else: + sample = typedArrayType(typed=obj._type) + else: + sample = structType() + empty = 1 + + # First scan list to see if all are the same type + same_type = 1 + + if not empty: + for i in data[1:]: + if type(sample) != type(i) or \ + (type(sample) == InstanceType and \ + sample.__class__ != i.__class__): + same_type = 0 + break + + ndecl = '' + if same_type: + if (isinstance(sample, structType)) or \ + type(sample) == DictType or \ + (isinstance(sample, anyType) and \ + (getattr(sample, "_complexType", None) and \ + sample._complexType)): # force to urn struct + try: + tns = obj._ns or NS.URN + except: + tns = NS.URN + + ns, ndecl = self.genns(ns_map, tns) + + try: + typename = sample._typename + except: + typename = "SOAPStruct" + + t = ns + typename + + elif isinstance(sample, anyType): + ns = sample._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ns, ndecl = self.genns(ns_map, ns) + t = ns + sample._type + else: + t = 'ur-type' + else: + typename = type(sample).__name__ + + # For Python 2.2+ + if type(sample) == StringType: typename = 'string' + + # HACK: python 'float' is actually a SOAP 'double'. + if typename=="float": typename="double" + t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ + typename + + else: + t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ + "ur-type" + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + ens, edecl = self.genns(ns_map, NS.ENC) + ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI) + + self.out.append( + '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' % + (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl, + self.genroot(ns_map), id, a)) + + typed = not same_type + + try: elemsname = obj._elemsname + except: elemsname = "item" + + for i in data: + self.dump(i, elemsname, typed, ns_map) + + self.out.append('\n' % tag) + + dump_tuple = dump_list + + def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}): + if Config.debug: print "In dump_dictionary." + tag = tag or self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + self.out.append('<%s%s%s%s>\n' % + (tag, id, a, self.genroot(ns_map))) + + for (k, v) in obj.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + self.out.append('\n' % tag) + + dump_dict = dump_dictionary # For Python 2.2+ + + def dump_instance(self, obj, tag, typed = 1, ns_map = {}): + if Config.debug: print "In dump_instance.", "obj=", obj, "tag=", tag + if not tag: + # If it has a name use it. + if isinstance(obj, anyType) and obj._name: + tag = obj._name + else: + tag = self.gentag() + tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding + + if isinstance(obj, arrayType): # Array + self.dump_list(obj, tag, typed, ns_map) + return + + if isinstance(obj, faultType): # Fault + cns, cdecl = self.genns(ns_map, NS.ENC) + vns, vdecl = self.genns(ns_map, NS.ENV) + self.out.append('''<%sFault %sroot="1"%s%s> +%s +%s +''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring)) + if hasattr(obj, "detail"): + self.dump(obj.detail, "detail", typed, ns_map) + self.out.append("\n" % vns) + return + + r = self.genroot(ns_map) + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + if isinstance(obj, voidType): # void + self.out.append("<%s%s%s>\n" % (tag, a, r, tag)) + return + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + if isinstance(obj, structType): + # Check for namespace + ndecl = '' + ns = obj._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ns, ndecl = self.genns(ns_map, ns) + tag = ns + tag + self.out.append("<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r)) + + keylist = obj.__dict__.keys() + + # first write out items with order information + if hasattr(obj, '_keyord'): + for i in range(len(obj._keyord)): + self.dump(obj._aslist(i), obj._keyord[i], 1, ns_map) + keylist.remove(obj._keyord[i]) + + # now write out the rest + for k in keylist: + if (k[0] != "_"): + self.dump(getattr(obj,k), k, 1, ns_map) + + if isinstance(obj, bodyType): + self.multis = 1 + + for v, k in self.multirefs: + self.dump(v, k, typed = typed, ns_map = ns_map) + + self.out.append('\n' % tag) + + elif isinstance(obj, anyType): + t = '' + + if typed: + ns = obj._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ons, ondecl = self.genns(ns_map, ns) + ins, indecl = self.genns(ns_map, + self.config.schemaNamespaceURI) + t = ' %stype="%s%s"%s%s' % \ + (ins, ons, obj._type, ondecl, indecl) + + self.out.append('<%s%s%s%s%s>%s\n' % + (tag, t, id, a, r, obj._marshalData(), tag)) + + else: # Some Class + self.out.append('<%s%s%s>\n' % (tag, id, r)) + + for (k, v) in obj.__dict__.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + self.out.append('\n' % tag) + + +################################################################################ +# SOAPBuilder's more public interface +################################################################################ +def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None, + methodattrs=None,envelope=1,encoding='UTF-8',config=Config,noroot = 0): + t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace, + header=header, methodattrs=methodattrs,envelope=envelope, + encoding=encoding, config=config,noroot=noroot) + return t.build() diff --git a/others/SOAPpy/Server.py b/others/SOAPpy/Server.py new file mode 100644 index 000000000..9a24c201c --- /dev/null +++ b/others/SOAPpy/Server.py @@ -0,0 +1,706 @@ +""" +################################################################################ +# +# SOAPpy - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# Gregory Warnes (gregory_r_warnes@groton.pfizer.com) +# Christopher Blunck (blunck@gst.com) +# +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id$' +from version import __version__ + +from __future__ import nested_scopes + +#import xml.sax +import re +import socket +import sys +import SocketServer +from types import * +import BaseHTTPServer +import thread + +# SOAPpy modules +from Parser import parseSOAPRPC +from Config import Config +from Types import faultType, voidType, simplify +from NS import NS +from SOAPBuilder import buildSOAP +from Utilities import debugHeader, debugFooter + +try: from M2Crypto import SSL +except: pass + +ident = '$Id$' + +from version import __version__ + +################################################################################ +# Call context dictionary +################################################################################ + +_contexts = dict() + +def GetSOAPContext(): + global _contexts + return _contexts[thread.get_ident()] + +################################################################################ +# Server +################################################################################ + +# Method Signature class for adding extra info to registered funcs, right now +# used just to indicate it should be called with keywords, instead of ordered +# params. +class MethodSig: + def __init__(self, func, keywords=0, context=0): + self.func = func + self.keywords = keywords + self.context = context + self.__name__ = func.__name__ + + def __call__(self, *args, **kw): + return apply(self.func,args,kw) + +class SOAPContext: + def __init__(self, header, body, attrs, xmldata, connection, httpheaders, + soapaction): + + self.header = header + self.body = body + self.attrs = attrs + self.xmldata = xmldata + self.connection = connection + self.httpheaders= httpheaders + self.soapaction = soapaction + +# A class to describe how header messages are handled +class HeaderHandler: + # Initially fail out if there are any problems. + def __init__(self, header, attrs): + for i in header.__dict__.keys(): + if i[0] == "_": + continue + + d = getattr(header, i) + + try: + fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')]) + except: + fault = 0 + + if fault: + raise faultType, ("%s:MustUnderstand" % NS.ENV_T, + "Required Header Misunderstood", + "%s" % i) + +################################################################################ +# SOAP Server +################################################################################ +class SOAPServerBase: + + def get_request(self): + sock, addr = SocketServer.TCPServer.get_request(self) + + if self.ssl_context: + sock = SSL.Connection(self.ssl_context, sock) + sock._setup_ssl(addr) + if sock.accept_ssl() != 1: + raise socket.error, "Couldn't accept SSL connection" + + return sock, addr + + def registerObject(self, object, namespace = '', path = ''): + if namespace == '' and path == '': namespace = self.namespace + if namespace == '' and path != '': + namespace = path.replace("/", ":") + if namespace[0] == ":": namespace = namespace[1:] + self.objmap[namespace] = object + + def registerFunction(self, function, namespace = '', funcName = None, + path = ''): + if not funcName : funcName = function.__name__ + if namespace == '' and path == '': namespace = self.namespace + if namespace == '' and path != '': + namespace = path.replace("/", ":") + if namespace[0] == ":": namespace = namespace[1:] + if self.funcmap.has_key(namespace): + self.funcmap[namespace][funcName] = function + else: + self.funcmap[namespace] = {funcName : function} + + def registerKWObject(self, object, namespace = '', path = ''): + if namespace == '' and path == '': namespace = self.namespace + if namespace == '' and path != '': + namespace = path.replace("/", ":") + if namespace[0] == ":": namespace = namespace[1:] + for i in dir(object.__class__): + if i[0] != "_" and callable(getattr(object, i)): + self.registerKWFunction(getattr(object,i), namespace) + + # convenience - wraps your func for you. + def registerKWFunction(self, function, namespace = '', funcName = None, + path = ''): + if namespace == '' and path == '': namespace = self.namespace + if namespace == '' and path != '': + namespace = path.replace("/", ":") + if namespace[0] == ":": namespace = namespace[1:] + self.registerFunction(MethodSig(function,keywords=1), namespace, + funcName) + + def unregisterObject(self, object, namespace = '', path = ''): + if namespace == '' and path == '': namespace = self.namespace + if namespace == '' and path != '': + namespace = path.replace("/", ":") + if namespace[0] == ":": namespace = namespace[1:] + + del self.objmap[namespace] + +class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def version_string(self): + return '' + \ + 'SOAPpy ' + __version__ + ' (Python ' + \ + sys.version.split()[0] + ')' + + def date_time_string(self): + self.__last_date_time_string = \ + BaseHTTPServer.BaseHTTPRequestHandler.\ + date_time_string(self) + + return self.__last_date_time_string + + def do_POST(self): + global _contexts + + status = 500 + try: + if self.server.config.dumpHeadersIn: + s = 'Incoming HTTP headers' + debugHeader(s) + print self.raw_requestline.strip() + print "\n".join(map (lambda x: x.strip(), + self.headers.headers)) + debugFooter(s) + + data = self.rfile.read(int(self.headers["Content-length"])) + + if self.server.config.dumpSOAPIn: + s = 'Incoming SOAP' + debugHeader(s) + print data, + if data[-1] != '\n': + print + debugFooter(s) + + (r, header, body, attrs) = \ + parseSOAPRPC(data, header = 1, body = 1, attrs = 1) + + method = r._name + args = r._aslist() + kw = r._asdict() + + if Config.simplify_objects: + args = simplify(args) + kw = simplify(kw) + + # Handle mixed named and unnamed arguments by assuming + # that all arguments with names of the form "v[0-9]+" + # are unnamed and should be passed in numeric order, + # other arguments are named and should be passed using + # this name. + + # This is a non-standard exension to the SOAP protocol, + # but is supported by Apache AXIS. + + # It is enabled by default. To disable, set + # Config.specialArgs to False. + + if Config.specialArgs: + + ordered_args = {} + named_args = {} + + for (k,v) in kw.items(): + + if k[0]=="v": + try: + i = int(k[1:]) + ordered_args[i] = v + except ValueError: + named_args[str(k)] = v + + else: + named_args[str(k)] = v + + # We have to decide namespace precedence + # I'm happy with the following scenario + # if r._ns is specified use it, if not check for + # a path, if it's specified convert it and use it as the + # namespace. If both are specified, use r._ns. + + ns = r._ns + + if len(self.path) > 1 and not ns: + ns = self.path.replace("/", ":") + if ns[0] == ":": ns = ns[1:] + + # authorization method + a = None + + keylist = ordered_args.keys() + keylist.sort() + + # create list in proper order w/o names + tmp = map( lambda x: ordered_args[x], keylist) + ordered_args = tmp + + #print '<-> Argument Matching Yielded:' + #print '<-> Ordered Arguments:' + str(ordered_args) + #print '<-> Named Arguments :' + str(named_args) + + resp = "" + + # For fault messages + if ns: + nsmethod = "%s:%s" % (ns, method) + else: + nsmethod = method + + try: + # First look for registered functions + if self.server.funcmap.has_key(ns) and \ + self.server.funcmap[ns].has_key(method): + f = self.server.funcmap[ns][method] + + # look for the authorization method + if self.server.config.authMethod != None: + authmethod = self.server.config.authMethod + if self.server.funcmap.has_key(ns) and \ + self.server.funcmap[ns].has_key(authmethod): + a = self.server.funcmap[ns][authmethod] + else: + # Now look at registered objects + # Check for nested attributes. This works even if + # there are none, because the split will return + # [method] + f = self.server.objmap[ns] + + # Look for the authorization method + if self.server.config.authMethod != None: + authmethod = self.server.config.authMethod + if hasattr(f, authmethod): + a = getattr(f, authmethod) + + # then continue looking for the method + l = method.split(".") + for i in l: + f = getattr(f, i) + except: + info = sys.exc_info() + try: + resp = buildSOAP(faultType("%s:Client" % NS.ENV_T, + "Method Not Found", + "%s : %s %s %s" % (nsmethod, + info[0], + info[1], + info[2])), + encoding = self.server.encoding, + config = self.server.config) + finally: + del info + status = 500 + else: + try: + if header: + x = HeaderHandler(header, attrs) + + fr = 1 + + # call context book keeping + # We're stuffing the method into the soapaction if there + # isn't one, someday, we'll set that on the client + # and it won't be necessary here + # for now we're doing both + + if "SOAPAction".lower() not in self.headers.keys() or \ + self.headers["SOAPAction"] == "\"\"": + self.headers["SOAPAction"] = method + + thread_id = thread.get_ident() + _contexts[thread_id] = SOAPContext(header, body, + attrs, data, + self.connection, + self.headers, + self.headers["SOAPAction"]) + + # Do an authorization check + if a != None: + if not apply(a, (), {"_SOAPContext" : + _contexts[thread_id] }): + raise faultType("%s:Server" % NS.ENV_T, + "Authorization failed.", + "%s" % nsmethod) + + # If it's wrapped, some special action may be needed + if isinstance(f, MethodSig): + c = None + + if f.context: # retrieve context object + c = _contexts[thread_id] + + if Config.specialArgs: + if c: + named_args["_SOAPContext"] = c + fr = apply(f, ordered_args, named_args) + elif f.keywords: + # This is lame, but have to de-unicode + # keywords + + strkw = {} + + for (k, v) in kw.items(): + strkw[str(k)] = v + if c: + strkw["_SOAPContext"] = c + fr = apply(f, (), strkw) + elif c: + fr = apply(f, args, {'_SOAPContext':c}) + else: + fr = apply(f, args, {}) + + else: + if Config.specialArgs: + fr = apply(f, ordered_args, named_args) + else: + fr = apply(f, args, {}) + + + if type(fr) == type(self) and \ + isinstance(fr, voidType): + resp = buildSOAP(kw = {'%sResponse' % method: fr}, + encoding = self.server.encoding, + config = self.server.config) + else: + resp = buildSOAP(kw = + {'%sResponse' % method: {'Result': fr}}, + encoding = self.server.encoding, + config = self.server.config) + + # Clean up _contexts + if _contexts.has_key(thread_id): + del _contexts[thread_id] + + except Exception, e: + import traceback + info = sys.exc_info() + + try: + if self.server.config.dumpFaultInfo: + s = 'Method %s exception' % nsmethod + debugHeader(s) + traceback.print_exception(info[0], info[1], + info[2]) + debugFooter(s) + + if isinstance(e, faultType): + f = e + else: + f = faultType("%s:Server" % NS.ENV_T, + "Method Failed", + "%s" % nsmethod) + + if self.server.config.returnFaultInfo: + f._setDetail("".join(traceback.format_exception( + info[0], info[1], info[2]))) + elif not hasattr(f, 'detail'): + f._setDetail("%s %s" % (info[0], info[1])) + finally: + del info + + resp = buildSOAP(f, encoding = self.server.encoding, + config = self.server.config) + status = 500 + else: + status = 200 + except faultType, e: + import traceback + info = sys.exc_info() + try: + if self.server.config.dumpFaultInfo: + s = 'Received fault exception' + debugHeader(s) + traceback.print_exception(info[0], info[1], + info[2]) + debugFooter(s) + + if self.server.config.returnFaultInfo: + e._setDetail("".join(traceback.format_exception( + info[0], info[1], info[2]))) + elif not hasattr(e, 'detail'): + e._setDetail("%s %s" % (info[0], info[1])) + finally: + del info + + resp = buildSOAP(e, encoding = self.server.encoding, + config = self.server.config) + status = 500 + except Exception, e: + # internal error, report as HTTP server error + + if self.server.config.dumpFaultInfo: + s = 'Internal exception %s' % e + import traceback + debugHeader(s) + info = sys.exc_info() + try: + traceback.print_exception(info[0], info[1], info[2]) + finally: + del info + + debugFooter(s) + + self.send_response(500) + self.end_headers() + + if self.server.config.dumpHeadersOut and \ + self.request_version != 'HTTP/0.9': + s = 'Outgoing HTTP headers' + debugHeader(s) + if self.responses.has_key(status): + s = ' ' + self.responses[status][0] + else: + s = '' + print "%s %d%s" % (self.protocol_version, 500, s) + print "Server:", self.version_string() + print "Date:", self.__last_date_time_string + debugFooter(s) + else: + # got a valid SOAP response + self.send_response(status) + + t = 'text/xml'; + if self.server.encoding != None: + t += '; charset="%s"' % self.server.encoding + self.send_header("Content-type", t) + self.send_header("Content-length", str(len(resp))) + self.end_headers() + + if self.server.config.dumpHeadersOut and \ + self.request_version != 'HTTP/0.9': + s = 'Outgoing HTTP headers' + debugHeader(s) + if self.responses.has_key(status): + s = ' ' + self.responses[status][0] + else: + s = '' + print "%s %d%s" % (self.protocol_version, status, s) + print "Server:", self.version_string() + print "Date:", self.__last_date_time_string + print "Content-type:", t + print "Content-length:", len(resp) + debugFooter(s) + + if self.server.config.dumpSOAPOut: + s = 'Outgoing SOAP' + debugHeader(s) + print resp, + if resp[-1] != '\n': + print + debugFooter(s) + + self.wfile.write(resp) + self.wfile.flush() + + # We should be able to shut down both a regular and an SSL + # connection, but under Python 2.1, calling shutdown on an + # SSL connections drops the output, so this work-around. + # This should be investigated more someday. + + if self.server.config.SSLserver and \ + isinstance(self.connection, SSL.Connection): + self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN | + SSL.SSL_RECEIVED_SHUTDOWN) + else: + self.connection.shutdown(1) + + def do_GET(self): + + #print 'command ', self.command + #print 'path ', self.path + #print 'request_version', self.request_version + #print 'headers' + #print ' type ', self.headers.type + #print ' maintype', self.headers.maintype + #print ' subtype ', self.headers.subtype + #print ' params ', self.headers.plist + + path = self.path.lower() + if path.endswith('wsdl'): + method = 'wsdl' + function = namespace = None + if self.server.funcmap.has_key(namespace) \ + and self.server.funcmap[namespace].has_key(method): + function = self.server.funcmap[namespace][method] + else: + if namespace in self.server.objmap.keys(): + function = self.server.objmap[namespace] + l = method.split(".") + for i in l: + function = getattr(function, i) + + if function: + self.send_response(200) + self.send_header("Content-type", 'text/plain') + self.end_headers() + response = apply(function, ()) + self.wfile.write(str(response)) + return + + # return error + self.send_response(200) + self.send_header("Content-type", 'text/html') + self.end_headers() + self.wfile.write('''\ + +<head>Error!</head> + + + +

Oops!

+ +

+ This server supports HTTP GET requests only for the the purpose of + obtaining Web Services Description Language (WSDL) for a specific + service. + + Either you requested an URL that does not end in "wsdl" or this + server does not implement a wsdl method. +

+ + +''') + + + def log_message(self, format, *args): + if self.server.log: + BaseHTTPServer.BaseHTTPRequestHandler.\ + log_message (self, format, *args) + + + +class SOAPServer(SOAPServerBase, SocketServer.TCPServer): + + def __init__(self, addr = ('localhost', 8000), + RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8', + config = Config, namespace = None, ssl_context = None): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + if ssl_context != None and not config.SSLserver: + raise AttributeError, \ + "SSL server not supported by this Python installation" + + self.namespace = namespace + self.objmap = {} + self.funcmap = {} + self.ssl_context = ssl_context + self.encoding = encoding + self.config = config + self.log = log + + self.allow_reuse_address= 1 + + SocketServer.TCPServer.__init__(self, addr, RequestHandler) + + +class ThreadingSOAPServer(SOAPServerBase, SocketServer.ThreadingTCPServer): + + def __init__(self, addr = ('localhost', 8000), + RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8', + config = Config, namespace = None, ssl_context = None): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + if ssl_context != None and not config.SSLserver: + raise AttributeError, \ + "SSL server not supported by this Python installation" + + self.namespace = namespace + self.objmap = {} + self.funcmap = {} + self.ssl_context = ssl_context + self.encoding = encoding + self.config = config + self.log = log + + self.allow_reuse_address= 1 + + SocketServer.ThreadingTCPServer.__init__(self, addr, RequestHandler) + +# only define class if Unix domain sockets are available +if hasattr(socket, "AF_UNIX"): + + class SOAPUnixSocketServer(SOAPServerBase, SocketServer.UnixStreamServer): + + def __init__(self, addr = 8000, + RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8', + config = Config, namespace = None, ssl_context = None): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + if ssl_context != None and not config.SSLserver: + raise AttributeError, \ + "SSL server not supported by this Python installation" + + self.namespace = namespace + self.objmap = {} + self.funcmap = {} + self.ssl_context = ssl_context + self.encoding = encoding + self.config = config + self.log = log + + self.allow_reuse_address= 1 + + SocketServer.UnixStreamServer.__init__(self, str(addr), RequestHandler) + diff --git a/others/SOAPpy/Types.py b/others/SOAPpy/Types.py new file mode 100644 index 000000000..9846eda88 --- /dev/null +++ b/others/SOAPpy/Types.py @@ -0,0 +1,1734 @@ +""" +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id$' +from version import __version__ + +from __future__ import nested_scopes + +import UserList +import base64 +import cgi +import urllib +import copy +import re +import time +from types import * + +# SOAPpy modules +from Errors import * +from NS import NS +from Utilities import encodeHexString, cleanDate +from Config import Config + +############################################################################### +# Utility functions +############################################################################### + +def isPrivate(name): return name[0]=='_' +def isPublic(name): return name[0]!='_' + +############################################################################### +# Types and Wrappers +############################################################################### + +class anyType: + _validURIs = (NS.XSD, NS.XSD2, NS.XSD3, NS.ENC) + + def __init__(self, data = None, name = None, typed = 1, attrs = None): + if self.__class__ == anyType: + raise Error, "anyType can't be instantiated directly" + + if type(name) in (ListType, TupleType): + self._ns, self._name = name + else: + self._ns = self._validURIs[0] + self._name = name + + self._typed = typed + self._attrs = {} + + self._cache = None + self._type = self._typeName() + + self._data = self._checkValueSpace(data) + + if attrs != None: + self._setAttrs(attrs) + + def __str__(self): + if hasattr(self,'_name') and self._name: + return "<%s %s at %d>" % (self.__class__, self._name, id(self)) + return "<%s at %d>" % (self.__class__, id(self)) + + __repr__ = __str__ + + def _checkValueSpace(self, data): + return data + + def _marshalData(self): + return str(self._data) + + def _marshalAttrs(self, ns_map, builder): + a = '' + + for attr, value in self._attrs.items(): + ns, n = builder.genns(ns_map, attr[0]) + a += n + ' %s%s="%s"' % \ + (ns, attr[1], cgi.escape(str(value), 1)) + + return a + + def _fixAttr(self, attr): + if type(attr) in (StringType, UnicodeType): + attr = (None, attr) + elif type(attr) == ListType: + attr = tuple(attr) + elif type(attr) != TupleType: + raise AttributeError, "invalid attribute type" + + if len(attr) != 2: + raise AttributeError, "invalid attribute length" + + if type(attr[0]) not in (NoneType, StringType, UnicodeType): + raise AttributeError, "invalid attribute namespace URI type" + + return attr + + def _getAttr(self, attr): + attr = self._fixAttr(attr) + + try: + return self._attrs[attr] + except: + return None + + def _setAttr(self, attr, value): + attr = self._fixAttr(attr) + + if type(value) is StringType: + value = unicode(value) + + self._attrs[attr] = value + + + def _setAttrs(self, attrs): + if type(attrs) in (ListType, TupleType): + for i in range(0, len(attrs), 2): + self._setAttr(attrs[i], attrs[i + 1]) + + return + + if type(attrs) == DictType: + d = attrs + elif isinstance(attrs, anyType): + d = attrs._attrs + else: + raise AttributeError, "invalid attribute type" + + for attr, value in d.items(): + self._setAttr(attr, value) + + def _setMustUnderstand(self, val): + self._setAttr((NS.ENV, "mustUnderstand"), val) + + def _getMustUnderstand(self): + return self._getAttr((NS.ENV, "mustUnderstand")) + + def _setActor(self, val): + self._setAttr((NS.ENV, "actor"), val) + + def _getActor(self): + return self._getAttr((NS.ENV, "actor")) + + def _typeName(self): + return self.__class__.__name__[:-4] + + def _validNamespaceURI(self, URI, strict): + if not hasattr(self, '_typed') or not self._typed: + return None + if URI in self._validURIs: + return URI + if not strict: + return self._ns + raise AttributeError, \ + "not a valid namespace for type %s" % self._type + +class voidType(anyType): + pass + +class stringType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type:" % self._type + + return data + +class untypedType(stringType): + def __init__(self, data = None, name = None, attrs = None): + stringType.__init__(self, data, name, 0, attrs) + +class IDType(stringType): pass +class NCNameType(stringType): pass +class NameType(stringType): pass +class ENTITYType(stringType): pass +class IDREFType(stringType): pass +class languageType(stringType): pass +class NMTOKENType(stringType): pass +class QNameType(stringType): pass + +class tokenType(anyType): + _validURIs = (NS.XSD2, NS.XSD3) + __invalidre = '[\n\t]|^ | $| ' + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + if type(self.__invalidre) == StringType: + self.__invalidre = re.compile(self.__invalidre) + + if self.__invalidre.search(data): + raise ValueError, "invalid %s value" % self._type + + return data + +class normalizedStringType(anyType): + _validURIs = (NS.XSD3,) + __invalidre = '[\n\r\t]' + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + if type(self.__invalidre) == StringType: + self.__invalidre = re.compile(self.__invalidre) + + if self.__invalidre.search(data): + raise ValueError, "invalid %s value" % self._type + + return data + +class CDATAType(normalizedStringType): + _validURIs = (NS.XSD2,) + +class booleanType(anyType): + def __int__(self): + return self._data + + __nonzero__ = __int__ + + def _marshalData(self): + return ['false', 'true'][self._data] + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if data in (0, '0', 'false', ''): + return 0 + if data in (1, '1', 'true'): + return 1 + raise ValueError, "invalid %s value" % self._type + +class decimalType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType): + raise Error, "invalid %s value" % self._type + + return data + +class floatType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType) or \ + data < -3.4028234663852886E+38 or \ + data > 3.4028234663852886E+38: + raise ValueError, "invalid %s value: %s" % (self._type, repr(data)) + + return data + + def _marshalData(self): + return "%.18g" % self._data # More precision + +class doubleType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType) or \ + data < -1.7976931348623158E+308 or \ + data > 1.7976931348623157E+308: + raise ValueError, "invalid %s value: %s" % (self._type, repr(data)) + + return data + + def _marshalData(self): + return "%.18g" % self._data # More precision + +class durationType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + try: + # A tuple or a scalar is OK, but make them into a list + + if type(data) == TupleType: + data = list(data) + elif type(data) != ListType: + data = [data] + + if len(data) > 6: + raise Exception, "too many values" + + # Now check the types of all the components, and find + # the first nonzero element along the way. + + f = -1 + + for i in range(len(data)): + if data[i] == None: + data[i] = 0 + continue + + if type(data[i]) not in \ + (IntType, LongType, FloatType): + raise Exception, "element %d a bad type" % i + + if data[i] and f == -1: + f = i + + # If they're all 0, just use zero seconds. + + if f == -1: + self._cache = 'PT0S' + + return (0,) * 6 + + # Make sure only the last nonzero element has a decimal fraction + # and only the first element is negative. + + d = -1 + + for i in range(f, len(data)): + if data[i]: + if d != -1: + raise Exception, \ + "all except the last nonzero element must be " \ + "integers" + if data[i] < 0 and i > f: + raise Exception, \ + "only the first nonzero element can be negative" + elif data[i] != long(data[i]): + d = i + + # Pad the list on the left if necessary. + + if len(data) < 6: + n = 6 - len(data) + f += n + d += n + data = [0] * n + data + + # Save index of the first nonzero element and the decimal + # element for _marshalData. + + self.__firstnonzero = f + self.__decimal = d + + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + t = 0 + + if d[self.__firstnonzero] < 0: + s = '-P' + else: + s = 'P' + + t = 0 + + for i in range(self.__firstnonzero, len(d)): + if d[i]: + if i > 2 and not t: + s += 'T' + t = 1 + if self.__decimal == i: + s += "%g" % abs(d[i]) + else: + s += "%d" % long(abs(d[i])) + s += ['Y', 'M', 'D', 'H', 'M', 'S'][i] + + self._cache = s + + return self._cache + +class timeDurationType(durationType): + _validURIs = (NS.XSD, NS.XSD2, NS.ENC) + +class dateTimeType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.time() + + if (type(data) in (IntType, LongType)): + data = list(time.gmtime(data)[:6]) + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[:6]) + data[5] += f + elif type(data) in (ListType, TupleType): + if len(data) < 6: + raise Exception, "not enough values" + if len(data) > 9: + raise Exception, "too many values" + + data = list(data[:6]) + + cleanDate(data) + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02d-%02dT%02d:%02d:%02d" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + f = d[5] - int(d[5]) + if f != 0: + s += ("%g" % f)[1:] + s += 'Z' + + self._cache = s + + return self._cache + +class recurringInstantType(anyType): + _validURIs = (NS.XSD,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = list(time.gmtime(time.time())[:6]) + if (type(data) in (IntType, LongType)): + data = list(time.gmtime(data)[:6]) + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[:6]) + data[5] += f + elif type(data) in (ListType, TupleType): + if len(data) < 1: + raise Exception, "not enough values" + if len(data) > 9: + raise Exception, "too many values" + + data = list(data[:6]) + + if len(data) < 6: + data += [0] * (6 - len(data)) + + f = len(data) + + for i in range(f): + if data[i] == None: + if f < i: + raise Exception, \ + "only leftmost elements can be none" + else: + f = i + break + + cleanDate(data, f) + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + e = list(d) + neg = '' + + if not e[0]: + e[0] = '--' + else: + if e[0] < 0: + neg = '-' + e[0] = abs(e[0]) + if e[0] < 100: + e[0] = '-' + "%02d" % e[0] + else: + e[0] = "%04d" % e[0] + + for i in range(1, len(e)): + if e[i] == None or (i < 3 and e[i] == 0): + e[i] = '-' + else: + if e[i] < 0: + neg = '-' + e[i] = abs(e[i]) + + e[i] = "%02d" % e[i] + + if d[5]: + f = abs(d[5] - int(d[5])) + + if f: + e[5] += ("%g" % f)[1:] + + s = "%s%s-%s-%sT%s:%s:%sZ" % ((neg,) + tuple(e)) + + self._cache = s + + return self._cache + +class timeInstantType(dateTimeType): + _validURIs = (NS.XSD, NS.XSD2, NS.ENC) + +class timePeriodType(dateTimeType): + _validURIs = (NS.XSD2, NS.ENC) + +class timeType(anyType): + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[3:6] + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[3:6]) + data[2] += f + elif type(data) in (IntType, LongType): + data = time.gmtime(data)[3:6] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[3:6] + elif len(data) > 3: + raise Exception, "too many values" + + data = [None, None, None] + list(data) + + if len(data) < 6: + data += [0] * (6 - len(data)) + + cleanDate(data, 3) + + data = data[3:] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = '' + + s = time.strftime("%H:%M:%S", (0, 0, 0) + d + (0, 0, -1)) + f = d[2] - int(d[2]) + if f != 0: + s += ("%g" % f)[1:] + s += 'Z' + + self._cache = s + + return self._cache + +class dateType(anyType): + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:3] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[0:3] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:3] + elif len(data) > 3: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 3: + data += [1, 1, 1][len(data):] + + data += [0, 0, 0] + + cleanDate(data) + + data = data[:3] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02d-%02dZ" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class gYearMonthType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:2] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[0:2] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:2] + elif len(data) > 2: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 2: + data += [1, 1][len(data):] + + data += [1, 0, 0, 0] + + cleanDate(data) + + data = data[:2] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02dZ" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class gYearType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:1] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:1] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04dZ" % abs(d) + if d < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class centuryType(anyType): + _validURIs = (NS.XSD2, NS.ENC) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:1] / 100 + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:1] / 100 + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%02dZ" % abs(d) + if d < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class yearType(gYearType): + _validURIs = (NS.XSD2, NS.ENC) + +class gMonthDayType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[1:3] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[1:3] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:2] + elif len(data) > 2: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 2: + data += [1, 1][len(data):] + + data = [0] + data + [0, 0, 0] + + cleanDate(data, 1) + + data = data[1:3] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + self._cache = "--%02d-%02dZ" % self._data + + return self._cache + +class recurringDateType(gMonthDayType): + _validURIs = (NS.XSD2, NS.ENC) + +class gMonthType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[1:2] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[1:2] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + + if data[0] < 1 or data[0] > 12: + raise Exception, "bad value" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + self._cache = "--%02d--Z" % self._data + + return self._cache + +class monthType(gMonthType): + _validURIs = (NS.XSD2, NS.ENC) + +class gDayType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[2:3] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[2:3] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + + if data[0] < 1 or data[0] > 31: + raise Exception, "bad value" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + self._cache = "---%02dZ" % self._data + + return self._cache + +class recurringDayType(gDayType): + _validURIs = (NS.XSD2, NS.ENC) + +class hexBinaryType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = encodeHexString(self._data) + + return self._cache + +class base64BinaryType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = base64.encodestring(self._data) + + return self._cache + +class base64Type(base64BinaryType): + _validURIs = (NS.ENC,) + +class binaryType(anyType): + _validURIs = (NS.XSD, NS.ENC) + + def __init__(self, data, name = None, typed = 1, encoding = 'base64', + attrs = None): + + anyType.__init__(self, data, name, typed, attrs) + + self._setAttr('encoding', encoding) + + def _marshalData(self): + if self._cache == None: + if self._getAttr((None, 'encoding')) == 'base64': + self._cache = base64.encodestring(self._data) + else: + self._cache = encodeHexString(self._data) + + return self._cache + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _setAttr(self, attr, value): + attr = self._fixAttr(attr) + + if attr[1] == 'encoding': + if attr[0] != None or value not in ('base64', 'hex'): + raise AttributeError, "invalid encoding" + + self._cache = None + + anyType._setAttr(self, attr, value) + + +class anyURIType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = urllib.quote(self._data) + + return self._cache + +class uriType(anyURIType): + _validURIs = (NS.XSD,) + +class uriReferenceType(anyURIType): + _validURIs = (NS.XSD2,) + +class NOTATIONType(anyType): + def __init__(self, data, name = None, typed = 1, attrs = None): + + if self.__class__ == NOTATIONType: + raise Error, "a NOTATION can't be instantiated directly" + + anyType.__init__(self, data, name, typed, attrs) + +class ENTITIESType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) in (StringType, UnicodeType): + return (data,) + + if type(data) not in (ListType, TupleType) or \ + filter (lambda x: type(x) not in (StringType, UnicodeType), data): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + return ' '.join(self._data) + +class IDREFSType(ENTITIESType): pass +class NMTOKENSType(ENTITIESType): pass + +class integerType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType): + raise ValueError, "invalid %s value" % self._type + + return data + +class nonPositiveIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data > 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class non_Positive_IntegerType(nonPositiveIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'non-positive-integer' + +class negativeIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data >= 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class negative_IntegerType(negativeIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'negative-integer' + +class longType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -9223372036854775808L or \ + data > 9223372036854775807L: + raise ValueError, "invalid %s value" % self._type + + return data + +class intType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -2147483648L or \ + data > 2147483647: + raise ValueError, "invalid %s value" % self._type + + return data + +class shortType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -32768 or \ + data > 32767: + raise ValueError, "invalid %s value" % self._type + + return data + +class byteType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -128 or \ + data > 127: + raise ValueError, "invalid %s value" % self._type + + return data + +class nonNegativeIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data < 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class non_Negative_IntegerType(nonNegativeIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'non-negative-integer' + +class unsignedLongType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 18446744073709551615L: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedIntType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 4294967295L: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedShortType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 65535: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedByteType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 255: + raise ValueError, "invalid %s value" % self._type + + return data + +class positiveIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data <= 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class positive_IntegerType(positiveIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'positive-integer' + +# Now compound types + +class compoundType(anyType): + def __init__(self, data = None, name = None, typed = 1, attrs = None): + if self.__class__ == compoundType: + raise Error, "a compound can't be instantiated directly" + + anyType.__init__(self, data, name, typed, attrs) + self._keyord = [] + + if type(data) == DictType: + self.__dict__.update(data) + + def _aslist(self, item=None): + if item: + return self.__dict__[self._keyord[item]] + else: + return map( lambda x: self.__dict__[x], self._keyord) + + def _asdict(self, item=None, encoding=Config.dict_encoding): + if item: + if type(item) in (UnicodeType,StringType): + item = item.encode(encoding) + return self.__dict__[item] + else: + retval = {} + def fun(x): retval[x.encode(encoding)] = self.__dict__[x] + + if hasattr(self, '_keyord'): + map( fun, self._keyord) + else: + for name in dir(self): + if isPublic(name): + retval[name] = getattr(self,name) + return retval + + + def __getitem__(self, item): + if type(item) == IntType: + return self.__dict__[self._keyord[item]] + else: + return getattr(self, item) + + def __len__(self): + return len(self._keyord) + + def __nonzero__(self): + return 1 + + def _keys(self): + return filter(lambda x: x[0] != '_', self.__dict__.keys()) + + def _addItem(self, name, value, attrs = None): + + if name in self._keyord: + if type(self.__dict__[name]) != ListType: + self.__dict__[name] = [self.__dict__[name]] + self.__dict__[name].append(value) + else: + self.__dict__[name] = value + self._keyord.append(name) + + def _placeItem(self, name, value, pos, subpos = 0, attrs = None): + + if subpos == 0 and type(self.__dict__[name]) != ListType: + self.__dict__[name] = value + else: + self.__dict__[name][subpos] = value + + self._keyord[pos] = name + + + def _getItemAsList(self, name, default = []): + try: + d = self.__dict__[name] + except: + return default + + if type(d) == ListType: + return d + return [d] + + def __str__(self): + return anyType.__str__(self) + ": " + str(self._asdict()) + + def __repr__(self): + return self.__str__() + +class structType(compoundType): + pass + +class headerType(structType): + _validURIs = (NS.ENV,) + + def __init__(self, data = None, typed = 1, attrs = None): + structType.__init__(self, data, "Header", typed, attrs) + +class bodyType(structType): + _validURIs = (NS.ENV,) + + def __init__(self, data = None, typed = 1, attrs = None): + structType.__init__(self, data, "Body", typed, attrs) + +class arrayType(UserList.UserList, compoundType): + def __init__(self, data = None, name = None, attrs = None, + offset = 0, rank = None, asize = 0, elemsname = None): + + if data: + if type(data) not in (ListType, TupleType): + raise Error, "Data must be a sequence" + + UserList.UserList.__init__(self, data) + compoundType.__init__(self, data, name, 0, attrs) + + self._elemsname = elemsname or "item" + + if data == None: + self._rank = rank + + # According to 5.4.2.2 in the SOAP spec, each element in a + # sparse array must have a position. _posstate keeps track of + # whether we've seen a position or not. It's possible values + # are: + # -1 No elements have been added, so the state is indeterminate + # 0 An element without a position has been added, so no + # elements can have positions + # 1 An element with a position has been added, so all elements + # must have positions + + self._posstate = -1 + + self._full = 0 + + if asize in ('', None): + asize = '0' + + self._dims = map (lambda x: int(x), str(asize).split(',')) + self._dims.reverse() # It's easier to work with this way + self._poss = [0] * len(self._dims) # This will end up + # reversed too + + for i in range(len(self._dims)): + if self._dims[i] < 0 or \ + self._dims[i] == 0 and len(self._dims) > 1: + raise TypeError, "invalid Array dimensions" + + if offset > 0: + self._poss[i] = offset % self._dims[i] + offset = int(offset / self._dims[i]) + + # Don't break out of the loop if offset is 0 so we test all the + # dimensions for > 0. + if offset: + raise AttributeError, "invalid Array offset" + + a = [None] * self._dims[0] + + for i in range(1, len(self._dims)): + b = [] + + for j in range(self._dims[i]): + b.append(copy.deepcopy(a)) + + a = b + + self.data = a + + + def _aslist(self, item=None): + if item: + return self.data[int(item)] + else: + return self.data + + def _asdict(self, item=None, encoding=Config.dict_encoding): + if item: + if type(item) in (UnicodeType,StringType): + item = item.encode(encoding) + return self.data[int(item)] + else: + retval = {} + def fun(x): retval[str(x).encode(encoding)] = self.data[x] + + map( fun, range(len(self.data)) ) + return retval + + def __getitem__(self, item): + try: + return self.data[int(item)] + except ValueError: + return getattr(self, item) + + def __len__(self): + return len(self.data) + + def __nonzero__(self): + return 1 + + def __str__(self): + return anyType.__str__(self) + ": " + str(self._aslist()) + + def _keys(self): + return filter(lambda x: x[0] != '_', self.__dict__.keys()) + + def _addItem(self, name, value, attrs): + if self._full: + raise ValueError, "Array is full" + + pos = attrs.get((NS.ENC, 'position')) + + if pos != None: + if self._posstate == 0: + raise AttributeError, \ + "all elements in a sparse Array must have a " \ + "position attribute" + + self._posstate = 1 + + try: + if pos[0] == '[' and pos[-1] == ']': + pos = map (lambda x: int(x), pos[1:-1].split(',')) + pos.reverse() + + if len(pos) == 1: + pos = pos[0] + + curpos = [0] * len(self._dims) + + for i in range(len(self._dims)): + curpos[i] = pos % self._dims[i] + pos = int(pos / self._dims[i]) + + if pos == 0: + break + + if pos: + raise Exception + elif len(pos) != len(self._dims): + raise Exception + else: + for i in range(len(self._dims)): + if pos[i] >= self._dims[i]: + raise Exception + + curpos = pos + else: + raise Exception + except: + raise AttributeError, \ + "invalid Array element position %s" % str(pos) + else: + if self._posstate == 1: + raise AttributeError, \ + "only elements in a sparse Array may have a " \ + "position attribute" + + self._posstate = 0 + + curpos = self._poss + + a = self.data + + for i in range(len(self._dims) - 1, 0, -1): + a = a[curpos[i]] + + if curpos[0] >= len(a): + a += [None] * (len(a) - curpos[0] + 1) + + a[curpos[0]] = value + + if pos == None: + self._poss[0] += 1 + + for i in range(len(self._dims) - 1): + if self._poss[i] < self._dims[i]: + break + + self._poss[i] = 0 + self._poss[i + 1] += 1 + + if self._dims[-1] and self._poss[-1] >= self._dims[-1]: + self._full = 1 + + def _placeItem(self, name, value, pos, subpos, attrs = None): + curpos = [0] * len(self._dims) + + for i in range(len(self._dims)): + if self._dims[i] == 0: + curpos[0] = pos + break + + curpos[i] = pos % self._dims[i] + pos = int(pos / self._dims[i]) + + if pos == 0: + break + + if self._dims[i] != 0 and pos: + raise Error, "array index out of range" + + a = self.data + + for i in range(len(self._dims) - 1, 0, -1): + a = a[curpos[i]] + + if curpos[0] >= len(a): + a += [None] * (len(a) - curpos[0] + 1) + + a[curpos[0]] = value + +class typedArrayType(arrayType): + def __init__(self, data = None, name = None, typed = None, attrs = None, + offset = 0, rank = None, asize = 0, elemsname = None, complexType = 0): + + arrayType.__init__(self, data, name, attrs, offset, rank, asize, + elemsname) + + self._typed = 1 + self._type = typed + self._complexType = complexType + +class faultType(structType, Error): + def __init__(self, faultcode = "", faultstring = "", detail = None): + self.faultcode = faultcode + self.faultstring = faultstring + if detail != None: + self.detail = detail + + structType.__init__(self, None, 0) + + def _setDetail(self, detail = None): + if detail != None: + self.detail = detail + else: + try: del self.detail + except AttributeError: pass + + def __repr__(self): + if getattr(self, 'detail', None) != None: + return "" % (self.faultcode, + self.faultstring, + self.detail) + else: + return "" % (self.faultcode, self.faultstring) + + __str__ = __repr__ + + def __call__(self): + return (self.faultcode, self.faultstring, self.detail) + +class SOAPException(Exception): + def __init__(self, code="", string="", detail=None): + self.value = ("SOAPpy SOAP Exception", code, string, detail) + self.code = code + self.string = string + self.detail = detail + + def __str__(self): + return repr(self.value) + +class RequiredHeaderMismatch(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +class MethodNotFound(Exception): + def __init__(self, value): + (val, detail) = value.split(":") + self.value = val + self.detail = detail + + def __str__(self): + return repr(self.value, self.detail) + +class AuthorizationFailed(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +class MethodFailed(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +####### +# Convert complex SOAPpy objects to native python equivalents +####### + +def simplify(object, level=0): + """ + Convert the SOAPpy objects and thier contents to simple python types. + + This function recursively converts the passed 'container' object, + and all public subobjects. (Private subobjects have names that + start with '_'.) + + Conversions: + - faultType --> raise python exception + - arrayType --> array + - compoundType --> dictionary + """ + + if level > 10: + return object + + if isinstance( object, faultType ): + if object.faultstring == "Required Header Misunderstood": + raise RequiredHeaderMismatch(object.detail) + elif object.faultstring == "Method Not Found": + raise MethodNotFound(object.detail) + elif object.faultstring == "Authorization Failed": + raise AuthorizationFailed(object.detail) + elif object.faultstring == "Method Failed": + raise MethodFailed(object.detail) + else: + se = SOAPException(object.faultcode, object.faultstring, + object.detail) + raise se + elif isinstance( object, arrayType ): + data = object._aslist() + for k in range(len(data)): + data[k] = simplify(data[k], level=level+1) + return data + elif isinstance( object, compoundType ) or isinstance(object, structType): + data = object._asdict() + for k in data.keys(): + if isPublic(k): + data[k] = simplify(data[k], level=level+1) + return data + elif type(object)==DictType: + for k in object.keys(): + if isPublic(k): + object[k] = simplify(object[k]) + return object + elif type(object)==list: + for k in range(len(object)): + object[k] = simplify(object[k]) + return object + else: + return object + + +def simplify_contents(object, level=0): + """ + Convert the contents of SOAPpy objects to simple python types. + + This function recursively converts the sub-objects contained in a + 'container' object to simple python types. + + Conversions: + - faultType --> raise python exception + - arrayType --> array + - compoundType --> dictionary + """ + + if level>10: return object + + if isinstance( object, faultType ): + for k in object._keys(): + if isPublic(k): + setattr(object, k, simplify(object[k], level=level+1)) + raise object + elif isinstance( object, arrayType ): + data = object._aslist() + for k in range(len(data)): + object[k] = simplify(data[k], level=level+1) + elif isinstance(object, structType): + data = object._asdict() + for k in data.keys(): + if isPublic(k): + setattr(object, k, simplify(data[k], level=level+1)) + elif isinstance( object, compoundType ) : + data = object._asdict() + for k in data.keys(): + if isPublic(k): + object[k] = simplify(data[k], level=level+1) + elif type(object)==DictType: + for k in object.keys(): + if isPublic(k): + object[k] = simplify(object[k]) + elif type(object)==list: + for k in range(len(object)): + object[k] = simplify(object[k]) + + return object + + diff --git a/others/SOAPpy/URLopener.py b/others/SOAPpy/URLopener.py new file mode 100644 index 000000000..09d4b8410 --- /dev/null +++ b/others/SOAPpy/URLopener.py @@ -0,0 +1,23 @@ +"""Provide a class for loading data from URL's that handles basic +authentication""" + +ident = '$Id$' +from version import __version__ + +from Config import Config +from urllib import FancyURLopener + +class URLopener(FancyURLopener): + + username = None + passwd = None + + + def __init__(self, username=None, passwd=None, *args, **kw): + FancyURLopener.__init__( self, *args, **kw) + self.username = username + self.passwd = passwd + + + def prompt_user_passwd(self, host, realm): + return self.username, self.passwd diff --git a/others/SOAPpy/Utilities.py b/others/SOAPpy/Utilities.py new file mode 100644 index 000000000..5944ee170 --- /dev/null +++ b/others/SOAPpy/Utilities.py @@ -0,0 +1,178 @@ +""" +################################################################################ +# Copyright (c) 2003, Pfizer +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +""" + +ident = '$Id$' +from version import __version__ + +import exceptions +import copy +import re +import string +import sys +from types import * + +# SOAPpy modules +from Errors import * + +################################################################################ +# Utility infielders +################################################################################ +def collapseWhiteSpace(s): + return re.sub('\s+', ' ', s).strip() + +def decodeHexString(data): + conv = { + '0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4, + '5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9, + + 'a': 0xa, 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe, + 'f': 0xf, + + 'A': 0xa, 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe, + 'F': 0xf, + } + + ws = string.whitespace + + bin = '' + + i = 0 + + while i < len(data): + if data[i] not in ws: + break + i += 1 + + low = 0 + + while i < len(data): + c = data[i] + + if c in string.whitespace: + break + + try: + c = conv[c] + except KeyError: + raise ValueError, \ + "invalid hex string character `%s'" % c + + if low: + bin += chr(high * 16 + c) + low = 0 + else: + high = c + low = 1 + + i += 1 + + if low: + raise ValueError, "invalid hex string length" + + while i < len(data): + if data[i] not in string.whitespace: + raise ValueError, \ + "invalid hex string character `%s'" % c + + i += 1 + + return bin + +def encodeHexString(data): + h = '' + + for i in data: + h += "%02X" % ord(i) + + return h + +def leapMonth(year, month): + return month == 2 and \ + year % 4 == 0 and \ + (year % 100 != 0 or year % 400 == 0) + +def cleanDate(d, first = 0): + ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61)) + months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds') + + if len(d) != 6: + raise ValueError, "date must have 6 elements" + + for i in range(first, 6): + s = d[i] + + if type(s) == FloatType: + if i < 5: + try: + s = int(s) + except OverflowError: + if i > 0: + raise + s = long(s) + + if s != d[i]: + raise ValueError, "%s must be integral" % names[i] + + d[i] = s + elif type(s) == LongType: + try: s = int(s) + except: pass + elif type(s) != IntType: + raise TypeError, "%s isn't a valid type" % names[i] + + if i == first and s < 0: + continue + + if ranges[i] != None and \ + (s < ranges[i][0] or ranges[i][1] < s): + raise ValueError, "%s out of range" % names[i] + + if first < 6 and d[5] >= 61: + raise ValueError, "seconds out of range" + + if first < 2: + leap = first < 1 and leapMonth(d[0], d[1]) + + if d[2] > months[d[1]] + leap: + raise ValueError, "day out of range" + +def debugHeader(title): + s = '*** ' + title + ' ' + print s + ('*' * (72 - len(s))) + +def debugFooter(title): + print '*' * 72 + sys.stdout.flush() diff --git a/others/SOAPpy/WSDL.py b/others/SOAPpy/WSDL.py new file mode 100644 index 000000000..67c20c15a --- /dev/null +++ b/others/SOAPpy/WSDL.py @@ -0,0 +1,102 @@ +"""Parse web services description language to get SOAP methods. + +Rudimentary support.""" + +ident = '$Id$' +from version import __version__ + +import wstools +from Client import SOAPProxy, SOAPAddress +from Config import Config +import urllib + +class Proxy: + """WSDL Proxy. + + SOAPProxy wrapper that parses method names, namespaces, soap actions from + the web service description language (WSDL) file passed into the + constructor. The WSDL reference can be passed in as a stream, an url, a + file name, or a string. + + Loads info into self.methods, a dictionary with methodname keys and values + of WSDLTools.SOAPCallinfo. + + For example, + + url = 'http://www.xmethods.org/sd/2001/TemperatureService.wsdl' + wsdl = WSDL.Proxy(url) + print len(wsdl.methods) # 1 + print wsdl.methods.keys() # getTemp + + + See WSDLTools.SOAPCallinfo for more info on each method's attributes. + """ + + def __init__(self, wsdlsource, config=Config, **kw ): + + reader = wstools.WSDLTools.WSDLReader() + self.wsdl = None + + # From Mark Pilgrim's "Dive Into Python" toolkit.py--open anything. + if self.wsdl is None and hasattr(wsdlsource, "read"): + #print 'stream' + self.wsdl = reader.loadFromStream(wsdlsource) + + # NOT TESTED (as of April 17, 2003) + #if self.wsdl is None and wsdlsource == '-': + # import sys + # self.wsdl = reader.loadFromStream(sys.stdin) + # print 'stdin' + + if self.wsdl is None: + try: + file(wsdlsource) + self.wsdl = reader.loadFromFile(wsdlsource) + #print 'file' + except (IOError, OSError): + pass + + if self.wsdl is None: + try: + stream = urllib.urlopen(wsdlsource) + self.wsdl = reader.loadFromStream(stream, wsdlsource) + except (IOError, OSError): pass + + if self.wsdl is None: + import StringIO + self.wsdl = reader.loadFromString(str(wsdlsource)) + #print 'string' + + # Package wsdl info as a dictionary of remote methods, with method name + # as key (based on ServiceProxy.__init__ in ZSI library). + self.methods = {} + service = self.wsdl.services[0] + port = service.ports[0] + name = service.name + binding = port.getBinding() + portType = binding.getPortType() + for operation in portType.operations: + callinfo = wstools.WSDLTools.callInfoFromWSDL(port, operation.name) + self.methods[callinfo.methodName] = callinfo + + self.soapproxy = SOAPProxy('http://localhost/dummy.webservice', + config=config, **kw) + + def __str__(self): + s = '' + for method in self.methods.values(): + s += str(method) + return s + + def __getattr__(self, name): + """Set up environment then let parent class handle call. + + Raises AttributeError is method name is not found.""" + + if not self.methods.has_key(name): raise AttributeError, name + + callinfo = self.methods[name] + self.soapproxy.proxy = SOAPAddress(callinfo.location) + self.soapproxy.namespace = callinfo.namespace + self.soapproxy.soapaction = callinfo.soapAction + return self.soapproxy.__getattr__(name) diff --git a/others/SOAPpy/__init__.py b/others/SOAPpy/__init__.py new file mode 100644 index 000000000..ecd0bfe7b --- /dev/null +++ b/others/SOAPpy/__init__.py @@ -0,0 +1,15 @@ + +ident = '$Id$' +from version import __version__ + +from Client import * +from Config import * +from Errors import * +from NS import * +from Parser import * +from SOAPBuilder import * +from Server import * +from Types import * +from Utilities import * +import wstools +import WSDL diff --git a/others/SOAPpy/version.py b/others/SOAPpy/version.py new file mode 100644 index 000000000..eda6139ca --- /dev/null +++ b/others/SOAPpy/version.py @@ -0,0 +1,2 @@ +__version__="0.11.6" + diff --git a/others/SOAPpy/wstools/Namespaces.py b/others/SOAPpy/wstools/Namespaces.py new file mode 100755 index 000000000..60eaa0f71 --- /dev/null +++ b/others/SOAPpy/wstools/Namespaces.py @@ -0,0 +1,92 @@ +#! /usr/bin/env python +"""Namespace module, so you don't need PyXML +""" + +try: + from xml.ns import SOAP, SCHEMA, WSDL, XMLNS, DSIG, ENCRYPTION +except: + class SOAP: + ENV = "http://schemas.xmlsoap.org/soap/envelope/" + ENC = "http://schemas.xmlsoap.org/soap/encoding/" + ACTOR_NEXT = "http://schemas.xmlsoap.org/soap/actor/next" + + class SCHEMA: + XSD1 = "http://www.w3.org/1999/XMLSchema" + XSD2 = "http://www.w3.org/2000/10/XMLSchema" + XSD3 = "http://www.w3.org/2001/XMLSchema" + XSD_LIST = [ XSD1, XSD2, XSD3 ] + XSI1 = "http://www.w3.org/1999/XMLSchema-instance" + XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" + XSI3 = "http://www.w3.org/2001/XMLSchema-instance" + XSI_LIST = [ XSI1, XSI2, XSI3 ] + BASE = XSD3 + + class WSDL: + BASE = "http://schemas.xmlsoap.org/wsdl/" + BIND_HTTP = "http://schemas.xmlsoap.org/wsdl/http/" + BIND_MIME = "http://schemas.xmlsoap.org/wsdl/mime/" + BIND_SOAP = "http://schemas.xmlsoap.org/wsdl/soap/" + + class XMLNS: + BASE = "http://www.w3.org/2000/xmlns/" + XML = "http://www.w3.org/XML/1998/namespace" + HTML = "http://www.w3.org/TR/REC-html40" + + class DSIG: + BASE = "http://www.w3.org/2000/09/xmldsig#" + C14N = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315" + C14N_COMM = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315#WithComments" + C14N_EXCL = "http://www.w3.org/2001/10/xml-exc-c14n#" + DIGEST_MD2 = "http://www.w3.org/2000/09/xmldsig#md2" + DIGEST_MD5 = "http://www.w3.org/2000/09/xmldsig#md5" + DIGEST_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1" + ENC_BASE64 = "http://www.w3.org/2000/09/xmldsig#base64" + ENVELOPED = "http://www.w3.org/2000/09/xmldsig#enveloped-signature" + HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1" + SIG_DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1" + SIG_RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + XPATH = "http://www.w3.org/TR/1999/REC-xpath-19991116" + XSLT = "http://www.w3.org/TR/1999/REC-xslt-19991116" + + class ENCRYPTION: + BASE = "http://www.w3.org/2001/04/xmlenc#" + BLOCK_3DES = "http://www.w3.org/2001/04/xmlenc#des-cbc" + BLOCK_AES128 = "http://www.w3.org/2001/04/xmlenc#aes128-cbc" + BLOCK_AES192 = "http://www.w3.org/2001/04/xmlenc#aes192-cbc" + BLOCK_AES256 = "http://www.w3.org/2001/04/xmlenc#aes256-cbc" + DIGEST_RIPEMD160 = "http://www.w3.org/2001/04/xmlenc#ripemd160" + DIGEST_SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256" + DIGEST_SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512" + KA_DH = "http://www.w3.org/2001/04/xmlenc#dh" + KT_RSA_1_5 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5" + KT_RSA_OAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" + STREAM_ARCFOUR = "http://www.w3.org/2001/04/xmlenc#arcfour" + WRAP_3DES = "http://www.w3.org/2001/04/xmlenc#kw-3des" + WRAP_AES128 = "http://www.w3.org/2001/04/xmlenc#kw-aes128" + WRAP_AES192 = "http://www.w3.org/2001/04/xmlenc#kw-aes192" + WRAP_AES256 = "http://www.w3.org/2001/04/xmlenc#kw-aes256" + + +class WSSE: + BASE = "http://schemas.xmlsoap.org/ws/2002/04/secext" + +class WSU: + BASE = "http://schemas.xmlsoap.org/ws/2002/04/utility" + UTILITY = "http://schemas.xmlsoap.org/ws/2002/07/utility" + +class WSR: + PROPERTIES = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceProperties" + LIFETIME = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceLifetime" + +class WSA: + ADDRESS = "http://schemas.xmlsoap.org/ws/2003/03/addressing" + ADDRESS2004 = "http://schemas.xmlsoap.org/ws/2004/03/addressing" + ANONYMOUS = "%s/role/anonymous" %ADDRESS + ANONYMOUS2004 = "%s/role/anonymous" %ADDRESS2004 + FAULT = "http://schemas.xmlsoap.org/ws/2004/03/addressing/fault" + +class WSP: + POLICY = "http://schemas.xmlsoap.org/ws/2002/12/policy" + + + diff --git a/others/SOAPpy/wstools/TimeoutSocket.py b/others/SOAPpy/wstools/TimeoutSocket.py new file mode 100755 index 000000000..48b898d89 --- /dev/null +++ b/others/SOAPpy/wstools/TimeoutSocket.py @@ -0,0 +1,179 @@ +"""Based on code from timeout_socket.py, with some tweaks for compatibility. + These tweaks should really be rolled back into timeout_socket, but it's + not totally clear who is maintaining it at this point. In the meantime, + we'll use a different module name for our tweaked version to avoid any + confusion. + + The original timeout_socket is by: + + Scott Cotton + Lloyd Zusman + Phil Mayes + Piers Lauder + Radovan Garabik +""" + +ident = "$Id$" + +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/others/SOAPpy/wstools/UserTuple.py b/others/SOAPpy/wstools/UserTuple.py new file mode 100755 index 000000000..b8c36539b --- /dev/null +++ b/others/SOAPpy/wstools/UserTuple.py @@ -0,0 +1,99 @@ +""" +A more or less complete user-defined wrapper around tuple objects. +Adapted version of the standard library's UserList. + +Taken from Stefan Schwarzer's ftputil library, available at +, and used under this license: + + + + +Copyright (C) 1999, Stefan Schwarzer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of the above author nor the names of the + contributors to the software may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" + + + + +# $Id$ + +#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/others/SOAPpy/wstools/Utility.py b/others/SOAPpy/wstools/Utility.py new file mode 100755 index 000000000..46860aa78 --- /dev/null +++ b/others/SOAPpy/wstools/Utility.py @@ -0,0 +1,839 @@ +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. + +ident = "$Id$" + +import types +import string, httplib, smtplib, urllib, socket, weakref +import xml.dom.minidom +from string import join, strip, split +from UserDict import UserDict +from StringIO import StringIO +from TimeoutSocket import TimeoutSocket, TimeoutError +from urlparse import urlparse +from httplib import HTTPConnection, HTTPSConnection +from exceptions import Exception + +try: + from xml.dom.ext import SplitQName +except: + def SplitQName(qname): + '''SplitQName(qname) -> (string, string) + + Split Qualified Name into a tuple of len 2, consisting + of the prefix and the local name. + + (prefix, localName) + + Special Cases: + xmlns -- (localName, 'xmlns') + None -- (None, localName) + ''' + + l = qname.split(':') + if len(l) == 1: + l.insert(0, None) + elif len(l) == 2: + if l[0] == 'xmlns': + l.reverse() + else: + return + return tuple(l) + +class RecursionError(Exception): + """Used to indicate a HTTP redirect recursion.""" + pass + +class HTTPResponse: + """Captures the information in an HTTP response message.""" + + def __init__(self, response): + self.status = response.status + self.reason = response.reason + self.headers = response.msg + self.body = response.read() or None + response.close() + +class TimeoutHTTP(HTTPConnection): + """A custom http connection object that supports socket timeout.""" + def __init__(self, host, port=None, timeout=20): + HTTPConnection.__init__(self, host, port) + self.timeout = timeout + + def connect(self): + self.sock = TimeoutSocket(self.timeout) + self.sock.connect((self.host, self.port)) + + +class TimeoutHTTPS(HTTPSConnection): + """A custom https object that supports socket timeout. Note that this + is not really complete. The builtin SSL support in the Python socket + module requires a real socket (type) to be passed in to be hooked to + SSL. That means our fake socket won't work and our timeout hacks are + bypassed for send and recv calls. Since our hack _is_ in place at + connect() time, it should at least provide some timeout protection.""" + def __init__(self, host, port=None, timeout=20, **kwargs): + HTTPSConnection.__init__(self, str(host), port, **kwargs) + self.timeout = timeout + + def connect(self): + sock = TimeoutSocket(self.timeout) + sock.connect((self.host, self.port)) + realsock = getattr(sock.sock, '_sock', sock.sock) + ssl = socket.ssl(realsock, self.key_file, self.cert_file) + self.sock = httplib.FakeSocket(sock, ssl) + +def urlopen(url, timeout=20, redirects=None): + """A minimal urlopen replacement hack that supports timeouts for http. + Note that this supports GET only.""" + scheme, host, path, params, query, frag = urlparse(url) + if not scheme in ('http', 'https'): + return urllib.urlopen(url) + if params: path = '%s;%s' % (path, params) + if query: path = '%s?%s' % (path, query) + if frag: path = '%s#%s' % (path, frag) + + if scheme == 'https': + # If ssl is not compiled into Python, you will not get an exception + # until a conn.endheaders() call. We need to know sooner, so use + # getattr. + if hasattr(socket, 'ssl'): + conn = TimeoutHTTPS(host, None, timeout) + else: + import M2Crypto + ctx = M2Crypto.SSL.Context() + ctx.set_session_timeout(timeout) + conn = M2Crypto.httpslib.HTTPSConnection(host, ssl_context=ctx) + #conn.set_debuglevel(1) + else: + conn = TimeoutHTTP(host, None, timeout) + + conn.putrequest('GET', path) + conn.putheader('Connection', 'close') + conn.endheaders() + response = None + while 1: + response = conn.getresponse() + if response.status != 100: + break + conn._HTTPConnection__state = httplib._CS_REQ_SENT + conn._HTTPConnection__response = None + + status = response.status + + # If we get an HTTP redirect, we will follow it automatically. + if status >= 300 and status < 400: + location = response.msg.getheader('location') + if location is not None: + response.close() + if redirects is not None and redirects.has_key(location): + raise RecursionError( + 'Circular HTTP redirection detected.' + ) + if redirects is None: + redirects = {} + redirects[location] = 1 + return urlopen(location, timeout, redirects) + raise HTTPResponse(response) + + if not (status >= 200 and status < 300): + raise HTTPResponse(response) + + body = StringIO(response.read()) + response.close() + return body + +class DOM: + """The DOM singleton defines a number of XML related constants and + provides a number of utility methods for DOM related tasks. It + also provides some basic abstractions so that the rest of the + package need not care about actual DOM implementation in use.""" + + # Namespace stuff related to the SOAP specification. + + NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/' + NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/' + + NS_SOAP_ENV_1_2 = 'http://www.w3.org/2001/06/soap-envelope' + NS_SOAP_ENC_1_2 = 'http://www.w3.org/2001/06/soap-encoding' + + NS_SOAP_ENV_ALL = (NS_SOAP_ENV_1_1, NS_SOAP_ENV_1_2) + NS_SOAP_ENC_ALL = (NS_SOAP_ENC_1_1, NS_SOAP_ENC_1_2) + + NS_SOAP_ENV = NS_SOAP_ENV_1_1 + NS_SOAP_ENC = NS_SOAP_ENC_1_1 + + _soap_uri_mapping = { + NS_SOAP_ENV_1_1 : '1.1', + NS_SOAP_ENV_1_2 : '1.2', + } + + SOAP_ACTOR_NEXT_1_1 = 'http://schemas.xmlsoap.org/soap/actor/next' + SOAP_ACTOR_NEXT_1_2 = 'http://www.w3.org/2001/06/soap-envelope/actor/next' + SOAP_ACTOR_NEXT_ALL = (SOAP_ACTOR_NEXT_1_1, SOAP_ACTOR_NEXT_1_2) + + def SOAPUriToVersion(self, uri): + """Return the SOAP version related to an envelope uri.""" + value = self._soap_uri_mapping.get(uri) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP envelope uri: %s' % uri + ) + + def GetSOAPEnvUri(self, version): + """Return the appropriate SOAP envelope uri for a given + human-friendly SOAP version string (e.g. '1.1').""" + attrname = 'NS_SOAP_ENV_%s' % join(split(version, '.'), '_') + value = getattr(self, attrname, None) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP version: %s' % version + ) + + def GetSOAPEncUri(self, version): + """Return the appropriate SOAP encoding uri for a given + human-friendly SOAP version string (e.g. '1.1').""" + attrname = 'NS_SOAP_ENC_%s' % join(split(version, '.'), '_') + value = getattr(self, attrname, None) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP version: %s' % version + ) + + def GetSOAPActorNextUri(self, version): + """Return the right special next-actor uri for a given + human-friendly SOAP version string (e.g. '1.1').""" + attrname = 'SOAP_ACTOR_NEXT_%s' % join(split(version, '.'), '_') + value = getattr(self, attrname, None) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP version: %s' % version + ) + + + # Namespace stuff related to XML Schema. + + NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema' + NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance' + + NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema' + NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance' + + NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema' + NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance' + + NS_XSD_ALL = (NS_XSD_99, NS_XSD_00, NS_XSD_01) + NS_XSI_ALL = (NS_XSI_99, NS_XSI_00, NS_XSI_01) + + NS_XSD = NS_XSD_01 + NS_XSI = NS_XSI_01 + + _xsd_uri_mapping = { + NS_XSD_99 : NS_XSI_99, + NS_XSD_00 : NS_XSI_00, + NS_XSD_01 : NS_XSI_01, + } + + for key, value in _xsd_uri_mapping.items(): + _xsd_uri_mapping[value] = key + + + def InstanceUriForSchemaUri(self, uri): + """Return the appropriate matching XML Schema instance uri for + the given XML Schema namespace uri.""" + return self._xsd_uri_mapping.get(uri) + + def SchemaUriForInstanceUri(self, uri): + """Return the appropriate matching XML Schema namespace uri for + the given XML Schema instance namespace uri.""" + return self._xsd_uri_mapping.get(uri) + + + # Namespace stuff related to WSDL. + + NS_WSDL_1_1 = 'http://schemas.xmlsoap.org/wsdl/' + NS_WSDL_ALL = (NS_WSDL_1_1,) + NS_WSDL = NS_WSDL_1_1 + + NS_SOAP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/' + NS_HTTP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/http/' + NS_MIME_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/mime/' + + NS_SOAP_BINDING_ALL = (NS_SOAP_BINDING_1_1,) + NS_HTTP_BINDING_ALL = (NS_HTTP_BINDING_1_1,) + NS_MIME_BINDING_ALL = (NS_MIME_BINDING_1_1,) + + NS_SOAP_BINDING = NS_SOAP_BINDING_1_1 + NS_HTTP_BINDING = NS_HTTP_BINDING_1_1 + NS_MIME_BINDING = NS_MIME_BINDING_1_1 + + NS_SOAP_HTTP_1_1 = 'http://schemas.xmlsoap.org/soap/http' + NS_SOAP_HTTP_ALL = (NS_SOAP_HTTP_1_1,) + NS_SOAP_HTTP = NS_SOAP_HTTP_1_1 + + + _wsdl_uri_mapping = { + NS_WSDL_1_1 : '1.1', + } + + def WSDLUriToVersion(self, uri): + """Return the WSDL version related to a WSDL namespace uri.""" + value = self._wsdl_uri_mapping.get(uri) + if value is not None: + return value + raise ValueError( + 'Unsupported SOAP envelope uri: %s' % uri + ) + + def GetWSDLUri(self, version): + attr = 'NS_WSDL_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + def GetWSDLSoapBindingUri(self, version): + attr = 'NS_SOAP_BINDING_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + def GetWSDLHttpBindingUri(self, version): + attr = 'NS_HTTP_BINDING_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + def GetWSDLMimeBindingUri(self, version): + attr = 'NS_MIME_BINDING_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + def GetWSDLHttpTransportUri(self, version): + attr = 'NS_SOAP_HTTP_%s' % join(split(version, '.'), '_') + value = getattr(self, attr, None) + if value is not None: + return value + raise ValueError( + 'Unsupported WSDL version: %s' % version + ) + + + # Other xml namespace constants. + NS_XMLNS = 'http://www.w3.org/2000/xmlns/' + + + + def isElement(self, node, name, nsuri=None): + """Return true if the given node is an element with the given + name and optional namespace uri.""" + if node.nodeType != node.ELEMENT_NODE: + return 0 + return node.localName == name and \ + (nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri)) + + def getElement(self, node, name, nsuri=None, default=join): + """Return the first child of node with a matching name and + namespace uri, or the default if one is provided.""" + nsmatch = self.nsUriMatch + ELEMENT_NODE = node.ELEMENT_NODE + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if ((child.localName == name or name is None) and + (nsuri is None or nsmatch(child.namespaceURI, nsuri)) + ): + return child + if default is not join: + return default + raise KeyError, name + + def getElementById(self, node, id, default=join): + """Return the first child of node matching an id reference.""" + attrget = self.getAttr + ELEMENT_NODE = node.ELEMENT_NODE + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if attrget(child, 'id') == id: + return child + if default is not join: + return default + raise KeyError, name + + def getMappingById(self, document, depth=None, element=None, + mapping=None, level=1): + """Create an id -> element mapping of those elements within a + document that define an id attribute. The depth of the search + may be controlled by using the (1-based) depth argument.""" + if document is not None: + element = document.documentElement + mapping = {} + attr = element._attrs.get('id', None) + if attr is not None: + mapping[attr.value] = element + if depth is None or depth > level: + level = level + 1 + ELEMENT_NODE = element.ELEMENT_NODE + for child in element.childNodes: + if child.nodeType == ELEMENT_NODE: + self.getMappingById(None, depth, child, mapping, level) + return mapping + + def getElements(self, node, name, nsuri=None): + """Return a sequence of the child elements of the given node that + match the given name and optional namespace uri.""" + nsmatch = self.nsUriMatch + result = [] + ELEMENT_NODE = node.ELEMENT_NODE + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if ((child.localName == name or name is None) and ( + (nsuri is None) or nsmatch(child.namespaceURI, nsuri))): + result.append(child) + return result + + def hasAttr(self, node, name, nsuri=None): + """Return true if element has attribute with the given name and + optional nsuri. If nsuri is not specified, returns true if an + attribute exists with the given name with any namespace.""" + if nsuri is None: + if node.hasAttribute(name): + return True + return False + return node.hasAttributeNS(nsuri, name) + + def getAttr(self, node, name, nsuri=None, default=join): + """Return the value of the attribute named 'name' with the + optional nsuri, or the default if one is specified. If + nsuri is not specified, an attribute that matches the + given name will be returned regardless of namespace.""" + if nsuri is None: + result = node._attrs.get(name, None) + if result is None: + for item in node._attrsNS.keys(): + if item[1] == name: + result = node._attrsNS[item] + break + else: + result = node._attrsNS.get((nsuri, name), None) + if result is not None: + return result.value + if default is not join: + return default + return '' + + def getAttrs(self, node): + """Return a Collection of all attributes + """ + attrs = {} + for k,v in node._attrs.items(): + attrs[k] = v.value + return attrs + + def getElementText(self, node, preserve_ws=None): + """Return the text value of an xml element node. Leading and trailing + whitespace is stripped from the value unless the preserve_ws flag + is passed with a true value.""" + result = [] + for child in node.childNodes: + nodetype = child.nodeType + if nodetype == child.TEXT_NODE or \ + nodetype == child.CDATA_SECTION_NODE: + result.append(child.nodeValue) + value = join(result, '') + if preserve_ws is None: + value = strip(value) + return value + + def findNamespaceURI(self, prefix, node): + """Find a namespace uri given a prefix and a context node.""" + attrkey = (self.NS_XMLNS, prefix) + DOCUMENT_NODE = node.DOCUMENT_NODE + ELEMENT_NODE = node.ELEMENT_NODE + while 1: + if node.nodeType != ELEMENT_NODE: + node = node.parentNode + continue + result = node._attrsNS.get(attrkey, None) + if result is not None: + return result.value + if hasattr(node, '__imported__'): + raise DOMException('Value for prefix %s not found.' % prefix) + node = node.parentNode + if node.nodeType == DOCUMENT_NODE: + raise DOMException('Value for prefix %s not found.' % prefix) + + def findDefaultNS(self, node): + """Return the current default namespace uri for the given node.""" + attrkey = (self.NS_XMLNS, 'xmlns') + DOCUMENT_NODE = node.DOCUMENT_NODE + ELEMENT_NODE = node.ELEMENT_NODE + while 1: + if node.nodeType != ELEMENT_NODE: + node = node.parentNode + continue + result = node._attrsNS.get(attrkey, None) + if result is not None: + return result.value + if hasattr(node, '__imported__'): + raise DOMException('Cannot determine default namespace.') + node = node.parentNode + if node.nodeType == DOCUMENT_NODE: + raise DOMException('Cannot determine default namespace.') + + def findTargetNS(self, node): + """Return the defined target namespace uri for the given node.""" + attrget = self.getAttr + attrkey = (self.NS_XMLNS, 'xmlns') + DOCUMENT_NODE = node.DOCUMENT_NODE + ELEMENT_NODE = node.ELEMENT_NODE + while 1: + if node.nodeType != ELEMENT_NODE: + node = node.parentNode + continue + result = attrget(node, 'targetNamespace', default=None) + if result is not None: + return result + node = node.parentNode + if node.nodeType == DOCUMENT_NODE: + raise DOMException('Cannot determine target namespace.') + + def getTypeRef(self, element): + """Return (namespaceURI, name) for a type attribue of the given + element, or None if the element does not have a type attribute.""" + typeattr = self.getAttr(element, 'type', default=None) + if typeattr is None: + return None + parts = typeattr.split(':', 1) + if len(parts) == 2: + nsuri = self.findNamespaceURI(parts[0], element) + else: + nsuri = self.findDefaultNS(element) + return (nsuri, parts[1]) + + def importNode(self, document, node, deep=0): + """Implements (well enough for our purposes) DOM node import.""" + nodetype = node.nodeType + if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE): + raise DOMException('Illegal node type for importNode') + if nodetype == node.ENTITY_REFERENCE_NODE: + deep = 0 + clone = node.cloneNode(deep) + self._setOwnerDoc(document, clone) + clone.__imported__ = 1 + return clone + + def _setOwnerDoc(self, document, node): + node.ownerDocument = document + for child in node.childNodes: + self._setOwnerDoc(document, child) + + def nsUriMatch(self, value, wanted, strict=0, tt=type(())): + """Return a true value if two namespace uri values match.""" + if value == wanted or (type(wanted) is tt) and value in wanted: + return 1 + if not strict: + wanted = type(wanted) is tt and wanted or (wanted,) + value = value[-1:] != '/' and value or value[:-1] + for item in wanted: + if item == value or item[:-1] == value: + return 1 + return 0 + + def createDocument(self, nsuri, qname, doctype=None): + """Create a new writable DOM document object.""" + impl = xml.dom.minidom.getDOMImplementation() + return impl.createDocument(nsuri, qname, doctype) + + def loadDocument(self, data): + """Load an xml file from a file-like object and return a DOM + document instance.""" + return xml.dom.minidom.parse(data) + + def loadFromURL(self, url): + """Load an xml file from a URL and return a DOM document.""" + file = urlopen(url) + try: result = self.loadDocument(file) + finally: file.close() + return result + + +class DOMException(Exception): + pass + +DOM = DOM() + + +class Collection(UserDict): + """Helper class for maintaining ordered named collections.""" + default = lambda self,k: k.name + def __init__(self, parent, key=None): + UserDict.__init__(self) + self.parent = weakref.ref(parent) + self.list = [] + self._func = key or self.default + + def __getitem__(self, key): + if type(key) is type(1): + return self.list[key] + return self.data[key] + + def __setitem__(self, key, item): + item.parent = weakref.ref(self) + self.list.append(item) + self.data[key] = item + + def keys(self): + return map(lambda i: self._func(i), self.list) + + def items(self): + return map(lambda i: (self._func(i), i), self.list) + + def values(self): + return self.list + + +class CollectionNS(UserDict): + """Helper class for maintaining ordered named collections.""" + default = lambda self,k: k.name + def __init__(self, parent, key=None): + UserDict.__init__(self) + self.parent = weakref.ref(parent) + self.targetNamespace = None + self.list = [] + self._func = key or self.default + + def __getitem__(self, key): + self.targetNamespace = self.parent().targetNamespace + if type(key) is types.IntType: + return self.list[key] + elif self.__isSequence(key): + nsuri,name = key + return self.data[nsuri][name] + return self.data[self.parent().targetNamespace][key] + + def __setitem__(self, key, item): + item.parent = weakref.ref(self) + self.list.append(item) + targetNamespace = getattr(item, 'targetNamespace', self.parent().targetNamespace) + if not self.data.has_key(targetNamespace): + self.data[targetNamespace] = {} + self.data[targetNamespace][key] = item + + def __isSequence(self, key): + return (type(key) in (types.TupleType,types.ListType) and len(key) == 2) + + def keys(self): + keys = [] + for tns in self.data.keys(): + keys.append(map(lambda i: (tns,self._func(i)), self.data[tns].values())) + return keys + + def items(self): + return map(lambda i: (self._func(i), i), self.list) + + def values(self): + return self.list + + + +# This is a runtime guerilla patch for pulldom (used by minidom) so +# that xml namespace declaration attributes are not lost in parsing. +# We need them to do correct QName linking for XML Schema and WSDL. +# The patch has been submitted to SF for the next Python version. + +from xml.dom.pulldom import PullDOM, START_ELEMENT +if 1: + def startPrefixMapping(self, prefix, uri): + if not hasattr(self, '_xmlns_attrs'): + self._xmlns_attrs = [] + self._xmlns_attrs.append((prefix or 'xmlns', uri)) + self._ns_contexts.append(self._current_context.copy()) + self._current_context[uri] = prefix or '' + + PullDOM.startPrefixMapping = startPrefixMapping + + def startElementNS(self, name, tagName , attrs): + # Retrieve xml namespace declaration attributes. + xmlns_uri = 'http://www.w3.org/2000/xmlns/' + xmlns_attrs = getattr(self, '_xmlns_attrs', None) + if xmlns_attrs is not None: + for aname, value in xmlns_attrs: + attrs._attrs[(xmlns_uri, aname)] = value + self._xmlns_attrs = [] + uri, localname = name + if uri: + # When using namespaces, the reader may or may not + # provide us with the original name. If not, create + # *a* valid tagName from the current context. + if tagName is None: + prefix = self._current_context[uri] + if prefix: + tagName = prefix + ":" + localname + else: + tagName = localname + if self.document: + node = self.document.createElementNS(uri, tagName) + else: + node = self.buildDocument(uri, tagName) + else: + # When the tagname is not prefixed, it just appears as + # localname + if self.document: + node = self.document.createElement(localname) + else: + node = self.buildDocument(None, localname) + + for aname,value in attrs.items(): + a_uri, a_localname = aname + if a_uri == xmlns_uri: + if a_localname == 'xmlns': + qname = a_localname + else: + qname = 'xmlns:' + a_localname + attr = self.document.createAttributeNS(a_uri, qname) + node.setAttributeNodeNS(attr) + elif a_uri: + prefix = self._current_context[a_uri] + if prefix: + qname = prefix + ":" + a_localname + else: + qname = a_localname + attr = self.document.createAttributeNS(a_uri, qname) + node.setAttributeNodeNS(attr) + else: + attr = self.document.createAttribute(a_localname) + node.setAttributeNode(attr) + attr.value = value + + self.lastEvent[1] = [(START_ELEMENT, node), None] + self.lastEvent = self.lastEvent[1] + self.push(node) + + PullDOM.startElementNS = startElementNS + +# +# This is a runtime guerilla patch for minidom so +# that xmlns prefixed attributes dont raise AttributeErrors +# during cloning. +# +# Namespace declarations can appear in any start-tag, must look for xmlns +# prefixed attribute names during cloning. +# +# key (attr.namespaceURI, tag) +# ('http://www.w3.org/2000/xmlns/', u'xsd') +# ('http://www.w3.org/2000/xmlns/', 'xmlns') +# +# xml.dom.minidom.Attr.nodeName = xmlns:xsd +# xml.dom.minidom.Attr.value = = http://www.w3.org/2001/XMLSchema + +if 1: + def _clone_node(node, deep, newOwnerDocument): + """ + Clone a node and give it the new owner document. + Called by Node.cloneNode and Document.importNode + """ + if node.ownerDocument.isSameNode(newOwnerDocument): + operation = xml.dom.UserDataHandler.NODE_CLONED + else: + operation = xml.dom.UserDataHandler.NODE_IMPORTED + if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE: + clone = newOwnerDocument.createElementNS(node.namespaceURI, + node.nodeName) + for attr in node.attributes.values(): + clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value) + + prefix, tag = xml.dom.minidom._nssplit(attr.nodeName) + if prefix == 'xmlns': + a = clone.getAttributeNodeNS(attr.namespaceURI, tag) + elif prefix: + a = clone.getAttributeNodeNS(attr.namespaceURI, tag) + else: + a = clone.getAttributeNodeNS(attr.namespaceURI, attr.nodeName) + a.specified = attr.specified + + if deep: + for child in node.childNodes: + c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument) + clone.appendChild(c) + elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_FRAGMENT_NODE: + clone = newOwnerDocument.createDocumentFragment() + if deep: + for child in node.childNodes: + c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument) + clone.appendChild(c) + + elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE: + clone = newOwnerDocument.createTextNode(node.data) + elif node.nodeType == xml.dom.minidom.Node.CDATA_SECTION_NODE: + clone = newOwnerDocument.createCDATASection(node.data) + elif node.nodeType == xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE: + clone = newOwnerDocument.createProcessingInstruction(node.target, + node.data) + elif node.nodeType == xml.dom.minidom.Node.COMMENT_NODE: + clone = newOwnerDocument.createComment(node.data) + elif node.nodeType == xml.dom.minidom.Node.ATTRIBUTE_NODE: + clone = newOwnerDocument.createAttributeNS(node.namespaceURI, + node.nodeName) + clone.specified = True + clone.value = node.value + elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_TYPE_NODE: + assert node.ownerDocument is not newOwnerDocument + operation = xml.dom.UserDataHandler.NODE_IMPORTED + clone = newOwnerDocument.implementation.createDocumentType( + node.name, node.publicId, node.systemId) + clone.ownerDocument = newOwnerDocument + if deep: + clone.entities._seq = [] + clone.notations._seq = [] + for n in node.notations._seq: + notation = xml.dom.minidom.Notation(n.nodeName, n.publicId, n.systemId) + notation.ownerDocument = newOwnerDocument + clone.notations._seq.append(notation) + if hasattr(n, '_call_user_data_handler'): + n._call_user_data_handler(operation, n, notation) + for e in node.entities._seq: + entity = xml.dom.minidom.Entity(e.nodeName, e.publicId, e.systemId, + e.notationName) + entity.actualEncoding = e.actualEncoding + entity.encoding = e.encoding + entity.version = e.version + entity.ownerDocument = newOwnerDocument + clone.entities._seq.append(entity) + if hasattr(e, '_call_user_data_handler'): + e._call_user_data_handler(operation, n, entity) + else: + # Note the cloning of Document and DocumentType nodes is + # implemenetation specific. minidom handles those cases + # directly in the cloneNode() methods. + raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node)) + + # Check for _call_user_data_handler() since this could conceivably + # used with other DOM implementations (one of the FourThought + # DOMs, perhaps?). + if hasattr(node, '_call_user_data_handler'): + node._call_user_data_handler(operation, node, clone) + return clone + + xml.dom.minidom._clone_node = _clone_node diff --git a/others/SOAPpy/wstools/WSDLTools.py b/others/SOAPpy/wstools/WSDLTools.py new file mode 100755 index 000000000..3c32ee622 --- /dev/null +++ b/others/SOAPpy/wstools/WSDLTools.py @@ -0,0 +1,1336 @@ +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. + +ident = "$Id$" + +from Utility import DOM, Collection, CollectionNS +from XMLSchema import XMLSchema, SchemaReader, WSDLToolsAdapter +from Namespaces import WSR, WSA +from StringIO import StringIO +import urllib + + +class WSDLReader: + """A WSDLReader creates WSDL instances from urls and xml data.""" + + # Custom subclasses of WSDLReader may wish to implement a caching + # strategy or other optimizations. Because application needs vary + # so widely, we don't try to provide any caching by default. + + def loadFromStream(self, stream, name=None): + """Return a WSDL instance loaded from a stream object.""" + document = DOM.loadDocument(stream) + wsdl = WSDL() + if name: + wsdl.location = name + elif hasattr(stream, 'name'): + wsdl.location = stream.name + wsdl.load(document) + return wsdl + + def loadFromURL(self, url): + """Return a WSDL instance loaded from the given url.""" + document = DOM.loadFromURL(url) + wsdl = WSDL() + wsdl.location = url + wsdl.load(document) + return wsdl + + def loadFromString(self, data): + """Return a WSDL instance loaded from an xml string.""" + return self.loadFromStream(StringIO(data)) + + def loadFromFile(self, filename): + """Return a WSDL instance loaded from the given file.""" + file = open(filename, 'rb') + try: + wsdl = self.loadFromStream(file) + finally: + file.close() + return wsdl + +class WSDL: + """A WSDL object models a WSDL service description. WSDL objects + may be created manually or loaded from an xml representation + using a WSDLReader instance.""" + + def __init__(self, targetNamespace=None, strict=1): + self.targetNamespace = targetNamespace or 'urn:this-document.wsdl' + self.documentation = '' + self.location = None + self.document = None + self.name = None + self.services = CollectionNS(self) + self.messages = CollectionNS(self) + self.portTypes = CollectionNS(self) + self.bindings = CollectionNS(self) + #self.imports = Collection(self) + self.types = Types(self) + self.extensions = [] + self.strict = strict + + def __del__(self): + if self.document is not None: + self.document.unlink() + + version = '1.1' + + def addService(self, name, documentation='', targetNamespace=None): + if self.services.has_key(name): + raise WSDLError( + 'Duplicate service element: %s' % name + ) + item = Service(name, documentation) + if targetNamespace: + item.targetNamespace = targetNamespace + self.services[name] = item + return item + + def addMessage(self, name, documentation='', targetNamespace=None): + if self.messages.has_key(name): + raise WSDLError( + 'Duplicate message element: %s.' % name + ) + item = Message(name, documentation) + if targetNamespace: + item.targetNamespace = targetNamespace + self.messages[name] = item + return item + + def addPortType(self, name, documentation='', targetNamespace=None): + if self.portTypes.has_key(name): + raise WSDLError( + 'Duplicate portType element: name' + ) + item = PortType(name, documentation) + if targetNamespace: + item.targetNamespace = targetNamespace + self.portTypes[name] = item + return item + + def addBinding(self, name, type, documentation='', targetNamespace=None): + if self.bindings.has_key(name): + raise WSDLError( + 'Duplicate binding element: %s' % name + ) + item = Binding(name, type, documentation) + if targetNamespace: + item.targetNamespace = targetNamespace + self.bindings[name] = item + return item + + #def addImport(self, namespace, location): + # item = ImportElement(namespace, location) + # self.imports[namespace] = item + # return item + + def load(self, document): + # We save a reference to the DOM document to ensure that elements + # saved as "extensions" will continue to have a meaningful context + # for things like namespace references. The lifetime of the DOM + # document is bound to the lifetime of the WSDL instance. + self.document = document + + definitions = DOM.getElement(document, 'definitions', None, None) + if definitions is None: + raise WSDLError( + 'Missing element.' + ) + self.version = DOM.WSDLUriToVersion(definitions.namespaceURI) + NS_WSDL = DOM.GetWSDLUri(self.version) + + self.targetNamespace = DOM.getAttr(definitions, 'targetNamespace', + None, None) + self.name = DOM.getAttr(definitions, 'name', None, None) + self.documentation = GetDocumentation(definitions) + + # Resolve (recursively) any import elements in the document. + imported = {} + base_location = self.location + while 1: + #XXX + imports = [] + for element in DOM.getElements(definitions, 'import', NS_WSDL): + location = DOM.getAttr(element, 'location') + # Resolve relative location, and save + location = urllib.basejoin(base_location, location) + + if not imported.has_key(location): + imports.append(element) + + if not imports: + break + for element in imports: + location = DOM.getAttr(element, 'location') + self._import(document, element, base_location) + location = urllib.basejoin(base_location, location) + imported[location] = 1 + base_location = '' + + #reader = SchemaReader(base_url=self.location) + for element in DOM.getElements(definitions, None, None): + targetNamespace = DOM.getAttr(element, 'targetNamespace') + localName = element.localName + + if not DOM.nsUriMatch(element.namespaceURI, NS_WSDL): + if localName == 'schema': + reader = SchemaReader(base_url=self.location) + schema = reader.loadFromNode(WSDLToolsAdapter(self), element) + schema.setBaseUrl(self.location) + self.types.addSchema(schema) + else: + self.extensions.append(element) + continue + + elif localName == 'message': + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + message = self.addMessage(name, docs, targetNamespace) + parts = DOM.getElements(element, 'part', NS_WSDL) + message.load(parts) + continue + + elif localName == 'portType': + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + ptype = self.addPortType(name, docs, targetNamespace) + #operations = DOM.getElements(element, 'operation', NS_WSDL) + #ptype.load(operations) + ptype.load(element) + continue + + elif localName == 'binding': + name = DOM.getAttr(element, 'name') + type = DOM.getAttr(element, 'type', default=None) + if type is None: + raise WSDLError( + 'Missing type attribute for binding %s.' % name + ) + type = ParseQName(type, element) + docs = GetDocumentation(element) + binding = self.addBinding(name, type, docs, targetNamespace) + operations = DOM.getElements(element, 'operation', NS_WSDL) + binding.load(operations) + binding.load_ex(GetExtensions(element)) + continue + + elif localName == 'service': + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + service = self.addService(name, docs, targetNamespace) + ports = DOM.getElements(element, 'port', NS_WSDL) + service.load(ports) + service.load_ex(GetExtensions(element)) + continue + + elif localName == 'types': + self.types.documentation = GetDocumentation(element) + base_location = DOM.getAttr(element, 'base-location') + if base_location: + element.removeAttribute('base-location') + base_location = base_location or self.location + reader = SchemaReader(base_url=base_location) + for item in DOM.getElements(element, None, None): + if item.localName == 'schema': + schema = reader.loadFromNode(WSDLToolsAdapter(self), item) + # XXX could have been imported + #schema.setBaseUrl(self.location) + schema.setBaseUrl(base_location) + self.types.addSchema(schema) + else: + self.types.addExtension(item) + # XXX remove the attribute + # element.removeAttribute('base-location') + continue + + def _import(self, document, element, base_location=None): + '''Algo take element's children, clone them, + and add them to the main document. Support for relative + locations is a bit complicated. The orig document context + is lost, so we need to store base location in DOM elements + representing , by creating a special temporary + "base-location" attribute, and , by resolving + the relative "location" and storing it as "location". + + document -- document we are loading + element -- DOM Element representing + base_location -- location of document from which this + was gleaned. + ''' + namespace = DOM.getAttr(element, 'namespace', default=None) + location = DOM.getAttr(element, 'location', default=None) + if namespace is None or location is None: + raise WSDLError( + 'Invalid import element (missing namespace or location).' + ) + if base_location: + location = urllib.basejoin(base_location, location) + element.setAttributeNS(None, 'location', location) + + #location = urllib.basejoin(self.location, location) + #obimport = self.addImport(namespace, location) + #obimport._loaded = 1 + + importdoc = DOM.loadFromURL(location) + try: + if location.find('#') > -1: + idref = location.split('#')[-1] + imported = DOM.getElementById(importdoc, idref) + else: + imported = importdoc.documentElement + if imported is None: + raise WSDLError( + 'Import target element not found for: %s' % location + ) + + imported_tns = DOM.findTargetNS(imported) + if imported_tns != namespace: + return + + if imported.localName == 'definitions': + imported_nodes = imported.childNodes + else: + imported_nodes = [imported] + parent = element.parentNode + for node in imported_nodes: + if node.nodeType != node.ELEMENT_NODE: + continue + child = DOM.importNode(document, node, 1) + parent.appendChild(child) + child.setAttribute('targetNamespace', namespace) + attrsNS = imported._attrsNS + for attrkey in attrsNS.keys(): + if attrkey[0] == DOM.NS_XMLNS: + attr = attrsNS[attrkey].cloneNode(1) + child.setAttributeNode(attr) + + #XXX Quick Hack, should be in WSDL Namespace. + if child.localName == 'import': + rlocation = child.getAttributeNS(None, 'location') + alocation = urllib.basejoin(location, rlocation) + child.setAttribute('location', alocation) + elif child.localName == 'types': + child.setAttribute('base-location', location) + + finally: + importdoc.unlink() + return location + +class Element: + """A class that provides common functions for WSDL element classes.""" + def __init__(self, name=None, documentation=''): + self.name = name + self.documentation = documentation + self.extensions = [] + + def addExtension(self, item): + self.extensions.append(item) + + +class ImportElement(Element): + def __init__(self, namespace, location): + self.namespace = namespace + self.location = location + + _loaded = None + + +class Types(Collection): + default = lambda self,k: k.targetNamespace + def __init__(self, parent): + Collection.__init__(self, parent) + self.documentation = '' + self.extensions = [] + + def addSchema(self, schema): + name = schema.targetNamespace + self[name] = schema + return schema + + def addExtension(self, item): + self.extensions.append(item) + + +class Message(Element): + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.parts = Collection(self) + + def addPart(self, name, type=None, element=None): + if self.parts.has_key(name): + raise WSDLError( + 'Duplicate message part element: %s' % name + ) + if type is None and element is None: + raise WSDLError( + 'Missing type or element attribute for part: %s' % name + ) + item = MessagePart(name) + item.element = element + item.type = type + self.parts[name] = item + return item + + def load(self, elements): + for element in elements: + name = DOM.getAttr(element, 'name') + part = MessagePart(name) + self.parts[name] = part + elemref = DOM.getAttr(element, 'element', default=None) + typeref = DOM.getAttr(element, 'type', default=None) + if typeref is None and elemref is None: + raise WSDLError( + 'No type or element attribute for part: %s' % name + ) + if typeref is not None: + part.type = ParseTypeRef(typeref, element) + if elemref is not None: + part.element = ParseTypeRef(elemref, element) + + +class MessagePart(Element): + def __init__(self, name): + Element.__init__(self, name, '') + self.element = None + self.type = None + + def getWSDL(self): + """Return the WSDL object that contains this Message Part.""" + return self.parent().parent().parent().parent() + + def getTypeDefinition(self): + wsdl = self.getWSDL() + nsuri,name = self.type + schema = wsdl.types.get(nsuri, {}) + return schema.get(name) + + def getElementDeclaration(self): + wsdl = self.getWSDL() + nsuri,name = self.element + schema = wsdl.types.get(nsuri, {}) + return schema.get(name) + + +class PortType(Element): + '''PortType has a anyAttribute, thus must provide for an extensible + mechanism for supporting such attributes. ResourceProperties is + specified in WS-ResourceProperties. wsa:Action is specified in + WS-Address. + + Instance Data: + name -- name attribute + resourceProperties -- optional. wsr:ResourceProperties attribute, + value is a QName this is Parsed into a (namespaceURI, name) + that represents a Global Element Declaration. + operations + ''' + + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.operations = Collection(self) + self.resourceProperties = None + + def getWSDL(self): + return self.parent().parent() + + def getResourceProperties(self): + return self.resourceProperties + + def addOperation(self, name, documentation='', parameterOrder=None): + item = Operation(name, documentation, parameterOrder) + self.operations[name] = item + return item + + def load(self, element): + self.name = DOM.getAttr(element, 'name') + self.documentation = GetDocumentation(element) + + if DOM.hasAttr(element, 'ResourceProperties', WSR.PROPERTIES): + rpref = DOM.getAttr(element, 'ResourceProperties', WSR.PROPERTIES) + self.resourceProperties = ParseQName(rpref, element) + + NS_WSDL = DOM.GetWSDLUri(self.getWSDL().version) + elements = DOM.getElements(element, 'operation', NS_WSDL) + for element in elements: + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + param_order = DOM.getAttr(element, 'parameterOrder', default=None) + if param_order is not None: + param_order = param_order.split(' ') + operation = self.addOperation(name, docs, param_order) + + item = DOM.getElement(element, 'input', None, None) + if item is not None: + name = DOM.getAttr(item, 'name') + docs = GetDocumentation(item) + msgref = DOM.getAttr(item, 'message') + message = ParseQName(msgref, item) + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + operation.setInput(message, name, docs, action) + + item = DOM.getElement(element, 'output', None, None) + if item is not None: + name = DOM.getAttr(item, 'name') + docs = GetDocumentation(item) + msgref = DOM.getAttr(item, 'message') + message = ParseQName(msgref, item) + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + operation.setOutput(message, name, docs, action) + + for item in DOM.getElements(element, 'fault', None): + name = DOM.getAttr(item, 'name') + docs = GetDocumentation(item) + msgref = DOM.getAttr(item, 'message') + message = ParseQName(msgref, item) + action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) + operation.addFault(message, name, docs, action) + + + +class Operation(Element): + def __init__(self, name, documentation='', parameterOrder=None): + Element.__init__(self, name, documentation) + self.parameterOrder = parameterOrder + self.faults = Collection(self) + self.input = None + self.output = None + + def getPortType(self): + return self.parent().parent() + + def getInputAction(self): + """wsa:Action attribute""" + return GetWSAActionInput(self) + + def getInputMessage(self): + if self.input is None: + return None + wsdl = self.getPortType().getWSDL() + return wsdl.messages[self.input.message] + + def getOutputAction(self): + """wsa:Action attribute""" + return GetWSAActionOutput(self) + + def getOutputMessage(self): + if self.output is None: + return None + wsdl = self.getPortType().getWSDL() + return wsdl.messages[self.output.message] + + def getFaultAction(self, name): + """wsa:Action attribute""" + return GetWSAActionFault(self, name) + + def getFaultMessage(self, name): + wsdl = self.getPortType().getWSDL() + return wsdl.messages[self.faults[name].message] + + def addFault(self, message, name, documentation='', action=None): + if self.faults.has_key(name): + raise WSDLError( + 'Duplicate fault element: %s' % name + ) + item = MessageRole('fault', message, name, documentation, action) + self.faults[name] = item + return item + + def setInput(self, message, name='', documentation='', action=None): + self.input = MessageRole('input', message, name, documentation, action) + return self.input + + def setOutput(self, message, name='', documentation='', action=None): + self.output = MessageRole('output', message, name, documentation, action) + return self.output + + +class MessageRole(Element): + def __init__(self, type, message, name='', documentation='', action=None): + Element.__init__(self, name, documentation) + self.message = message + self.type = type + self.action = action + + +class Binding(Element): + def __init__(self, name, type, documentation=''): + Element.__init__(self, name, documentation) + self.operations = Collection(self) + self.type = type + + def getWSDL(self): + """Return the WSDL object that contains this binding.""" + return self.parent().parent() + + def getPortType(self): + """Return the PortType object associated with this binding.""" + return self.getWSDL().portTypes[self.type] + + def findBinding(self, kind): + for item in self.extensions: + if isinstance(item, kind): + return item + return None + + def findBindings(self, kind): + return [ item for item in self.extensions if isinstance(item, kind) ] + + def addOperationBinding(self, name, documentation=''): + item = OperationBinding(name, documentation) + self.operations[name] = item + return item + + def load(self, elements): + for element in elements: + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + opbinding = self.addOperationBinding(name, docs) + opbinding.load_ex(GetExtensions(element)) + + item = DOM.getElement(element, 'input', None, None) + if item is not None: + mbinding = MessageRoleBinding('input') + mbinding.documentation = GetDocumentation(item) + opbinding.input = mbinding + mbinding.load_ex(GetExtensions(item)) + + item = DOM.getElement(element, 'output', None, None) + if item is not None: + mbinding = MessageRoleBinding('output') + mbinding.documentation = GetDocumentation(item) + opbinding.output = mbinding + mbinding.load_ex(GetExtensions(item)) + + for item in DOM.getElements(element, 'fault', None): + name = DOM.getAttr(item, 'name') + mbinding = MessageRoleBinding('fault', name) + mbinding.documentation = GetDocumentation(item) + opbinding.faults[name] = mbinding + mbinding.load_ex(GetExtensions(item)) + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_SOAP_BINDING_ALL and name == 'binding': + transport = DOM.getAttr(e, 'transport', default=None) + style = DOM.getAttr(e, 'style', default='document') + ob = SoapBinding(transport, style) + self.addExtension(ob) + continue + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'binding': + verb = DOM.getAttr(e, 'verb') + ob = HttpBinding(verb) + self.addExtension(ob) + continue + else: + self.addExtension(e) + + +class OperationBinding(Element): + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.input = None + self.output = None + self.faults = Collection(self) + + def getBinding(self): + """Return the parent Binding object of the operation binding.""" + return self.parent().parent() + + def getOperation(self): + """Return the abstract Operation associated with this binding.""" + return self.getBinding().getPortType().operations[self.name] + + def findBinding(self, kind): + for item in self.extensions: + if isinstance(item, kind): + return item + return None + + def findBindings(self, kind): + return [ item for item in self.extensions if isinstance(item, kind) ] + + def addInputBinding(self, binding): + if self.input is None: + self.input = MessageRoleBinding('input') + self.input.addExtension(binding) + return binding + + def addOutputBinding(self, binding): + if self.output is None: + self.output = MessageRoleBinding('output') + self.output.addExtension(binding) + return binding + + def addFaultBinding(self, name, binding): + fault = self.get(name, None) + if fault is None: + fault = MessageRoleBinding('fault', name) + fault.addExtension(binding) + return binding + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_SOAP_BINDING_ALL and name == 'operation': + soapaction = DOM.getAttr(e, 'soapAction', default=None) + style = DOM.getAttr(e, 'style', default=None) + ob = SoapOperationBinding(soapaction, style) + self.addExtension(ob) + continue + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'operation': + location = DOM.getAttr(e, 'location') + ob = HttpOperationBinding(location) + self.addExtension(ob) + continue + else: + self.addExtension(e) + + +class MessageRoleBinding(Element): + def __init__(self, type, name='', documentation=''): + Element.__init__(self, name, documentation) + self.type = type + + def findBinding(self, kind): + for item in self.extensions: + if isinstance(item, kind): + return item + return None + + def findBindings(self, kind): + return [ item for item in self.extensions if isinstance(item, kind) ] + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_SOAP_BINDING_ALL and name == 'body': + encstyle = DOM.getAttr(e, 'encodingStyle', default=None) + namespace = DOM.getAttr(e, 'namespace', default=None) + parts = DOM.getAttr(e, 'parts', default=None) + use = DOM.getAttr(e, 'use', default=None) + if use is None: + raise WSDLError( + 'Invalid soap:body binding element.' + ) + ob = SoapBodyBinding(use, namespace, encstyle, parts) + self.addExtension(ob) + continue + + elif ns in DOM.NS_SOAP_BINDING_ALL and name == 'fault': + encstyle = DOM.getAttr(e, 'encodingStyle', default=None) + namespace = DOM.getAttr(e, 'namespace', default=None) + name = DOM.getAttr(e, 'name', default=None) + use = DOM.getAttr(e, 'use', default=None) + if use is None or name is None: + raise WSDLError( + 'Invalid soap:fault binding element.' + ) + ob = SoapFaultBinding(name, use, namespace, encstyle) + self.addExtension(ob) + continue + + elif ns in DOM.NS_SOAP_BINDING_ALL and name in ( + 'header', 'headerfault' + ): + encstyle = DOM.getAttr(e, 'encodingStyle', default=None) + namespace = DOM.getAttr(e, 'namespace', default=None) + message = DOM.getAttr(e, 'message') + part = DOM.getAttr(e, 'part') + use = DOM.getAttr(e, 'use') + if name == 'header': + _class = SoapHeaderBinding + else: + _class = SoapHeaderFaultBinding + message = ParseQName(message, e) + ob = _class(message, part, use, namespace, encstyle) + self.addExtension(ob) + continue + + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'urlReplacement': + ob = HttpUrlReplacementBinding() + self.addExtension(ob) + continue + + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'urlEncoded': + ob = HttpUrlEncodedBinding() + self.addExtension(ob) + continue + + elif ns in DOM.NS_MIME_BINDING_ALL and name == 'multipartRelated': + ob = MimeMultipartRelatedBinding() + self.addExtension(ob) + ob.load_ex(GetExtensions(e)) + continue + + elif ns in DOM.NS_MIME_BINDING_ALL and name == 'content': + part = DOM.getAttr(e, 'part', default=None) + type = DOM.getAttr(e, 'type', default=None) + ob = MimeContentBinding(part, type) + self.addExtension(ob) + continue + + elif ns in DOM.NS_MIME_BINDING_ALL and name == 'mimeXml': + part = DOM.getAttr(e, 'part', default=None) + ob = MimeXmlBinding(part) + self.addExtension(ob) + continue + + else: + self.addExtension(e) + + +class Service(Element): + def __init__(self, name, documentation=''): + Element.__init__(self, name, documentation) + self.ports = Collection(self) + + def getWSDL(self): + return self.parent().parent() + + def addPort(self, name, binding, documentation=''): + item = Port(name, binding, documentation) + self.ports[name] = item + return item + + def load(self, elements): + for element in elements: + name = DOM.getAttr(element, 'name', default=None) + docs = GetDocumentation(element) + binding = DOM.getAttr(element, 'binding', default=None) + if name is None or binding is None: + raise WSDLError( + 'Invalid port element.' + ) + binding = ParseQName(binding, element) + port = self.addPort(name, binding, docs) + port.load_ex(GetExtensions(element)) + + def load_ex(self, elements): + for e in elements: + self.addExtension(e) + + +class Port(Element): + def __init__(self, name, binding, documentation=''): + Element.__init__(self, name, documentation) + self.binding = binding + + def getService(self): + """Return the Service object associated with this port.""" + return self.parent().parent() + + def getBinding(self): + """Return the Binding object that is referenced by this port.""" + wsdl = self.getService().getWSDL() + return wsdl.bindings[self.binding] + + def getPortType(self): + """Return the PortType object that is referenced by this port.""" + wsdl = self.getService().getWSDL() + binding = wsdl.bindings[self.binding] + return wsdl.portTypes[binding.type] + + def getAddressBinding(self): + """A convenience method to obtain the extension element used + as the address binding for the port, or None if undefined.""" + for item in self.extensions: + if isinstance(item, SoapAddressBinding) or \ + isinstance(item, HttpAddressBinding): + return item + raise WSDLError( + 'No address binding found in port.' + ) + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_SOAP_BINDING_ALL and name == 'address': + location = DOM.getAttr(e, 'location', default=None) + ob = SoapAddressBinding(location) + self.addExtension(ob) + continue + elif ns in DOM.NS_HTTP_BINDING_ALL and name == 'address': + location = DOM.getAttr(e, 'location', default=None) + ob = HttpAddressBinding(location) + self.addExtension(ob) + continue + else: + self.addExtension(e) + + +class SoapBinding: + def __init__(self, transport, style='rpc'): + self.transport = transport + self.style = style + + +class SoapAddressBinding: + def __init__(self, location): + self.location = location + + +class SoapOperationBinding: + def __init__(self, soapAction=None, style=None): + self.soapAction = soapAction + self.style = style + + +class SoapBodyBinding: + def __init__(self, use, namespace=None, encodingStyle=None, parts=None): + if not use in ('literal', 'encoded'): + raise WSDLError( + 'Invalid use attribute value: %s' % use + ) + self.encodingStyle = encodingStyle + self.namespace = namespace + if type(parts) in (type(''), type(u'')): + parts = parts.split() + self.parts = parts + self.use = use + +class SoapFaultBinding: + def __init__(self, name, use, namespace=None, encodingStyle=None): + if not use in ('literal', 'encoded'): + raise WSDLError( + 'Invalid use attribute value: %s' % use + ) + self.encodingStyle = encodingStyle + self.namespace = namespace + self.name = name + self.use = use + + +class SoapHeaderBinding: + def __init__(self, message, part, use, namespace=None, encodingStyle=None): + if not use in ('literal', 'encoded'): + raise WSDLError( + 'Invalid use attribute value: %s' % use + ) + self.encodingStyle = encodingStyle + self.namespace = namespace + self.message = message + self.part = part + self.use = use + + tagname = 'header' + +class SoapHeaderFaultBinding(SoapHeaderBinding): + tagname = 'headerfault' + + +class HttpBinding: + def __init__(self, verb): + self.verb = verb + +class HttpAddressBinding: + def __init__(self, location): + self.location = location + + +class HttpOperationBinding: + def __init__(self, location): + self.location = location + +class HttpUrlReplacementBinding: + pass + + +class HttpUrlEncodedBinding: + pass + + +class MimeContentBinding: + def __init__(self, part=None, type=None): + self.part = part + self.type = type + + +class MimeXmlBinding: + def __init__(self, part=None): + self.part = part + + +class MimeMultipartRelatedBinding: + def __init__(self): + self.parts = [] + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_MIME_BINDING_ALL and name == 'part': + self.parts.append(MimePartBinding()) + continue + + +class MimePartBinding: + def __init__(self): + self.items = [] + + def load_ex(self, elements): + for e in elements: + ns, name = e.namespaceURI, e.localName + if ns in DOM.NS_MIME_BINDING_ALL and name == 'content': + part = DOM.getAttr(e, 'part', default=None) + type = DOM.getAttr(e, 'type', default=None) + ob = MimeContentBinding(part, type) + self.items.append(ob) + continue + + elif ns in DOM.NS_MIME_BINDING_ALL and name == 'mimeXml': + part = DOM.getAttr(e, 'part', default=None) + ob = MimeXmlBinding(part) + self.items.append(ob) + continue + + elif ns in DOM.NS_SOAP_BINDING_ALL and name == 'body': + encstyle = DOM.getAttr(e, 'encodingStyle', default=None) + namespace = DOM.getAttr(e, 'namespace', default=None) + parts = DOM.getAttr(e, 'parts', default=None) + use = DOM.getAttr(e, 'use', default=None) + if use is None: + raise WSDLError( + 'Invalid soap:body binding element.' + ) + ob = SoapBodyBinding(use, namespace, encstyle, parts) + self.items.append(ob) + continue + + +class WSDLError(Exception): + pass + + + +def DeclareNSPrefix(writer, prefix, nsuri): + if writer.hasNSPrefix(nsuri): + return + writer.declareNSPrefix(prefix, nsuri) + +def ParseTypeRef(value, element): + parts = value.split(':', 1) + if len(parts) == 1: + return (DOM.findTargetNS(element), value) + nsuri = DOM.findNamespaceURI(parts[0], element) + return (nsuri, parts[1]) + +def ParseQName(value, element): + nameref = value.split(':', 1) + if len(nameref) == 2: + nsuri = DOM.findNamespaceURI(nameref[0], element) + name = nameref[-1] + else: + nsuri = DOM.findTargetNS(element) + name = nameref[-1] + return nsuri, name + +def GetDocumentation(element): + docnode = DOM.getElement(element, 'documentation', None, None) + if docnode is not None: + return DOM.getElementText(docnode) + return '' + +def GetExtensions(element): + return [ item for item in DOM.getElements(element, None, None) + if item.namespaceURI != DOM.NS_WSDL ] + +def GetWSAActionFault(operation, name): + """Find wsa:Action attribute, and return value or WSA.FAULT + for the default. + """ + attr = operation.faults[name].action + if attr is not None: + return attr + return WSA.FAULT + +def GetWSAActionInput(operation): + """Find wsa:Action attribute, and return value or the default.""" + attr = operation.input.action + if attr is not None: + return attr + portType = operation.getPortType() + targetNamespace = portType.getWSDL().targetNamespace + ptName = portType.name + msgName = operation.input.name + if not msgName: + msgName = operation.name + 'Request' + if targetNamespace.endswith('/'): + return '%s%s/%s' %(targetNamespace, ptName, msgName) + return '%s/%s/%s' %(targetNamespace, ptName, msgName) + +def GetWSAActionOutput(operation): + """Find wsa:Action attribute, and return value or the default.""" + attr = operation.output.action + if attr is not None: + return attr + targetNamespace = operation.getPortType().getWSDL().targetNamespace + ptName = operation.getPortType().name + msgName = operation.output.name + if not msgName: + msgName = operation.name + 'Response' + if targetNamespace.endswith('/'): + return '%s%s/%s' %(targetNamespace, ptName, msgName) + return '%s/%s/%s' %(targetNamespace, ptName, msgName) + +def FindExtensions(object, kind, t_type=type(())): + if isinstance(kind, t_type): + result = [] + namespaceURI, name = kind + return [ item for item in object.extensions + if hasattr(item, 'nodeType') \ + and DOM.nsUriMatch(namespaceURI, item.namespaceURI) \ + and item.name == name ] + return [ item for item in object.extensions if isinstance(item, kind) ] + +def FindExtension(object, kind, t_type=type(())): + if isinstance(kind, t_type): + namespaceURI, name = kind + for item in object.extensions: + if hasattr(item, 'nodeType') \ + and DOM.nsUriMatch(namespaceURI, item.namespaceURI) \ + and item.name == name: + return item + else: + for item in object.extensions: + if isinstance(item, kind): + return item + return None + + +class SOAPCallInfo: + """SOAPCallInfo captures the important binding information about a + SOAP operation, in a structure that is easier to work with than + raw WSDL structures.""" + + def __init__(self, methodName): + self.methodName = methodName + self.inheaders = [] + self.outheaders = [] + self.inparams = [] + self.outparams = [] + self.retval = None + + encodingStyle = DOM.NS_SOAP_ENC + documentation = '' + soapAction = None + transport = None + namespace = None + location = None + use = 'encoded' + style = 'rpc' + + def addInParameter(self, name, type, namespace=None, element_type=0): + """Add an input parameter description to the call info.""" + parameter = ParameterInfo(name, type, namespace, element_type) + self.inparams.append(parameter) + return parameter + + def addOutParameter(self, name, type, namespace=None, element_type=0): + """Add an output parameter description to the call info.""" + parameter = ParameterInfo(name, type, namespace, element_type) + self.outparams.append(parameter) + return parameter + + def setReturnParameter(self, name, type, namespace=None, element_type=0): + """Set the return parameter description for the call info.""" + parameter = ParameterInfo(name, type, namespace, element_type) + self.retval = parameter + return parameter + + def addInHeaderInfo(self, name, type, namespace, element_type=0, + mustUnderstand=0): + """Add an input SOAP header description to the call info.""" + headerinfo = HeaderInfo(name, type, namespace, element_type) + if mustUnderstand: + headerinfo.mustUnderstand = 1 + self.inheaders.append(headerinfo) + return headerinfo + + def addOutHeaderInfo(self, name, type, namespace, element_type=0, + mustUnderstand=0): + """Add an output SOAP header description to the call info.""" + headerinfo = HeaderInfo(name, type, namespace, element_type) + if mustUnderstand: + headerinfo.mustUnderstand = 1 + self.outheaders.append(headerinfo) + return headerinfo + + def getInParameters(self): + """Return a sequence of the in parameters of the method.""" + return self.inparams + + def getOutParameters(self): + """Return a sequence of the out parameters of the method.""" + return self.outparams + + def getReturnParameter(self): + """Return param info about the return value of the method.""" + return self.retval + + def getInHeaders(self): + """Return a sequence of the in headers of the method.""" + return self.inheaders + + def getOutHeaders(self): + """Return a sequence of the out headers of the method.""" + return self.outheaders + + +class ParameterInfo: + """A ParameterInfo object captures parameter binding information.""" + def __init__(self, name, type, namespace=None, element_type=0): + if element_type: + self.element_type = 1 + if namespace is not None: + self.namespace = namespace + self.name = name + self.type = type + + element_type = 0 + namespace = None + default = None + + +class HeaderInfo(ParameterInfo): + """A HeaderInfo object captures SOAP header binding information.""" + def __init__(self, name, type, namespace, element_type=None): + ParameterInfo.__init__(self, name, type, namespace, element_type) + + mustUnderstand = 0 + actor = None + + +def callInfoFromWSDL(port, name): + """Return a SOAPCallInfo given a WSDL port and operation name.""" + wsdl = port.getService().getWSDL() + binding = port.getBinding() + portType = binding.getPortType() + operation = portType.operations[name] + opbinding = binding.operations[name] + messages = wsdl.messages + callinfo = SOAPCallInfo(name) + + addrbinding = port.getAddressBinding() + if not isinstance(addrbinding, SoapAddressBinding): + raise ValueError, 'Unsupported binding type.' + callinfo.location = addrbinding.location + + soapbinding = binding.findBinding(SoapBinding) + if soapbinding is None: + raise ValueError, 'Missing soap:binding element.' + callinfo.transport = soapbinding.transport + callinfo.style = soapbinding.style or 'document' + + soap_op_binding = opbinding.findBinding(SoapOperationBinding) + if soap_op_binding is not None: + callinfo.soapAction = soap_op_binding.soapAction + callinfo.style = soap_op_binding.style or callinfo.style + + parameterOrder = operation.parameterOrder + + if operation.input is not None: + message = messages[operation.input.message] + msgrole = opbinding.input + + mime = msgrole.findBinding(MimeMultipartRelatedBinding) + if mime is not None: + raise ValueError, 'Mime bindings are not supported.' + else: + for item in msgrole.findBindings(SoapHeaderBinding): + part = messages[item.message].parts[item.part] + header = callinfo.addInHeaderInfo( + part.name, + part.element or part.type, + item.namespace, + element_type = part.element and 1 or 0 + ) + header.encodingStyle = item.encodingStyle + + body = msgrole.findBinding(SoapBodyBinding) + if body is None: + raise ValueError, 'Missing soap:body binding.' + callinfo.encodingStyle = body.encodingStyle + callinfo.namespace = body.namespace + callinfo.use = body.use + + if body.parts is not None: + parts = [] + for name in body.parts: + parts.append(message.parts[name]) + else: + parts = message.parts.values() + + for part in parts: + callinfo.addInParameter( + part.name, + part.element or part.type, + element_type = part.element and 1 or 0 + ) + + if operation.output is not None: + try: + message = messages[operation.output.message] + except KeyError: + if self.strict: + raise RuntimeError( + "Recieved message not defined in the WSDL schema: %s" % + operation.output.message) + else: + message = wsdl.addMessage(operation.output.message) + print "Warning:", \ + "Recieved message not defined in the WSDL schema.", \ + "Adding it." + print "Message:", operation.output.message + + msgrole = opbinding.output + + mime = msgrole.findBinding(MimeMultipartRelatedBinding) + if mime is not None: + raise ValueError, 'Mime bindings are not supported.' + else: + for item in msgrole.findBindings(SoapHeaderBinding): + part = messages[item.message].parts[item.part] + header = callinfo.addOutHeaderInfo( + part.name, + part.element or part.type, + item.namespace, + element_type = part.element and 1 or 0 + ) + header.encodingStyle = item.encodingStyle + + body = msgrole.findBinding(SoapBodyBinding) + if body is None: + raise ValueError, 'Missing soap:body binding.' + callinfo.encodingStyle = body.encodingStyle + callinfo.namespace = body.namespace + callinfo.use = body.use + + if body.parts is not None: + parts = [] + for name in body.parts: + parts.append(message.parts[name]) + else: + parts = message.parts.values() + + if parts: + # XXX no idea what this is for, but it breaks everything. jrb + #callinfo.setReturnParameter( + # parts[0].name, + # parts[0].element or parts[0].type, + # element_type = parts[0].element and 1 or 0 + # ) + #for part in parts[1:]: + for part in parts: + callinfo.addOutParameter( + part.name, + part.element or part.type, + element_type = part.element and 1 or 0 + ) + + return callinfo diff --git a/others/SOAPpy/wstools/XMLSchema.py b/others/SOAPpy/wstools/XMLSchema.py new file mode 100755 index 000000000..a1aecc4aa --- /dev/null +++ b/others/SOAPpy/wstools/XMLSchema.py @@ -0,0 +1,2976 @@ +# Copyright (c) 2003, The Regents of the University of California, +# through Lawrence Berkeley National Laboratory (subject to receipt of +# any required approvals from the U.S. Dept. of Energy). All rights +# reserved. +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. + +ident = "$Id$" + +import types, weakref, urllib, sys +from threading import RLock +from Namespaces import XMLNS +from Utility import DOM, DOMException, Collection, SplitQName +from StringIO import StringIO + +def GetSchema(component): + """convience function for finding the parent XMLSchema instance. + """ + parent = component + while not isinstance(parent, XMLSchema): + parent = parent._parent() + return parent + +class SchemaReader: + """A SchemaReader creates XMLSchema objects from urls and xml data. + """ + def __init__(self, domReader=None, base_url=None): + """domReader -- class must implement DOMAdapterInterface + base_url -- base url string + """ + self.__base_url = base_url + self.__readerClass = domReader + if not self.__readerClass: + self.__readerClass = DOMAdapter + self._includes = {} + self._imports = {} + + def __setImports(self, schema): + """Add dictionary of imports to schema instance. + schema -- XMLSchema instance + """ + for ns,val in schema.imports.items(): + if self._imports.has_key(ns): + schema.addImportSchema(self._imports[ns]) + + def __setIncludes(self, schema): + """Add dictionary of includes to schema instance. + schema -- XMLSchema instance + """ + for schemaLocation, val in schema.includes.items(): + if self._includes.has_key(schemaLocation): + schema.addIncludeSchema(self._imports[schemaLocation]) + + def addSchemaByLocation(self, location, schema): + """provide reader with schema document for a location. + """ + self._includes[location] = schema + + def addSchemaByNamespace(self, schema): + """provide reader with schema document for a targetNamespace. + """ + self._imports[schema.targetNamespace] = schema + + def loadFromNode(self, parent, element): + """element -- DOM node or document + parent -- WSDLAdapter instance + """ + reader = self.__readerClass(element) + schema = XMLSchema(parent) + #HACK to keep a reference + schema.wsdl = parent + schema.setBaseUrl(self.__base_url) + schema.load(reader) + return schema + + def loadFromStream(self, file): + """Return an XMLSchema instance loaded from a file object. + file -- file object + """ + reader = self.__readerClass() + reader.loadDocument(file) + schema = XMLSchema() + schema.setBaseUrl(self.__base_url) + schema.load(reader) + self.__setIncludes(schema) + self.__setImports(schema) + return schema + + def loadFromString(self, data): + """Return an XMLSchema instance loaded from an XML string. + data -- XML string + """ + return self.loadFromStream(StringIO(data)) + + def loadFromURL(self, url): + """Return an XMLSchema instance loaded from the given url. + url -- URL to dereference + """ + if not url.endswith('xsd'): + raise SchemaError, 'unknown file type %s' %url + reader = self.__readerClass() + if self.__base_url: + url = urllib.basejoin(self.__base_url,url) + reader.loadFromURL(url) + schema = XMLSchema() + schema.setBaseUrl(self.__base_url) + schema.load(reader) + self.__setIncludes(schema) + self.__setImports(schema) + return schema + + def loadFromFile(self, filename): + """Return an XMLSchema instance loaded from the given file. + filename -- name of file to open + """ + file = open(filename, 'rb') + try: schema = self.loadFromStream(file) + finally: file.close() + return schema + + +class SchemaError(Exception): + pass + +########################### +# DOM Utility Adapters +########################## +class DOMAdapterInterface: + def hasattr(self, attr, ns=None): + """return true if node has attribute + attr -- attribute to check for + ns -- namespace of attribute, by default None + """ + raise NotImplementedError, 'adapter method not implemented' + + def getContentList(self, *contents): + """returns an ordered list of child nodes + *contents -- list of node names to return + """ + raise NotImplementedError, 'adapter method not implemented' + + def setAttributeDictionary(self, attributes): + """set attribute dictionary + """ + raise NotImplementedError, 'adapter method not implemented' + + def getAttributeDictionary(self): + """returns a dict of node's attributes + """ + raise NotImplementedError, 'adapter method not implemented' + + def getNamespace(self, prefix): + """returns namespace referenced by prefix. + """ + raise NotImplementedError, 'adapter method not implemented' + + def getTagName(self): + """returns tagName of node + """ + raise NotImplementedError, 'adapter method not implemented' + + + def getParentNode(self): + """returns parent element in DOMAdapter or None + """ + raise NotImplementedError, 'adapter method not implemented' + + def loadDocument(self, file): + """load a Document from a file object + file -- + """ + raise NotImplementedError, 'adapter method not implemented' + + def loadFromURL(self, url): + """load a Document from an url + url -- URL to dereference + """ + raise NotImplementedError, 'adapter method not implemented' + + +class DOMAdapter(DOMAdapterInterface): + """Adapter for ZSI.Utility.DOM + """ + def __init__(self, node=None): + """Reset all instance variables. + element -- DOM document, node, or None + """ + if hasattr(node, 'documentElement'): + self.__node = node.documentElement + else: + self.__node = node + self.__attributes = None + + def hasattr(self, attr, ns=None): + """attr -- attribute + ns -- optional namespace, None means unprefixed attribute. + """ + if not self.__attributes: + self.setAttributeDictionary() + if ns: + return self.__attributes.get(ns,{}).has_key(attr) + return self.__attributes.has_key(attr) + + def getContentList(self, *contents): + nodes = [] + ELEMENT_NODE = self.__node.ELEMENT_NODE + for child in DOM.getElements(self.__node, None): + if child.nodeType == ELEMENT_NODE and\ + SplitQName(child.tagName)[1] in contents: + nodes.append(child) + return map(self.__class__, nodes) + + def setAttributeDictionary(self): + self.__attributes = {} + for v in self.__node._attrs.values(): + self.__attributes[v.nodeName] = v.nodeValue + + def getAttributeDictionary(self): + if not self.__attributes: + self.setAttributeDictionary() + return self.__attributes + + def getTagName(self): + return self.__node.tagName + + def getParentNode(self): + if self.__node.parentNode.nodeType == self.__node.ELEMENT_NODE: + return DOMAdapter(self.__node.parentNode) + return None + + def getNamespace(self, prefix): + """prefix -- deference namespace prefix in node's context. + Ascends parent nodes until found. + """ + namespace = None + if prefix == 'xmlns': + namespace = DOM.findDefaultNS(prefix, self.__node) + else: + try: + namespace = DOM.findNamespaceURI(prefix, self.__node) + except DOMException, ex: + if prefix != 'xml': + raise SchemaError, '%s namespace not declared for %s'\ + %(prefix, self.__node._get_tagName()) + namespace = XMLNS.XML + return namespace + + def loadDocument(self, file): + self.__node = DOM.loadDocument(file) + if hasattr(self.__node, 'documentElement'): + self.__node = self.__node.documentElement + + def loadFromURL(self, url): + self.__node = DOM.loadFromURL(url) + if hasattr(self.__node, 'documentElement'): + self.__node = self.__node.documentElement + + +class XMLBase: + """ These class variables are for string indentation. + """ + __indent = 0 + __rlock = RLock() + + def __str__(self): + XMLBase.__rlock.acquire() + XMLBase.__indent += 1 + tmp = "<" + str(self.__class__) + '>\n' + for k,v in self.__dict__.items(): + tmp += "%s* %s = %s\n" %(XMLBase.__indent*' ', k, v) + XMLBase.__indent -= 1 + XMLBase.__rlock.release() + return tmp + + +"""Marker Interface: can determine something about an instances properties by using + the provided convenience functions. + +""" +class DefinitionMarker: + """marker for definitions + """ + pass + +class DeclarationMarker: + """marker for declarations + """ + pass + +class AttributeMarker: + """marker for attributes + """ + pass + +class AttributeGroupMarker: + """marker for attribute groups + """ + pass + +class WildCardMarker: + """marker for wildcards + """ + pass + +class ElementMarker: + """marker for wildcards + """ + pass + +class ReferenceMarker: + """marker for references + """ + pass + +class ModelGroupMarker: + """marker for model groups + """ + pass + +class ExtensionMarker: + """marker for extensions + """ + pass + +class RestrictionMarker: + """marker for restrictions + """ + facets = ['enumeration', 'length', 'maxExclusive', 'maxInclusive',\ + 'maxLength', 'minExclusive', 'minInclusive', 'minLength',\ + 'pattern', 'fractionDigits', 'totalDigits', 'whiteSpace'] + +class SimpleMarker: + """marker for simple type information + """ + pass + +class ComplexMarker: + """marker for complex type information + """ + pass + +class LocalMarker: + """marker for complex type information + """ + pass + + +class MarkerInterface: + def isDefinition(self): + return isinstance(self, DefinitionMarker) + + def isDeclaration(self): + return isinstance(self, DeclarationMarker) + + def isAttribute(self): + return isinstance(self, AttributeMarker) + + def isAttributeGroup(self): + return isinstance(self, AttributeGroupMarker) + + def isElement(self): + return isinstance(self, ElementMarker) + + def isReference(self): + return isinstance(self, ReferenceMarker) + + def isWildCard(self): + return isinstance(self, WildCardMarker) + + def isModelGroup(self): + return isinstance(self, ModelGroupMarker) + + def isExtension(self): + return isinstance(self, ExtensionMarker) + + def isRestriction(self): + return isinstance(self, RestrictionMarker) + + def isSimple(self): + return isinstance(self, SimpleMarker) + + def isComplex(self): + return isinstance(self, ComplexMarker) + + def isLocal(self): + return isinstance(self, LocalMarker) + + +########################################################## +# Schema Components +######################################################### +class XMLSchemaComponent(XMLBase, MarkerInterface): + """ + class variables: + required -- list of required attributes + attributes -- dict of default attribute values, including None. + Value can be a function for runtime dependencies. + contents -- dict of namespace keyed content lists. + 'xsd' content of xsd namespace. + xmlns_key -- key for declared xmlns namespace. + xmlns -- xmlns is special prefix for namespace dictionary + xml -- special xml prefix for xml namespace. + """ + required = [] + attributes = {} + contents = {} + xmlns_key = '' + xmlns = 'xmlns' + xml = 'xml' + + def __init__(self, parent=None): + """parent -- parent instance + instance variables: + attributes -- dictionary of node's attributes + """ + self.attributes = None + self._parent = parent + if self._parent: + self._parent = weakref.ref(parent) + + if not self.__class__ == XMLSchemaComponent\ + and not (type(self.__class__.required) == type(XMLSchemaComponent.required)\ + and type(self.__class__.attributes) == type(XMLSchemaComponent.attributes)\ + and type(self.__class__.contents) == type(XMLSchemaComponent.contents)): + raise RuntimeError, 'Bad type for a class variable in %s' %self.__class__ + + def getTargetNamespace(self): + """return targetNamespace + """ + parent = self + targetNamespace = 'targetNamespace' + tns = self.attributes.get(targetNamespace) + while not tns: + parent = parent._parent() + tns = parent.attributes.get(targetNamespace) + return tns + + def getTypeDefinition(self, attribute): + """attribute -- attribute with a QName value (eg. type). + collection -- check types collection in parent Schema instance + """ + return self.getQNameAttribute('types', attribute) + + def getElementDeclaration(self, attribute): + """attribute -- attribute with a QName value (eg. element). + collection -- check elements collection in parent Schema instance. + """ + return self.getQNameAttribute('elements', attribute) + + def getQNameAttribute(self, collection, attribute): + """returns object instance representing QName --> (namespace,name), + or if does not exist return None. + attribute -- an information item attribute, with a QName value. + collection -- collection in parent Schema instance to search. + """ + obj = None + tdc = self.attributes.get(attribute) + if tdc: + parent = GetSchema(self) + targetNamespace = tdc.getTargetNamespace() + if parent.targetNamespace == targetNamespace: + item = tdc.getName() + try: + obj = getattr(parent, collection)[item] + except KeyError, ex: + raise KeyError, "targetNamespace(%s) collection(%s) has no item(%s)"\ + %(targetNamespace, collection, item) + elif parent.imports.has_key(targetNamespace): + schema = parent.imports[targetNamespace].getSchema() + item = tdc.getName() + try: + obj = getattr(schema, collection)[item] + except KeyError, ex: + raise KeyError, "targetNamespace(%s) collection(%s) has no item(%s)"\ + %(targetNamespace, collection, item) + return obj + + def getXMLNS(self, prefix=None): + """deference prefix or by default xmlns, returns namespace. + """ + if prefix == XMLSchemaComponent.xml: + return XMLNS.XML + parent = self + ns = self.attributes[XMLSchemaComponent.xmlns].get(prefix or\ + XMLSchemaComponent.xmlns_key) + while not ns: + parent = parent._parent() + ns = parent.attributes[XMLSchemaComponent.xmlns].get(prefix or\ + XMLSchemaComponent.xmlns_key) + if not ns and isinstance(parent, WSDLToolsAdapter): + raise SchemaError, 'unknown prefix %s' %prefix + return ns + + def getAttribute(self, attribute): + """return requested attribute or None + """ + return self.attributes.get(attribute) + + def setAttributes(self, node): + """Sets up attribute dictionary, checks for required attributes and + sets default attribute values. attr is for default attribute values + determined at runtime. + + structure of attributes dictionary + ['xmlns'][xmlns_key] -- xmlns namespace + ['xmlns'][prefix] -- declared namespace prefix + [namespace][prefix] -- attributes declared in a namespace + [attribute] -- attributes w/o prefix, default namespaces do + not directly apply to attributes, ie Name can't collide + with QName. + """ + self.attributes = {XMLSchemaComponent.xmlns:{}} + for k,v in node.getAttributeDictionary().items(): + prefix,value = SplitQName(k) + if value == XMLSchemaComponent.xmlns: + self.attributes[value][prefix or XMLSchemaComponent.xmlns_key] = v + elif prefix: + ns = node.getNamespace(prefix) + if not ns: + raise SchemaError, 'no namespace for attribute prefix %s'\ + %prefix + if not self.attributes.has_key(ns): + self.attributes[ns] = {} + elif self.attributes[ns].has_key(value): + raise SchemaError, 'attribute %s declared multiple times in %s'\ + %(value, ns) + self.attributes[ns][value] = v + elif not self.attributes.has_key(value): + self.attributes[value] = v + else: + raise SchemaError, 'attribute %s declared multiple times' %value + + self.__checkAttributes() + self.__setAttributeDefaults() + + #set QNames + for k in ['type', 'element', 'base', 'ref', 'substitutionGroup', 'itemType']: + if self.attributes.has_key(k): + prefix, value = SplitQName(self.attributes.get(k)) + self.attributes[k] = \ + TypeDescriptionComponent((self.getXMLNS(prefix), value)) + + #Union, memberTypes is a whitespace separated list of QNames + for k in ['memberTypes']: + if self.attributes.has_key(k): + qnames = self.attributes[k] + self.attributes[k] = [] + for qname in qnames.split(): + prefix, value = SplitQName(qname) + self.attributes['memberTypes'].append(\ + TypeDescriptionComponent(\ + (self.getXMLNS(prefix), value))) + + def getContents(self, node): + """retrieve xsd contents + """ + return node.getContentList(*self.__class__.contents['xsd']) + + def __setAttributeDefaults(self): + """Looks for default values for unset attributes. If + class variable representing attribute is None, then + it must be defined as an instance variable. + """ + for k,v in self.__class__.attributes.items(): + if v and not self.attributes.has_key(k): + if isinstance(v, types.FunctionType): + self.attributes[k] = v(self) + else: + self.attributes[k] = v + + def __checkAttributes(self): + """Checks that required attributes have been defined, + attributes w/default cannot be required. Checks + all defined attributes are legal, attribute + references are not subject to this test. + """ + for a in self.__class__.required: + if not self.attributes.has_key(a): + raise SchemaError,\ + 'class instance %s, missing required attribute %s'\ + %(self.__class__, a) + for a in self.attributes.keys(): + if (a not in (XMLSchemaComponent.xmlns, XMLNS.XML)) and\ + (a not in self.__class__.attributes.keys()) and not\ + (self.isAttribute() and self.isReference()): + raise SchemaError, '%s, unknown attribute' %a + + +class WSDLToolsAdapter(XMLSchemaComponent): + """WSDL Adapter to grab the attributes from the wsdl document node. + """ + attributes = {'name':None, 'targetNamespace':None} + + def __init__(self, wsdl): + #XMLSchemaComponent.__init__(self, None) + XMLSchemaComponent.__init__(self, parent=wsdl) + self.setAttributes(DOMAdapter(wsdl.document)) + + def getImportSchemas(self): + """returns WSDLTools.WSDL types Collection + """ + return self._parent().types + + +class Notation(XMLSchemaComponent): + """ + parent: + schema + attributes: + id -- ID + name -- NCName, Required + public -- token, Required + system -- anyURI + contents: + annotation? + """ + required = ['name', 'public'] + attributes = {'id':None, 'name':None, 'public':None, 'system':None} + contents = {'xsd':('annotation')} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class Annotation(XMLSchemaComponent): + """ + parent: + all,any,anyAttribute,attribute,attributeGroup,choice,complexContent, + complexType,element,extension,field,group,import,include,key,keyref, + list,notation,redefine,restriction,schema,selector,simpleContent, + simpleType,union,unique + attributes: + id -- ID + contents: + (documentation | appinfo)* + """ + attributes = {'id':None} + contents = {'xsd':('documentation', 'appinfo')} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'documentation': + #print_debug('class %s, documentation skipped' %self.__class__, 5) + continue + elif component == 'appinfo': + #print_debug('class %s, appinfo skipped' %self.__class__, 5) + continue + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + + class Documentation(XMLSchemaComponent): + """ + parent: + annotation + attributes: + source, anyURI + xml:lang, language + contents: + mixed, any + """ + attributes = {'source':None, 'xml:lang':None} + contents = {'xsd':('mixed', 'any')} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'mixed': + #print_debug('class %s, mixed skipped' %self.__class__, 5) + continue + elif component == 'any': + #print_debug('class %s, any skipped' %self.__class__, 5) + continue + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + + class Appinfo(XMLSchemaComponent): + """ + parent: + annotation + attributes: + source, anyURI + contents: + mixed, any + """ + attributes = {'source':None, 'anyURI':None} + contents = {'xsd':('mixed', 'any')} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'mixed': + #print_debug('class %s, mixed skipped' %self.__class__, 5) + continue + elif component == 'any': + #print_debug('class %s, any skipped' %self.__class__, 5) + continue + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + +class XMLSchemaFake: + # This is temporary, for the benefit of WSDL until the real thing works. + def __init__(self, element): + self.targetNamespace = DOM.getAttr(element, 'targetNamespace') + self.element = element + +class XMLSchema(XMLSchemaComponent): + """A schema is a collection of schema components derived from one + or more schema documents, that is, one or more element + information items. It represents the abstract notion of a schema + rather than a single schema document (or other representation). + + + parent: + ROOT + attributes: + id -- ID + version -- token + xml:lang -- language + targetNamespace -- anyURI + attributeFormDefault -- 'qualified' | 'unqualified', 'unqualified' + elementFormDefault -- 'qualified' | 'unqualified', 'unqualified' + blockDefault -- '#all' | list of + ('substitution | 'extension' | 'restriction') + finalDefault -- '#all' | list of + ('extension' | 'restriction' | 'list' | 'union') + + contents: + ((include | import | redefine | annotation)*, + (attribute, attributeGroup, complexType, element, group, + notation, simpleType)*, annotation*)* + + + attributes -- schema attributes + imports -- import statements + includes -- include statements + redefines -- + types -- global simpleType, complexType definitions + elements -- global element declarations + attr_decl -- global attribute declarations + attr_groups -- attribute Groups + model_groups -- model Groups + notations -- global notations + """ + attributes = {'id':None, + 'version':None, + 'xml:lang':None, + 'targetNamespace':None, + 'attributeFormDefault':'unqualified', + 'elementFormDefault':'unqualified', + 'blockDefault':None, + 'finalDefault':None} + contents = {'xsd':('include', 'import', 'redefine', 'annotation', 'attribute',\ + 'attributeGroup', 'complexType', 'element', 'group',\ + 'notation', 'simpleType', 'annotation')} + empty_namespace = '' + + def __init__(self, parent=None): + """parent -- + instance variables: + targetNamespace -- schema's declared targetNamespace, or empty string. + _imported_schemas -- namespace keyed dict of schema dependencies, if + a schema is provided instance will not resolve import statement. + _included_schemas -- schemaLocation keyed dict of component schemas, + if schema is provided instance will not resolve include statement. + _base_url -- needed for relative URLs support, only works with URLs + relative to initial document. + includes -- collection of include statements + imports -- collection of import statements + elements -- collection of global element declarations + types -- collection of global type definitions + attr_decl -- collection of global attribute declarations + attr_groups -- collection of global attribute group definitions + model_groups -- collection of model group definitions + notations -- collection of notations + + """ + self.targetNamespace = None + XMLSchemaComponent.__init__(self, parent) + f = lambda k: k.attributes['name'] + ns = lambda k: k.attributes['namespace'] + sl = lambda k: k.attributes['schemaLocation'] + self.includes = Collection(self, key=sl) + self.imports = Collection(self, key=ns) + self.elements = Collection(self, key=f) + self.types = Collection(self, key=f) + self.attr_decl = Collection(self, key=f) + self.attr_groups = Collection(self, key=f) + self.model_groups = Collection(self, key=f) + self.notations = Collection(self, key=f) + + self._imported_schemas = {} + self._included_schemas = {} + self._base_url = None + + def addImportSchema(self, schema): + """for resolving import statements in Schema instance + schema -- schema instance + _imported_schemas + """ + if not isinstance(schema, XMLSchema): + raise TypeError, 'expecting a Schema instance' + if schema.targetNamespace != self.targetNamespace: + self._imported_schemas[schema.targetNamespace] = schema + else: + raise SchemaError, 'import schema bad targetNamespace' + + def addIncludeSchema(self, schemaLocation, schema): + """for resolving include statements in Schema instance + schemaLocation -- schema location + schema -- schema instance + _included_schemas + """ + if not isinstance(schema, XMLSchema): + raise TypeError, 'expecting a Schema instance' + if not schema.targetNamespace or\ + schema.targetNamespace == self.targetNamespace: + self._included_schemas[schemaLocation] = schema + else: + raise SchemaError, 'include schema bad targetNamespace' + + def setImportSchemas(self, schema_dict): + """set the import schema dictionary, which is used to + reference depedent schemas. + """ + self._imported_schemas = schema_dict + + def getImportSchemas(self): + """get the import schema dictionary, which is used to + reference depedent schemas. + """ + return self._imported_schemas + + def getSchemaNamespacesToImport(self): + """returns tuple of namespaces the schema instance has declared + itself to be depedent upon. + """ + return tuple(self.includes.keys()) + + def setIncludeSchemas(self, schema_dict): + """set the include schema dictionary, which is keyed with + schemaLocation (uri). + This is a means of providing + schemas to the current schema for content inclusion. + """ + self._included_schemas = schema_dict + + def getIncludeSchemas(self): + """get the include schema dictionary, which is keyed with + schemaLocation (uri). + """ + return self._included_schemas + + def getBaseUrl(self): + """get base url, used for normalizing all relative uri's + """ + return self._base_url + + def setBaseUrl(self, url): + """set base url, used for normalizing all relative uri's + """ + self._base_url = url + + def getElementFormDefault(self): + """return elementFormDefault attribute + """ + return self.attributes.get('elementFormDefault') + + def getAttributeFormDefault(self): + """return attributeFormDefault attribute + """ + return self.attributes.get('attributeFormDefault') + + def getBlockDefault(self): + """return blockDefault attribute + """ + return self.attributes.get('blockDefault') + + def getFinalDefault(self): + """return finalDefault attribute + """ + return self.attributes.get('finalDefault') + + def load(self, node): + pnode = node.getParentNode() + if pnode: + pname = SplitQName(pnode.getTagName())[1] + if pname == 'types': + attributes = {} + self.setAttributes(pnode) + attributes.update(self.attributes) + self.setAttributes(node) + for k,v in attributes['xmlns'].items(): + if not self.attributes['xmlns'].has_key(k): + self.attributes['xmlns'][k] = v + else: + self.setAttributes(node) + else: + self.setAttributes(node) + + self.targetNamespace = self.getTargetNamespace() + contents = self.getContents(node) + + indx = 0 + num = len(contents) + while indx < num: + while indx < num: + node = contents[indx] + component = SplitQName(node.getTagName())[1] + + if component == 'include': + tp = self.__class__.Include(self) + tp.fromDom(node) + self.includes[tp.attributes['schemaLocation']] = tp + + schema = tp.getSchema() + if schema.targetNamespace and \ + schema.targetNamespace != self.targetNamespace: + raise SchemaError, 'included schema bad targetNamespace' + + for collection in ['imports','elements','types',\ + 'attr_decl','attr_groups','model_groups','notations']: + for k,v in getattr(schema,collection).items(): + if not getattr(self,collection).has_key(k): + v._parent = weakref.ref(self) + getattr(self,collection)[k] = v + + elif component == 'import': + tp = self.__class__.Import(self) + tp.fromDom(node) + import_ns = tp.getAttribute('namespace') + if import_ns: + if import_ns == self.targetNamespace: + raise SchemaError,\ + 'import and schema have same targetNamespace' + self.imports[import_ns] = tp + else: + self.imports[self.__class__.empty_namespace] = tp + + if not self.getImportSchemas().has_key(import_ns) and\ + tp.getAttribute('schemaLocation'): + self.addImportSchema(tp.getSchema()) + + elif component == 'redefine': + #print_debug('class %s, redefine skipped' %self.__class__, 5) + pass + elif component == 'annotation': + #print_debug('class %s, annotation skipped' %self.__class__, 5) + pass + else: + break + indx += 1 + + # (attribute, attributeGroup, complexType, element, group, + # notation, simpleType)*, annotation*)* + while indx < num: + node = contents[indx] + component = SplitQName(node.getTagName())[1] + + if component == 'attribute': + tp = AttributeDeclaration(self) + tp.fromDom(node) + self.attr_decl[tp.getAttribute('name')] = tp + elif component == 'attributeGroup': + tp = AttributeGroupDefinition(self) + tp.fromDom(node) + self.attr_groups[tp.getAttribute('name')] = tp + elif component == 'complexType': + tp = ComplexType(self) + tp.fromDom(node) + self.types[tp.getAttribute('name')] = tp + elif component == 'element': + tp = ElementDeclaration(self) + tp.fromDom(node) + self.elements[tp.getAttribute('name')] = tp + elif component == 'group': + tp = ModelGroupDefinition(self) + tp.fromDom(node) + self.model_groups[tp.getAttribute('name')] = tp + elif component == 'notation': + tp = Notation(self) + tp.fromDom(node) + self.notations[tp.getAttribute('name')] = tp + elif component == 'simpleType': + tp = SimpleType(self) + tp.fromDom(node) + self.types[tp.getAttribute('name')] = tp + else: + break + indx += 1 + + while indx < num: + node = contents[indx] + component = SplitQName(node.getTagName())[1] + + if component == 'annotation': + #print_debug('class %s, annotation 2 skipped' %self.__class__, 5) + pass + else: + break + indx += 1 + + + class Import(XMLSchemaComponent): + """ + parent: + schema + attributes: + id -- ID + namespace -- anyURI + schemaLocation -- anyURI + contents: + annotation? + """ + attributes = {'id':None, + 'namespace':None, + 'schemaLocation':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self._schema = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + if self.attributes['namespace'] == self._parent().attributes['targetNamespace']: + raise SchemaError, 'namespace of schema and import match' + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + def getSchema(self): + """if schema is not defined, first look for a Schema class instance + in parent Schema. Else if not defined resolve schemaLocation + and create a new Schema class instance, and keep a hard reference. + """ + if not self._schema: + ns = self.attributes['namespace'] + schema = self._parent().getImportSchemas().get(ns) + if not schema and self._parent()._parent: + schema = self._parent()._parent().getImportSchemas().get(ns) + if not schema: + url = self.attributes.get('schemaLocation') + if not url: + raise SchemaError, 'namespace(%s) is unknown' %ns + base_url = self._parent().getBaseUrl() + reader = SchemaReader(base_url=base_url) + reader._imports = self._parent().getImportSchemas() + reader._includes = self._parent().getIncludeSchemas() + self._schema = reader.loadFromURL(url) + return self._schema or schema + + + class Include(XMLSchemaComponent): + """ + parent: + schema + attributes: + id -- ID + schemaLocation -- anyURI, required + contents: + annotation? + """ + required = ['schemaLocation'] + attributes = {'id':None, + 'schemaLocation':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self._schema = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + def getSchema(self): + """if schema is not defined, first look for a Schema class instance + in parent Schema. Else if not defined resolve schemaLocation + and create a new Schema class instance. + """ + if not self._schema: + #schema = self._parent()._parent() + schema = self._parent() + #self._schema = schema.getIncludeSchemas(\ + # self.attributes['schemaLocation']) + self._schema = schema.getIncludeSchemas().get(\ + self.attributes['schemaLocation'] + ) + if not self._schema: + url = self.attributes['schemaLocation'] + reader = SchemaReader(base_url=schema.getBaseUrl()) + reader._imports = schema.getImportSchemas() + reader._includes = schema.getIncludeSchemas() + self._schema = reader.loadFromURL(url) + return self._schema + + +class AttributeDeclaration(XMLSchemaComponent,\ + AttributeMarker,\ + DeclarationMarker): + """ + parent: + schema + attributes: + id -- ID + name -- NCName, required + type -- QName + default -- string + fixed -- string + contents: + annotation?, simpleType? + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'type':None, + 'default':None, + 'fixed':None} + contents = {'xsd':['annotation','simpleType']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + """ No list or union support + """ + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + elif component == 'simpleType': + self.content = AnonymousSimpleType(self) + self.content.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class LocalAttributeDeclaration(AttributeDeclaration,\ + AttributeMarker,\ + LocalMarker,\ + DeclarationMarker): + """ + parent: + complexType, restriction, extension, attributeGroup + attributes: + id -- ID + name -- NCName, required + type -- QName + form -- ('qualified' | 'unqualified'), schema.attributeFormDefault + use -- ('optional' | 'prohibited' | 'required'), optional + default -- string + fixed -- string + contents: + annotation?, simpleType? + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'type':None, + 'form':lambda self: GetSchema(self).getAttributeFormDefault(), + 'use':'optional', + 'default':None, + 'fixed':None} + contents = {'xsd':['annotation','simpleType']} + + def __init__(self, parent): + AttributeDeclaration.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + elif component == 'simpleType': + self.content = AnonymousSimpleType(self) + self.content.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class AttributeWildCard(XMLSchemaComponent,\ + AttributeMarker,\ + DeclarationMarker,\ + WildCardMarker): + """ + parents: + complexType, restriction, extension, attributeGroup + attributes: + id -- ID + namespace -- '##any' | '##other' | + (anyURI* | '##targetNamespace' | '##local'), ##any + processContents -- 'lax' | 'skip' | 'strict', strict + contents: + annotation? + """ + attributes = {'id':None, + 'namespace':'##any', + 'processContents':'strict'} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class AttributeReference(XMLSchemaComponent,\ + AttributeMarker,\ + ReferenceMarker): + """ + parents: + complexType, restriction, extension, attributeGroup + attributes: + id -- ID + ref -- QName, required + use -- ('optional' | 'prohibited' | 'required'), optional + default -- string + fixed -- string + contents: + annotation? + """ + required = ['ref'] + attributes = {'id':None, + 'ref':None, + 'use':'optional', + 'default':None, + 'fixed':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class AttributeGroupDefinition(XMLSchemaComponent,\ + AttributeGroupMarker,\ + DefinitionMarker): + """ + parents: + schema, redefine + attributes: + id -- ID + name -- NCName, required + contents: + annotation?, (attribute | attributeGroup)*, anyAttribute? + """ + required = ['name'] + attributes = {'id':None, + 'name':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for indx in range(len(contents)): + component = SplitQName(contents[indx].getTagName())[1] + if (component == 'annotation') and (not indx): + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + elif (component == 'attribute'): + if contents[indx].hasattr('name'): + content.append(AttributeDeclaration()) + elif contents[indx].hasattr('ref'): + content.append(AttributeReference()) + else: + raise SchemaError, 'Unknown attribute type' + content[-1].fromDom(contents[indx]) + elif (component == 'attributeGroup'): + content.append(AttributeGroupReference()) + content[-1].fromDom(contents[indx]) + elif (component == 'anyAttribute') and (len(contents) == x+1): + content.append(AttributeWildCard()) + content[-1].fromDom(contents[indx]) + else: + raise SchemaError, 'Unknown component (%s)' %(contents[indx].getTagName()) + + self.attr_content = tuple(content) + +class AttributeGroupReference(XMLSchemaComponent,\ + AttributeGroupMarker,\ + ReferenceMarker): + """ + parents: + complexType, restriction, extension, attributeGroup + attributes: + id -- ID + ref -- QName, required + contents: + annotation? + """ + required = ['ref'] + attributes = {'id':None, + 'ref':None} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + + +###################################################### +# Elements +##################################################### +class IdentityConstrants(XMLSchemaComponent): + """Allow one to uniquely identify nodes in a document and ensure the + integrity of references between them. + + attributes -- dictionary of attributes + selector -- XPath to selected nodes + fields -- list of XPath to key field + """ + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.selector = None + self.fields = None + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + fields = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + elif component == 'selector': + self.selector = self.Selector(self) + self.selector.fromDom(i) + continue + elif component == 'field': + fields.append(self.Field(self)) + fields[-1].fromDom(i) + continue + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.fields = tuple(fields) + + + class Constraint(XMLSchemaComponent): + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + class Selector(Constraint): + """ + parent: + unique, key, keyref + attributes: + id -- ID + xpath -- XPath subset, required + contents: + annotation? + """ + required = ['xpath'] + attributes = {'id':None, + 'xpath':None} + contents = {'xsd':['annotation']} + + class Field(Constraint): + """ + parent: + unique, key, keyref + attributes: + id -- ID + xpath -- XPath subset, required + contents: + annotation? + """ + required = ['xpath'] + attributes = {'id':None, + 'xpath':None} + contents = {'xsd':['annotation']} + + +class Unique(IdentityConstrants): + """ Enforce fields are unique w/i a specified scope. + + parent: + element + attributes: + id -- ID + name -- NCName, required + contents: + annotation?, selector, field+ + """ + required = ['name'] + attributes = {'id':None, + 'name':None} + contents = {'xsd':['annotation', 'selector', 'field']} + + +class Key(IdentityConstrants): + """ Enforce fields are unique w/i a specified scope, and all + field values are present w/i document. Fields cannot + be nillable. + + parent: + element + attributes: + id -- ID + name -- NCName, required + contents: + annotation?, selector, field+ + """ + required = ['name'] + attributes = {'id':None, + 'name':None} + contents = {'xsd':['annotation', 'selector', 'field']} + + +class KeyRef(IdentityConstrants): + """ Ensure a match between two sets of values in an + instance. + parent: + element + attributes: + id -- ID + name -- NCName, required + refer -- QName, required + contents: + annotation?, selector, field+ + """ + required = ['name', 'refer'] + attributes = {'id':None, + 'name':None, + 'refer':None} + contents = {'xsd':['annotation', 'selector', 'field']} + + +class ElementDeclaration(XMLSchemaComponent,\ + ElementMarker,\ + DeclarationMarker): + """ + parents: + schema + attributes: + id -- ID + name -- NCName, required + type -- QName + default -- string + fixed -- string + nillable -- boolean, false + abstract -- boolean, false + substitutionGroup -- QName + block -- ('#all' | ('substition' | 'extension' | 'restriction')*), + schema.blockDefault + final -- ('#all' | ('extension' | 'restriction')*), + schema.finalDefault + contents: + annotation?, (simpleType,complexType)?, (key | keyref | unique)* + + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'type':None, + 'default':None, + 'fixed':None, + 'nillable':0, + 'abstract':0, + 'substitutionGroup':None, + 'block':lambda self: self._parent().getBlockDefault(), + 'final':lambda self: self._parent().getFinalDefault()} + contents = {'xsd':['annotation', 'simpleType', 'complexType', 'key',\ + 'keyref', 'unique']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.constraints = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + constraints = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + elif component == 'simpleType' and not self.content: + self.content = AnonymousSimpleType(self) + self.content.fromDom(i) + elif component == 'complexType' and not self.content: + self.content = LocalComplexType(self) + self.content.fromDom(i) + elif component == 'key': + constraints.append(Key(self)) + constraints[-1].fromDom(i) + elif component == 'keyref': + constraints.append(KeyRef(self)) + constraints[-1].fromDom(i) + elif component == 'unique': + constraints.append(Unique(self)) + constraints[-1].fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.constraints = tuple(constraints) + + +class LocalElementDeclaration(ElementDeclaration,\ + LocalMarker): + """ + parents: + all, choice, sequence + attributes: + id -- ID + name -- NCName, required + form -- ('qualified' | 'unqualified'), schema.elementFormDefault + type -- QName + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + default -- string + fixed -- string + nillable -- boolean, false + block -- ('#all' | ('extension' | 'restriction')*), schema.blockDefault + contents: + annotation?, (simpleType,complexType)?, (key | keyref | unique)* + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'form':lambda self: GetSchema(self).getElementFormDefault(), + 'type':None, + 'minOccurs':'1', + 'maxOccurs':'1', + 'default':None, + 'fixed':None, + 'nillable':0, + 'abstract':0, + 'block':lambda self: GetSchema(self).getBlockDefault()} + contents = {'xsd':['annotation', 'simpleType', 'complexType', 'key',\ + 'keyref', 'unique']} + + +class ElementReference(XMLSchemaComponent,\ + ElementMarker,\ + ReferenceMarker): + """ + parents: + all, choice, sequence + attributes: + id -- ID + ref -- QName, required + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + contents: + annotation? + """ + required = ['ref'] + attributes = {'id':None, + 'ref':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.annotation = None + self.setAttributes(node) + for i in self.getContents(node): + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class ElementWildCard(LocalElementDeclaration,\ + WildCardMarker): + """ + parents: + choice, sequence + attributes: + id -- ID + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + namespace -- '##any' | '##other' | + (anyURI* | '##targetNamespace' | '##local'), ##any + processContents -- 'lax' | 'skip' | 'strict', strict + contents: + annotation? + """ + required = [] + attributes = {'id':None, + 'minOccurs':'1', + 'maxOccurs':'1', + 'namespace':'##any', + 'processContents':'strict'} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.annotation = None + self.setAttributes(node) + for i in self.getContents(node): + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +###################################################### +# Model Groups +##################################################### +class Sequence(XMLSchemaComponent,\ + ModelGroupMarker): + """ + parents: + complexType, extension, restriction, group, choice, sequence + attributes: + id -- ID + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + + contents: + annotation?, (element | group | choice | sequence | any)* + """ + attributes = {'id':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation', 'element', 'group', 'choice', 'sequence',\ + 'any']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'element': + if i.hasattr('ref'): + content.append(ElementReference(self)) + else: + content.append(LocalElementDeclaration(self)) + elif component == 'group': + content.append(ModelGroupReference(self)) + elif component == 'choice': + content.append(Choice(self)) + elif component == 'sequence': + content.append(Sequence(self)) + elif component == 'any': + content.append(ElementWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + content[-1].fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + +class All(XMLSchemaComponent,\ + ModelGroupMarker): + """ + parents: + complexType, extension, restriction, group + attributes: + id -- ID + minOccurs -- '0' | '1', 1 + maxOccurs -- '1', 1 + + contents: + annotation?, element* + """ + attributes = {'id':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation', 'element']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'element': + if i.hasattr('ref'): + content.append(ElementReference(self)) + else: + content.append(LocalElementDeclaration(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + content[-1].fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + +class Choice(XMLSchemaComponent,\ + ModelGroupMarker): + """ + parents: + complexType, extension, restriction, group, choice, sequence + attributes: + id -- ID + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + + contents: + annotation?, (element | group | choice | sequence | any)* + """ + attributes = {'id':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation', 'element', 'group', 'choice', 'sequence',\ + 'any']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'element': + if i.hasattr('ref'): + content.append(ElementReference(self)) + else: + content.append(LocalElementDeclaration(self)) + elif component == 'group': + content.append(ModelGroupReference(self)) + elif component == 'choice': + content.append(Choice(self)) + elif component == 'sequence': + content.append(Sequence(self)) + elif component == 'any': + content.append(ElementWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + content[-1].fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + +class ModelGroupDefinition(XMLSchemaComponent,\ + ModelGroupMarker,\ + DefinitionMarker): + """ + parents: + redefine, schema + attributes: + id -- ID + name -- NCName, required + + contents: + annotation?, (all | choice | sequence)? + """ + required = ['name'] + attributes = {'id':None, + 'name':None} + contents = {'xsd':['annotation', 'all', 'choice', 'sequence']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'all' and not self.content: + self.content = All(self) + elif component == 'choice' and not self.content: + self.content = Choice(self) + elif component == 'sequence' and not self.content: + self.content = Sequence(self) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class ModelGroupReference(XMLSchemaComponent,\ + ModelGroupMarker,\ + ReferenceMarker): + """ + parents: + choice, complexType, extension, restriction, sequence + attributes: + id -- ID + ref -- NCName, required + minOccurs -- Whole Number, 1 + maxOccurs -- (Whole Number | 'unbounded'), 1 + + contents: + annotation? + """ + required = ['ref'] + attributes = {'id':None, + 'ref':None, + 'minOccurs':'1', + 'maxOccurs':'1'} + contents = {'xsd':['annotation']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + + +class ComplexType(XMLSchemaComponent,\ + DefinitionMarker,\ + ComplexMarker): + """ + parents: + redefine, schema + attributes: + id -- ID + name -- NCName, required + mixed -- boolean, false + abstract -- boolean, false + block -- ('#all' | ('extension' | 'restriction')*), schema.blockDefault + final -- ('#all' | ('extension' | 'restriction')*), schema.finalDefault + + contents: + annotation?, (simpleContent | complexContent | + ((group | all | choice | sequence)?, (attribute | attributeGroup)*, anyAttribute?)) + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'mixed':0, + 'abstract':0, + 'block':lambda self: self._parent().getBlockDefault(), + 'final':lambda self: self._parent().getFinalDefault()} + contents = {'xsd':['annotation', 'simpleContent', 'complexContent',\ + 'group', 'all', 'choice', 'sequence', 'attribute', 'attributeGroup',\ + 'anyAttribute', 'any']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + #XXX ugly + if not num: + return + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + component = SplitQName(contents[indx].getTagName())[1] + + self.content = None + if component == 'simpleContent': + self.content = self.__class__.SimpleContent(self) + self.content.fromDom(contents[indx]) + elif component == 'complexContent': + self.content = self.__class__.ComplexContent(self) + self.content.fromDom(contents[indx]) + else: + if component == 'all': + self.content = All(self) + elif component == 'choice': + self.content = Choice(self) + elif component == 'sequence': + self.content = Sequence(self) + elif component == 'group': + self.content = ModelGroupReference(self) + + if self.content: + self.content.fromDom(contents[indx]) + indx += 1 + + self.attr_content = [] + while indx < num: + component = SplitQName(contents[indx].getTagName())[1] + if component == 'attribute': + if contents[indx].hasattr('ref'): + self.attr_content.append(AttributeReference(self)) + else: + self.attr_content.append(LocalAttributeDeclaration(self)) + elif component == 'attributeGroup': + self.attr_content.append(AttributeGroupReference(self)) + elif component == 'anyAttribute': + self.attr_content.append(AttributeWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(contents[indx].getTagName()) + self.attr_content[-1].fromDom(contents[indx]) + indx += 1 + + class _DerivedType(XMLSchemaComponent): + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.derivation = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + for i in contents: + component = SplitQName(i.getTagName())[1] + if component in self.__class__.contents['xsd']: + if component == 'annotation' and not self.annotation: + self.annotation = Annotation(self) + self.annotation.fromDom(i) + continue + elif component == 'restriction' and not self.derivation: + self.derivation = self.__class__.Restriction(self) + elif component == 'extension' and not self.derivation: + self.derivation = self.__class__.Extension(self) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.derivation.fromDom(i) + + class ComplexContent(_DerivedType,\ + ComplexMarker): + """ + parents: + complexType + attributes: + id -- ID + mixed -- boolean, false + + contents: + annotation?, (restriction | extension) + """ + attributes = {'id':None, + 'mixed':0 } + contents = {'xsd':['annotation', 'restriction', 'extension']} + + class _DerivationBase(XMLSchemaComponent): + """, + parents: + complexContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (group | all | choice | sequence)?, + (attribute | attributeGroup)*, anyAttribute? + """ + required = ['base'] + attributes = {'id':None, + 'base':None } + contents = {'xsd':['annotation', 'group', 'all', 'choice',\ + 'sequence', 'attribute', 'attributeGroup', 'anyAttribute']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + #XXX ugly + if not num: + return + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + component = SplitQName(contents[indx].getTagName())[1] + + if component == 'all': + self.content = All(self) + self.content.fromDom(contents[indx]) + indx += 1 + elif component == 'choice': + self.content = Choice(self) + self.content.fromDom(contents[indx]) + indx += 1 + elif component == 'sequence': + self.content = Sequence(self) + self.content.fromDom(contents[indx]) + indx += 1 + elif component == 'group': + self.content = ModelGroupReference(self) + self.content.fromDom(contents[indx]) + indx += 1 + else: + self.content = None + + self.attr_content = [] + while indx < num: + component = SplitQName(contents[indx].getTagName())[1] + if component == 'attribute': + if contents[indx].hasattr('ref'): + self.attr_content.append(AttributeReference(self)) + else: + self.attr_content.append(LocalAttributeDeclaration(self)) + elif component == 'attributeGroup': + if contents[indx].hasattr('ref'): + self.attr_content.append(AttributeGroupReference(self)) + else: + self.attr_content.append(AttributeGroupDefinition(self)) + elif component == 'anyAttribute': + self.attr_content.append(AttributeWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)' %(contents[indx].getTagName()) + self.attr_content[-1].fromDom(contents[indx]) + indx += 1 + + class Extension(_DerivationBase, + ExtensionMarker): + """ + parents: + complexContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (group | all | choice | sequence)?, + (attribute | attributeGroup)*, anyAttribute? + """ + pass + + class Restriction(_DerivationBase,\ + RestrictionMarker): + """ + parents: + complexContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (group | all | choice | sequence)?, + (attribute | attributeGroup)*, anyAttribute? + """ + pass + + + class SimpleContent(_DerivedType,\ + SimpleMarker): + """ + parents: + complexType + attributes: + id -- ID + + contents: + annotation?, (restriction | extension) + """ + attributes = {'id':None} + contents = {'xsd':['annotation', 'restriction', 'extension']} + + class Extension(XMLSchemaComponent,\ + ExtensionMarker): + """ + parents: + simpleContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, (attribute | attributeGroup)*, anyAttribute? + """ + required = ['base'] + attributes = {'id':None, + 'base':None } + contents = {'xsd':['annotation', 'attribute', 'attributeGroup', + 'anyAttribute']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + component = SplitQName(contents[indx].getTagName())[1] + + content = [] + while indx < num: + component = SplitQName(contents[indx].getTagName())[1] + if component == 'attribute': + if contents[indx].hasattr('ref'): + content.append(AttributeReference(self)) + else: + content.append(LocalAttributeDeclaration(self)) + elif component == 'attributeGroup': + content.append(AttributeGroupReference(self)) + elif component == 'anyAttribute': + content.append(AttributeWildCard(self)) + else: + raise SchemaError, 'Unknown component (%s)'\ + %(contents[indx].getTagName()) + content[-1].fromDom(contents[indx]) + indx += 1 + self.attr_content = tuple(content) + + + class Restriction(XMLSchemaComponent,\ + RestrictionMarker): + """ + parents: + simpleContent + attributes: + id -- ID + base -- QName, required + + contents: + annotation?, simpleType?, (enumeration | length | + maxExclusive | maxInclusive | maxLength | minExclusive | + minInclusive | minLength | pattern | fractionDigits | + totalDigits | whiteSpace)*, (attribute | attributeGroup)*, + anyAttribute? + """ + required = ['base'] + attributes = {'id':None, + 'base':None } + contents = {'xsd':['annotation', 'simpleType', 'attribute',\ + 'attributeGroup', 'anyAttribute'] + RestrictionMarker.facets} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + self.attr_content = None + + def getAttributeContent(self): + return self.attr_content + + def fromDom(self, node): + self.content = [] + self.setAttributes(node) + contents = self.getContents(node) + + indx = 0 + num = len(contents) + component = SplitQName(contents[indx].getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + indx += 1 + component = SplitQName(contents[indx].getTagName())[1] + + content = [] + while indx < num: + component = SplitQName(contents[indx].getTagName())[1] + if component == 'attribute': + if contents[indx].hasattr('ref'): + content.append(AttributeReference(self)) + else: + content.append(LocalAttributeDeclaration(self)) + elif component == 'attributeGroup': + content.append(AttributeGroupReference(self)) + elif component == 'anyAttribute': + content.append(AttributeWildCard(self)) + elif component == 'simpleType': + self.content.append(LocalSimpleType(self)) + self.content[-1].fromDom(contents[indx]) + else: + raise SchemaError, 'Unknown component (%s)'\ + %(contents[indx].getTagName()) + content[-1].fromDom(contents[indx]) + indx += 1 + self.attr_content = tuple(content) + + +class LocalComplexType(ComplexType,\ + LocalMarker): + """ + parents: + element + attributes: + id -- ID + mixed -- boolean, false + + contents: + annotation?, (simpleContent | complexContent | + ((group | all | choice | sequence)?, (attribute | attributeGroup)*, anyAttribute?)) + """ + required = [] + attributes = {'id':None, + 'mixed':0} + + +class SimpleType(XMLSchemaComponent,\ + DefinitionMarker,\ + SimpleMarker): + """ + parents: + redefine, schema + attributes: + id -- ID + name -- NCName, required + final -- ('#all' | ('extension' | 'restriction' | 'list' | 'union')*), + schema.finalDefault + + contents: + annotation?, (restriction | list | union) + """ + required = ['name'] + attributes = {'id':None, + 'name':None, + 'final':lambda self: self._parent().getFinalDefault()} + contents = {'xsd':['annotation', 'restriction', 'list', 'union']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + for child in contents: + component = SplitQName(child.getTagName())[1] + if component == 'annotation': + self.annotation = Annotation(self) + self.annotation.fromDom(child) + continue + break + else: + return + if component == 'restriction': + self.content = self.__class__.Restriction(self) + elif component == 'list': + self.content = self.__class__.List(self) + elif component == 'union': + self.content = self.__class__.Union(self) + else: + raise SchemaError, 'Unknown component (%s)' %(component) + self.content.fromDom(child) + + class Restriction(XMLSchemaComponent,\ + RestrictionMarker): + """ + parents: + simpleType + attributes: + id -- ID + base -- QName, required or simpleType child + + contents: + annotation?, simpleType?, (enumeration | length | + maxExclusive | maxInclusive | maxLength | minExclusive | + minInclusive | minLength | pattern | fractionDigits | + totalDigits | whiteSpace)* + """ + attributes = {'id':None, + 'base':None } + contents = {'xsd':['annotation', 'simpleType']+RestrictionMarker.facets} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for indx in range(len(contents)): + component = SplitQName(contents[indx].getTagName())[1] + if (component == 'annotation') and (not indx): + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + continue + elif (component == 'simpleType') and (not indx or indx == 1): + content.append(AnonymousSimpleType(self)) + content[-1].fromDom(contents[indx]) + elif component in RestrictionMarker.facets: + #print_debug('%s class instance, skipping %s' %(self.__class__, component)) + pass + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + + class Union(XMLSchemaComponent): + """ + parents: + simpleType + attributes: + id -- ID + memberTypes -- list of QNames, required or simpleType child. + + contents: + annotation?, simpleType* + """ + attributes = {'id':None, + 'memberTypes':None } + contents = {'xsd':['annotation', 'simpleType']} + + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + content = [] + + for indx in range(len(contents)): + component = SplitQName(contents[indx].getTagName())[1] + if (component == 'annotation') and (not indx): + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + elif (component == 'simpleType'): + content.append(AnonymousSimpleType(self)) + content[-1].fromDom(contents[indx]) + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + self.content = tuple(content) + + class List(XMLSchemaComponent): + """ + parents: + simpleType + attributes: + id -- ID + itemType -- QName, required or simpleType child. + + contents: + annotation?, simpleType? + """ + attributes = {'id':None, + 'itemType':None } + contents = {'xsd':['annotation', 'simpleType']} + def __init__(self, parent): + XMLSchemaComponent.__init__(self, parent) + self.annotation = None + self.content = None + + def fromDom(self, node): + self.setAttributes(node) + contents = self.getContents(node) + self.content = [] + + for indx in range(len(contents)): + component = SplitQName(contents[indx].getTagName())[1] + if (component == 'annotation') and (not indx): + self.annotation = Annotation(self) + self.annotation.fromDom(contents[indx]) + elif (component == 'simpleType'): + self.content = AnonymousSimpleType(self) + self.content.fromDom(contents[indx]) + break + else: + raise SchemaError, 'Unknown component (%s)' %(i.getTagName()) + + +class AnonymousSimpleType(SimpleType,\ + SimpleMarker): + """ + parents: + attribute, element, list, restriction, union + attributes: + id -- ID + + contents: + annotation?, (restriction | list | union) + """ + required = [] + attributes = {'id':None} + + +class Redefine: + """ + parents: + attributes: + + contents: + """ + pass + +########################### +########################### + + +if sys.version_info[:2] >= (2, 2): + tupleClass = tuple +else: + import UserTuple + tupleClass = UserTuple.UserTuple + +class TypeDescriptionComponent(tupleClass): + """Tuple of length 2, consisting of + a namespace and unprefixed name. + """ + def __init__(self, args): + """args -- (namespace, name) + Remove the name's prefix, irrelevant. + """ + if len(args) != 2: + raise TypeError, 'expecting tuple (namespace, name), got %s' %args + elif args[1].find(':') >= 0: + args = (args[0], SplitQName(args[1])[1]) + tuple.__init__(self, args) + return + + def getTargetNamespace(self): + return self[0] + + def getName(self): + return self[1] + + +''' +import string, types, base64, re +from Utility import DOM, Collection +from StringIO import StringIO + + +class SchemaReader: + """A SchemaReader creates XMLSchema objects from urls and xml data.""" + + def loadFromStream(self, file): + """Return an XMLSchema instance loaded from a file object.""" + document = DOM.loadDocument(file) + schema = XMLSchema() + schema.load(document) + return schema + + def loadFromString(self, data): + """Return an XMLSchema instance loaded from an xml string.""" + return self.loadFromStream(StringIO(data)) + + def loadFromURL(self, url): + """Return an XMLSchema instance loaded from the given url.""" + document = DOM.loadFromURL(url) + schema = XMLSchema() + schema.location = url + schema.load(document) + return schema + + def loadFromFile(self, filename): + """Return an XMLSchema instance loaded from the given file.""" + file = open(filename, 'rb') + try: schema = self.loadFromStream(file) + finally: file.close() + return schema + +class SchemaError(Exception): + pass + +class XMLSchema: + # This is temporary, for the benefit of WSDL until the real thing works. + def __init__(self, element): + self.targetNamespace = DOM.getAttr(element, 'targetNamespace') + self.element = element + +class realXMLSchema: + """A schema is a collection of schema components derived from one + or more schema documents, that is, one or more element + information items. It represents the abstract notion of a schema + rather than a single schema document (or other representation).""" + def __init__(self): + self.simpleTypes = Collection(self) + self.complexTypes = Collection(self) + self.attributes = Collection(self) + self.elements = Collection(self) + self.attrGroups = Collection(self) + self.idConstraints=None + self.modelGroups = None + self.notations = None + self.extensions = [] + + targetNamespace = None + attributeFormDefault = 'unqualified' + elementFormDefault = 'unqualified' + blockDefault = None + finalDefault = None + location = None + version = None + id = None + + def load(self, document): + if document.nodeType == document.DOCUMENT_NODE: + schema = DOM.getElement(document, 'schema', None, None) + else: + schema = document + if schema is None: + raise SchemaError('Missing element.') + + self.namespace = namespace = schema.namespaceURI + if not namespace in DOM.NS_XSD_ALL: + raise SchemaError( + 'Unknown XML schema namespace: %s.' % self.namespace + ) + + for attrname in ( + 'targetNamespace', 'attributeFormDefault', 'elementFormDefault', + 'blockDefault', 'finalDefault', 'version', 'id' + ): + value = DOM.getAttr(schema, attrname, None, None) + if value is not None: + setattr(self, attrname, value) + + + # Resolve imports and includes here? +## imported = {} +## while 1: +## imports = [] +## for element in DOM.getElements(definitions, 'import', NS_WSDL): +## location = DOM.getAttr(element, 'location') +## if not imported.has_key(location): +## imports.append(element) +## if not imports: +## break +## for element in imports: +## self._import(document, element) +## imported[location] = 1 + + for element in DOM.getElements(schema, None, None): + localName = element.localName + + if not DOM.nsUriMatch(element.namespaceURI, namespace): + self.extensions.append(element) + continue + + elif localName == 'message': + name = DOM.getAttr(element, 'name') + docs = GetDocumentation(element) + message = self.addMessage(name, docs) + parts = DOM.getElements(element, 'part', NS_WSDL) + message.load(parts) + continue + + def _import(self, document, element): + namespace = DOM.getAttr(element, 'namespace', default=None) + location = DOM.getAttr(element, 'location', default=None) + if namespace is None or location is None: + raise WSDLError( + 'Invalid import element (missing namespace or location).' + ) + + # Sort-of support relative locations to simplify unit testing. The + # WSDL specification actually doesn't allow relative URLs, so its + # ok that this only works with urls relative to the initial document. + location = urllib.basejoin(self.location, location) + + obimport = self.addImport(namespace, location) + obimport._loaded = 1 + + importdoc = DOM.loadFromURL(location) + try: + if location.find('#') > -1: + idref = location.split('#')[-1] + imported = DOM.getElementById(importdoc, idref) + else: + imported = importdoc.documentElement + if imported is None: + raise WSDLError( + 'Import target element not found for: %s' % location + ) + + imported_tns = DOM.getAttr(imported, 'targetNamespace') + importer_tns = namespace + + if imported_tns != importer_tns: + return + + if imported.localName == 'definitions': + imported_nodes = imported.childNodes + else: + imported_nodes = [imported] + parent = element.parentNode + for node in imported_nodes: + if node.nodeType != node.ELEMENT_NODE: + continue + child = DOM.importNode(document, node, 1) + parent.appendChild(child) + child.setAttribute('targetNamespace', importer_tns) + attrsNS = imported._attrsNS + for attrkey in attrsNS.keys(): + if attrkey[0] == DOM.NS_XMLNS: + attr = attrsNS[attrkey].cloneNode(1) + child.setAttributeNode(attr) + finally: + importdoc.unlink() + + +class Element: + """Common base class for element representation classes.""" + def __init__(self, name=None, documentation=''): + self.name = name + self.documentation = documentation + self.extensions = [] + + def addExtension(self, item): + self.extensions.append(item) + + +class SimpleTypeDefinition: + """Represents an xml schema simple type definition.""" + +class ComplexTypeDefinition: + """Represents an xml schema complex type definition.""" + +class AttributeDeclaration: + """Represents an xml schema attribute declaration.""" + +class ElementDeclaration: + """Represents an xml schema element declaration.""" + def __init__(self, name, type=None, targetNamespace=None): + self.name = name + + targetNamespace = None + annotation = None + nillable = 0 + abstract = 0 + default = None + fixed = None + scope = 'global' + type = None + form = 0 + # Things we will not worry about for now. + id_constraint_defs = None + sub_group_exclude = None + sub_group_affils = None + disallowed_subs = None + + + + + + + + + + +class AttributeGroupDefinition: + """Represents an xml schema attribute group definition.""" + +class IdentityConstraintDefinition: + """Represents an xml schema identity constraint definition.""" + +class ModelGroupDefinition: + """Represents an xml schema model group definition.""" + +class NotationDeclaration: + """Represents an xml schema notation declaration.""" + +class Annotation: + """Represents an xml schema annotation.""" + +class ModelGroup: + """Represents an xml schema model group.""" + +class Particle: + """Represents an xml schema particle.""" + +class WildCard: + """Represents an xml schema wildcard.""" + +class AttributeUse: + """Represents an xml schema attribute use.""" + + +class ElementComponent: + namespace = '' + name = '' + type = None + form = 'qualified | unqualified' + scope = 'global or complex def' + constraint = ('value', 'default | fixed') + nillable = 0 + id_constraint_defs = None + sub_group_affil = None + sub_group_exclusions = None + disallowed_subs = 'substitution, extension, restriction' + abstract = 0 + minOccurs = 1 + maxOccurs = 1 + ref = '' + +class AttributeThing: + name = '' + namespace = '' + typeName = '' + typeUri = '' + scope = 'global | local to complex def' + constraint = ('value:default', 'value:fixed') + use = 'optional | prohibited | required' + +class ElementDataType: + namespace = '' + name = '' + element_form = 'qualified | unqualified' + attr_form = None + type_name = '' + type_uri = '' + def __init__(self, name, namespace, type_name, type_uri): + self.namespace = namespace + self.name = name + # type may be anonymous... + self.type_name = type_name + self.type_uri = type_uri + + def checkValue(self, value, context): + # Delegate value checking to the type of the element. + typeref = (self.type_uri, self.type_name) + handler = context.serializer.getType(typeref) + return handler.checkValue(value, context) + + def serialize(self, name, namespace, value, context, **kwargs): + if context.check_values: + self.checkValue(value, context) + # Delegate serialization to the type of the element. + typeref = (self.type_uri, self.type_name) + handler = context.serializer.getType(typeref) + return handler.serialize(self.name, self.namespace, value, context) + + def deserialize(self, element, context): + if element_is_null(element, context): + return None + # Delegate deserialization to the type of the element. + typeref = (self.type_uri, self.type_name) + handler = context.serializer.getType(typeref) + return handler.deserialize(element, context) + + + +def parse_schema(data): + targetNS = '' + attributeFormDefault = 0 + elementFormDefault = 0 + blockDefault = '' + finalDefault = '' + language = None + version = None + id = '' +''' diff --git a/others/SOAPpy/wstools/XMLname.py b/others/SOAPpy/wstools/XMLname.py new file mode 100755 index 000000000..e15b3dc1c --- /dev/null +++ b/others/SOAPpy/wstools/XMLname.py @@ -0,0 +1,88 @@ +"""Translate strings to and from SOAP 1.2 XML name encoding + +Implements rules for mapping application defined name to XML names +specified by the w3 SOAP working group for SOAP version 1.2 in +Appendix A of "SOAP Version 1.2 Part 2: Adjuncts", W3C Working Draft +17, December 2001, + +Also see . + +Author: Gregory R. Warnes +Date:: 2002-04-25 +Version 0.9.0 + +""" + +ident = "$Id$" + +from re import * + + +def _NCNameChar(x): + return x.isalpha() or x.isdigit() or x=="." or x=='-' or x=="_" + + +def _NCNameStartChar(x): + return x.isalpha() or x=="_" + + +def _toUnicodeHex(x): + hexval = hex(ord(x[0]))[2:] + hexlen = len(hexval) + # Make hexval have either 4 or 8 digits by prepending 0's + if (hexlen==1): hexval = "000" + hexval + elif (hexlen==2): hexval = "00" + hexval + elif (hexlen==3): hexval = "0" + hexval + elif (hexlen==4): hexval = "" + hexval + elif (hexlen==5): hexval = "000" + hexval + elif (hexlen==6): hexval = "00" + hexval + elif (hexlen==7): hexval = "0" + hexval + elif (hexlen==8): hexval = "" + hexval + else: raise Exception, "Illegal Value returned from hex(ord(x))" + + return "_x"+ hexval + "_" + + +def _fromUnicodeHex(x): + return eval( r'u"\u'+x[2:-1]+'"' ) + + +def toXMLname(string): + """Convert string to a XML name.""" + if string.find(':') != -1 : + (prefix, localname) = string.split(':',1) + else: + prefix = None + localname = string + + T = unicode(localname) + + N = len(localname) + X = []; + for i in range(N) : + if i< N-1 and T[i]==u'_' and T[i+1]==u'x': + X.append(u'_x005F_') + elif i==0 and N >= 3 and \ + ( T[0]==u'x' or T[0]==u'X' ) and \ + ( T[1]==u'm' or T[1]==u'M' ) and \ + ( T[2]==u'l' or T[2]==u'L' ): + X.append(u'_xFFFF_' + T[0]) + elif (not _NCNameChar(T[i])) or (i==0 and not _NCNameStartChar(T[i])): + X.append(_toUnicodeHex(T[i])) + else: + X.append(T[i]) + + return u''.join(X) + + +def fromXMLname(string): + """Convert XML name to unicode string.""" + + retval = sub(r'_xFFFF_','', string ) + + def fun( matchobj ): + return _fromUnicodeHex( matchobj.group(0) ) + + retval = sub(r'_x[0-9A-Za-z]+_', fun, retval ) + + return retval diff --git a/others/SOAPpy/wstools/__init__.py b/others/SOAPpy/wstools/__init__.py new file mode 100644 index 000000000..73fa65738 --- /dev/null +++ b/others/SOAPpy/wstools/__init__.py @@ -0,0 +1,36 @@ +#! /usr/bin/env python +"""WSDL parsing services package for Web Services for Python.""" + +ident = "$Id$" + +import WSDLTools +import XMLname +from logging import getLogger as _getLogger +import logging.config as _config + +LOGGING = 'logging.txt' +DEBUG = True + +# +# If LOGGING configuration file is not found, turn off logging +# and use _noLogger class because logging module's performance +# is terrible. +# + +try: + _config.fileConfig(LOGGING) +except: + DEBUG = False + + +class Base: + def __init__(self, module=__name__): + self.logger = _noLogger() + if DEBUG is True: + self.logger = _getLogger('%s-%s(%x)' %(module, self.__class__, id(self))) + +class _noLogger: + def __init__(self, *args): pass + def warning(self, *args): pass + def debug(self, *args): pass + def error(self, *args): pass diff --git a/others/google.py b/others/google.py index dd5720be4..ab89a8cbf 100644 --- a/others/google.py +++ b/others/google.py @@ -1,433 +1,638 @@ -"""Python wrapper for Google web APIs - -This module allows you to access Google's web APIs through SOAP, -to do things like search Google and get the results programmatically. -Described here: - http://www.google.com/apis/ - -You need a Google-provided license key to use these services. -Follow the link above to get one. These functions will look in -several places (in this order) for the license key: -- the "license_key" argument of each function -- the module-level LICENSE_KEY variable (call setLicense once to set it) -- an environment variable called GOOGLE_LICENSE_KEY -- a file called ".googlekey" in the current directory -- a file called "googlekey.txt" in the current directory -- a file called ".googlekey" in your home directory -- a file called "googlekey.txt" in your home directory -- a file called ".googlekey" in the same directory as google.py -- a file called "googlekey.txt" in the same directory as google.py - -Sample usage: ->>> import google ->>> google.setLicense('...') # must get your own key! ->>> data = google.doGoogleSearch('python') ->>> data.meta.searchTime -0.043221000000000002 ->>> data.results[0].URL -'http://www.python.org/' ->>> data.results[0].title -'Python Language Website' - -See documentation of SearchResultsMetaData and SearchResult classes -for other available attributes. -""" - -__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" -__version__ = "0.5.2" -__cvsversion__ = "$Revision$"[11:-2] -__date__ = "$Date$"[7:-2] -__copyright__ = "Copyright (c) 2002 Mark Pilgrim" -__license__ = "Python" -__credits__ = """David Ascher, for the install script -Erik Max Francis, for the command line interface -Michael Twomey, for HTTP proxy support""" - -import SOAP -import os, sys, getopt - -LICENSE_KEY = None -HTTP_PROXY = None - -# don't touch the rest of these constants -class NoLicenseKey(Exception): pass -_url = 'http://api.google.com/search/beta2' -_namespace = 'urn:GoogleSearch' -_false = SOAP.booleanType(0) -_true = SOAP.booleanType(1) -_googlefile1 = ".googlekey" -_googlefile2 = "googlekey.txt" -_licenseLocations = ( - (lambda key: key, 'passed to the function in license_key variable'), - (lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'), - (lambda key: os.environ.get('GOOGLE_LICENSE_KEY', None), 'an environment variable called GOOGLE_LICENSE_KEY'), - (lambda key: _contentsOf(os.getcwd(), _googlefile1), '%s in the current directory' % _googlefile1), - (lambda key: _contentsOf(os.getcwd(), _googlefile2), '%s in the current directory' % _googlefile2), - (lambda key: _contentsOf(os.environ.get('HOME', ''), _googlefile1), '%s in your home directory' % _googlefile1), - (lambda key: _contentsOf(os.environ.get('HOME', ''), _googlefile2), '%s in your home directory' % _googlefile2), - (lambda key: _contentsOf(_getScriptDir(), _googlefile1), '%s in the google.py directory' % _googlefile1), - (lambda key: _contentsOf(_getScriptDir(), _googlefile2), '%s in the google.py directory' % _googlefile2) - ) - -## administrative functions -def version(): - print """PyGoogle %(__version__)s -%(__copyright__)s -released %(__date__)s - -Thanks to: -%(__credits__)s""" % globals() - -def usage(): - program = os.path.basename(sys.argv[0]) - print """Usage: %(program)s [options] [querytype] query - -options: - -k, --key= Google license key (see important note below) - -1, -l, --lucky show only first hit - -m, --meta show meta information - -r, --reverse show results in reverse order - -x, --proxy= use HTTP proxy - -h, --help print this help - -v, --version print version and copyright information - -t, --test run test queries - -querytype: - -s, --search= search (default) - -c, --cache= retrieve cached page - -p, --spelling= check spelling - -IMPORTANT NOTE: all Google functions require a valid license key; -visit http://www.google.com/apis/ to get one. %(program)s will look in -these places (in order) and use the first license key it finds: - * the key specified on the command line""" % vars() - for get, location in _licenseLocations[2:]: - print " *", location - -## utility functions -def setLicense(license_key): - """set license key""" - global LICENSE_KEY - LICENSE_KEY = license_key - -def getLicense(license_key = None): - """get license key - - license key can come from any number of locations; - see module docs for search order""" - for get, location in _licenseLocations: - rc = get(license_key) - if rc: return rc - #usage() - raise NoLicenseKey, 'get a license key at http://www.google.com/apis/' - -def setProxy(http_proxy): - """set HTTP proxy""" - global HTTP_PROXY - HTTP_PROXY = http_proxy - -def getProxy(http_proxy = None): - """get HTTP proxy""" - return http_proxy or HTTP_PROXY - -def _contentsOf(dirname, filename): - filename = os.path.join(dirname, filename) - if not os.path.exists(filename): return None - fsock = open(filename) - contents = fsock.read() - fsock.close() - return contents - -def _getScriptDir(): - if __name__ == '__main__': - return os.path.abspath(os.path.dirname(sys.argv[0])) - else: - return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__)) - -def _marshalBoolean(value): - if value: - return _true - else: - return _false - -## output formatters -def makeFormatter(outputFormat): - classname = "%sOutputFormatter" % outputFormat.capitalize() - return globals()[classname]() - -def output(results, params): - formatter = makeFormatter(params.get("outputFormat", "text")) - outputmethod = getattr(formatter, params["func"]) - outputmethod(results, params) - -class OutputFormatter: - def boil(self, data): - if type(data) == type(u""): - return data.encode("utf-8", "replace") - else: - return data - -class TextOutputFormatter(OutputFormatter): - def common(self, data, params): - if params.get("showMeta", 0): - meta = data.meta - for category in meta.directoryCategories: - print "directoryCategory: %s" % self.boil(category["fullViewableName"]) - for attr in [node for node in dir(meta) if node <> "directoryCategories" and node[:2] <> '__']: - print "%s:" % attr, self.boil(getattr(meta, attr)) - - def doGoogleSearch(self, data, params): - results = data.results - if params.get("feelingLucky", 0): - results = results[:1] - if params.get("reverseOrder", 0): - results.reverse() - for result in results: - for attr in dir(result): - if attr == "directoryCategory": - print "directoryCategory:", self.boil(result.directoryCategory["fullViewableName"]) - elif attr[:2] <> '__': - print "%s:" % attr, self.boil(getattr(result, attr)) - print - self.common(data, params) - - def doGetCachedPage(self, data, params): - print data - self.common(data, params) - - doSpellingSuggestion = doGetCachedPage - -## search results classes -class _SearchBase: - def __init__(self, params): - for k, v in params.items(): - if isinstance(v, SOAP.structType): - v = v._asdict - try: - if isinstance(v[0], SOAP.structType): - v = [node._asdict for node in v] - except: - pass - self.__dict__[str(k)] = v - -class SearchResultsMetaData(_SearchBase): - """metadata of search query results - - Available attributes: - documentFiltering - flag indicates whether duplicate page filtering was perfomed in this search - searchComments - human-readable informational message (example: "'the' is a very common word - and was not included in your search") - estimatedTotalResultsCount - estimated total number of results for this query - estimateIsExact - flag indicates whether estimatedTotalResultsCount is an exact value - searchQuery - search string that initiated this search - startIndex - index of first result returned (zero-based) - endIndex - index of last result returned (zero-based) - searchTips - human-readable informational message on how to use Google bette - directoryCategories - list of dictionaries like this: - {'fullViewableName': Open Directory category, - 'specialEncoding': encoding scheme of this directory category} - searchTime - total search time, in seconds - """ - pass - -class SearchResult(_SearchBase): - """search result - - Available attributes: - URL - URL - title - title (HTML) - snippet - snippet showing query context (HTML) - cachedSize - size of cached version of this result, (KB) - relatedInformationPresent - flag indicates that the "related:" keyword is supported for this URL - hostName: When filtering occurs, a maximum of two results from any given host is returned. - When this occurs, the second resultElement that comes from that host contains - the host name in this parameter. - directoryCategory: dictionary like this: - {'fullViewableName': Open Directory category, - 'specialEncoding': encoding scheme of this directory category} - directoryTitle: Open Directory title of this result (or blank) - summary - Open Directory summary for this result (or blank) - """ - pass - -class SearchReturnValue: - """complete search results for a single query - - Available attributes: - meta - SearchResultsMetaData - results - list of SearchResult - """ - def __init__(self, metadata, results): - self.meta = metadata - self.results = results - -## main functions -def doGoogleSearch(q, start=0, maxResults=10, filter=1, restrict='', - safeSearch=0, language='', inputencoding='', outputencoding='', - license_key = None, http_proxy = None): - """search Google - - You need a license key to call this function; see - http://www.google.com/apis/ to get one. Then you can either pass it to - this function every time, or set it globally; see the module docs for details. - - Parameters: - q - search string. Anything you could type at google.com, you can pass here. - See http://www.google.com/help/features.html for examples of advanced features. - start (optional) - zero-based index of first desired result (for paging through - multiple pages of results) - maxResults (optional) - maximum number of results, currently capped at 10 - filter (optional) - set to 1 to filter out similar results, set to 0 to see everything - restrict (optional) - restrict results by country or topic. Examples: - Ukraine - search only sites located in Ukraine - linux - search Linux sites only - mac - search Mac sites only - bsd - search FreeBSD sites only - See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) - for more advanced examples and a full list of country codes and topics. - safeSearch (optional) - set to 1 to filter results with SafeSearch (no adult material) - language (optional) - restricts search to documents in one or more languages. Example: - lang_en - only return pages in English - lang_fr - only return pages in French - See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) - for more advanced examples and a full list of language codes. - inputencoding (optional) - sets the character encoding of q parameter - outputencoding (optional) - sets the character encoding of the returned results - See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) - for a full list of encodings. - http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages - - Returns: SearchReturnValue - .meta - SearchMetaData - .results - list of SearchResult - See documentation of these individual classes for list of available attributes - """ - http_proxy = getProxy(http_proxy) - remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) - license_key = getLicense(license_key) - filter = _marshalBoolean(filter) - safeSearch = _marshalBoolean(safeSearch) - data = remoteserver.doGoogleSearch(license_key, q, start, maxResults, filter, restrict, - safeSearch, language, inputencoding, outputencoding) - metadata = data._asdict - del metadata["resultElements"] - metadata = SearchResultsMetaData(metadata) - results = [SearchResult(node._asdict) for node in data.resultElements] - return SearchReturnValue(metadata, results) - -def doGetCachedPage(url, license_key = None, http_proxy = None): - """get page from Google cache - - You need a license key to call this function; see - http://www.google.com/apis/ to get one. Then you can either pass it to - this function every time, or set it globally; see the module docs for details. - - Parameters: - url - address of page to get - license_key (optional) - Google license key - http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages - - Returns: string, text of cached page - """ - http_proxy = getProxy(http_proxy) - remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) - license_key = getLicense(license_key) - return remoteserver.doGetCachedPage(license_key, url) - -def doSpellingSuggestion(phrase, license_key = None, http_proxy = None): - """get spelling suggestions from Google - - You need a license key to call this function; see - http://www.google.com/apis/ to get one. Then you can either pass it to - this function every time, or set it globally; see the module docs for details. - - Parameters: - phrase - word or phrase to spell-check - http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages - - Returns: text of suggested replacement, or None - """ - http_proxy = getProxy(http_proxy) - remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) - license_key = getLicense(license_key) - return remoteserver.doSpellingSuggestion(license_key, phrase) - -## functional test suite (see googletest.py for unit test suite) -def test(): - try: - getLicense(None) - except NoLicenseKey: - return - print "Searching for Python at google.com..." - data = doGoogleSearch("Python") - output(data, {"func": "doGoogleSearch"}) - - print "\nSearching for 5 _French_ pages about Python, encoded in ISO-8859-1..." - data = doGoogleSearch("Python", language='lang_fr', outputencoding='ISO-8859-1', maxResults=5) - output(data, {"func": "doGoogleSearch"}) - - phrase = "Pyhton programming languager" - print "\nTesting spelling suggetions for '%s'..." % phrase - data = doSpellingSuggestion(phrase) - output(data, {"func": "doSpellingSuggestion"}) - -## main driver for command-line use -def main(argv): - if not argv: - usage() - return - q = None - func = None - http_proxy = None - license_key = None - feelingLucky = 0 - showMeta = 0 - reverseOrder = 0 - runTest = 0 - outputFormat = "text" - try: - opts, args = getopt.getopt(argv, "s:c:p:k:lmrx:hvt1", - ["search=", "cache=", "spelling=", "key=", "lucky", "meta", "reverse", "proxy=", "help", "version", "test"]) - except getopt.GetoptError: - usage() - sys.exit(2) - for opt, arg in opts: - if opt in ("-s", "--search"): - q = arg - func = "doGoogleSearch" - elif opt in ("-c", "--cache"): - q = arg - func = "doGetCachedPage" - elif opt in ("-p", "--spelling"): - q = arg - func = "doSpellingSuggestion" - elif opt in ("-k", "--key"): - license_key = arg - elif opt in ("-l", "-1", "--lucky"): - feelingLucky = 1 - elif opt in ("-m", "--meta"): - showMeta = 1 - elif opt in ("-r", "--reverse"): - reverseOrder = 1 - elif opt in ("-x", "--proxy"): - http_proxy = arg - elif opt in ("-h", "--help"): - usage() - elif opt in ("-v", "--version"): - version() - elif opt in ("-t", "--test"): - runTest = 1 - if runTest: - setLicense(license_key) - setProxy(http_proxy) - test() - if args and not q: - q = args[0] - func = "doGoogleSearch" - if func: - results = globals()[func](q, http_proxy=http_proxy, license_key=license_key) - output(results, locals()) - -if __name__ == '__main__': - main(sys.argv[1:]) -# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: +""" +Python wrapper for Google web APIs + +This module allows you to access Google's web APIs through SOAP, +to do things like search Google and get the results programmatically. +Described U{here } + +You need a Google-provided license key to use these services. +Follow the link above to get one. These functions will look in +several places (in this order) for the license key: + + - the "license_key" argument of each function + - the module-level LICENSE_KEY variable (call setLicense once to set it) + - an environment variable called GOOGLE_LICENSE_KEY + - a file called ".googlekey" in the current directory + - a file called "googlekey.txt" in the current directory + - a file called ".googlekey" in your home directory + - a file called "googlekey.txt" in your home directory + - a file called ".googlekey" in the same directory as google.py + - a file called "googlekey.txt" in the same directory as google.py + +Sample usage:: + + >>> import google + >>> google.setLicense('...') # must get your own key! + >>> data = google.doGoogleSearch('python') + >>> data.meta.searchTime + 0.043221000000000002 + + >>> data.results[0].URL + 'http://www.python.org/' + + >>> data.results[0].title + 'Python Language Website' + +@newfield contrib: Contributors +@author: Mark Pilgrim +@author: Brian Landers +@license: Python +@version: 0.6 +@contrib: David Ascher, for the install script +@contrib: Erik Max Francis, for the command line interface +@contrib: Michael Twomey, for HTTP proxy support +@contrib: Mark Recht, for patches to support SOAPpy +""" + +__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" +__version__ = "0.6" +__cvsversion__ = "$Revision$"[11:-2] +__date__ = "$Date$"[7:-2] +__copyright__ = "Copyright (c) 2002 Mark Pilgrim" +__license__ = "Python" +__credits__ = """David Ascher, for the install script +Erik Max Francis, for the command line interface +Michael Twomey, for HTTP proxy support""" + +import os, sys, getopt +import GoogleSOAPFacade + +LICENSE_KEY = None +HTTP_PROXY = None + +# +# Constants +# +_url = 'http://api.google.com/search/beta2' +_namespace = 'urn:GoogleSearch' +_googlefile1 = ".googlekey" +_googlefile2 = "googlekey.txt" + +_false = GoogleSOAPFacade.false +_true = GoogleSOAPFacade.true + +_licenseLocations = ( + ( lambda key: key, + 'passed to the function in license_key variable' ), + ( lambda key: LICENSE_KEY, + 'module-level LICENSE_KEY variable (call setLicense to set it)' ), + ( lambda key: os.environ.get( 'GOOGLE_LICENSE_KEY', None ), + 'an environment variable called GOOGLE_LICENSE_KEY' ), + ( lambda key: _contentsOf( os.getcwd(), _googlefile1 ), + '%s in the current directory' % _googlefile1), + ( lambda key: _contentsOf( os.getcwd(), _googlefile2 ), + '%s in the current directory' % _googlefile2), + ( lambda key: _contentsOf( os.environ.get( 'HOME', '' ), _googlefile1 ), + '%s in your home directory' % _googlefile1), + ( lambda key: _contentsOf( os.environ.get( 'HOME', '' ), _googlefile2 ), + '%s in your home directory' % _googlefile2 ), + ( lambda key: _contentsOf( _getScriptDir(), _googlefile1 ), + '%s in the google.py directory' % _googlefile1 ), + ( lambda key: _contentsOf( _getScriptDir(), _googlefile2 ), + '%s in the google.py directory' % _googlefile2 ) +) + +## ---------------------------------------------------------------------- +## Exceptions +## ---------------------------------------------------------------------- + +class NoLicenseKey(Exception): + """ + Thrown when the API is unable to find a valid license key. + """ + pass + +## ---------------------------------------------------------------------- +## administrative functions (non-API) +## ---------------------------------------------------------------------- + +def _version(): + """ + Display a formatted version string for the module + """ + print """PyGoogle %(__version__)s +%(__copyright__)s +released %(__date__)s + +Thanks to: +%(__credits__)s""" % globals() + + +def _usage(): + """ + Display usage information for the command-line interface + """ + program = os.path.basename(sys.argv[0]) + print """Usage: %(program)s [options] [querytype] query + +options: + -k, --key= Google license key (see important note below) + -1, -l, --lucky show only first hit + -m, --meta show meta information + -r, --reverse show results in reverse order + -x, --proxy= use HTTP proxy + -h, --help print this help + -v, --version print version and copyright information + -t, --test run test queries + +querytype: + -s, --search= search (default) + -c, --cache= retrieve cached page + -p, --spelling= check spelling + +IMPORTANT NOTE: all Google functions require a valid license key; +visit http://www.google.com/apis/ to get one. %(program)s will look in +these places (in order) and use the first license key it finds: + * the key specified on the command line""" % vars() + for get, location in _licenseLocations[2:]: + print " *", location + +## ---------------------------------------------------------------------- +## utility functions (API) +## ---------------------------------------------------------------------- + +def setLicense(license_key): + """ + Set the U{Google APIs } license key + + @param license_key: The new key to use + @type license_key: String + @todo: validate the key? + """ + global LICENSE_KEY + LICENSE_KEY = license_key + + +def getLicense(license_key = None): + """ + Get the U{Google APIs } license key + + The key can be read from any number of locations. See the module-leve + documentation for the search order. + + @return: the license key + @rtype: String + @raise NoLicenseKey: if no valid key could be found + """ + for get, location in _licenseLocations: + rc = get(license_key) + if rc: return rc + _usage() + raise NoLicenseKey, 'get a license key at http://www.google.com/apis/' + + +def setProxy(http_proxy): + """ + Set the HTTP proxy to be used when accessing Google + + @param http_proxy: the proxy to use + @type http_proxy: String + @todo: validiate the input? + """ + global HTTP_PROXY + HTTP_PROXY = http_proxy + + +def getProxy(http_proxy = None): + """ + Get the HTTP proxy we use for accessing Google + + @return: the proxy + @rtype: String + """ + return http_proxy or HTTP_PROXY + + +def _contentsOf(dirname, filename): + filename = os.path.join(dirname, filename) + if not os.path.exists(filename): return None + fsock = open(filename) + contents = fsock.read() + fsock.close() + return contents + + +def _getScriptDir(): + if __name__ == '__main__': + return os.path.abspath(os.path.dirname(sys.argv[0])) + else: + return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__)) + + +def _marshalBoolean(value): + if value: + return _true + else: + return _false + + +def _getRemoteServer( http_proxy ): + return GoogleSOAPFacade.getProxy( _url, _namespace, http_proxy ) + + +## ---------------------------------------------------------------------- +## search results classes +## ---------------------------------------------------------------------- + +class _SearchBase: + def __init__(self, params): + for k, v in params.items(): + if isinstance(v, GoogleSOAPFacade.structType): + v = GoogleSOAPFacade.toDict( v ) + + try: + if isinstance(v[0], GoogleSOAPFacade.structType): + v = [ SOAPProxy.toDict( node ) for node in v ] + + except: + pass + self.__dict__[str(k)] = v + +## ---------------------------------------------------------------------- + +class SearchResultsMetaData(_SearchBase): + """ + Container class for metadata about a given search query's results. + + @ivar documentFiltering: is duplicate page filtering active? + + @ivar searchComments: human-readable informational message + + example:: + + "'the' is a very common word and was not included in your search" + + @ivar estimatedTotalResultsCount: estimated total number of results + for this query. + + @ivar estimateIsExact: is estimatedTotalResultsCount an exact value? + + @ivar searchQuery: search string that initiated this search + + @ivar startIndex: index of the first result returned (zero-based) + + @ivar endIndex: index of the last result returned (zero-based) + + @ivar searchTips: human-readable informational message on how to better + use Google. + + @ivar directoryCategories: list of categories for the search results + + This field is a list of dictionaries, like so:: + + { 'fullViewableName': 'the Open Directory category', + 'specialEncoding': 'encoding scheme of this directory category' + } + + @ivar searchTime: total search time, in seconds + """ + pass + +## ---------------------------------------------------------------------- + +class SearchResult(_SearchBase): + """ + Encapsulates the results from a search. + + @ivar URL: URL + + @ivar title: title (HTML) + + @ivar snippet: snippet showing query context (HTML + + @ivar cachedSize: size of cached version of this result, (KB) + + @ivar relatedInformationPresent: is the "related:" keyword supported? + + Flag indicates that the "related:" keyword is supported for this URL + + @ivar hostName: used when filtering occurs + + When filtering occurs, a maximum of two results from any given + host is returned. When this occurs, the second resultElement + that comes from that host contains the host name in this parameter. + + @ivar directoryCategory: Open Directory category information + + This field is a dictionary with the following values:: + + { 'fullViewableName': 'the Open Directory category', + 'specialEncoding' : 'encoding scheme of this directory category' + } + + @ivar directoryTitle: Open Directory title of this result (or blank) + + @ivar summary: Open Directory summary for this result (or blank) + """ + pass + +## ---------------------------------------------------------------------- + +class SearchReturnValue: + """ + complete search results for a single query + + @ivar meta: L{SearchResultsMetaData} instance for this query + + @ivar results: list of L{SearchResult} objects for this query + """ + def __init__( self, metadata, results ): + self.meta = metadata + self.results = results + +## ---------------------------------------------------------------------- +## main functions +## ---------------------------------------------------------------------- + +def doGoogleSearch( q, start = 0, maxResults = 10, filter = 1, + restrict='', safeSearch = 0, language = '', + inputencoding = '', outputencoding = '',\ + license_key = None, http_proxy = None ): + """ + Search Google using the SOAP API and return the results. + + You need a license key to call this function; see the + U{Google APIs } site to get one. + Then you can either pass it to this function every time, or + set it globally; see the L{google} module-level docs for details. + + See U{http://www.google.com/help/features.html} + for examples of advanced features. Anything that works at the + Google web site will work as a query string in this method. + + You can use the C{start} and C{maxResults} parameters to page + through multiple pages of results. Note that 'maxResults' is + currently limited by Google to 10. + + See the API reference for more advanced examples and a full list of + country codes and topics for use in the C{restrict} parameter, along + with legal values for the C{language}, C{inputencoding}, and + C{outputencoding} parameters. + + You can download the API documentation + U{http://www.google.com/apis/download.html }. + + @param q: search string. + @type q: String + + @param start: (optional) zero-based index of first desired result. + @type start: int + + @param maxResults: (optional) maximum number of results to return. + @type maxResults: int + + @param filter: (optional) flag to request filtering of similar results + @type filter: int + + @param restrict: (optional) restrict results by country or topic. + @type restrict: String + + @param safeSearch: (optional) + @type safeSearch: int + + @param language: (optional) + @type language: String + + @param inputencoding: (optional) + @type inputencoding: String + + @param outputencoding: (optional) + @type outputencoding: String + + @param license_key: (optional) the Google API license key to use + @type license_key: String + + @param http_proxy: (optional) the HTTP proxy to use for talking to Google + @type http_proxy: String + + @return: the search results encapsulated in an object + @rtype: L{SearchReturnValue} + """ + license_key = getLicense( license_key ) + http_proxy = getProxy( http_proxy ) + remoteserver = _getRemoteServer( http_proxy ) + + filter = _marshalBoolean( filter ) + safeSearch = _marshalBoolean( safeSearch ) + + data = remoteserver.doGoogleSearch( license_key, q, start, maxResults, + filter, restrict, safeSearch, + language, inputencoding, + outputencoding ) + + metadata = GoogleSOAPFacade.toDict( data ) + del metadata["resultElements"] + + metadata = SearchResultsMetaData( metadata ) + + results = [ SearchResult( GoogleSOAPFacade.toDict( node ) ) \ + for node in data.resultElements ] + + return SearchReturnValue( metadata, results ) + +## ---------------------------------------------------------------------- + +def doGetCachedPage( url, license_key = None, http_proxy = None ): + """ + Retrieve a page from the Google cache. + + You need a license key to call this function; see the + U{Google APIs } site to get one. + Then you can either pass it to this function every time, or + set it globally; see the L{google} module-level docs for details. + + @param url: full URL to the page to retrieve + @type url: String + + @param license_key: (optional) the Google API key to use + @type license_key: String + + @param http_proxy: (optional) the HTTP proxy server to use + @type http_proxy: String + + @return: full text of the cached page + @rtype: String + """ + license_key = getLicense( license_key ) + http_proxy = getProxy( http_proxy ) + remoteserver = _getRemoteServer( http_proxy ) + + return remoteserver.doGetCachedPage( license_key, url ) + +## ---------------------------------------------------------------------- + +def doSpellingSuggestion( phrase, license_key = None, http_proxy = None ): + """ + Get spelling suggestions from Google + + You need a license key to call this function; see the + U{Google APIs } site to get one. + Then you can either pass it to this function every time, or + set it globally; see the L{google} module-level docs for details. + + @param phrase: word or phrase to spell-check + @type phrase: String + + @param license_key: (optional) the Google API key to use + @type license_key: String + + @param http_proxy: (optional) the HTTP proxy to use + @type http_proxy: String + + @return: text of any suggested replacement, or None + """ + license_key = getLicense( license_key ) + http_proxy = getProxy( http_proxy) + remoteserver = _getRemoteServer( http_proxy ) + + return remoteserver.doSpellingSuggestion( license_key, phrase ) + +## ---------------------------------------------------------------------- +## functional test suite (see googletest.py for unit test suite) +## ---------------------------------------------------------------------- + +def _test(): + """ + Run functional test suite. + """ + try: + getLicense(None) + except NoLicenseKey: + return + + print "Searching for Python at google.com..." + data = doGoogleSearch( "Python" ) + _output( data, { "func": "doGoogleSearch"} ) + + print "\nSearching for 5 _French_ pages about Python, " + print "encoded in ISO-8859-1..." + + data = doGoogleSearch( "Python", language = 'lang_fr', + outputencoding = 'ISO-8859-1', + maxResults = 5 ) + + _output( data, { "func": "doGoogleSearch" } ) + + phrase = "Pyhton programming languager" + print "\nTesting spelling suggestions for '%s'..." % phrase + + data = doSpellingSuggestion( phrase ) + + _output( data, { "func": "doSpellingSuggestion" } ) + +## ---------------------------------------------------------------------- +## Command-line interface +## ---------------------------------------------------------------------- + +class _OutputFormatter: + def boil(self, data): + if type(data) == type(u""): + return data.encode("ISO-8859-1", "replace") + else: + return data + +class _TextOutputFormatter(_OutputFormatter): + def common(self, data, params): + if params.get("showMeta", 0): + meta = data.meta + for category in meta.directoryCategories: + print "directoryCategory: %s" % \ + self.boil(category["fullViewableName"]) + for attr in [node for node in dir(meta) if \ + node <> "directoryCategories" and node[:2] <> '__']: + print "%s:" % attr, self.boil(getattr(meta, attr)) + + def doGoogleSearch(self, data, params): + results = data.results + if params.get("feelingLucky", 0): + results = results[:1] + if params.get("reverseOrder", 0): + results.reverse() + for result in results: + for attr in dir(result): + if attr == "directoryCategory": + print "directoryCategory:", \ + self.boil(result.directoryCategory["fullViewableName"]) + elif attr[:2] <> '__': + print "%s:" % attr, self.boil(getattr(result, attr)) + print + self.common(data, params) + + def doGetCachedPage(self, data, params): + print data + self.common(data, params) + + doSpellingSuggestion = doGetCachedPage + +def _makeFormatter(outputFormat): + classname = "_%sOutputFormatter" % outputFormat.capitalize() + return globals()[classname]() + +def _output(results, params): + formatter = _makeFormatter(params.get("outputFormat", "text")) + outputmethod = getattr(formatter, params["func"]) + outputmethod(results, params) + +def main(argv): + """ + Command-line interface. + """ + if not argv: + _usage() + return + q = None + func = None + http_proxy = None + license_key = None + feelingLucky = 0 + showMeta = 0 + reverseOrder = 0 + runTest = 0 + outputFormat = "text" + try: + opts, args = getopt.getopt(argv, "s:c:p:k:lmrx:hvt1", + ["search=", "cache=", "spelling=", "key=", "lucky", "meta", + "reverse", "proxy=", "help", "version", "test"]) + except getopt.GetoptError: + _usage() + sys.exit(2) + for opt, arg in opts: + if opt in ("-s", "--search"): + q = arg + func = "doGoogleSearch" + elif opt in ("-c", "--cache"): + q = arg + func = "doGetCachedPage" + elif opt in ("-p", "--spelling"): + q = arg + func = "doSpellingSuggestion" + elif opt in ("-k", "--key"): + license_key = arg + elif opt in ("-l", "-1", "--lucky"): + feelingLucky = 1 + elif opt in ("-m", "--meta"): + showMeta = 1 + elif opt in ("-r", "--reverse"): + reverseOrder = 1 + elif opt in ("-x", "--proxy"): + http_proxy = arg + elif opt in ("-h", "--help"): + _usage() + elif opt in ("-v", "--version"): + _version() + elif opt in ("-t", "--test"): + runTest = 1 + if runTest: + setLicense(license_key) + setProxy(http_proxy) + _test() + if args and not q: + q = args[0] + func = "doGoogleSearch" + if func: + results = globals()[func]( q, http_proxy=http_proxy, + license_key=license_key ) + _output(results, locals()) + +if __name__ == '__main__': + main(sys.argv[1:])