From 3ea146e17c8f1679d58fbc38b06734851673d9f9 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 14 Jun 2006 15:01:17 -0400 Subject: [PATCH] Initial start page implementation --- cut-n-paste/GoogleSOAPFacade.py | 85 + cut-n-paste/GoogleSOAPFacade.pyc | Bin 0 -> 2057 bytes cut-n-paste/SOAP.py | 3974 ++++++++++++++++++++++++++++++ cut-n-paste/SOAP.pyc | Bin 0 -> 106975 bytes cut-n-paste/google.py | 638 +++++ cut-n-paste/google.pyc | Bin 0 -> 21306 bytes sugar/presence/Buddy.py | 1 + sugar/shell/StartPage.py | 80 + sugar/shell/shell.py | 9 +- sugar/sugar | 9 +- 10 files changed, 4788 insertions(+), 8 deletions(-) create mode 100644 cut-n-paste/GoogleSOAPFacade.py create mode 100644 cut-n-paste/GoogleSOAPFacade.pyc create mode 100755 cut-n-paste/SOAP.py create mode 100644 cut-n-paste/SOAP.pyc create mode 100755 cut-n-paste/google.py create mode 100644 cut-n-paste/google.pyc create mode 100644 sugar/shell/StartPage.py diff --git a/cut-n-paste/GoogleSOAPFacade.py b/cut-n-paste/GoogleSOAPFacade.py new file mode 100644 index 00000000..0aab3cf4 --- /dev/null +++ b/cut-n-paste/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/cut-n-paste/GoogleSOAPFacade.pyc b/cut-n-paste/GoogleSOAPFacade.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87cd222a3d7df1b8aa701062d386e87e16715de6 GIT binary patch literal 2057 zcma)6dv6;>5T8B2;&bf0+N4xSU0%Wx^qQyzi3%0d1Q1e{hD#yU@yBU>w{|u;@0#0n zf~81Eq#uB<$Jc-_z|5W`0NA|i18+dG0VluweWSWnscu5PN`cTB9)9@-K%Z z4u-`{W=6O#9!I7eKMMzi%A~f^n;gy2T8D!RG*CwSz1`@u=t+3kM{I#zMe@QJD}Smq z?1#AVf8y90D(*&~yC#0CFj;^sEyC|UE_)w|15gaE0Im?c9>3QCV~FbGKDjt_A!$01 zofq;#7K%3ebuwH*aw$r!^w>JuA;GnfnMiBJSUL*it@;!;CY_k}`nW3M9w3*^+(Z3v zDy)-_zVp4O;Z&2wDZ~`=4lYYxfLSVOH`?2a9(%vGjWSsrmFCyEr2wzyQ%vD0a?8GP zGx8(Hp$_H9l#+jBtFfMIbE)}oHYVsGil!yjsU7#-Iht1q@lk0ed^FRU!#QEak^N*5 z6+LR>xSC8&>0}=9SLGZPcp=9kn=f$|*+;1VqZY%$BWa`-7({bZR$6>t5ZE%KozgQI zg$hk*I@luW0WGS1AD4ZG1ZQXN1H)$s=ZC|i5e9G-;1vvxHJvmT!@Yg@pZQYvw7`)~ zMJD~UPo0~lQ)*_P!g&Z)H^;rzYDWpp$|9zj#{>rt15)+ai957^bNKJnWABy>5F~zT z&ylx1B*0p%iT_@$jWna$m&=#Uaf8b40mGUVmWs32D@*Cv7e*F`RSJ}_&(jRgJT_Bd zbCo%Ny<&=ZJ1w7^IY6Bs@YI8ilIjJ0e_g1YG|f$xrZM5s(OZ-&>t;?BHd@?dcl+Y{ z#-?fYT%@U^)}-k|`$k^84JcD)@eF}5ktfw1^Vh(wr1vEn})SrVHz@ zp1%dY=w((E^?MK0Z^{zkx{30EM8s!;aJ`H~$@>Zx8C1$-JT#_|LX&47Vk8PHeSBBW zD)d<7GUqiYospOoUW&UY#gxB)zGF_}77?HI`@~V|@jW7UiBQAiZ6eg^nBD;2h4?+< zJ|IH=D$kVk?O75Kz9O920g`6WX5E0$)oiTRn~f%`u{PTZ9@I92UeLq+7VdOek8L+v OYz_aUUwzw+_S!#EL8)N? literal 0 HcmV?d00001 diff --git a/cut-n-paste/SOAP.py b/cut-n-paste/SOAP.py new file mode 100755 index 00000000..032a452d --- /dev/null +++ b/cut-n-paste/SOAP.py @@ -0,0 +1,3974 @@ +#!/usr/bin/python +################################################################################ +# +# 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: SOAP.py,v 1.1.1.1 2004/01/16 16:15:18 bluecoat93 Exp $' + +__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) + #print m + + r = self.transport.call(self.proxy, m, sa, encoding = self.encoding, + http_proxy = self.http_proxy, + config = self.config) + + #print r + 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 + 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 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) diff --git a/cut-n-paste/SOAP.pyc b/cut-n-paste/SOAP.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75e423522bd8858bcbfb60af2a32985b06755745 GIT binary patch literal 106975 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⁡YK%DbvB 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

bamjj4vXZA{Ii5mheEUUF}mQGu(;3JjyE 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 literal 0 HcmV?d00001 diff --git a/cut-n-paste/google.py b/cut-n-paste/google.py new file mode 100755 index 00000000..a20ba513 --- /dev/null +++ b/cut-n-paste/google.py @@ -0,0 +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 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: 1.5 $"[11:-2] +__date__ = "$Date: 2004/02/25 23:46:07 $"[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:]) diff --git a/cut-n-paste/google.pyc b/cut-n-paste/google.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5519ec8b27d599846bfbfd160e96b8ae29970a13 GIT binary patch literal 21306 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%0DSN%qs8pY;x! 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 Nv5~PoV;2up{x{yzv04BC literal 0 HcmV?d00001 diff --git a/sugar/presence/Buddy.py b/sugar/presence/Buddy.py index 9d94b9dd..cc6be570 100644 --- a/sugar/presence/Buddy.py +++ b/sugar/presence/Buddy.py @@ -79,6 +79,7 @@ class Buddy(gobject.GObject): # A buddy isn't valid until its official presence # service has been found and resolved self._valid = True + print 'Requesting buddy icon %s' % self._nick_name self._request_buddy_icon(service) return True diff --git a/sugar/shell/StartPage.py b/sugar/shell/StartPage.py new file mode 100644 index 00000000..1c87ee56 --- /dev/null +++ b/sugar/shell/StartPage.py @@ -0,0 +1,80 @@ +import pygtk +pygtk.require('2.0') +import gtk +import dbus + +import google + +class ActivitiesModel(gtk.ListStore): + def __init__(self): + gtk.ListStore.__init__(self, str, str) + + def add_web_page(self, title, address): + self.append([ title, address ]) + +class ActivitiesView(gtk.TreeView): + def __init__(self, model): + gtk.TreeView.__init__(self, model) + + self.set_headers_visible(False) + + column = gtk.TreeViewColumn('') + self.append_column(column) + + cell = gtk.CellRendererText() + column.pack_start(cell, True) + column.add_attribute(cell, 'text', 0) + + self.connect('row-activated', self._row_activated_cb) + + def _row_activated_cb(self, treeview, path, column): + bus = dbus.SessionBus() + proxy_obj = bus.get_object('com.redhat.Sugar.Browser', '/com/redhat/Sugar/Browser') + browser_shell = dbus.Interface(proxy_obj, 'com.redhat.Sugar.BrowserShell') + + model = self.get_model() + address = model.get_value(model.get_iter(path), 1) + browser_shell.open_browser(address) + +class StartPage(gtk.HBox): + def __init__(self): + gtk.HBox.__init__(self) + + vbox = gtk.VBox() + + search_box = gtk.HBox(False, 6) + search_box.set_border_width(24) + + self._search_entry = gtk.Entry() + search_box.pack_start(self._search_entry) + self._search_entry.show() + + search_button = gtk.Button("Search") + search_button.connect('clicked', self._search_button_clicked_cb) + search_box.pack_start(search_button, False) + search_button.show() + + vbox.pack_start(search_box, False, True) + search_box.show() + + exp_space = gtk.Label('') + vbox.pack_start(exp_space) + exp_space.show() + + self.pack_start(vbox) + vbox.show() + + self._activities_model = ActivitiesModel() + + activities = ActivitiesView(self._activities_model) + self.pack_start(activities) + activities.show() + + def _search_button_clicked_cb(self, button): + self.search(self._search_entry.get_text()) + + def search(self, text): + google.LICENSE_KEY = '1As9KaJQFHIJ1L0W5EZPl6vBOFvh/Vaf' + data = google.doGoogleSearch(text) + for result in data.results: + self._activities_model.add_web_page(result.title, result.URL) diff --git a/sugar/shell/shell.py b/sugar/shell/shell.py index aaf15694..12a3396d 100755 --- a/sugar/shell/shell.py +++ b/sugar/shell/shell.py @@ -10,6 +10,7 @@ import pango import sugar.util from sugar.shell.PresenceWindow import PresenceWindow from sugar.shell.Owner import ShellOwner +from sugar.shell.StartPage import StartPage class ActivityHost(dbus.service.Object): @@ -226,10 +227,10 @@ class ActivityContainer(dbus.service.Object): self.window.set_geometry_hints(min_width = 640, max_width = 640, min_height = 480, max_height = 480) self.notebook = gtk.Notebook() - #tab_label = gtk.Label("My Laptop") - #empty_label = gtk.Label("This activity could launch other activities / be a help page") - #empty_label.show() - #self.notebook.append_page(empty_label, tab_label) + tab_label = gtk.Label("Everyone") + tab_page = StartPage() + self.notebook.append_page(tab_page, tab_label) + tab_page.show() self.notebook.show() self.notebook.connect("switch-page", self.notebook_tab_changed) diff --git a/sugar/sugar b/sugar/sugar index f6340894..f90ed4c5 100755 --- a/sugar/sugar +++ b/sugar/sugar @@ -7,7 +7,8 @@ import pygtk pygtk.require('2.0') import gobject -def append_to_python_path(path): +def add_to_python_path(path): + sys.path.insert(0, path) if os.environ.has_key('PYTHONPATH'): os.environ['PYTHONPATH'] += ':' + path else: @@ -59,13 +60,13 @@ if os.path.isfile(os.path.join(curdir, '__uninstalled__.py')): print 'Running sugar from current directory...' else: print 'Running sugar from ' + basedir + ' ...' - sys.path.insert(0, basedir) - append_to_python_path(basedir) + add_to_python_path(basedir) + add_to_python_path(os.path.join(basedir, 'cut-n-paste')) console = True else: print 'Running the installed sugar...' -append_to_python_path(os.path.expanduser('~/.sugar/activities')) +add_to_python_path(os.path.expanduser('~/.sugar/activities')) if console: os.environ['SUGAR_USE_CONSOLE'] = 'yes'