"""
################################################################################
# Copyright (c) 2003, Pfizer
# Copyright (c) 2001, Cayce Ullman.
# Copyright (c) 2001, Brian Matthews.
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# Neither the name of actzero, inc. nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
################################################################################
"""

ident = '$Id: Types.py,v 1.12 2004/02/18 04:28:57 warnes Exp $'
from version import __version__


import UserList
import base64
import cgi
import urllib
import copy
import re
import time
from types import *

# SOAPpy modules
from Errors    import *
from NS        import NS
from Utilities import encodeHexString, cleanDate
from Config    import Config

################################################################################
# Types and Wrappers
################################################################################

class anyType:
    _validURIs = (NS.XSD, NS.XSD2, NS.XSD3, NS.ENC)

    def __init__(self, data = None, name = None, typed = 1, attrs = None):
        if self.__class__ == anyType:
            raise Error, "anyType can't be instantiated directly"

        if type(name) in (ListType, TupleType):
            self._ns, self._name = name
        else:
            self._ns = self._validURIs[0]
            self._name = name
            
        self._typed = typed
        self._attrs = {}

        self._cache = None
        self._type = self._typeName()

        self._data = self._checkValueSpace(data)

        if attrs != None:
            self._setAttrs(attrs)

    def __str__(self):
        if self._name:
            return "<%s %s at %d>" % (self.__class__, self._name, id(self))
        return "<%s at %d>" % (self.__class__, id(self))

    __repr__ = __str__

    def _checkValueSpace(self, data):
        return data

    def _marshalData(self):
        return str(self._data)

    def _marshalAttrs(self, ns_map, builder):
        a = ''

        for attr, value in self._attrs.items():
            ns, n = builder.genns(ns_map, attr[0])
            a += n + ' %s%s="%s"' % \
                (ns, attr[1], cgi.escape(str(value), 1))

        return a

    def _fixAttr(self, attr):
        if type(attr) in (StringType, UnicodeType):
            attr = (None, attr)
        elif type(attr) == ListType:
            attr = tuple(attr)
        elif type(attr) != TupleType:
            raise AttributeError, "invalid attribute type"

        if len(attr) != 2:
            raise AttributeError, "invalid attribute length"

        if type(attr[0]) not in (NoneType, StringType, UnicodeType):
            raise AttributeError, "invalid attribute namespace URI type"

        return attr

    def _getAttr(self, attr):
        attr = self._fixAttr(attr)

        try:
            return self._attrs[attr]
        except:
            return None

    def _setAttr(self, attr, value):
        attr = self._fixAttr(attr)

        if type(value) is StringType:
            value = unicode(value)

        self._attrs[attr] = value
            

    def _setAttrs(self, attrs):
        if type(attrs) in (ListType, TupleType):
            for i in range(0, len(attrs), 2):
                self._setAttr(attrs[i], attrs[i + 1])

            return

        if type(attrs) == DictType:
            d = attrs
        elif isinstance(attrs, anyType):
            d = attrs._attrs
        else:
            raise AttributeError, "invalid attribute type"

        for attr, value in d.items():
            self._setAttr(attr, value)

    def _setMustUnderstand(self, val):
        self._setAttr((NS.ENV, "mustUnderstand"), val)

    def _getMustUnderstand(self):
        return self._getAttr((NS.ENV, "mustUnderstand"))

    def _setActor(self, val):
        self._setAttr((NS.ENV, "actor"), val)

    def _getActor(self):
        return self._getAttr((NS.ENV, "actor"))

    def _typeName(self):
        return self.__class__.__name__[:-4]

    def _validNamespaceURI(self, URI, strict):
        if not self._typed:
            return None
        if URI in self._validURIs:
            return URI
        if not strict:
            return self._ns
        raise AttributeError, \
            "not a valid namespace for type %s" % self._type

class voidType(anyType):
    pass

class stringType(anyType):
    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (StringType, UnicodeType):
            raise AttributeError, "invalid %s type:" % self._type

        return data

class untypedType(stringType):
    def __init__(self, data = None, name = None, attrs = None):
        stringType.__init__(self, data, name, 0, attrs)

class IDType(stringType): pass
class NCNameType(stringType): pass
class NameType(stringType): pass
class ENTITYType(stringType): pass
class IDREFType(stringType): pass
class languageType(stringType): pass
class NMTOKENType(stringType): pass
class QNameType(stringType): pass

class tokenType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3)
    __invalidre = '[\n\t]|^ | $|  '

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (StringType, UnicodeType):
            raise AttributeError, "invalid %s type" % self._type

        if type(self.__invalidre) == StringType:
            self.__invalidre = re.compile(self.__invalidre)

            if self.__invalidre.search(data):
                raise ValueError, "invalid %s value" % self._type

        return data

class normalizedStringType(anyType):
    _validURIs = (NS.XSD3,)
    __invalidre = '[\n\r\t]'

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (StringType, UnicodeType):
            raise AttributeError, "invalid %s type" % self._type

        if type(self.__invalidre) == StringType:
            self.__invalidre = re.compile(self.__invalidre)

            if self.__invalidre.search(data):
                raise ValueError, "invalid %s value" % self._type

        return data

class CDATAType(normalizedStringType):
    _validURIs = (NS.XSD2,)

class booleanType(anyType):
    def __int__(self):
        return self._data

    __nonzero__ = __int__

    def _marshalData(self):
        return ['false', 'true'][self._data]

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if data in (0, '0', 'false', ''):
            return 0
        if data in (1, '1', 'true'):
            return 1
        raise ValueError, "invalid %s value" % self._type

class decimalType(anyType):
    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType, FloatType):
            raise Error, "invalid %s value" % self._type

        return data

class floatType(anyType):
    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType, FloatType) or \
            data < -3.4028234663852886E+38 or \
            data >  3.4028234663852886E+38:
            raise ValueError, "invalid %s value: %s" % (self._type, repr(data))

        return data

    def _marshalData(self):
        return "%.18g" % self._data # More precision

class doubleType(anyType):
    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType, FloatType) or \
            data < -1.7976931348623158E+308 or \
            data  > 1.7976931348623157E+308:
            raise ValueError, "invalid %s value: %s" % (self._type, repr(data))

        return data

    def _marshalData(self):
        return "%.18g" % self._data # More precision

class durationType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        try:
            # A tuple or a scalar is OK, but make them into a list

            if type(data) == TupleType:
                data = list(data)
            elif type(data) != ListType:
                data = [data]

            if len(data) > 6:
                raise Exception, "too many values"

            # Now check the types of all the components, and find
            # the first nonzero element along the way.

            f = -1

            for i in range(len(data)):
                if data[i] == None:
                    data[i] = 0
                    continue

                if type(data[i]) not in \
                    (IntType, LongType, FloatType):
                    raise Exception, "element %d a bad type" % i

                if data[i] and f == -1:
                    f = i

            # If they're all 0, just use zero seconds.

            if f == -1:
                self._cache = 'PT0S'

                return (0,) * 6

            # Make sure only the last nonzero element has a decimal fraction
            # and only the first element is negative.

            d = -1

            for i in range(f, len(data)):
                if data[i]:
                    if d != -1:
                        raise Exception, \
                            "all except the last nonzero element must be " \
                            "integers"
                    if data[i] < 0 and i > f:
                        raise Exception, \
                            "only the first nonzero element can be negative"
                    elif data[i] != long(data[i]):
                        d = i

            # Pad the list on the left if necessary.

            if len(data) < 6:
                n = 6 - len(data)
                f += n
                d += n
                data = [0] * n + data

            # Save index of the first nonzero element and the decimal
            # element for _marshalData.

            self.__firstnonzero = f
            self.__decimal = d

        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return tuple(data)

    def _marshalData(self):
        if self._cache == None:
            d = self._data
            t = 0

            if d[self.__firstnonzero] < 0:
                s = '-P'
            else:
                s = 'P'

            t = 0

            for i in range(self.__firstnonzero, len(d)):
                if d[i]:
                    if i > 2 and not t:
                        s += 'T'
                        t = 1
                    if self.__decimal == i:
                        s += "%g" % abs(d[i])
                    else:
                        s += "%d" % long(abs(d[i]))
                    s += ['Y', 'M', 'D', 'H', 'M', 'S'][i]

            self._cache = s

        return self._cache

class timeDurationType(durationType):
    _validURIs = (NS.XSD, NS.XSD2, NS.ENC)

class dateTimeType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.time()

            if (type(data) in (IntType, LongType)):
                data = list(time.gmtime(data)[:6])
            elif (type(data) == FloatType):
                f = data - int(data)
                data = list(time.gmtime(int(data))[:6])
                data[5] += f
            elif type(data) in (ListType, TupleType):
                if len(data) < 6:
                    raise Exception, "not enough values"
                if len(data) > 9:
                    raise Exception, "too many values"

                data = list(data[:6])

                cleanDate(data)
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return tuple(data)

    def _marshalData(self):
        if self._cache == None:
            d = self._data
            s = "%04d-%02d-%02dT%02d:%02d:%02d" % ((abs(d[0]),) + d[1:])
            if d[0] < 0:
                s = '-' + s
            f = d[5] - int(d[5])
            if f != 0:
                s += ("%g" % f)[1:]
            s += 'Z'

            self._cache = s

        return self._cache

class recurringInstantType(anyType):
    _validURIs = (NS.XSD,)

    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = list(time.gmtime(time.time())[:6])
            if (type(data) in (IntType, LongType)):
                data = list(time.gmtime(data)[:6])
            elif (type(data) == FloatType):
                f = data - int(data)
                data = list(time.gmtime(int(data))[:6])
                data[5] += f
            elif type(data) in (ListType, TupleType):
                if len(data) < 1:
                    raise Exception, "not enough values"
                if len(data) > 9:
                    raise Exception, "too many values"

                data = list(data[:6])

                if len(data) < 6:
                    data += [0] * (6 - len(data))

                f = len(data)

                for i in range(f):
                    if data[i] == None:
                        if f < i:
                            raise Exception, \
                                "only leftmost elements can be none"
                    else:
                        f = i
                        break

                cleanDate(data, f)
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return tuple(data)

    def _marshalData(self):
        if self._cache == None:
            d = self._data
            e = list(d)
            neg = ''

            if not e[0]:
                e[0] = '--'
            else:
                if e[0] < 0:
                    neg = '-'
                    e[0] = abs(e[0])
                if e[0] < 100:
                    e[0] = '-' + "%02d" % e[0]
                else:
                    e[0] = "%04d" % e[0]

            for i in range(1, len(e)):
                if e[i] == None or (i < 3 and e[i] == 0):
                    e[i] = '-'
                else:
                    if e[i] < 0:
                        neg = '-'
                        e[i] = abs(e[i])

                    e[i] = "%02d" % e[i]

            if d[5]:
                f = abs(d[5] - int(d[5]))

                if f:
                    e[5] += ("%g" % f)[1:]

            s = "%s%s-%s-%sT%s:%s:%sZ" % ((neg,) + tuple(e))

            self._cache = s

        return self._cache

class timeInstantType(dateTimeType):
    _validURIs = (NS.XSD, NS.XSD2, NS.ENC)

class timePeriodType(dateTimeType):
    _validURIs = (NS.XSD2, NS.ENC)

class timeType(anyType):
    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.gmtime(time.time())[3:6]
            elif (type(data) == FloatType):
                f = data - int(data)
                data = list(time.gmtime(int(data))[3:6])
                data[2] += f
            elif type(data) in (IntType, LongType):
                data = time.gmtime(data)[3:6]
            elif type(data) in (ListType, TupleType):
                if len(data) == 9:
                    data = data[3:6]
                elif len(data) > 3:
                    raise Exception, "too many values"

                data = [None, None, None] + list(data)

                if len(data) < 6:
                    data += [0] * (6 - len(data))

                cleanDate(data, 3)

                data = data[3:]
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return tuple(data)

    def _marshalData(self):
        if self._cache == None:
            d = self._data
            s = ''

            s = time.strftime("%H:%M:%S", (0, 0, 0) + d + (0, 0, -1))
            f = d[2] - int(d[2])
            if f != 0:
                s += ("%g" % f)[1:]
            s += 'Z'

            self._cache = s

        return self._cache

class dateType(anyType):
    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.gmtime(time.time())[0:3]
            elif type(data) in (IntType, LongType, FloatType):
                data = time.gmtime(data)[0:3]
            elif type(data) in (ListType, TupleType):
                if len(data) == 9:
                    data = data[0:3]
                elif len(data) > 3:
                    raise Exception, "too many values"

                data = list(data)

                if len(data) < 3:
                    data += [1, 1, 1][len(data):]

                data += [0, 0, 0]

                cleanDate(data)

                data = data[:3]
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return tuple(data)

    def _marshalData(self):
        if self._cache == None:
            d = self._data
            s = "%04d-%02d-%02dZ" % ((abs(d[0]),) + d[1:])
            if d[0] < 0:
                s = '-' + s

            self._cache = s

        return self._cache

class gYearMonthType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.gmtime(time.time())[0:2]
            elif type(data) in (IntType, LongType, FloatType):
                data = time.gmtime(data)[0:2]
            elif type(data) in (ListType, TupleType):
                if len(data) == 9:
                    data = data[0:2]
                elif len(data) > 2:
                    raise Exception, "too many values"

                data = list(data)

                if len(data) < 2:
                    data += [1, 1][len(data):]

                data += [1, 0, 0, 0]

                cleanDate(data)

                data = data[:2]
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return tuple(data)

    def _marshalData(self):
        if self._cache == None:
            d = self._data
            s = "%04d-%02dZ" % ((abs(d[0]),) + d[1:])
            if d[0] < 0:
                s = '-' + s

            self._cache = s

        return self._cache

class gYearType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.gmtime(time.time())[0:1]
            elif type(data) in (IntType, LongType, FloatType):
                data = [data]

            if type(data) in (ListType, TupleType):
                if len(data) == 9:
                    data = data[0:1]
                elif len(data) < 1:
                    raise Exception, "too few values"
                elif len(data) > 1:
                    raise Exception, "too many values"

                if type(data[0]) == FloatType:
                    try: s = int(data[0])
                    except: s = long(data[0])

                    if s != data[0]:
                        raise Exception, "not integral"

                    data = [s]
                elif type(data[0]) not in (IntType, LongType):
                    raise Exception, "bad type"
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return data[0]

    def _marshalData(self):
        if self._cache == None:
            d = self._data
            s = "%04dZ" % abs(d)
            if d < 0:
                s = '-' + s

            self._cache = s

        return self._cache

class centuryType(anyType):
    _validURIs = (NS.XSD2, NS.ENC)

    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.gmtime(time.time())[0:1] / 100
            elif type(data) in (IntType, LongType, FloatType):
                data = [data]

            if type(data) in (ListType, TupleType):
                if len(data) == 9:
                    data = data[0:1] / 100
                elif len(data) < 1:
                    raise Exception, "too few values"
                elif len(data) > 1:
                    raise Exception, "too many values"

                if type(data[0]) == FloatType:
                    try: s = int(data[0])
                    except: s = long(data[0])

                    if s != data[0]:
                        raise Exception, "not integral"

                    data = [s]
                elif type(data[0]) not in (IntType, LongType):
                    raise Exception, "bad type"
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return data[0]

    def _marshalData(self):
        if self._cache == None:
            d = self._data
            s = "%02dZ" % abs(d)
            if d < 0:
                s = '-' + s

            self._cache = s

        return self._cache

class yearType(gYearType):
    _validURIs = (NS.XSD2, NS.ENC)

class gMonthDayType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.gmtime(time.time())[1:3]
            elif type(data) in (IntType, LongType, FloatType):
                data = time.gmtime(data)[1:3]
            elif type(data) in (ListType, TupleType):
                if len(data) == 9:
                    data = data[0:2]
                elif len(data) > 2:
                    raise Exception, "too many values"

                data = list(data)

                if len(data) < 2:
                    data += [1, 1][len(data):]

                data = [0] + data + [0, 0, 0]

                cleanDate(data, 1)

                data = data[1:3]
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return tuple(data)

    def _marshalData(self):
        if self._cache == None:
            self._cache = "--%02d-%02dZ" % self._data

        return self._cache

class recurringDateType(gMonthDayType):
    _validURIs = (NS.XSD2, NS.ENC)

class gMonthType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.gmtime(time.time())[1:2]
            elif type(data) in (IntType, LongType, FloatType):
                data = [data]

            if type(data) in (ListType, TupleType):
                if len(data) == 9:
                    data = data[1:2]
                elif len(data) < 1:
                    raise Exception, "too few values"
                elif len(data) > 1:
                    raise Exception, "too many values"

                if type(data[0]) == FloatType:
                    try: s = int(data[0])
                    except: s = long(data[0])

                    if s != data[0]:
                        raise Exception, "not integral"

                    data = [s]
                elif type(data[0]) not in (IntType, LongType):
                    raise Exception, "bad type"

                if data[0] < 1 or data[0] > 12:
                    raise Exception, "bad value"
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return data[0]

    def _marshalData(self):
        if self._cache == None:
            self._cache = "--%02d--Z" % self._data

        return self._cache

class monthType(gMonthType):
    _validURIs = (NS.XSD2, NS.ENC)

class gDayType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        try:
            if data == None:
                data = time.gmtime(time.time())[2:3]
            elif type(data) in (IntType, LongType, FloatType):
                data = [data]

            if type(data) in (ListType, TupleType):
                if len(data) == 9:
                    data = data[2:3]
                elif len(data) < 1:
                    raise Exception, "too few values"
                elif len(data) > 1:
                    raise Exception, "too many values"

                if type(data[0]) == FloatType:
                    try: s = int(data[0])
                    except: s = long(data[0])

                    if s != data[0]:
                        raise Exception, "not integral"

                    data = [s]
                elif type(data[0]) not in (IntType, LongType):
                    raise Exception, "bad type"

                if data[0] < 1 or data[0] > 31:
                    raise Exception, "bad value"
            else:
                raise Exception, "invalid type"
        except Exception, e:
            raise ValueError, "invalid %s value - %s" % (self._type, e)

        return data[0]

    def _marshalData(self):
        if self._cache == None:
            self._cache = "---%02dZ" % self._data

        return self._cache

class recurringDayType(gDayType):
    _validURIs = (NS.XSD2, NS.ENC)

class hexBinaryType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (StringType, UnicodeType):
            raise AttributeError, "invalid %s type" % self._type

        return data

    def _marshalData(self):
        if self._cache == None:
            self._cache = encodeHexString(self._data)

        return self._cache

class base64BinaryType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (StringType, UnicodeType):
            raise AttributeError, "invalid %s type" % self._type

        return data

    def _marshalData(self):
        if self._cache == None:
            self._cache = base64.encodestring(self._data)

        return self._cache

class base64Type(base64BinaryType):
    _validURIs = (NS.ENC,)

class binaryType(anyType):
    _validURIs = (NS.XSD, NS.ENC)

    def __init__(self, data, name = None, typed = 1, encoding = 'base64',
        attrs = None):

        anyType.__init__(self, data, name, typed, attrs)

        self._setAttr('encoding', encoding)

    def _marshalData(self):
        if self._cache == None:
            if self._getAttr((None, 'encoding')) == 'base64':
                self._cache = base64.encodestring(self._data)
            else:
                self._cache = encodeHexString(self._data)

        return self._cache

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (StringType, UnicodeType):
            raise AttributeError, "invalid %s type" % self._type

        return data

    def _setAttr(self, attr, value):
        attr = self._fixAttr(attr)

        if attr[1] == 'encoding':
            if attr[0] != None or value not in ('base64', 'hex'):
                raise AttributeError, "invalid encoding"

            self._cache = None

        anyType._setAttr(self, attr, value)


class anyURIType(anyType):
    _validURIs = (NS.XSD3,)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (StringType, UnicodeType):
            raise AttributeError, "invalid %s type" % self._type

        return data

    def _marshalData(self):
        if self._cache == None:
            self._cache = urllib.quote(self._data)

        return self._cache

class uriType(anyURIType):
    _validURIs = (NS.XSD,)

class uriReferenceType(anyURIType):
    _validURIs = (NS.XSD2,)

class NOTATIONType(anyType):
    def __init__(self, data, name = None, typed = 1, attrs = None):

        if self.__class__ == NOTATIONType:
            raise Error, "a NOTATION can't be instantiated directly"

        anyType.__init__(self, data, name, typed, attrs)

class ENTITIESType(anyType):
    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) in (StringType, UnicodeType):
            return (data,)

        if type(data) not in (ListType, TupleType) or \
            filter (lambda x: type(x) not in (StringType, UnicodeType), data):
            raise AttributeError, "invalid %s type" % self._type

        return data

    def _marshalData(self):
        return ' '.join(self._data)

class IDREFSType(ENTITIESType): pass
class NMTOKENSType(ENTITIESType): pass

class integerType(anyType):
    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType):
            raise ValueError, "invalid %s value" % self._type

        return data

class nonPositiveIntegerType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or data > 0:
            raise ValueError, "invalid %s value" % self._type

        return data

class non_Positive_IntegerType(nonPositiveIntegerType):
    _validURIs = (NS.XSD,)

    def _typeName(self):
        return 'non-positive-integer'

class negativeIntegerType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or data >= 0:
            raise ValueError, "invalid %s value" % self._type

        return data

class negative_IntegerType(negativeIntegerType):
    _validURIs = (NS.XSD,)

    def _typeName(self):
        return 'negative-integer'

class longType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or \
            data < -9223372036854775808L or \
            data >  9223372036854775807L:
            raise ValueError, "invalid %s value" % self._type

        return data

class intType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or \
            data < -2147483648L or \
            data >  2147483647:
            raise ValueError, "invalid %s value" % self._type

        return data

class shortType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or \
            data < -32768 or \
            data >  32767:
            raise ValueError, "invalid %s value" % self._type

        return data

class byteType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or \
            data < -128 or \
            data >  127:
            raise ValueError, "invalid %s value" % self._type

        return data

class nonNegativeIntegerType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or data < 0:
            raise ValueError, "invalid %s value" % self._type

        return data

class non_Negative_IntegerType(nonNegativeIntegerType):
    _validURIs = (NS.XSD,)

    def _typeName(self):
        return 'non-negative-integer'

class unsignedLongType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or \
            data < 0 or \
            data > 18446744073709551615L:
            raise ValueError, "invalid %s value" % self._type

        return data

class unsignedIntType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or \
            data < 0 or \
            data > 4294967295L:
            raise ValueError, "invalid %s value" % self._type

        return data

class unsignedShortType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or \
            data < 0 or \
            data > 65535:
            raise ValueError, "invalid %s value" % self._type

        return data

class unsignedByteType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or \
            data < 0 or \
            data > 255:
            raise ValueError, "invalid %s value" % self._type

        return data

class positiveIntegerType(anyType):
    _validURIs = (NS.XSD2, NS.XSD3, NS.ENC)

    def _checkValueSpace(self, data):
        if data == None:
            raise ValueError, "must supply initial %s value" % self._type

        if type(data) not in (IntType, LongType) or data <= 0:
            raise ValueError, "invalid %s value" % self._type

        return data

class positive_IntegerType(positiveIntegerType):
    _validURIs = (NS.XSD,)

    def _typeName(self):
        return 'positive-integer'

# Now compound types

class compoundType(anyType):
    def __init__(self, data = None, name = None, typed = 1, attrs = None):
        if self.__class__ == compoundType:
            raise Error, "a compound can't be instantiated directly"

        anyType.__init__(self, data, name, typed, attrs)
        self._keyord    = []

        if type(data) == DictType:
            self.__dict__.update(data)

    def _aslist(self, item=None):
        if item:
            return self.__dict__[self._keyord[item]]
        else:
            return map( lambda x: self.__dict__[x], self._keyord)

    def _asdict(self, item=None, encoding=Config.dict_encoding):
        if item:
            if type(item) in (UnicodeType,StringType):
                item = item.encode(encoding)
            return self.__dict__[item]
        else:
            retval = {}
            def fun(x): retval[x.encode(encoding)] = self.__dict__[x]
            
            map( fun, self._keyord) 
            return retval

 
    def __getitem__(self, item):
        if type(item) == IntType:
            return self.__dict__[self._keyord[item]]
        else:
            return getattr(self, item)

    def __len__(self):
        return len(self._keyord)

    def __nonzero__(self):
        return 1

    def _keys(self):
        return filter(lambda x: x[0] != '_', self.__dict__.keys())

    def _addItem(self, name, value, attrs = None):

        if name in self._keyord:
            if type(self.__dict__[name]) != ListType:
                self.__dict__[name] = [self.__dict__[name]]
            self.__dict__[name].append(value)
        else:
            self.__dict__[name] = value
            self._keyord.append(name)
            
    def _placeItem(self, name, value, pos, subpos = 0, attrs = None):

        if subpos == 0 and type(self.__dict__[name]) != ListType:
            self.__dict__[name] = value
        else:
            self.__dict__[name][subpos] = value

        self._keyord[pos] = name


    def _getItemAsList(self, name, default = []):
        try:
            d = self.__dict__[name]
        except:
            return default

        if type(d) == ListType:
            return d
        return [d]

    def __str__(self):
        return anyType.__str__(self) + ": " + str(self._asdict())

    def __repr__(self):
        return self.__str__()

class structType(compoundType):
    pass

class headerType(structType):
    _validURIs = (NS.ENV,)

    def __init__(self, data = None, typed = 1, attrs = None):
        structType.__init__(self, data, "Header", typed, attrs)

class bodyType(structType):
    _validURIs = (NS.ENV,)

    def __init__(self, data = None, typed = 1, attrs = None):
        structType.__init__(self, data, "Body", typed, attrs)

class arrayType(UserList.UserList, compoundType):
    def __init__(self, data = None, name = None, attrs = None,
        offset = 0, rank = None, asize = 0, elemsname = None):

        if data:
            if type(data) not in (ListType, TupleType):
                raise Error, "Data must be a sequence"

        UserList.UserList.__init__(self, data)
        compoundType.__init__(self, data, name, 0, attrs)

        self._elemsname = elemsname or "item"

        if data == None:
            self._rank = rank

            # According to 5.4.2.2 in the SOAP spec, each element in a
            # sparse array must have a position. _posstate keeps track of
            # whether we've seen a position or not. It's possible values
            # are:
            # -1 No elements have been added, so the state is indeterminate
            #  0 An element without a position has been added, so no
            #    elements can have positions
            #  1 An element with a position has been added, so all elements
            #    must have positions

            self._posstate = -1

            self._full = 0

            if asize in ('', None):
                asize = '0'

            self._dims = map (lambda x: int(x), str(asize).split(','))
            self._dims.reverse()   # It's easier to work with this way
            self._poss = [0] * len(self._dims)      # This will end up
                                                    # reversed too

            for i in range(len(self._dims)):
                if self._dims[i] < 0 or \
                    self._dims[i] == 0 and len(self._dims) > 1:
                    raise TypeError, "invalid Array dimensions"

                if offset > 0:
                    self._poss[i] = offset % self._dims[i]
                    offset = int(offset / self._dims[i])

                # Don't break out of the loop if offset is 0 so we test all the
                # dimensions for > 0.
            if offset:
                raise AttributeError, "invalid Array offset"

            a = [None] * self._dims[0]

            for i in range(1, len(self._dims)):
                b = []

                for j in range(self._dims[i]):
                    b.append(copy.deepcopy(a))

                a = b

            self.data = a


    def _aslist(self, item=None):
        if item:
            return self.data[int(item)]
        else:
            return self.data

    def _asdict(self, item=None, encoding=Config.dict_encoding):
        if item:
            if type(item) in (UnicodeType,StringType):
                item = item.encode(encoding)
            return self.data[int(item)]
        else:
            retval = {}
            def fun(x): retval[str(x).encode(encoding)] = self.data[x]
            
            map( fun, range(len(self.data)) )
            return retval
 
    def __getitem__(self, item):
        try:
            return self.data[int(item)]
        except ValueError:
            return getattr(self, item)

    def __len__(self):
        return len(self.data)

    def __nonzero__(self):
        return 1

    def __str__(self):
        return anyType.__str__(self) + ": " + str(self._aslist())

    def _keys(self):
        return filter(lambda x: x[0] != '_', self.__dict__.keys())

    def _addItem(self, name, value, attrs):
        if self._full:
            raise ValueError, "Array is full"

        pos = attrs.get((NS.ENC, 'position'))

        if pos != None:
            if self._posstate == 0:
                raise AttributeError, \
                    "all elements in a sparse Array must have a " \
                    "position attribute"

            self._posstate = 1

            try:
                if pos[0] == '[' and pos[-1] == ']':
                    pos = map (lambda x: int(x), pos[1:-1].split(','))
                    pos.reverse()

                    if len(pos) == 1:
                        pos = pos[0]

                        curpos = [0] * len(self._dims)

                        for i in range(len(self._dims)):
                            curpos[i] = pos % self._dims[i]
                            pos = int(pos / self._dims[i])

                            if pos == 0:
                                break

                        if pos:
                            raise Exception
                    elif len(pos) != len(self._dims):
                        raise Exception
                    else:
                        for i in range(len(self._dims)):
                            if pos[i] >= self._dims[i]:
                                raise Exception

                        curpos = pos
                else:
                    raise Exception
            except:
                raise AttributeError, \
                    "invalid Array element position %s" % str(pos)
        else:
            if self._posstate == 1:
                raise AttributeError, \
                    "only elements in a sparse Array may have a " \
                    "position attribute"

            self._posstate = 0

            curpos = self._poss

        a = self.data

        for i in range(len(self._dims) - 1, 0, -1):
            a = a[curpos[i]]

        if curpos[0] >= len(a):
            a += [None] * (len(a) - curpos[0] + 1)

        a[curpos[0]] = value

        if pos == None:
            self._poss[0] += 1

            for i in range(len(self._dims) - 1):
                if self._poss[i] < self._dims[i]:
                    break

                self._poss[i] = 0
                self._poss[i + 1] += 1

            if self._dims[-1] and self._poss[-1] >= self._dims[-1]:
                self._full = 1

    def _placeItem(self, name, value, pos, subpos, attrs = None):
        curpos = [0] * len(self._dims)

        for i in range(len(self._dims)):
            if self._dims[i] == 0:
                curpos[0] = pos
                break

            curpos[i] = pos % self._dims[i]
            pos = int(pos / self._dims[i])

            if pos == 0:
                break

        if self._dims[i] != 0 and pos:
            raise Error, "array index out of range"

        a = self.data

        for i in range(len(self._dims) - 1, 0, -1):
            a = a[curpos[i]]

        if curpos[0] >= len(a):
            a += [None] * (len(a) - curpos[0] + 1)

        a[curpos[0]] = value

class typedArrayType(arrayType):
    def __init__(self, data = None, name = None, typed = None, attrs = None,
        offset = 0, rank = None, asize = 0, elemsname = None):

        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):
        if getattr(self, 'detail', None) != None:
            return "<Fault %s: %s: %s>" % (self.faultcode,
                                           self.faultstring,
                                           self.detail)
        else:
            return "<Fault %s: %s>" % (self.faultcode, self.faultstring)

    __str__ = __repr__

    def __call__(self):
        return (self.faultcode, self.faultstring, self.detail)        

class SOAPException(Exception):
    def __init__(self, code="", string="", detail=None):
        self.args = ("SOAPpy SOAP Exception", code, string, detail)
        self.code = code
        self.string = string
        self.detail = detail
        
#######
# Convert complex SOAPpy objects to native python equivalents
#######

def isPrivate(name): return name[0]=='_'
def isPublic(name):  return name[0]!='_'

def simplify(object, level=0):
    """
    Convert the SOAPpy objects and thier contents to simple python types.

    This function recursively converts the passed 'container' object,
    and all public subobjects. (Private subobjects have names that
    start with '_'.)
    
    Conversions:
    - faultType    --> raise python exception
    - arrayType    --> array
    - compoundType --> dictionary
    """
    
    if level > 10:
        return object
    
    if isinstance( object, faultType ):
        se = SOAPException(object.faultcode, object.faultstring, object.detail)
        raise se
    elif isinstance( object, arrayType ):
        data = object._aslist()
        for k in range(len(data)):
            data[k] = simplify(data[k], level=level+1)
        return data
    elif isinstance( object, compoundType ) or isinstance(object, structType):
        data = object._asdict()
        for k in data.keys():
            if isPublic(k):
                data[k] = simplify(data[k], level=level+1)
        return data
    elif type(object)==dict:
        for k in object.keys():
            if isPublic(k):
                object[k] = simplify(object[k])
        return object
    elif type(object)==list:
        for k in range(len(object)):
            object[k] = simplify(object[k])
        return object
    else:
        return object


def simplify_contents(object, level=0):
    """
    Convert the contents of SOAPpy objects to simple python types.

    This function recursively converts the sub-objects contained in a
    'container' object to simple python types.
    
    Conversions:
    - faultType    --> raise python exception
    - arrayType    --> array
    - compoundType --> dictionary
    """
    
    if level>10: return object

    if isinstance( object, faultType ):
        for k in object._keys():
            if isPublic(k):
                setattr(object, k, simplify(object[k], level=level+1))
        raise object
    elif isinstance( object, arrayType ): 
        data = object._aslist()
        for k in range(len(data)):
            object[k] = simplify(data[k], level=level+1)
    elif isinstance(object, structType):
        data = object._asdict()
        for k in data.keys():
            if isPublic(k):
                setattr(object, k, simplify(data[k], level=level+1))
    elif isinstance( object, compoundType ) :
        data = object._asdict()
        for k in data.keys():
            if isPublic(k):
                object[k] = simplify(data[k], level=level+1)
    elif type(object)==dict:
        for k in object.keys():
            if isPublic(k):
                object[k] = simplify(object[k])
    elif type(object)==list:
        for k in range(len(object)):
            object[k] = simplify(object[k])
    
    return object


