diff --git a/__init__.py b/__init__.py
index 6a92b00ff601c1cd84eaed391d56972ba833522a..7ff5c6b2c1cc9aa210496f9e27bae4533809586f 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1 +1 @@
-all = [ "plugins" ]
+all = [ "plugins", 'suds' ]
diff --git a/biomodelsclient.py b/biomodelsclient.py
index 403da12681ff4d440c81af6474d521033aff19fa..6a24ba288fede7cfffa0a350f8756e4c50d9e154 100644
--- a/biomodelsclient.py
+++ b/biomodelsclient.py
@@ -45,7 +45,6 @@
 
 # Code:
 
-
 from suds.client import Client
 from suds.transport.http import HttpTransport as SudsHttpTransport
 import os
diff --git a/mgui.py b/mgui.py
index 9627137b617bca3198a90b7d659fad467b6380be..ede1ba954889a588d8ef37f41645daa6d0f0ec13 100644
--- a/mgui.py
+++ b/mgui.py
@@ -1101,27 +1101,25 @@ class MWindow(QtGui.QMainWindow):
                               self.tr('Load model from file'))
 
         if dialog.exec_():
-            fileNames = dialog.selectedFiles()
-            for fileName in fileNames:
-                modelName = dialog.getTargetPath()
-                if '/' in modelName:
-                    raise mexception.ElementNameError('Model name cannot contain `/`')
-                ret = loadFile(str(fileName),'%s' %(modelName),merge=False)
-                #ret = loadFile(str(fileName), '/model/%s' % (modelName), merge=False)
-		        #Harsha: This will clear out object editor's objectpath and make it invisible
-                self.objectEditSlot('/',False)
-
-                # Harsha: if subtype is None, in case of cspace then pluginLookup = /cspace/None
-                #     which will not call kkit plugin so cleaning to /cspace
-                pluginLookup = '%s/%s' % (ret['modeltype'], ret['subtype'])
-                try:
-                    pluginName = subtype_plugin_map['%s/%s' % (ret['modeltype'], ret['subtype'])]
-                except KeyError:
-                    pluginName = 'default'
-                print 'Loaded model', ret['model'].path
-                # if not moose.exists(ret['model'].path+'/info'):
-                #     moose.Annotator(ret['model'].path+'/info')
-
+            valid = False
+            ret = []
+            ret,pluginName = self.checkPlugin(dialog)
+            if pluginName == 'kkit':
+                compt = moose.wildcardFind(ret['model'].path+'/##[ISA=ChemCompt]')
+                if not len(compt):
+                    reply = QtGui.QMessageBox.question(self, "Model is empty","Model has no compartment, atleast one compartment should exist to display the widget\n Do you want another file",
+                                               QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
+                    if reply == QtGui.QMessageBox.Yes:
+                        dialog = LoaderDialog(self,self.tr('Load model from file'))
+                        if dialog.exec_():
+                            ret,pluginName = self.checkPlugin(dialog)
+                            ret,valid = self.dialog_check(ret)
+                    else:
+                        QtGui.QApplication.restoreOverrideCursor()        
+                        return
+                else:
+                    valid = True
+            if valid == True:
                 modelAnno = moose.Annotator(ret['model'].path+'/info')
                 if ret['subtype']:
                     modelAnno.modeltype = ret['subtype']
@@ -1133,6 +1131,46 @@ class MWindow(QtGui.QMainWindow):
                 if pluginName == 'kkit':
                     QtCore.QCoreApplication.sendEvent(self.plugin.getEditorView().getCentralWidget().view, QtGui.QKeyEvent(QtCore.QEvent.KeyPress, Qt.Qt.Key_A, Qt.Qt.NoModifier))
 
+    def checkPlugin(self,dialog):
+        fileNames = dialog.selectedFiles()
+        for fileName in fileNames:
+            modelName = dialog.getTargetPath()
+            if '/' in modelName:
+                raise mexception.ElementNameError('Model name cannot contain `/`')
+            ret = loadFile(str(fileName),'%s' %(modelName),merge=False)
+            #ret = loadFile(str(fileName), '/model/%s' % (modelName), merge=False)
+            #This will clear out object editor's objectpath and make it invisible
+            self.objectEditSlot('/',False)
+            #if subtype is None, in case of cspace then pluginLookup = /cspace/None
+            #     which will not call kkit plugin so cleaning to /cspace
+            pluginLookup = '%s/%s' % (ret['modeltype'], ret['subtype'])
+            try:
+                pluginName = subtype_plugin_map['%s/%s' % (ret['modeltype'], ret['subtype'])]
+            except KeyError:
+                pluginName = 'default'
+            print 'Loaded model', ret['model'].path
+            return ret,pluginName
+
+    def dialog_check(self,ret):
+        pluginLookup = '%s/%s' % (ret['modeltype'], ret['subtype'])
+        try:
+            pluginName = subtype_plugin_map['%s/%s' % (ret['modeltype'], ret['subtype'])]
+        except KeyError:
+            pluginName = 'default'
+        if pluginName == 'kkit':
+            compt = moose.wildcardFind(ret['model'].path+'/##[ISA=ChemCompt]')
+            if not len(compt):
+                reply = QtGui.QMessageBox.question(self, "Model is empty","Model has no compartment, atleast one compartment should exist to display the widget\n Do you want another file",
+                                           QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
+                if reply == QtGui.QMessageBox.Yes:
+                    dialog = LoaderDialog(self,self.tr('Load model from file'))
+                    if dialog.exec_():
+                        ret,pluginName = self.checkPlugin(dialog)
+                else:
+                    QtGui.QApplication.restoreOverrideCursor()        
+                    return
+            else:
+                return ret,True
     def newModelDialogSlot(self):
         #Harsha: Create a new dialog widget for model building
         self.popup.close()
diff --git a/mload.py b/mload.py
index fc6432501d023e00f74c2fc68b7b363afb54877f..d495f2c812c558a5988effc3321097a116760b55 100644
--- a/mload.py
+++ b/mload.py
@@ -56,13 +56,13 @@ from PyQt4 import QtGui, QtCore, Qt
 from plugins.setsolver import *
 
 def loadGenCsp(target,filename,solver="gsl"):
+    target = target.replace(" ", "")
     path = '/'+target
-    #Harsha: Moving the model under /modelname/model and graphs under /model/graphs.
+    #Moving the model under /modelname/model and graphs under /model/graphs.
     #This is passed while loading-time which will be easy for setting the stoich path
     mpath = '/'+target+'/'+"model"
     if moose.exists(mpath):
         moose.delete(mpath)
-    
     modelpath1 = moose.Neutral('%s' %(target))
     modelpath = moose.Neutral('%s/%s' %(modelpath1.path,"model"))
     model = moose.loadModel(filename, modelpath.path,solver)
diff --git a/plugins/kkitUtil.py b/plugins/kkitUtil.py
index 84f5a17c96a68906285d8108a2ad2faf6587b8f6..7904407fd2d55763db1a45e30193b4816bf93a1f 100644
--- a/plugins/kkitUtil.py
+++ b/plugins/kkitUtil.py
@@ -62,8 +62,11 @@ def colorCheck(fc_bgcolor,fcbg):
         elif fc_bgcolor.isdigit():
             """ color is int  a map from int to r,g,b triplets from pickled color map file """
             tc = int(fc_bgcolor)
-            tc = 2*tc
-            pickledColor = colorMap[tc]
+            tc = tc*2
+            if tc < len(colorMap):
+                pickledColor = colorMap[tc]
+            else:
+                pickledColor = (255, 0, 0)
             fc_bgcolor = QColor(*pickledColor)
 
         elif fc_bgcolor.isalpha() or fc_bgcolor.isalnum():
diff --git a/suds/__init__.py b/suds/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f8a6d908effffb3405d7652ab096f4f778c0615
--- /dev/null
+++ b/suds/__init__.py
@@ -0,0 +1,192 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Suds is a lightweight SOAP Python client providing a Web Service proxy.
+"""
+
+import sys
+
+
+#
+# Project properties
+#
+
+from version import __build__, __version__
+
+
+#
+# Exceptions
+#
+
+class MethodNotFound(Exception):
+    def __init__(self, name):
+        Exception.__init__(self, u"Method not found: '%s'" % name)
+
+class PortNotFound(Exception):
+    def __init__(self, name):
+        Exception.__init__(self, u"Port not found: '%s'" % name)
+
+class ServiceNotFound(Exception):
+    def __init__(self, name):
+        Exception.__init__(self, u"Service not found: '%s'" % name)
+
+class TypeNotFound(Exception):
+    def __init__(self, name):
+        Exception.__init__(self, u"Type not found: '%s'" % tostr(name))
+
+class BuildError(Exception):
+    msg = """
+        An error occurred while building an instance of (%s). As a result the
+        object you requested could not be constructed. It is recommended that
+        you construct the type manually using a Suds object. Please open a
+        ticket with a description of this error.
+        Reason: %s
+        """
+    def __init__(self, name, exception):
+        Exception.__init__(self, BuildError.msg % (name, exception))
+
+class SoapHeadersNotPermitted(Exception):
+    msg = """
+        Method (%s) was invoked with SOAP headers. The WSDL does not define
+        SOAP headers for this method. Retry without the soapheaders keyword
+        argument.
+        """
+    def __init__(self, name):
+        Exception.__init__(self, self.msg % name)
+
+class WebFault(Exception):
+    def __init__(self, fault, document):
+        if hasattr(fault, 'faultstring'):
+            Exception.__init__(self, u"Server raised fault: '%s'" %
+                fault.faultstring)
+        self.fault = fault
+        self.document = document
+
+
+#
+# Logging
+#
+
+class Repr:
+    def __init__(self, x):
+        self.x = x
+    def __str__(self):
+        return repr(self.x)
+
+
+#
+# Utility
+#
+
+class null:
+    """
+    The I{null} object.
+    Used to pass NULL for optional XML nodes.
+    """
+    pass
+
+def objid(obj):
+    return obj.__class__.__name__ + ':' + hex(id(obj))
+
+def tostr(object, encoding=None):
+    """ get a unicode safe string representation of an object """
+    if isinstance(object, basestring):
+        if encoding is None:
+            return object
+        else:
+            return object.encode(encoding)
+    if isinstance(object, tuple):
+        s = ['(']
+        for item in object:
+            if isinstance(item, basestring):
+                s.append(item)
+            else:
+                s.append(tostr(item))
+            s.append(', ')
+        s.append(')')
+        return ''.join(s)
+    if isinstance(object, list):
+        s = ['[']
+        for item in object:
+            if isinstance(item, basestring):
+                s.append(item)
+            else:
+                s.append(tostr(item))
+            s.append(', ')
+        s.append(']')
+        return ''.join(s)
+    if isinstance(object, dict):
+        s = ['{']
+        for item in object.items():
+            if isinstance(item[0], basestring):
+                s.append(item[0])
+            else:
+                s.append(tostr(item[0]))
+            s.append(' = ')
+            if isinstance(item[1], basestring):
+                s.append(item[1])
+            else:
+                s.append(tostr(item[1]))
+            s.append(', ')
+        s.append('}')
+        return ''.join(s)
+    try:
+        return unicode(object)
+    except:
+        return str(object)
+
+
+#
+# Python 3 compatibility
+#
+
+if sys.version_info < (3, 0):
+    from cStringIO import StringIO as BytesIO
+else:
+    from io import BytesIO
+
+# Idea from 'http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python'.
+class UnicodeMixin(object):
+    if sys.version_info >= (3, 0):
+        # For Python 3, __str__() and __unicode__() should be identical.
+        __str__ = lambda x: x.__unicode__()
+    else:
+        __str__ = lambda x: unicode(x).encode('utf-8')
+
+#   Used instead of byte literals because they are not supported on Python
+# versions prior to 2.6.
+def byte_str(s='', encoding='utf-8', input_encoding='utf-8', errors='strict'):
+    """
+    Returns a bytestring version of 's', encoded as specified in 'encoding'.
+
+    Accepts str & unicode objects, interpreting non-unicode strings as byte
+    strings encoded using the given input encoding.
+
+    """
+    assert isinstance(s, basestring)
+    if isinstance(s, unicode):
+        return s.encode(encoding, errors)
+    if s and encoding != input_encoding:
+        return s.decode(input_encoding, errors).encode(encoding, errors)
+    return s
+
+# Class used to represent a byte string. Useful for asserting that correct
+# string types are being passed around where needed.
+if sys.version_info >= (3, 0):
+    byte_str_class = bytes
+else:
+    byte_str_class = str
diff --git a/suds/argparser.py b/suds/argparser.py
new file mode 100644
index 0000000000000000000000000000000000000000..84ac0262aae90840ff6826b41402a38ae3bf3ea0
--- /dev/null
+++ b/suds/argparser.py
@@ -0,0 +1,419 @@
+# -*- coding: utf-8 -*-
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jurko Gospodnetić jurko.gospodnetic@pke.hr )
+
+"""
+Suds web service operation invocation function argument parser.
+
+See the parse_args() function description for more detailed information.
+
+"""
+
+__all__ = ["parse_args"]
+
+
+def parse_args(method_name, param_defs, args, kwargs, external_param_processor,
+    extra_parameter_errors):
+    """
+    Parse arguments for suds web service operation invocation functions.
+
+    Suds prepares Python function objects for invoking web service operations.
+    This function implements generic binding agnostic part of processing the
+    arguments passed when calling those function objects.
+
+    Argument parsing rules:
+      * Each input parameter element should be represented by single regular
+        Python function argument.
+      * At most one input parameter belonging to a single choice parameter
+        structure may have its value specified as something other than None.
+      * Positional arguments are mapped to choice group input parameters the
+        same as is done for a simple all/sequence group - each in turn.
+
+    Expects to be passed the web service operation's parameter definitions
+    (parameter name, type & optional ancestry information) in order and, based
+    on that, extracts the values for those parameter from the arguments
+    provided in the web service operation invocation call.
+
+    Ancestry information describes parameters constructed based on suds
+    library's automatic input parameter structure unwrapping. It is expected to
+    include the parameter's XSD schema 'ancestry' context, i.e. a list of all
+    the parent XSD schema tags containing the parameter's <element> tag. Such
+    ancestry context provides detailed information about how the parameter's
+    value is expected to be used, especially in relation to other input
+    parameters, e.g. at most one parameter value may be specified for
+    parameters directly belonging to the same choice input group.
+
+    Rules on acceptable ancestry items:
+      * Ancestry item's choice() method must return whether the item
+        represents a <choice> XSD schema tag.
+      * Passed ancestry items are used 'by address' internally and the same XSD
+        schema tag is expected to be identified by the exact same ancestry item
+        object during the whole argument processing.
+
+    During processing, each parameter's definition and value, together with any
+    additional pertinent information collected from the encountered parameter
+    definition structure, is passed on to the provided external parameter
+    processor function. There that information is expected to be used to
+    construct the actual binding specific web service operation invocation
+    request.
+
+    Raises a TypeError exception in case any argument related errors are
+    detected. The exceptions raised have been constructed to make them as
+    similar as possible to their respective exceptions raised during regular
+    Python function argument checking.
+
+    Does not support multiple same-named input parameters.
+
+    """
+    arg_parser = _ArgParser(method_name, param_defs, external_param_processor)
+    return arg_parser(args, kwargs, extra_parameter_errors)
+
+
+class _ArgParser:
+    """Internal argument parser implementation function object."""
+
+    def __init__(self, method_name, param_defs, external_param_processor):
+        self.__method_name = method_name
+        self.__param_defs = param_defs
+        self.__external_param_processor = external_param_processor
+        self.__stack = []
+
+    def __call__(self, args, kwargs, extra_parameter_errors):
+        """
+        Runs the main argument parsing operation.
+
+        Passed args & kwargs objects are not modified during parsing.
+
+        Returns an informative 2-tuple containing the number of required &
+        allowed arguments.
+
+        """
+        assert not self.active(), "recursive argument parsing not allowed"
+        self.__init_run(args, kwargs, extra_parameter_errors)
+        try:
+            self.__process_parameters()
+            return self.__all_parameters_processed()
+        finally:
+            self.__cleanup_run()
+            assert not self.active()
+
+    def active(self):
+        """
+        Return whether this object is currently running argument processing.
+
+        Used to avoid recursively entering argument processing from within an
+        external parameter processor.
+
+        """
+        return bool(self.__stack)
+
+    def __all_parameters_processed(self):
+        """
+        Finish the argument processing.
+
+        Should be called after all the web service operation's parameters have
+        been successfully processed and, afterwards, no further parameter
+        processing is allowed.
+
+        Returns a 2-tuple containing the number of required & allowed
+        arguments.
+
+        See the _ArgParser class description for more detailed information.
+
+        """
+        assert self.active()
+        sentinel_frame = self.__stack[0]
+        self.__pop_frames_above(sentinel_frame)
+        assert len(self.__stack) == 1
+        self.__pop_top_frame()
+        assert not self.active()
+        args_required = sentinel_frame.args_required()
+        args_allowed = sentinel_frame.args_allowed()
+        self.__check_for_extra_arguments(args_required, args_allowed)
+        return args_required, args_allowed
+
+    def __check_for_extra_arguments(self, args_required, args_allowed):
+        """
+        Report an error in case any extra arguments are detected.
+
+        Does nothing if reporting extra arguments as exceptions has not been
+        enabled.
+
+        May only be called after the argument processing has been completed.
+
+        """
+        assert not self.active()
+        if not self.__extra_parameter_errors:
+            return
+
+        if self.__kwargs:
+            param_name = self.__kwargs.keys()[0]
+            if param_name in self.__params_with_arguments:
+                msg = "got multiple values for parameter '%s'"
+            else:
+                msg = "got an unexpected keyword argument '%s'"
+            self.__error(msg % (param_name,))
+
+        if self.__args:
+            def plural_suffix(count):
+                if count == 1:
+                    return ""
+                return "s"
+            def plural_was_were(count):
+                if count == 1:
+                    return "was"
+                return "were"
+            expected = args_required
+            if args_required != args_allowed:
+                expected = "%d to %d" % (args_required, args_allowed)
+            given = self.__args_count
+            msg_parts = ["takes %s positional argument" % (expected,),
+                plural_suffix(expected), " but %d " % (given,),
+                plural_was_were(given), " given"]
+            self.__error("".join(msg_parts))
+
+    def __cleanup_run(self):
+        """Cleans up after a completed argument parsing run."""
+        self.__stack = []
+        assert not self.active()
+
+    def __error(self, message):
+        """Report an argument processing error."""
+        raise TypeError("%s() %s" % (self.__method_name, message))
+
+    def __frame_factory(self, ancestry_item):
+        """Construct a new frame representing the given ancestry item."""
+        frame_class = Frame
+        if ancestry_item is not None and ancestry_item.choice():
+            frame_class = ChoiceFrame
+        return frame_class(ancestry_item, self.__error,
+            self.__extra_parameter_errors)
+
+    def __get_param_value(self, name):
+        """
+        Extract a parameter value from the remaining given arguments.
+
+        Returns a 2-tuple consisting of the following:
+          * Boolean indicating whether an argument has been specified for the
+            requested input parameter.
+          * Parameter value.
+
+        """
+        if self.__args:
+            return True, self.__args.pop(0)
+        try:
+            value = self.__kwargs.pop(name)
+        except KeyError:
+            return False, None
+        return True, value
+
+    def __in_choice_context(self):
+        """
+        Whether we are currently processing a choice parameter group.
+
+        This includes processing a parameter defined directly or indirectly
+        within such a group.
+
+        May only be called during parameter processing or the result will be
+        calculated based on the context left behind by the previous parameter
+        processing if any.
+
+        """
+        for x in self.__stack:
+            if x.__class__ is ChoiceFrame:
+                return True
+        return False
+
+    def __init_run(self, args, kwargs, extra_parameter_errors):
+        """Initializes data for a new argument parsing run."""
+        assert not self.active()
+        self.__args = list(args)
+        self.__kwargs = dict(kwargs)
+        self.__extra_parameter_errors = extra_parameter_errors
+        self.__args_count = len(args) + len(kwargs)
+        self.__params_with_arguments = set()
+        self.__stack = []
+        self.__push_frame(None)
+
+    def __match_ancestry(self, ancestry):
+        """
+        Find frames matching the given ancestry.
+
+        Returns a tuple containing the following:
+          * Topmost frame matching the given ancestry or the bottom-most sentry
+            frame if no frame matches.
+          * Unmatched ancestry part.
+
+        """
+        stack = self.__stack
+        if len(stack) == 1:
+            return stack[0], ancestry
+        previous = stack[0]
+        for frame, n in zip(stack[1:], xrange(len(ancestry))):
+            if frame.id() is not ancestry[n]:
+                return previous, ancestry[n:]
+            previous = frame
+        return frame, ancestry[n + 1:]
+
+    def __pop_frames_above(self, frame):
+        """Pops all the frames above, but not including the given frame."""
+        while self.__stack[-1] is not frame:
+            self.__pop_top_frame()
+        assert self.__stack
+
+    def __pop_top_frame(self):
+        """Pops the top frame off the frame stack."""
+        popped = self.__stack.pop()
+        if self.__stack:
+            self.__stack[-1].process_subframe(popped)
+
+    def __process_parameter(self, param_name, param_type, ancestry=None):
+        """Collect values for a given web service operation input parameter."""
+        assert self.active()
+        param_optional = param_type.optional()
+        has_argument, value = self.__get_param_value(param_name)
+        if has_argument:
+            self.__params_with_arguments.add(param_name)
+        self.__update_context(ancestry)
+        self.__stack[-1].process_parameter(param_optional, value is not None)
+        self.__external_param_processor(param_name, param_type,
+            self.__in_choice_context(), value)
+
+    def __process_parameters(self):
+        """Collect values for given web service operation input parameters."""
+        for pdef in self.__param_defs:
+            self.__process_parameter(*pdef)
+
+    def __push_frame(self, ancestry_item):
+        """Push a new frame on top of the frame stack."""
+        frame = self.__frame_factory(ancestry_item)
+        self.__stack.append(frame)
+
+    def __push_frames(self, ancestry):
+        """
+        Push new frames representing given ancestry items.
+
+        May only be given ancestry items other than None. Ancestry item None
+        represents the internal sentinel item and should never appear in a
+        given parameter's ancestry information.
+
+        """
+        for x in ancestry:
+            assert x is not None
+            self.__push_frame(x)
+
+    def __update_context(self, ancestry):
+        if not ancestry:
+            return
+        match_result = self.__match_ancestry(ancestry)
+        last_matching_frame, unmatched_ancestry = match_result
+        self.__pop_frames_above(last_matching_frame)
+        self.__push_frames(unmatched_ancestry)
+
+
+class Frame:
+    """
+    Base _ArgParser context frame.
+
+    When used directly, as opposed to using a derived class, may represent any
+    input parameter context/ancestry item except a choice order indicator.
+
+    """
+
+    def __init__(self, id, error, extra_parameter_errors):
+        """
+        Construct a new Frame instance.
+
+        Passed error function is used to report any argument checking errors.
+
+        """
+        assert self.__class__ != Frame or not id or not id.choice()
+        self.__id = id
+        self._error = error
+        self._extra_parameter_errors = extra_parameter_errors
+        self._args_allowed = 0
+        self._args_required = 0
+        self._has_value = False
+
+    def args_allowed(self):
+        return self._args_allowed
+
+    def args_required(self):
+        return self._args_required
+
+    def has_value(self):
+        return self._has_value
+
+    def id(self):
+        return self.__id
+
+    def process_parameter(self, optional, has_value):
+        args_required = 1
+        if optional:
+            args_required = 0
+        self._process_item(has_value, 1, args_required)
+
+    def process_subframe(self, subframe):
+        self._process_item(
+            subframe.has_value(),
+            subframe.args_allowed(),
+            subframe.args_required())
+
+    def _process_item(self, has_value, args_allowed, args_required):
+        self._args_allowed += args_allowed
+        self._args_required += args_required
+        if has_value:
+            self._has_value = True
+
+
+class ChoiceFrame(Frame):
+    """
+    _ArgParser context frame representing a choice order indicator.
+
+    A choice requires as many input arguments as are needed to satisfy the
+    least requiring of its items. For example, if we use I(n) to identify an
+    item requiring n parameter, then a choice containing I(2), I(3) & I(7)
+    requires 2 arguments while a choice containing I(5) & I(4) requires 4.
+
+    Accepts an argument for each of its contained elements but allows at most
+    one of its directly contained items to have a defined value.
+
+    """
+
+    def __init__(self, id, error, extra_parameter_errors):
+        assert id.choice()
+        Frame.__init__(self, id, error, extra_parameter_errors)
+        self.__has_item = False
+
+    def _process_item(self, has_value, args_allowed, args_required):
+        self._args_allowed += args_allowed
+        self.__update_args_required_for_item(args_required)
+        self.__update_has_value_for_item(has_value)
+
+    def __update_args_required_for_item(self, item_args_required):
+        if not self.__has_item:
+            self.__has_item = True
+            self._args_required = item_args_required
+            return
+        self._args_required = min(self.args_required(), item_args_required)
+
+    def __update_has_value_for_item(self, item_has_value):
+        if item_has_value:
+            if self.has_value() and self._extra_parameter_errors:
+                self._error("got multiple values for a single choice "
+                    "parameter")
+            self._has_value = True
diff --git a/suds/bindings/__init__.py b/suds/bindings/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1704fa9d72390a0303d0a527466731f8547eb396
--- /dev/null
+++ b/suds/bindings/__init__.py
@@ -0,0 +1,18 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides modules containing classes to support Web Services (SOAP) bindings.
+"""
diff --git a/suds/bindings/binding.py b/suds/bindings/binding.py
new file mode 100644
index 0000000000000000000000000000000000000000..62f641c2bca99fb8cb3986a409e3e6b25d89a8a8
--- /dev/null
+++ b/suds/bindings/binding.py
@@ -0,0 +1,474 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides classes for (WS) SOAP bindings.
+"""
+
+from suds import *
+from suds.sax import Namespace
+from suds.sax.document import Document
+from suds.sax.element import Element
+from suds.sudsobject import Factory
+from suds.mx import Content
+from suds.mx.literal import Literal as MxLiteral
+from suds.umx.typed import Typed as UmxTyped
+from suds.bindings.multiref import MultiRef
+from suds.xsd.query import TypeQuery, ElementQuery
+from suds.xsd.sxbasic import Element as SchemaElement
+from suds.options import Options
+from suds.plugin import PluginContainer
+
+from copy import deepcopy
+
+
+envns = ('SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/')
+
+
+class Binding:
+    """
+    The SOAP binding class used to process outgoing and incoming SOAP messages
+    per the WSDL port binding.
+    @ivar wsdl: The WSDL.
+    @type wsdl: L{suds.wsdl.Definitions}
+    @ivar schema: The collective schema contained within the WSDL.
+    @type schema: L{xsd.schema.Schema}
+    @ivar options: A dictionary options.
+    @type options: L{Options}
+    """
+
+    def __init__(self, wsdl):
+        """
+        @param wsdl: A WSDL.
+        @type wsdl: L{wsdl.Definitions}
+        """
+        self.wsdl = wsdl
+        self.multiref = MultiRef()
+
+    def schema(self):
+        return self.wsdl.schema
+
+    def options(self):
+        return self.wsdl.options
+
+    def unmarshaller(self):
+        """
+        Get the appropriate schema based XML decoder.
+        @return: Typed unmarshaller.
+        @rtype: L{UmxTyped}
+        """
+        return UmxTyped(self.schema())
+
+    def marshaller(self):
+        """
+        Get the appropriate XML encoder.
+        @return: An L{MxLiteral} marshaller.
+        @rtype: L{MxLiteral}
+        """
+        return MxLiteral(self.schema(), self.options().xstq)
+
+    def param_defs(self, method):
+        """
+        Get parameter definitions.
+        Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject})
+        @param method: A service method.
+        @type method: I{service.Method}
+        @return: A collection of parameter definitions
+        @rtype: [I{pdef},..]
+        """
+        raise Exception, 'not implemented'
+
+    def get_message(self, method, args, kwargs):
+        """
+        Get a SOAP message for the specified method, args and SOAP headers.
+        This is the entry point for creating an outbound SOAP message.
+        @param method: The method being invoked.
+        @type method: I{service.Method}
+        @param args: A list of args for the method invoked.
+        @type args: list
+        @param kwargs: Named (keyword) args for the method invoked.
+        @type kwargs: dict
+        @return: The SOAP envelope.
+        @rtype: L{Document}
+        """
+
+        content = self.headercontent(method)
+        header = self.header(content)
+        content = self.bodycontent(method, args, kwargs)
+        body = self.body(content)
+        env = self.envelope(header, body)
+        if self.options().prefixes:
+            body.normalizePrefixes()
+            env.promotePrefixes()
+        else:
+            env.refitPrefixes()
+        return Document(env)
+
+    def get_reply(self, method, replyroot):
+        """
+        Process the I{reply} for the specified I{method} by unmarshalling it
+        into into Python object(s).
+        @param method: The name of the invoked method.
+        @type method: str
+        @param replyroot: The reply XML root node received after invoking the
+            specified method.
+        @type reply: L{Element}
+        @return: The unmarshalled reply.  The returned value is an L{Object} or
+            a I{list} depending on whether the service returns a single object
+            or a collection.
+        @rtype: L{Object} or I{list}
+        """
+        soapenv = replyroot.getChild('Envelope', envns)
+        soapenv.promotePrefixes()
+        soapbody = soapenv.getChild('Body', envns)
+        soapbody = self.multiref.process(soapbody)
+        nodes = self.replycontent(method, soapbody)
+        rtypes = self.returned_types(method)
+        if len(rtypes) > 1:
+            return self.replycomposite(rtypes, nodes)
+        if len(rtypes) == 0:
+            return
+        if rtypes[0].multi_occurrence():
+            return self.replylist(rtypes[0], nodes)
+        if len(nodes):
+            resolved = rtypes[0].resolve(nobuiltin=True)
+            return self.unmarshaller().process(nodes[0], resolved)
+
+    def replylist(self, rt, nodes):
+        """
+        Construct a I{list} reply. This mehod is called when it has been
+        detected that the reply is a list.
+        @param rt: The return I{type}.
+        @type rt: L{suds.xsd.sxbase.SchemaObject}
+        @param nodes: A collection of XML nodes.
+        @type nodes: [L{Element},...]
+        @return: A list of I{unmarshalled} objects.
+        @rtype: [L{Object},...]
+        """
+        result = []
+        resolved = rt.resolve(nobuiltin=True)
+        unmarshaller = self.unmarshaller()
+        for node in nodes:
+            sobject = unmarshaller.process(node, resolved)
+            result.append(sobject)
+        return result
+
+    def replycomposite(self, rtypes, nodes):
+        """
+        Construct a I{composite} reply. This method is called when it has been
+        detected that the reply has multiple root nodes.
+        @param rtypes: A list of known return I{types}.
+        @type rtypes: [L{suds.xsd.sxbase.SchemaObject},...]
+        @param nodes: A collection of XML nodes.
+        @type nodes: [L{Element},...]
+        @return: The I{unmarshalled} composite object.
+        @rtype: L{Object},...
+        """
+        dictionary = {}
+        for rt in rtypes:
+            dictionary[rt.name] = rt
+        unmarshaller = self.unmarshaller()
+        composite = Factory.object('reply')
+        for node in nodes:
+            tag = node.name
+            rt = dictionary.get(tag, None)
+            if rt is None:
+                if node.get('id') is None:
+                    raise Exception('<%s/> not mapped to message part' % tag)
+                else:
+                    continue
+            resolved = rt.resolve(nobuiltin=True)
+            sobject = unmarshaller.process(node, resolved)
+            value = getattr(composite, tag, None)
+            if value is None:
+                if rt.multi_occurrence():
+                    value = []
+                    setattr(composite, tag, value)
+                    value.append(sobject)
+                else:
+                    setattr(composite, tag, sobject)
+            else:
+                if not isinstance(value, list):
+                    value = [value,]
+                    setattr(composite, tag, value)
+                value.append(sobject)
+        return composite
+
+    def mkparam(self, method, pdef, object):
+        """
+        Builds a parameter for the specified I{method} using the parameter
+        definition (pdef) and the specified value (object).
+        @param method: A method name.
+        @type method: str
+        @param pdef: A parameter definition.
+        @type pdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject})
+        @param object: The parameter value.
+        @type object: any
+        @return: The parameter fragment.
+        @rtype: L{Element}
+        """
+        marshaller = self.marshaller()
+        content = Content(tag=pdef[0], value=object, type=pdef[1],
+            real=pdef[1].resolve())
+        return marshaller.process(content)
+
+    def mkheader(self, method, hdef, object):
+        """
+        Builds a soapheader for the specified I{method} using the header
+        definition (hdef) and the specified value (object).
+        @param method: A method name.
+        @type method: str
+        @param hdef: A header definition.
+        @type hdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject})
+        @param object: The header value.
+        @type object: any
+        @return: The parameter fragment.
+        @rtype: L{Element}
+        """
+        marshaller = self.marshaller()
+        if isinstance(object, (list, tuple)):
+            tags = []
+            for item in object:
+                tags.append(self.mkheader(method, hdef, item))
+            return tags
+        content = Content(tag=hdef[0], value=object, type=hdef[1])
+        return marshaller.process(content)
+
+    def envelope(self, header, body):
+        """
+        Build the B{<Envelope/>} for a SOAP outbound message.
+        @param header: The SOAP message B{header}.
+        @type header: L{Element}
+        @param body: The SOAP message B{body}.
+        @type body: L{Element}
+        @return: The SOAP envelope containing the body and header.
+        @rtype: L{Element}
+        """
+        env = Element('Envelope', ns=envns)
+        env.addPrefix(Namespace.xsins[0], Namespace.xsins[1])
+        env.append(header)
+        env.append(body)
+        return env
+
+    def header(self, content):
+        """
+        Build the B{<Body/>} for a SOAP outbound message.
+        @param content: The header content.
+        @type content: L{Element}
+        @return: the SOAP body fragment.
+        @rtype: L{Element}
+        """
+        header = Element('Header', ns=envns)
+        header.append(content)
+        return header
+
+    def bodycontent(self, method, args, kwargs):
+        """
+        Get the content for the SOAP I{body} node.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @param args: method parameter values
+        @type args: list
+        @param kwargs: Named (keyword) args for the method invoked.
+        @type kwargs: dict
+        @return: The XML content for the <body/>
+        @rtype: [L{Element},..]
+        """
+        raise Exception, 'not implemented'
+
+    def headercontent(self, method):
+        """
+        Get the content for the SOAP I{Header} node.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @return: The XML content for the <body/>
+        @rtype: [L{Element},..]
+        """
+        n = 0
+        content = []
+        wsse = self.options().wsse
+        if wsse is not None:
+            content.append(wsse.xml())
+        headers = self.options().soapheaders
+        if not isinstance(headers, (tuple,list,dict)):
+            headers = (headers,)
+        if len(headers) == 0:
+            return content
+        pts = self.headpart_types(method)
+        if isinstance(headers, (tuple,list)):
+            for header in headers:
+                if isinstance(header, Element):
+                    content.append(deepcopy(header))
+                    continue
+                if len(pts) == n: break
+                h = self.mkheader(method, pts[n], header)
+                ns = pts[n][1].namespace('ns0')
+                h.setPrefix(ns[0], ns[1])
+                content.append(h)
+                n += 1
+        else:
+            for pt in pts:
+                header = headers.get(pt[0])
+                if header is None:
+                    continue
+                h = self.mkheader(method, pt, header)
+                ns = pt[1].namespace('ns0')
+                h.setPrefix(ns[0], ns[1])
+                content.append(h)
+        return content
+
+    def replycontent(self, method, body):
+        """
+        Get the reply body content.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @param body: The SOAP body.
+        @type body: L{Element}
+        @return: The body content.
+        @rtype: [L{Element},...]
+        """
+        raise Exception, 'not implemented'
+
+    def body(self, content):
+        """
+        Build the B{<Body/>} for a SOAP outbound message.
+        @param content: The body content.
+        @type content: L{Element}
+        @return: The SOAP body fragment.
+        @rtype: L{Element}
+        """
+        body = Element('Body', ns=envns)
+        body.append(content)
+        return body
+
+    def bodypart_types(self, method, input=True):
+        """
+        Get a list of I{parameter definitions} (pdef) defined for the specified
+        method. Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject}).
+        @param method: A service method.
+        @type method: I{service.Method}
+        @param input: Defines input/output message.
+        @type input: boolean
+        @return:  A list of parameter definitions
+        @rtype: [I{pdef},]
+        """
+        result = []
+        if input:
+            parts = method.soap.input.body.parts
+        else:
+            parts = method.soap.output.body.parts
+        for p in parts:
+            if p.element is not None:
+                query = ElementQuery(p.element)
+            else:
+                query = TypeQuery(p.type)
+            pt = query.execute(self.schema())
+            if pt is None:
+                raise TypeNotFound(query.ref)
+            if p.type is not None:
+                pt = PartElement(p.name, pt)
+            if input:
+                if pt.name is None:
+                    result.append((p.name, pt))
+                else:
+                    result.append((pt.name, pt))
+            else:
+                result.append(pt)
+        return result
+
+    def headpart_types(self, method, input=True):
+        """
+        Get a list of I{parameter definitions} (pdef) defined for the specified
+        method. Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject}).
+        @param method: A service method.
+        @type method: I{service.Method}
+        @param input: Defines input/output message.
+        @type input: boolean
+        @return:  A list of parameter definitions
+        @rtype: [I{pdef},]
+        """
+        result = []
+        if input:
+            headers = method.soap.input.headers
+        else:
+            headers = method.soap.output.headers
+        for header in headers:
+            part = header.part
+            if part.element is not None:
+                query = ElementQuery(part.element)
+            else:
+                query = TypeQuery(part.type)
+            pt = query.execute(self.schema())
+            if pt is None:
+                raise TypeNotFound(query.ref)
+            if part.type is not None:
+                pt = PartElement(part.name, pt)
+            if input:
+                if pt.name is None:
+                    result.append((part.name, pt))
+                else:
+                    result.append((pt.name, pt))
+            else:
+                result.append(pt)
+        return result
+
+    def returned_types(self, method):
+        """
+        Get the L{xsd.sxbase.SchemaObject} returned by the I{method}.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @return: The name of the type return by the method.
+        @rtype: [I{rtype},..]
+        """
+        result = []
+        for rt in self.bodypart_types(method, input=False):
+            result.append(rt)
+        return result
+
+
+class PartElement(SchemaElement):
+    """
+    A part used to represent a message part when the part
+    references a schema type and thus assumes to be an element.
+    @ivar resolved: The part type.
+    @type resolved: L{suds.xsd.sxbase.SchemaObject}
+    """
+
+    def __init__(self, name, resolved):
+        """
+        @param name: The part name.
+        @type name: str
+        @param resolved: The part type.
+        @type resolved: L{suds.xsd.sxbase.SchemaObject}
+        """
+        root = Element('element', ns=Namespace.xsdns)
+        SchemaElement.__init__(self, resolved.schema, root)
+        self.__resolved = resolved
+        self.name = name
+        self.form_qualified = False
+
+    def implany(self):
+        return self
+
+    def optional(self):
+        return True
+
+    def namespace(self, prefix=None):
+        return Namespace.default
+
+    def resolve(self, nobuiltin=False):
+        if nobuiltin and self.__resolved.builtin():
+            return self
+        return self.__resolved
diff --git a/suds/bindings/document.py b/suds/bindings/document.py
new file mode 100644
index 0000000000000000000000000000000000000000..84ef566edeb2edf4a5c356d536209000b0b93424
--- /dev/null
+++ b/suds/bindings/document.py
@@ -0,0 +1,157 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides classes for the (WS) SOAP I{document/literal} binding.
+
+"""
+
+from suds import *
+from suds.argparser import parse_args
+from suds.bindings.binding import Binding
+from suds.sax.element import Element
+
+
+class Document(Binding):
+    """
+    The document/literal style. Literal is the only (@use) supported since
+    document/encoded is pretty much dead.
+
+    Although the SOAP specification supports multiple documents within the SOAP
+    <body/>, it is very uncommon. As such, suds library supports presenting an
+    I{RPC} view of service methods defined with only a single document
+    parameter. To support the complete specification, service methods defined
+    with multiple documents (multiple message parts), are still presented using
+    a full I{document} view.
+
+    More detailed description:
+
+    An interface is considered I{wrapped} if:
+      - There is exactly one message part in that interface.
+      - The message part resolves to an element of a non-builtin type.
+    Otherwise it is considered I{bare}.
+
+    I{Bare} interface is interpreted directly as specified in the WSDL schema,
+    with each message part represented by a single parameter in the suds
+    library web service operation proxy interface (input or output).
+
+    I{Wrapped} interface is interpreted without the external wrapping document
+    structure, with each of its contained elements passed through suds
+    library's web service operation proxy interface (input or output)
+    individually instead of as a single I{document} object.
+
+    """
+    def bodycontent(self, method, args, kwargs):
+        if not len(method.soap.input.body.parts):
+            return ()
+        wrapped = method.soap.input.body.wrapped
+        if wrapped:
+            pts = self.bodypart_types(method)
+            root = self.document(pts[0])
+        else:
+            root = []
+
+        def add_param(param_name, param_type, in_choice_context, value):
+            """
+            Construct request data for the given input parameter.
+
+            Called by our argument parser for every input parameter, in order.
+
+            """
+            # Do not construct request data for undefined input parameters
+            # defined inside a choice order indicator. An empty choice
+            # parameter can still be included in the constructed request by
+            # explicitly providing an empty string value for it.
+            #TODO: This functionality might be better placed inside the
+            # mkparam() function but to do that we would first need to better
+            # understand how different Binding subclasses in suds work and how
+            # they would be affected by this change.
+            if in_choice_context and value is None:
+                return
+
+            # Construct request data for the current input parameter.
+            pdef = (param_name, param_type)
+            p = self.mkparam(method, pdef, value)
+            if p is None:
+                return
+            if not wrapped:
+                ns = param_type.namespace("ns0")
+                p.setPrefix(ns[0], ns[1])
+            root.append(p)
+
+        parse_args(method.name, self.param_defs(method), args, kwargs,
+            add_param, self.options().extraArgumentErrors)
+
+        return root
+
+    def replycontent(self, method, body):
+        wrapped = method.soap.output.body.wrapped
+        if wrapped:
+            return body[0].children
+        return body.children
+
+    def document(self, wrapper):
+        """
+        Get the document root. For I{document/literal}, this is the name of the
+        wrapper element qualified by the schema's target namespace.
+        @param wrapper: The method name.
+        @type wrapper: L{xsd.sxbase.SchemaObject}
+        @return: A root element.
+        @rtype: L{Element}
+        """
+        tag = wrapper[1].name
+        ns = wrapper[1].namespace("ns0")
+        return Element(tag, ns=ns)
+
+    def mkparam(self, method, pdef, object):
+        """
+        Expand list parameters into individual parameters each with the type
+        information. This is because in document arrays are simply
+        multi-occurrence elements.
+
+        """
+        if isinstance(object, (list, tuple)):
+            tags = []
+            for item in object:
+                tags.append(self.mkparam(method, pdef, item))
+            return tags
+        return Binding.mkparam(self, method, pdef, object)
+
+    def param_defs(self, method):
+        """Get parameter definitions for document literal."""
+        pts = self.bodypart_types(method)
+        wrapped = method.soap.input.body.wrapped
+        if not wrapped:
+            return pts
+        result = []
+        for p in pts:
+            for child, ancestry in p[1].resolve():
+                if not child.isattr():
+                    result.append((child.name, child, ancestry))
+        return result
+
+    def returned_types(self, method):
+        result = []
+        wrapped = method.soap.output.body.wrapped
+        rts = self.bodypart_types(method, input=False)
+        if wrapped:
+            for pt in rts:
+                resolved = pt.resolve(nobuiltin=True)
+                for child, ancestry in resolved:
+                    result.append(child)
+                break
+        else:
+            result += rts
+        return result
diff --git a/suds/bindings/multiref.py b/suds/bindings/multiref.py
new file mode 100644
index 0000000000000000000000000000000000000000..52fa47add1f691552b105969ad7f56d80fe34158
--- /dev/null
+++ b/suds/bindings/multiref.py
@@ -0,0 +1,124 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides classes for handling soap multirefs.
+"""
+
+from suds import *
+from suds.sax.element import Element
+
+
+soapenc = (None, 'http://schemas.xmlsoap.org/soap/encoding/')
+
+class MultiRef:
+    """
+    Resolves and replaces multirefs.
+    @ivar nodes: A list of non-multiref nodes.
+    @type nodes: list
+    @ivar catalog: A dictionary of multiref nodes by id.
+    @type catalog: dict
+    """
+
+    def __init__(self):
+        self.nodes = []
+        self.catalog = {}
+
+    def process(self, body):
+        """
+        Process the specified soap envelope body and replace I{multiref} node
+        references with the contents of the referenced node.
+        @param body: A soap envelope body node.
+        @type body: L{Element}
+        @return: The processed I{body}
+        @rtype: L{Element}
+        """
+        self.nodes = []
+        self.catalog = {}
+        self.build_catalog(body)
+        self.update(body)
+        body.children = self.nodes
+        return body
+
+    def update(self, node):
+        """
+        Update the specified I{node} by replacing the I{multiref} references with
+        the contents of the referenced nodes and remove the I{href} attribute.
+        @param node: A node to update.
+        @type node: L{Element}
+        @return: The updated node
+        @rtype: L{Element}
+        """
+        self.replace_references(node)
+        for c in node.children:
+            self.update(c)
+        return node
+
+    def replace_references(self, node):
+        """
+        Replacing the I{multiref} references with the contents of the
+        referenced nodes and remove the I{href} attribute.  Warning:  since
+        the I{ref} is not cloned,
+        @param node: A node to update.
+        @type node: L{Element}
+        """
+        href = node.getAttribute('href')
+        if href is None:
+            return
+        id = href.getValue()
+        ref = self.catalog.get(id)
+        if ref is None:
+            import logging
+            log = logging.getLogger(__name__)
+            log.error('soap multiref: %s, not-resolved', id)
+            return
+        node.append(ref.children)
+        node.setText(ref.getText())
+        for a in ref.attributes:
+            if a.name != 'id':
+                node.append(a)
+        node.remove(href)
+
+    def build_catalog(self, body):
+        """
+        Create the I{catalog} of multiref nodes by id and the list of
+        non-multiref nodes.
+        @param body: A soap envelope body node.
+        @type body: L{Element}
+        """
+        for child in body.children:
+            if self.soaproot(child):
+                self.nodes.append(child)
+            id = child.get('id')
+            if id is None: continue
+            key = '#%s' % id
+            self.catalog[key] = child
+
+    def soaproot(self, node):
+        """
+        Get whether the specified I{node} is a soap encoded root.
+        This is determined by examining @soapenc:root='1'.
+        The node is considered to be a root when the attribute
+        is not specified.
+        @param node: A node to evaluate.
+        @type node: L{Element}
+        @return: True if a soap encoded root.
+        @rtype: bool
+        """
+        root = node.getAttribute('root', ns=soapenc)
+        if root is None:
+            return True
+        else:
+            return ( root.value == '1' )
diff --git a/suds/bindings/rpc.py b/suds/bindings/rpc.py
new file mode 100644
index 0000000000000000000000000000000000000000..d164960d22903a54c9f6c7da01483a0d84dbae7d
--- /dev/null
+++ b/suds/bindings/rpc.py
@@ -0,0 +1,91 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides classes for the (WS) SOAP I{rpc/literal} and I{rpc/encoded} bindings.
+"""
+
+from suds import *
+from suds.mx.encoded import Encoded as MxEncoded
+from suds.umx.encoded import Encoded as UmxEncoded
+from suds.bindings.binding import Binding, envns
+from suds.sax.element import Element
+
+
+encns = ('SOAP-ENC', 'http://schemas.xmlsoap.org/soap/encoding/')
+
+class RPC(Binding):
+    """
+    RPC/Literal binding style.
+    """
+
+    def param_defs(self, method):
+        return self.bodypart_types(method)
+
+    def envelope(self, header, body):
+        env = Binding.envelope(self, header, body)
+        env.addPrefix(encns[0], encns[1])
+        env.set('%s:encodingStyle' % envns[0],
+                'http://schemas.xmlsoap.org/soap/encoding/')
+        return env
+
+    def bodycontent(self, method, args, kwargs):
+        n = 0
+        root = self.method(method)
+        for pd in self.param_defs(method):
+            if n < len(args):
+                value = args[n]
+            else:
+                value = kwargs.get(pd[0])
+            p = self.mkparam(method, pd, value)
+            if p is not None:
+                root.append(p)
+            n += 1
+        return root
+
+    def replycontent(self, method, body):
+        return body[0].children
+
+    def method(self, method):
+        """
+        Get the document root.  For I{rpc/(literal|encoded)}, this is the
+        name of the method qualified by the schema tns.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @return: A root element.
+        @rtype: L{Element}
+        """
+        ns = method.soap.input.body.namespace
+        if ns[0] is None:
+            ns = ('ns0', ns[1])
+        method = Element(method.name, ns=ns)
+        return method
+
+
+class Encoded(RPC):
+    """
+    RPC/Encoded (section 5)  binding style.
+    """
+
+    def marshaller(self):
+        return MxEncoded(self.schema())
+
+    def unmarshaller(self):
+        """
+        Get the appropriate schema based XML decoder.
+        @return: Typed unmarshaller.
+        @rtype: L{UmxTyped}
+        """
+        return UmxEncoded(self.schema())
diff --git a/suds/builder.py b/suds/builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ebfb3853f6ad6a767de03d442eaec0c9efa17ee
--- /dev/null
+++ b/suds/builder.py
@@ -0,0 +1,117 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{builder} module provides an wsdl/xsd defined types factory
+"""
+
+from suds import *
+from suds.sudsobject import Factory
+
+
+class Builder:
+    """ Builder used to construct an object for types defined in the schema """
+
+    def __init__(self, resolver):
+        """
+        @param resolver: A schema object name resolver.
+        @type resolver: L{resolver.Resolver}
+        """
+        self.resolver = resolver
+
+    def build(self, name):
+        """ build a an object for the specified typename as defined in the schema """
+        if isinstance(name, basestring):
+            type = self.resolver.find(name)
+            if type is None:
+                raise TypeNotFound(name)
+        else:
+            type = name
+        cls = type.name
+        if type.mixed():
+            data = Factory.property(cls)
+        else:
+            data = Factory.object(cls)
+        resolved = type.resolve()
+        md = data.__metadata__
+        md.sxtype = resolved
+        md.ordering = self.ordering(resolved)
+        history = []
+        self.add_attributes(data, resolved)
+        for child, ancestry in type.children():
+            if self.skip_child(child, ancestry):
+                continue
+            self.process(data, child, history[:])
+        return data
+
+    def process(self, data, type, history):
+        """ process the specified type then process its children """
+        if type in history:
+            return
+        if type.enum():
+            return
+        history.append(type)
+        resolved = type.resolve()
+        value = None
+        if type.multi_occurrence():
+            value = []
+        else:
+            if len(resolved) > 0:
+                if resolved.mixed():
+                    value = Factory.property(resolved.name)
+                    md = value.__metadata__
+                    md.sxtype = resolved
+                else:
+                    value = Factory.object(resolved.name)
+                    md = value.__metadata__
+                    md.sxtype = resolved
+                    md.ordering = self.ordering(resolved)
+        setattr(data, type.name, value)
+        if value is not None:
+            data = value
+        if not isinstance(data, list):
+            self.add_attributes(data, resolved)
+            for child, ancestry in resolved.children():
+                if self.skip_child(child, ancestry):
+                    continue
+                self.process(data, child, history[:])
+
+    def add_attributes(self, data, type):
+        """ add required attributes """
+        for attr, ancestry in type.attributes():
+            name = '_%s' % attr.name
+            value = attr.get_default()
+            setattr(data, name, value)
+
+    def skip_child(self, child, ancestry):
+        """ get whether or not to skip the specified child """
+        if child.any(): return True
+        for x in ancestry:
+            if x.choice():
+                return True
+        return False
+
+    def ordering(self, type):
+        """ get the ordering """
+        result = []
+        for child, ancestry in type.resolve():
+            name = child.name
+            if child.name is None:
+                continue
+            if child.isattr():
+                name = '_%s' % child.name
+            result.append(name)
+        return result
diff --git a/suds/cache.py b/suds/cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..e156d1910bde0be7e24104b986bf020b3fc112d0
--- /dev/null
+++ b/suds/cache.py
@@ -0,0 +1,301 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Contains basic caching classes.
+"""
+
+import suds
+from suds.transport import *
+from suds.sax.parser import Parser
+from suds.sax.element import Element
+
+from datetime import datetime as dt
+from datetime import timedelta
+import os
+from tempfile import gettempdir as tmp
+try:
+    import cPickle as pickle
+except Exception:
+    import pickle
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Cache:
+    """
+    An object object cache.
+    """
+
+    def get(self, id):
+        """
+        Get a object from the cache by ID.
+        @param id: The object ID.
+        @type id: str
+        @return: The object, else None
+        @rtype: any
+        """
+        raise Exception('not-implemented')
+
+    def put(self, id, object):
+        """
+        Put a object into the cache.
+        @param id: The object ID.
+        @type id: str
+        @param object: The object to add.
+        @type object: any
+        """
+        raise Exception('not-implemented')
+
+    def purge(self, id):
+        """
+        Purge a object from the cache by id.
+        @param id: A object ID.
+        @type id: str
+        """
+        raise Exception('not-implemented')
+
+    def clear(self):
+        """
+        Clear all objects from the cache.
+        """
+        raise Exception('not-implemented')
+
+
+class NoCache(Cache):
+    """
+    The passthru object cache.
+    """
+
+    def get(self, id):
+        return None
+
+    def put(self, id, object):
+        pass
+
+
+class FileCache(Cache):
+    """
+    A file-based URL cache.
+    @cvar fnprefix: The file name prefix.
+    @type fnsuffix: str
+    @ivar duration: The cached file duration which defines how
+        long the file will be cached.
+    @type duration: (unit, value)
+    @ivar location: The directory for the cached files.
+    @type location: str
+    """
+    fnprefix = 'suds'
+    units = ('months', 'weeks', 'days', 'hours', 'minutes', 'seconds')
+
+    def __init__(self, location=None, **duration):
+        """
+        @param location: The directory for the cached files.
+        @type location: str
+        @param duration: The cached file duration which defines how
+            long the file will be cached.  A duration=0 means forever.
+            The duration may be: (months|weeks|days|hours|minutes|seconds).
+        @type duration: {unit:value}
+        """
+        if location is None:
+            location = os.path.join(tmp(), 'suds')
+        self.location = location
+        self.duration = (None, 0)
+        self.setduration(**duration)
+        self.checkversion()
+
+    def fnsuffix(self):
+        """
+        Get the file name suffix
+        @return: The suffix
+        @rtype: str
+        """
+        return 'gcf'
+
+    def setduration(self, **duration):
+        """
+        Set the caching duration which defines how long the
+        file will be cached.
+        @param duration: The cached file duration which defines how
+            long the file will be cached.  A duration=0 means forever.
+            The duration may be: (months|weeks|days|hours|minutes|seconds).
+        @type duration: {unit:value}
+        """
+        if len(duration) == 1:
+            arg = duration.items()[0]
+            if not arg[0] in self.units:
+                raise Exception('must be: %s' % str(self.units))
+            self.duration = arg
+        return self
+
+    def setlocation(self, location):
+        """
+        Set the location (directory) for the cached files.
+        @param location: The directory for the cached files.
+        @type location: str
+        """
+        self.location = location
+
+    def mktmp(self):
+        """
+        Make the I{location} directory if it doesn't already exits.
+        """
+        try:
+            if not os.path.isdir(self.location):
+                os.makedirs(self.location)
+        except Exception:
+            log.debug(self.location, exc_info=1)
+        return self
+
+    def put(self, id, bfr):
+        try:
+            fn = self.__fn(id)
+            f = self.open(fn, 'wb')
+            try:
+                f.write(bfr)
+            finally:
+                f.close()
+            return bfr
+        except Exception:
+            log.debug(id, exc_info=1)
+            return bfr
+
+    def get(self, id):
+        try:
+            f = self.getf(id)
+            try:
+                return f.read()
+            finally:
+                f.close()
+        except Exception:
+            pass
+
+    def getf(self, id):
+        try:
+            fn = self.__fn(id)
+            self.validate(fn)
+            return self.open(fn, 'rb')
+        except Exception:
+            pass
+
+    def validate(self, fn):
+        """
+        Validate that the file has not expired based on the I{duration}.
+        @param fn: The file name.
+        @type fn: str
+        """
+        if self.duration[1] < 1:
+            return
+        created = dt.fromtimestamp(os.path.getctime(fn))
+        d = {self.duration[0]:self.duration[1]}
+        expired = created + timedelta(**d)
+        if expired < dt.now():
+            log.debug('%s expired, deleted', fn)
+            os.remove(fn)
+
+    def clear(self):
+        for fn in os.listdir(self.location):
+            path = os.path.join(self.location, fn)
+            if os.path.isdir(path):
+                continue
+            if fn.startswith(self.fnprefix):
+                os.remove(path)
+                log.debug('deleted: %s', path)
+
+    def purge(self, id):
+        fn = self.__fn(id)
+        try:
+            os.remove(fn)
+        except Exception:
+            pass
+
+    def open(self, fn, *args):
+        """
+        Open the cache file making sure the directory is created.
+        """
+        self.mktmp()
+        return open(fn, *args)
+
+    def checkversion(self):
+        path = os.path.join(self.location, 'version')
+        try:
+            f = self.open(path)
+            version = f.read()
+            f.close()
+            if version != suds.__version__:
+                raise Exception()
+        except Exception:
+            self.clear()
+            f = self.open(path, 'w')
+            f.write(suds.__version__)
+            f.close()
+
+    def __fn(self, id):
+        name = id
+        suffix = self.fnsuffix()
+        fn = '%s-%s.%s' % (self.fnprefix, name, suffix)
+        return os.path.join(self.location, fn)
+
+
+class DocumentCache(FileCache):
+    """
+    Provides xml document caching.
+    """
+
+    def fnsuffix(self):
+        return 'xml'
+
+    def get(self, id):
+        try:
+            fp = self.getf(id)
+            if fp is None:
+                return None
+            p = Parser()
+            return p.parse(fp)
+        except Exception:
+            self.purge(id)
+
+    def put(self, id, object):
+        if isinstance(object, Element):
+            FileCache.put(self, id, suds.byte_str(str(object)))
+        return object
+
+
+class ObjectCache(FileCache):
+    """
+    Provides pickled object caching.
+    @cvar protocol: The pickling protocol.
+    @type protocol: int
+    """
+    protocol = 2
+
+    def fnsuffix(self):
+        return 'px'
+
+    def get(self, id):
+        try:
+            fp = self.getf(id)
+            if fp is None:
+                return None
+            return pickle.load(fp)
+        except Exception:
+            self.purge(id)
+
+    def put(self, id, object):
+        bfr = pickle.dumps(object, self.protocol)
+        FileCache.put(self, id, bfr)
+        return object
diff --git a/suds/client.py b/suds/client.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7fda6020420f34e56b9f1b5c87c6d3f1e44c5e1
--- /dev/null
+++ b/suds/client.py
@@ -0,0 +1,832 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{2nd generation} service proxy provides access to web services.
+See I{README.txt}
+"""
+
+import suds
+from suds import *
+import suds.bindings.binding
+from suds.builder import Builder
+from suds.cache import ObjectCache
+import suds.metrics as metrics
+from suds.options import Options
+from suds.plugin import PluginContainer
+from suds.properties import Unskin
+from suds.reader import DefinitionsReader
+from suds.resolver import PathResolver
+from suds.sax.document import Document
+from suds.sax.parser import Parser
+from suds.servicedefinition import ServiceDefinition
+from suds.transport import TransportError, Request
+from suds.transport.https import HttpAuthenticated
+from suds.umx.basic import Basic as UmxBasic
+from suds.wsdl import Definitions
+import sudsobject
+
+from cookielib import CookieJar
+from copy import deepcopy
+import httplib
+from urlparse import urlparse
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Client(UnicodeMixin):
+    """
+    A lightweight web services client.
+    I{(2nd generation)} API.
+    @ivar wsdl: The WSDL object.
+    @type wsdl:L{Definitions}
+    @ivar service: The service proxy used to invoke operations.
+    @type service: L{Service}
+    @ivar factory: The factory used to create objects.
+    @type factory: L{Factory}
+    @ivar sd: The service definition
+    @type sd: L{ServiceDefinition}
+    @ivar messages: The last sent/received messages.
+    @type messages: str[2]
+    """
+    @classmethod
+    def items(cls, sobject):
+        """
+        Extract the I{items} from a suds object much like the
+        items() method works on I{dict}.
+        @param sobject: A suds object
+        @type sobject: L{Object}
+        @return: A list of items contained in I{sobject}.
+        @rtype: [(key, value),...]
+        """
+        return sudsobject.items(sobject)
+
+    @classmethod
+    def dict(cls, sobject):
+        """
+        Convert a sudsobject into a dictionary.
+        @param sobject: A suds object
+        @type sobject: L{Object}
+        @return: A python dictionary containing the
+            items contained in I{sobject}.
+        @rtype: dict
+        """
+        return sudsobject.asdict(sobject)
+
+    @classmethod
+    def metadata(cls, sobject):
+        """
+        Extract the metadata from a suds object.
+        @param sobject: A suds object
+        @type sobject: L{Object}
+        @return: The object's metadata
+        @rtype: L{sudsobject.Metadata}
+        """
+        return sobject.__metadata__
+
+    def __init__(self, url, **kwargs):
+        """
+        @param url: The URL for the WSDL.
+        @type url: str
+        @param kwargs: keyword arguments.
+        @see: L{Options}
+        """
+        options = Options()
+        options.transport = HttpAuthenticated()
+        self.options = options
+        if "cache" not in kwargs:
+            kwargs["cache"] = ObjectCache(days=1)
+        self.set_options(**kwargs)
+        reader = DefinitionsReader(options, Definitions)
+        self.wsdl = reader.open(url)
+        plugins = PluginContainer(options.plugins)
+        plugins.init.initialized(wsdl=self.wsdl)
+        self.factory = Factory(self.wsdl)
+        self.service = ServiceSelector(self, self.wsdl.services)
+        self.sd = []
+        for s in self.wsdl.services:
+            sd = ServiceDefinition(self.wsdl, s)
+            self.sd.append(sd)
+        self.messages = dict(tx=None, rx=None)
+
+    def set_options(self, **kwargs):
+        """
+        Set options.
+        @param kwargs: keyword arguments.
+        @see: L{Options}
+        """
+        p = Unskin(self.options)
+        p.update(kwargs)
+
+    def add_prefix(self, prefix, uri):
+        """
+        Add I{static} mapping of an XML namespace prefix to a namespace.
+        This is useful for cases when a wsdl and referenced schemas make heavy
+        use of namespaces and those namespaces are subject to change.
+        @param prefix: An XML namespace prefix.
+        @type prefix: str
+        @param uri: An XML namespace URI.
+        @type uri: str
+        @raise Exception: when prefix is already mapped.
+        """
+        root = self.wsdl.root
+        mapped = root.resolvePrefix(prefix, None)
+        if mapped is None:
+            root.addPrefix(prefix, uri)
+            return
+        if mapped[1] != uri:
+            raise Exception('"%s" already mapped as "%s"' % (prefix, mapped))
+
+    def clone(self):
+        """
+        Get a shallow clone of this object.
+        The clone only shares the WSDL.  All other attributes are
+        unique to the cloned object including options.
+        @return: A shallow clone.
+        @rtype: L{Client}
+        """
+        class Uninitialized(Client):
+            def __init__(self):
+                pass
+        clone = Uninitialized()
+        clone.options = Options()
+        cp = Unskin(clone.options)
+        mp = Unskin(self.options)
+        cp.update(deepcopy(mp))
+        clone.wsdl = self.wsdl
+        clone.factory = self.factory
+        clone.service = ServiceSelector(clone, self.wsdl.services)
+        clone.sd = self.sd
+        clone.messages = dict(tx=None, rx=None)
+        return clone
+
+    def __unicode__(self):
+        s = ['\n']
+        s.append('Suds ( https://fedorahosted.org/suds/ )')
+        s.append('  version: %s' % suds.__version__)
+        if ( suds.__build__ ):
+            s.append('  build: %s' % suds.__build__)
+        for sd in self.sd:
+            s.append('\n\n%s' % unicode(sd))
+        return ''.join(s)
+
+
+class Factory:
+    """
+    A factory for instantiating types defined in the wsdl
+    @ivar resolver: A schema type resolver.
+    @type resolver: L{PathResolver}
+    @ivar builder: A schema object builder.
+    @type builder: L{Builder}
+    """
+
+    def __init__(self, wsdl):
+        """
+        @param wsdl: A schema object.
+        @type wsdl: L{wsdl.Definitions}
+        """
+        self.wsdl = wsdl
+        self.resolver = PathResolver(wsdl)
+        self.builder = Builder(self.resolver)
+
+    def create(self, name):
+        """
+        create a WSDL type by name
+        @param name: The name of a type defined in the WSDL.
+        @type name: str
+        @return: The requested object.
+        @rtype: L{Object}
+        """
+        timer = metrics.Timer()
+        timer.start()
+        type = self.resolver.find(name)
+        if type is None:
+            raise TypeNotFound(name)
+        if type.enum():
+            result = sudsobject.Factory.object(name)
+            for e, a in type.children():
+                setattr(result, e.name, e.name)
+        else:
+            try:
+                result = self.builder.build(type)
+            except Exception, e:
+                log.error("create '%s' failed", name, exc_info=True)
+                raise BuildError(name, e)
+        timer.stop()
+        metrics.log.debug('%s created: %s', name, timer)
+        return result
+
+    def separator(self, ps):
+        """
+        Set the path separator.
+        @param ps: The new path separator.
+        @type ps: char
+        """
+        self.resolver = PathResolver(self.wsdl, ps)
+
+
+class ServiceSelector:
+    """
+    The B{service} selector is used to select a web service.
+    In most cases, the wsdl only defines (1) service in which access
+    by subscript is passed through to a L{PortSelector}.  This is also the
+    behavior when a I{default} service has been specified.  In cases
+    where multiple services have been defined and no default has been
+    specified, the service is found by name (or index) and a L{PortSelector}
+    for the service is returned.  In all cases, attribute access is
+    forwarded to the L{PortSelector} for either the I{first} service or the
+    I{default} service (when specified).
+    @ivar __client: A suds client.
+    @type __client: L{Client}
+    @ivar __services: A list of I{wsdl} services.
+    @type __services: list
+    """
+    def __init__(self, client, services):
+        """
+        @param client: A suds client.
+        @type client: L{Client}
+        @param services: A list of I{wsdl} services.
+        @type services: list
+        """
+        self.__client = client
+        self.__services = services
+
+    def __getattr__(self, name):
+        """
+        Request to access an attribute is forwarded to the
+        L{PortSelector} for either the I{first} service or the
+        I{default} service (when specified).
+        @param name: The name of a method.
+        @type name: str
+        @return: A L{PortSelector}.
+        @rtype: L{PortSelector}.
+        """
+        default = self.__ds()
+        if default is None:
+            port = self.__find(0)
+        else:
+            port = default
+        return getattr(port, name)
+
+    def __getitem__(self, name):
+        """
+        Provides selection of the I{service} by name (string) or
+        index (integer).  In cases where only (1) service is defined
+        or a I{default} has been specified, the request is forwarded
+        to the L{PortSelector}.
+        @param name: The name (or index) of a service.
+        @type name: (int|str)
+        @return: A L{PortSelector} for the specified service.
+        @rtype: L{PortSelector}.
+        """
+        if len(self.__services) == 1:
+            port = self.__find(0)
+            return port[name]
+        default = self.__ds()
+        if default is not None:
+            port = default
+            return port[name]
+        return self.__find(name)
+
+    def __find(self, name):
+        """
+        Find a I{service} by name (string) or index (integer).
+        @param name: The name (or index) of a service.
+        @type name: (int|str)
+        @return: A L{PortSelector} for the found service.
+        @rtype: L{PortSelector}.
+        """
+        service = None
+        if not len(self.__services):
+            raise Exception, 'No services defined'
+        if isinstance(name, int):
+            try:
+                service = self.__services[name]
+                name = service.name
+            except IndexError:
+                raise ServiceNotFound, 'at [%d]' % name
+        else:
+            for s in self.__services:
+                if name == s.name:
+                    service = s
+                    break
+        if service is None:
+            raise ServiceNotFound, name
+        return PortSelector(self.__client, service.ports, name)
+
+    def __ds(self):
+        """
+        Get the I{default} service if defined in the I{options}.
+        @return: A L{PortSelector} for the I{default} service.
+        @rtype: L{PortSelector}.
+        """
+        ds = self.__client.options.service
+        if ds is None:
+            return None
+        else:
+            return self.__find(ds)
+
+
+class PortSelector:
+    """
+    The B{port} selector is used to select a I{web service} B{port}.
+    In cases where multiple ports have been defined and no default has been
+    specified, the port is found by name (or index) and a L{MethodSelector}
+    for the port is returned.  In all cases, attribute access is
+    forwarded to the L{MethodSelector} for either the I{first} port or the
+    I{default} port (when specified).
+    @ivar __client: A suds client.
+    @type __client: L{Client}
+    @ivar __ports: A list of I{service} ports.
+    @type __ports: list
+    @ivar __qn: The I{qualified} name of the port (used for logging).
+    @type __qn: str
+    """
+    def __init__(self, client, ports, qn):
+        """
+        @param client: A suds client.
+        @type client: L{Client}
+        @param ports: A list of I{service} ports.
+        @type ports: list
+        @param qn: The name of the service.
+        @type qn: str
+        """
+        self.__client = client
+        self.__ports = ports
+        self.__qn = qn
+
+    def __getattr__(self, name):
+        """
+        Request to access an attribute is forwarded to the
+        L{MethodSelector} for either the I{first} port or the
+        I{default} port (when specified).
+        @param name: The name of a method.
+        @type name: str
+        @return: A L{MethodSelector}.
+        @rtype: L{MethodSelector}.
+        """
+        default = self.__dp()
+        if default is None:
+            m = self.__find(0)
+        else:
+            m = default
+        return getattr(m, name)
+
+    def __getitem__(self, name):
+        """
+        Provides selection of the I{port} by name (string) or
+        index (integer).  In cases where only (1) port is defined
+        or a I{default} has been specified, the request is forwarded
+        to the L{MethodSelector}.
+        @param name: The name (or index) of a port.
+        @type name: (int|str)
+        @return: A L{MethodSelector} for the specified port.
+        @rtype: L{MethodSelector}.
+        """
+        default = self.__dp()
+        if default is None:
+            return self.__find(name)
+        else:
+            return default
+
+    def __find(self, name):
+        """
+        Find a I{port} by name (string) or index (integer).
+        @param name: The name (or index) of a port.
+        @type name: (int|str)
+        @return: A L{MethodSelector} for the found port.
+        @rtype: L{MethodSelector}.
+        """
+        port = None
+        if not len(self.__ports):
+            raise Exception, 'No ports defined: %s' % self.__qn
+        if isinstance(name, int):
+            qn = '%s[%d]' % (self.__qn, name)
+            try:
+                port = self.__ports[name]
+            except IndexError:
+                raise PortNotFound, qn
+        else:
+            qn = '.'.join((self.__qn, name))
+            for p in self.__ports:
+                if name == p.name:
+                    port = p
+                    break
+        if port is None:
+            raise PortNotFound, qn
+        qn = '.'.join((self.__qn, port.name))
+        return MethodSelector(self.__client, port.methods, qn)
+
+    def __dp(self):
+        """
+        Get the I{default} port if defined in the I{options}.
+        @return: A L{MethodSelector} for the I{default} port.
+        @rtype: L{MethodSelector}.
+        """
+        dp = self.__client.options.port
+        if dp is None:
+            return None
+        else:
+            return self.__find(dp)
+
+
+class MethodSelector:
+    """
+    The B{method} selector is used to select a B{method} by name.
+    @ivar __client: A suds client.
+    @type __client: L{Client}
+    @ivar __methods: A dictionary of methods.
+    @type __methods: dict
+    @ivar __qn: The I{qualified} name of the method (used for logging).
+    @type __qn: str
+    """
+    def __init__(self, client, methods, qn):
+        """
+        @param client: A suds client.
+        @type client: L{Client}
+        @param methods: A dictionary of methods.
+        @type methods: dict
+        @param qn: The I{qualified} name of the port.
+        @type qn: str
+        """
+        self.__client = client
+        self.__methods = methods
+        self.__qn = qn
+
+    def __getattr__(self, name):
+        """
+        Get a method by name and return it in an I{execution wrapper}.
+        @param name: The name of a method.
+        @type name: str
+        @return: An I{execution wrapper} for the specified method name.
+        @rtype: L{Method}
+        """
+        return self[name]
+
+    def __getitem__(self, name):
+        """
+        Get a method by name and return it in an I{execution wrapper}.
+        @param name: The name of a method.
+        @type name: str
+        @return: An I{execution wrapper} for the specified method name.
+        @rtype: L{Method}
+        """
+        m = self.__methods.get(name)
+        if m is None:
+            qn = '.'.join((self.__qn, name))
+            raise MethodNotFound, qn
+        return Method(self.__client, m)
+
+
+class Method:
+    """
+    The I{method} (namespace) object.
+    @ivar client: A client object.
+    @type client: L{Client}
+    @ivar method: A I{wsdl} method.
+    @type I{wsdl} Method.
+    """
+
+    def __init__(self, client, method):
+        """
+        @param client: A client object.
+        @type client: L{Client}
+        @param method: A I{raw} method.
+        @type I{raw} Method.
+        """
+        self.client = client
+        self.method = method
+
+    def __call__(self, *args, **kwargs):
+        """
+        Invoke the method.
+        """
+        clientclass = self.clientclass(kwargs)
+        client = clientclass(self.client, self.method)
+        try:
+            return client.invoke(args, kwargs)
+        except WebFault, e:
+            if self.faults():
+                raise
+            return (httplib.INTERNAL_SERVER_ERROR, e)
+
+    def faults(self):
+        """ get faults option """
+        return self.client.options.faults
+
+    def clientclass(self, kwargs):
+        """ get soap client class """
+        if SimClient.simulation(kwargs):
+            return SimClient
+        return SoapClient
+
+
+class SoapClient:
+    """
+    A lightweight soap based web client B{**not intended for external use}
+    @ivar service: The target method.
+    @type service: L{Service}
+    @ivar method: A target method.
+    @type method: L{Method}
+    @ivar options: A dictonary of options.
+    @type options: dict
+    @ivar cookiejar: A cookie jar.
+    @type cookiejar: libcookie.CookieJar
+    """
+
+    def __init__(self, client, method):
+        """
+        @param client: A suds client.
+        @type client: L{Client}
+        @param method: A target method.
+        @type method: L{Method}
+        """
+        self.client = client
+        self.method = method
+        self.options = client.options
+        self.cookiejar = CookieJar()
+
+    def invoke(self, args, kwargs):
+        """
+        Send the required soap message to invoke the specified method
+        @param args: A list of args for the method invoked.
+        @type args: list
+        @param kwargs: Named (keyword) args for the method invoked.
+        @type kwargs: dict
+        @return: The result of the method invocation.
+        @rtype: I{builtin}|I{subclass of} L{Object}
+        """
+        timer = metrics.Timer()
+        timer.start()
+        binding = self.method.binding.input
+        soapenv = binding.get_message(self.method, args, kwargs)
+        timer.stop()
+        metrics.log.debug("message for '%s' created: %s", self.method.name,
+            timer)
+        timer.start()
+        result = self.send(soapenv)
+        timer.stop()
+        metrics.log.debug("method '%s' invoked: %s", self.method.name, timer)
+        return result
+
+    def send(self, soapenv):
+        """
+        Send soap message.
+        @param soapenv: A soap envelope to send.
+        @type soapenv: L{Document}
+        @return: The reply to the sent message.
+        @rtype: I{builtin} or I{subclass of} L{Object}
+        """
+        location = self.location()
+        log.debug('sending to (%s)\nmessage:\n%s', location, soapenv)
+        original_soapenv = soapenv
+        plugins = PluginContainer(self.options.plugins)
+        plugins.message.marshalled(envelope=soapenv.root())
+        if self.options.prettyxml:
+            soapenv = soapenv.str()
+        else:
+            soapenv = soapenv.plain()
+        soapenv = soapenv.encode('utf-8')
+        ctx = plugins.message.sending(envelope=soapenv)
+        soapenv = ctx.envelope
+        if self.options.nosend:
+            return RequestContext(self, soapenv, original_soapenv)
+        request = Request(location, soapenv)
+        request.headers = self.headers()
+        try:
+            timer = metrics.Timer()
+            timer.start()
+            reply = self.options.transport.send(request)
+            timer.stop()
+            metrics.log.debug('waited %s on server reply', timer)
+        except TransportError, e:
+            content = e.fp and e.fp.read() or ''
+            return self.process_reply(reply=content, status=e.httpcode,
+                description=tostr(e), original_soapenv=original_soapenv)
+        return self.process_reply(reply=reply.message,
+            original_soapenv=original_soapenv)
+
+    def process_reply(self, reply, status=None, description=None,
+        original_soapenv=None):
+        if status is None:
+            status = httplib.OK
+        if status in (httplib.ACCEPTED, httplib.NO_CONTENT):
+            return
+        failed = True
+        try:
+            if status == httplib.OK:
+                log.debug('HTTP succeeded:\n%s', reply)
+            else:
+                log.debug('HTTP failed - %d - %s:\n%s', status, description,
+                    reply)
+
+            # (todo)
+            #   Consider whether and how to allow plugins to handle error,
+            # httplib.ACCEPTED & httplib.NO_CONTENT replies as well as
+            # successful ones.
+            #                                 (todo) (27.03.2013.) (Jurko)
+            plugins = PluginContainer(self.options.plugins)
+            ctx = plugins.message.received(reply=reply)
+            reply = ctx.reply
+
+            # SOAP standard states that SOAP errors must be accompanied by HTTP
+            # status code 500 - internal server error:
+            #
+            # From SOAP 1.1 Specification:
+            #   In case of a SOAP error while processing the request, the SOAP
+            # HTTP server MUST issue an HTTP 500 "Internal Server Error"
+            # response and include a SOAP message in the response containing a
+            # SOAP Fault element (see section 4.4) indicating the SOAP
+            # processing error.
+            #
+            # From WS-I Basic profile:
+            #   An INSTANCE MUST use a "500 Internal Server Error" HTTP status
+            # code if the response message is a SOAP Fault.
+            replyroot = None
+            if status in (httplib.OK, httplib.INTERNAL_SERVER_ERROR):
+                replyroot = _parse(reply)
+                plugins.message.parsed(reply=replyroot)
+                fault = self.get_fault(replyroot)
+                if fault:
+                    if status != httplib.INTERNAL_SERVER_ERROR:
+                        log.warn("Web service reported a SOAP processing "
+                            "fault using an unexpected HTTP status code %d. "
+                            "Reporting as an internal server error.", status)
+                    if self.options.faults:
+                        raise WebFault(fault, replyroot)
+                    return (httplib.INTERNAL_SERVER_ERROR, fault)
+            if status != httplib.OK:
+                if self.options.faults:
+                    # (todo)
+                    #   Use a more specific exception class here.
+                    #                         (27.03.2013.) (Jurko)
+                    raise Exception((status, description))
+                return (status, description)
+
+            if self.options.retxml:
+                failed = False
+                return reply
+
+            result = replyroot and self.method.binding.output.get_reply(
+                self.method, replyroot)
+            ctx = plugins.message.unmarshalled(reply=result)
+            result = ctx.reply
+            failed = False
+            if self.options.faults:
+                return result
+            return (httplib.OK, result)
+        finally:
+            if failed and original_soapenv:
+                log.error(original_soapenv)
+
+    def get_fault(self, replyroot):
+        """Extract fault information from the specified SOAP reply.
+
+          Returns an I{unmarshalled} fault L{Object} or None in case the given
+        XML document does not contain the SOAP <Fault> element.
+
+        @param replyroot: A SOAP reply message root XML element or None.
+        @type replyroot: L{Element}
+        @return: A fault object.
+        @rtype: L{Object}
+        """
+        envns = suds.bindings.binding.envns
+        soapenv = replyroot and replyroot.getChild('Envelope', envns)
+        soapbody = soapenv and soapenv.getChild('Body', envns)
+        fault = soapbody and soapbody.getChild('Fault', envns)
+        return fault is not None and UmxBasic().process(fault)
+
+    def headers(self):
+        """
+        Get HTTP headers or the HTTP/HTTPS request.
+        @return: A dictionary of header/values.
+        @rtype: dict
+        """
+        action = self.method.soap.action
+        if isinstance(action, unicode):
+            action = action.encode('utf-8')
+        stock = {'Content-Type':'text/xml; charset=utf-8', 'SOAPAction':action}
+        result = dict(stock, **self.options.headers)
+        log.debug('headers = %s', result)
+        return result
+
+    def location(self):
+        """
+        Returns the SOAP request's target location URL.
+
+        """
+        return Unskin(self.options).get('location', self.method.location)
+
+
+class SimClient(SoapClient):
+    """
+    Loopback client used for message/reply simulation.
+    """
+
+    injkey = '__inject'
+
+    @classmethod
+    def simulation(cls, kwargs):
+        """ get whether loopback has been specified in the I{kwargs}. """
+        return kwargs.has_key(SimClient.injkey)
+
+    def invoke(self, args, kwargs):
+        """
+        Send the required soap message to invoke the specified method
+        @param args: A list of args for the method invoked.
+        @type args: list
+        @param kwargs: Named (keyword) args for the method invoked.
+        @type kwargs: dict
+        @return: The result of the method invocation.
+        @rtype: I{builtin} or I{subclass of} L{Object}
+        """
+        simulation = kwargs[self.injkey]
+        msg = simulation.get('msg')
+        if msg is not None:
+            assert msg.__class__ is suds.byte_str_class
+            return self.send(_parse(msg))
+        msg = self.method.binding.input.get_message(self.method, args, kwargs)
+        log.debug('inject (simulated) send message:\n%s', msg)
+        reply = simulation.get('reply')
+        if reply is not None:
+            assert reply.__class__ is suds.byte_str_class
+            status = simulation.get('status')
+            description=simulation.get('description')
+            if description is None:
+                description = 'injected reply'
+            return self.process_reply(reply=reply, status=status,
+                description=description, original_soapenv=msg)
+        raise Exception('reply or msg injection parameter expected');
+
+
+class RequestContext:
+    """
+    A request context.
+    Returned when the ''nosend'' options is specified. Allows the caller to
+    take care of sending the request himself and simply return the reply data
+    for further processing.
+    @ivar client: The suds client.
+    @type client: L{Client}
+    @ivar envelope: The request SOAP envelope.
+    @type envelope: str
+    @ivar original_envelope: The original request SOAP envelope before plugin
+                             processing.
+    @type original_envelope: str
+    """
+
+    def __init__(self, client, envelope, original_envelope):
+        """
+        @param client: The suds client.
+        @type client: L{Client}
+        @param envelope: The request SOAP envelope.
+        @type envelope: str
+        @param original_envelope: The original request SOAP envelope before
+                                  plugin processing.
+        @type original_envelope: str
+        """
+        self.client = client
+        self.envelope = envelope
+        self.original_envelope = original_envelope
+
+    def process_reply(self, reply, status=None, description=None):
+        """
+        Re-entry for processing a successful reply.
+        @param reply: The reply SOAP envelope.
+        @type reply: str
+        @param status: The HTTP status code
+        @type status: int
+        @param description: Additional status description.
+        @type description: str
+        @return: The returned value for the invoked method.
+        @return: The result of the method invocation.
+        @rtype: I{builtin}|I{subclass of} L{Object}
+        """
+        return self.client.process_reply(reply=reply, status=status,
+            description=description, original_soapenv=self.original_envelope)
+
+
+def _parse(string):
+    """
+    Parses the given XML document content and returns the resulting root XML
+    element node. Returns None if the given XML content is empty.
+    @param string: XML document content to parse.
+    @type string: str
+    @return: Resulting root XML element node or None.
+    @rtype: L{Element}
+    """
+    if len(string) > 0:
+        return Parser().parse(string=string)
diff --git a/suds/metrics.py b/suds/metrics.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b15f18a053e0a83d129d39244dbe15871aa1f62
--- /dev/null
+++ b/suds/metrics.py
@@ -0,0 +1,63 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{metrics} module defines classes and other resources
+designed for collecting and reporting performance metrics.
+"""
+
+import time
+from suds import *
+from math import modf
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Timer:
+
+    def __init__(self):
+        self.started = 0
+        self.stopped = 0
+
+    def start(self):
+        self.started = time.time()
+        self.stopped = 0
+        return self
+
+    def stop(self):
+        if self.started > 0:
+            self.stopped = time.time()
+        return self
+
+    def duration(self):
+        return ( self.stopped - self.started )
+
+    def __str__(self):
+        if self.started == 0:
+            return 'not-running'
+        if self.started > 0 and self.stopped == 0:
+            return 'started: %d (running)' % self.started
+        duration = self.duration()
+        jmod = ( lambda m : (m[1], m[0]*1000) )
+        if duration < 1:
+            ms = (duration*1000)
+            return '%d (ms)' % ms
+        if duration < 60:
+            m = modf(duration)
+            return '%d.%.3d (seconds)' % jmod(m)
+        m = modf(duration/60)
+        return '%d.%.3d (minutes)' % jmod(m)
diff --git a/suds/mx/__init__.py b/suds/mx/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..719e52df714873ab6a7b54600533c7e046968d25
--- /dev/null
+++ b/suds/mx/__init__.py
@@ -0,0 +1,59 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides modules containing classes to support
+marshalling (XML).
+"""
+
+from suds.sudsobject import Object
+
+
+class Content(Object):
+    """
+    Marshaller Content.
+    @ivar tag: The content tag.
+    @type tag: str
+    @ivar value: The content's value.
+    @type value: I{any}
+    """
+
+    extensions = []
+
+    def __init__(self, tag=None, value=None, **kwargs):
+        """
+        @param tag: The content tag.
+        @type tag: str
+        @param value: The content's value.
+        @type value: I{any}
+        """
+        Object.__init__(self)
+        self.tag = tag
+        self.value = value
+        for k,v in kwargs.items():
+            setattr(self, k, v)
+
+    def __getattr__(self, name):
+        if name not in self.__dict__:
+            if name in self.extensions:
+                v = None
+                setattr(self, name, v)
+            else:
+                raise AttributeError, \
+                    'Content has no attribute %s' % name
+        else:
+            v = self.__dict__[name]
+        return v
diff --git a/suds/mx/appender.py b/suds/mx/appender.py
new file mode 100644
index 0000000000000000000000000000000000000000..f900338ff5f1971550f324cb89382c8d87397037
--- /dev/null
+++ b/suds/mx/appender.py
@@ -0,0 +1,302 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides appender classes for I{marshalling}.
+"""
+
+from suds import *
+from suds.mx import *
+from suds.sudsobject import footprint
+from suds.sudsobject import Object, Property
+from suds.sax.element import Element
+from suds.sax.text import Text
+
+
+class Matcher:
+    """
+    Appender matcher.
+    @ivar cls: A class object.
+    @type cls: I{classobj}
+    """
+
+    def __init__(self, cls):
+        """
+        @param cls: A class object.
+        @type cls: I{classobj}
+        """
+        self.cls = cls
+
+    def __eq__(self, x):
+        if self.cls is None:
+            return x is None
+        return isinstance(x, self.cls)
+
+
+class ContentAppender:
+    """
+    Appender used to add content to marshalled objects.
+    @ivar default: The default appender.
+    @type default: L{Appender}
+    @ivar appenders: A I{table} of appenders mapped by class.
+    @type appenders: I{table}
+    """
+
+    def __init__(self, marshaller):
+        """
+        @param marshaller: A marshaller.
+        @type marshaller: L{suds.mx.core.Core}
+        """
+        self.default = PrimativeAppender(marshaller)
+        self.appenders = (
+            (Matcher(None), NoneAppender(marshaller)),
+            (Matcher(null), NoneAppender(marshaller)),
+            (Matcher(Property), PropertyAppender(marshaller)),
+            (Matcher(Object), ObjectAppender(marshaller)),
+            (Matcher(Element), ElementAppender(marshaller)),
+            (Matcher(Text), TextAppender(marshaller)),
+            (Matcher(list), ListAppender(marshaller)),
+            (Matcher(tuple), ListAppender(marshaller)),
+            (Matcher(dict), DictAppender(marshaller)))
+
+    def append(self, parent, content):
+        """
+        Select an appender and append the content to parent.
+        @param parent: A parent node.
+        @type parent: L{Element}
+        @param content: The content to append.
+        @type content: L{Content}
+        """
+        appender = self.default
+        for matcher, candidate_appender in self.appenders:
+            if matcher == content.value:
+                appender = candidate_appender
+                break
+        appender.append(parent, content)
+
+
+class Appender:
+    """
+    An appender used by the marshaller to append content.
+    @ivar marshaller: A marshaller.
+    @type marshaller: L{suds.mx.core.Core}
+    """
+
+    def __init__(self, marshaller):
+        """
+        @param marshaller: A marshaller.
+        @type marshaller: L{suds.mx.core.Core}
+        """
+        self.marshaller  = marshaller
+
+    def node(self, content):
+        """
+        Create and return an XML node that is qualified
+        using the I{type}.  Also, make sure all referenced namespace
+        prefixes are declared.
+        @param content: The content for which processing has ended.
+        @type content: L{Object}
+        @return: A new node.
+        @rtype: L{Element}
+        """
+        return self.marshaller.node(content)
+
+    def setnil(self, node, content):
+        """
+        Set the value of the I{node} to nill.
+        @param node: A I{nil} node.
+        @type node: L{Element}
+        @param content: The content for which processing has ended.
+        @type content: L{Object}
+        """
+        self.marshaller.setnil(node, content)
+
+    def setdefault(self, node, content):
+        """
+        Set the value of the I{node} to a default value.
+        @param node: A I{nil} node.
+        @type node: L{Element}
+        @param content: The content for which processing has ended.
+        @type content: L{Object}
+        @return: The default.
+        """
+        return self.marshaller.setdefault(node, content)
+
+    def optional(self, content):
+        """
+        Get whether the specified content is optional.
+        @param content: The content which to check.
+        @type content: L{Content}
+        """
+        return self.marshaller.optional(content)
+
+    def suspend(self, content):
+        """
+        Notify I{marshaller} that appending this content has suspended.
+        @param content: The content for which processing has been suspended.
+        @type content: L{Object}
+        """
+        self.marshaller.suspend(content)
+
+    def resume(self, content):
+        """
+        Notify I{marshaller} that appending this content has resumed.
+        @param content: The content for which processing has been resumed.
+        @type content: L{Object}
+        """
+        self.marshaller.resume(content)
+
+    def append(self, parent, content):
+        """
+        Append the specified L{content} to the I{parent}.
+        @param content: The content to append.
+        @type content: L{Object}
+        """
+        self.marshaller.append(parent, content)
+
+
+class PrimativeAppender(Appender):
+    """
+    An appender for python I{primative} types.
+    """
+
+    def append(self, parent, content):
+        if content.tag.startswith('_'):
+            attr = content.tag[1:]
+            value = tostr(content.value)
+            if value:
+                parent.set(attr, value)
+        else:
+            child = self.node(content)
+            child.setText(tostr(content.value))
+            parent.append(child)
+
+
+class NoneAppender(Appender):
+    """
+    An appender for I{None} values.
+    """
+
+    def append(self, parent, content):
+        child = self.node(content)
+        default = self.setdefault(child, content)
+        if default is None:
+            self.setnil(child, content)
+        parent.append(child)
+
+
+class PropertyAppender(Appender):
+    """
+    A L{Property} appender.
+    """
+
+    def append(self, parent, content):
+        p = content.value
+        child = self.node(content)
+        child.setText(p.get())
+        parent.append(child)
+        for item in p.items():
+            cont = Content(tag=item[0], value=item[1])
+            Appender.append(self, child, cont)
+
+
+class ObjectAppender(Appender):
+    """
+    An L{Object} appender.
+    """
+
+    def append(self, parent, content):
+        object = content.value
+        if self.optional(content) and footprint(object) == 0:
+            return
+        child = self.node(content)
+        parent.append(child)
+        for item in object:
+            cont = Content(tag=item[0], value=item[1])
+            Appender.append(self, child, cont)
+
+
+class DictAppender(Appender):
+    """
+    An python I{dict} appender.
+    """
+
+    def append(self, parent, content):
+        d = content.value
+        if self.optional(content) and len(d) == 0:
+            return
+        child = self.node(content)
+        parent.append(child)
+        for item in d.items():
+            cont = Content(tag=item[0], value=item[1])
+            Appender.append(self, child, cont)
+
+
+class ElementWrapper(Element):
+    """
+    Element wrapper.
+    """
+
+    def __init__(self, content):
+        Element.__init__(self, content.name, content.parent)
+        self.__content = content
+
+    def str(self, indent=0):
+        return self.__content.str(indent)
+
+
+class ElementAppender(Appender):
+    """
+    An appender for I{Element} types.
+    """
+
+    def append(self, parent, content):
+        if content.tag.startswith('_'):
+            raise Exception('raw XML not valid as attribute value')
+        child = ElementWrapper(content.value)
+        parent.append(child)
+
+
+class ListAppender(Appender):
+    """
+    A list/tuple appender.
+    """
+
+    def append(self, parent, content):
+        collection = content.value
+        if len(collection):
+            self.suspend(content)
+            for item in collection:
+                cont = Content(tag=content.tag, value=item)
+                Appender.append(self, parent, cont)
+            self.resume(content)
+
+
+class TextAppender(Appender):
+    """
+    An appender for I{Text} values.
+    """
+
+    def append(self, parent, content):
+        if content.tag.startswith('_'):
+            attr = content.tag[1:]
+            value = tostr(content.value)
+            if value:
+                parent.set(attr, value)
+        else:
+            child = self.node(content)
+            child.setText(content.value)
+            parent.append(child)
diff --git a/suds/mx/basic.py b/suds/mx/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2de1611ea0a17007e28c2f742246bdb5d33cdf8
--- /dev/null
+++ b/suds/mx/basic.py
@@ -0,0 +1,45 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides basic I{marshaller} classes.
+"""
+
+from suds import *
+from suds.mx import *
+from suds.mx.core import Core
+
+
+class Basic(Core):
+    """
+    A I{basic} (untyped) marshaller.
+    """
+
+    def process(self, value, tag=None):
+        """
+        Process (marshal) the tag with the specified value using the
+        optional type information.
+        @param value: The value (content) of the XML node.
+        @type value: (L{Object}|any)
+        @param tag: The (optional) tag name for the value.  The default is
+            value.__class__.__name__
+        @type tag: str
+        @return: An xml node.
+        @rtype: L{Element}
+        """
+        content = Content(tag=tag, value=value)
+        result = Core.process(self, content)
+        return result
diff --git a/suds/mx/core.py b/suds/mx/core.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7fe24c157bff496963ec7a58418e54668fde7fe
--- /dev/null
+++ b/suds/mx/core.py
@@ -0,0 +1,154 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides I{marshaller} core classes.
+"""
+
+from suds import *
+from suds.mx import *
+from suds.mx.appender import ContentAppender
+from suds.sax.element import Element
+from suds.sax.document import Document
+from suds.sudsobject import Property
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Core:
+    """
+    An I{abstract} marshaller.  This class implement the core
+    functionality of the marshaller.
+    @ivar appender: A content appender.
+    @type appender: L{ContentAppender}
+    """
+
+    def __init__(self):
+        """
+        """
+        self.appender = ContentAppender(self)
+
+    def process(self, content):
+        """
+        Process (marshal) the tag with the specified value using the
+        optional type information.
+        @param content: The content to process.
+        @type content: L{Object}
+        """
+        log.debug('processing:\n%s', content)
+        self.reset()
+        if content.tag is None:
+            content.tag = content.value.__class__.__name__
+        document = Document()
+        if isinstance(content.value, Property):
+            root = self.node(content)
+        self.append(document, content)
+        return document.root()
+
+    def append(self, parent, content):
+        """
+        Append the specified L{content} to the I{parent}.
+        @param parent: The parent node to append to.
+        @type parent: L{Element}
+        @param content: The content to append.
+        @type content: L{Object}
+        """
+        log.debug('appending parent:\n%s\ncontent:\n%s', parent, content)
+        if self.start(content):
+            self.appender.append(parent, content)
+            self.end(parent, content)
+
+    def reset(self):
+        """
+        Reset the marshaller.
+        """
+        pass
+
+    def node(self, content):
+        """
+        Create and return an XML node.
+        @param content: The content for which processing has been suspended.
+        @type content: L{Object}
+        @return: An element.
+        @rtype: L{Element}
+        """
+        return Element(content.tag)
+
+    def start(self, content):
+        """
+        Appending this content has started.
+        @param content: The content for which processing has started.
+        @type content: L{Content}
+        @return: True to continue appending
+        @rtype: boolean
+        """
+        return True
+
+    def suspend(self, content):
+        """
+        Appending this content has suspended.
+        @param content: The content for which processing has been suspended.
+        @type content: L{Content}
+        """
+        pass
+
+    def resume(self, content):
+        """
+        Appending this content has resumed.
+        @param content: The content for which processing has been resumed.
+        @type content: L{Content}
+        """
+        pass
+
+    def end(self, parent, content):
+        """
+        Appending this content has ended.
+        @param parent: The parent node ending.
+        @type parent: L{Element}
+        @param content: The content for which processing has ended.
+        @type content: L{Content}
+        """
+        pass
+
+    def setnil(self, node, content):
+        """
+        Set the value of the I{node} to nill.
+        @param node: A I{nil} node.
+        @type node: L{Element}
+        @param content: The content to set nil.
+        @type content: L{Content}
+        """
+        pass
+
+    def setdefault(self, node, content):
+        """
+        Set the value of the I{node} to a default value.
+        @param node: A I{nil} node.
+        @type node: L{Element}
+        @param content: The content to set the default value.
+        @type content: L{Content}
+        @return: The default.
+        """
+        pass
+
+    def optional(self, content):
+        """
+        Get whether the specified content is optional.
+        @param content: The content which to check.
+        @type content: L{Content}
+        """
+        return False
diff --git a/suds/mx/encoded.py b/suds/mx/encoded.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec09536836bf043bdfa5085911d8860054e592e5
--- /dev/null
+++ b/suds/mx/encoded.py
@@ -0,0 +1,131 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides encoded I{marshaller} classes.
+"""
+
+from suds import *
+from suds.mx import *
+from suds.mx.literal import Literal
+from suds.mx.typer import Typer
+from suds.sudsobject import Factory, Object
+from suds.xsd.query import TypeQuery
+
+
+#
+# Add encoded extensions
+# aty = The soap (section 5) encoded array type.
+#
+Content.extensions.append('aty')
+
+
+class Encoded(Literal):
+    """
+    A SOAP section (5) encoding marshaller.
+    This marshaller supports rpc/encoded soap styles.
+    """
+
+    def start(self, content):
+        #
+        # For soap encoded arrays, the 'aty' (array type) information
+        # is extracted and added to the 'content'.  Then, the content.value
+        # is replaced with an object containing an 'item=[]' attribute
+        # containing values that are 'typed' suds objects.
+        #
+        start = Literal.start(self, content)
+        if start and isinstance(content.value, (list,tuple)):
+            resolved = content.type.resolve()
+            for c in resolved:
+                if hasattr(c[0], 'aty'):
+                    content.aty = (content.tag, c[0].aty)
+                    self.cast(content)
+                    break
+        return start
+
+    def end(self, parent, content):
+        #
+        # For soap encoded arrays, the soapenc:arrayType attribute is
+        # added with proper type and size information.
+        # Eg: soapenc:arrayType="xs:int[3]"
+        #
+        Literal.end(self, parent, content)
+        if content.aty is None:
+            return
+        tag, aty = content.aty
+        ns0 = ('at0', aty[1])
+        ns1 = ('at1', 'http://schemas.xmlsoap.org/soap/encoding/')
+        array = content.value.item
+        child = parent.getChild(tag)
+        child.addPrefix(ns0[0], ns0[1])
+        child.addPrefix(ns1[0], ns1[1])
+        name = '%s:arrayType' % ns1[0]
+        value = '%s:%s[%d]' % (ns0[0], aty[0], len(array))
+        child.set(name, value)
+
+    def encode(self, node, content):
+        if content.type.any():
+            Typer.auto(node, content.value)
+            return
+        if content.real.any():
+            Typer.auto(node, content.value)
+            return
+        ns = None
+        name = content.real.name
+        if self.xstq:
+            ns = content.real.namespace()
+        Typer.manual(node, name, ns)
+
+    def cast(self, content):
+        """
+        Cast the I{untyped} list items found in content I{value}.
+        Each items contained in the list is checked for XSD type information.
+        Items (values) that are I{untyped}, are replaced with suds objects and
+        type I{metadata} is added.
+        @param content: The content holding the collection.
+        @type content: L{Content}
+        @return: self
+        @rtype: L{Encoded}
+        """
+        aty = content.aty[1]
+        resolved = content.type.resolve()
+        array = Factory.object(resolved.name)
+        array.item = []
+        query = TypeQuery(aty)
+        ref = query.execute(self.schema)
+        if ref is None:
+            raise TypeNotFound(qref)
+        for x in content.value:
+            if isinstance(x, (list, tuple)):
+                array.item.append(x)
+                continue
+            if isinstance(x, Object):
+                md = x.__metadata__
+                md.sxtype = ref
+                array.item.append(x)
+                continue
+            if isinstance(x, dict):
+                x = Factory.object(ref.name, x)
+                md = x.__metadata__
+                md.sxtype = ref
+                array.item.append(x)
+                continue
+            x = Factory.property(ref.name, x)
+            md = x.__metadata__
+            md.sxtype = ref
+            array.item.append(x)
+        content.value = array
+        return self
diff --git a/suds/mx/literal.py b/suds/mx/literal.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b3480f739d4be131681b457ba8a5942db507231
--- /dev/null
+++ b/suds/mx/literal.py
@@ -0,0 +1,287 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides literal I{marshaller} classes.
+"""
+
+from suds import *
+from suds.mx import *
+from suds.mx.core import Core
+from suds.mx.typer import Typer
+from suds.resolver import GraphResolver, Frame
+from suds.sax.element import Element
+from suds.sudsobject import Factory
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+#
+# Add typed extensions
+# type = The expected xsd type
+# real = The 'true' XSD type
+# ancestry = The 'type' ancestry
+#
+Content.extensions.append('type')
+Content.extensions.append('real')
+Content.extensions.append('ancestry')
+
+
+class Typed(Core):
+    """
+    A I{typed} marshaller.
+    This marshaller is semi-typed as needed to support both I{document/literal}
+    and I{rpc/literal} soap message styles.
+    @ivar schema: An xsd schema.
+    @type schema: L{xsd.schema.Schema}
+    @ivar resolver: A schema type resolver.
+    @type resolver: L{GraphResolver}
+    """
+
+    def __init__(self, schema, xstq=True):
+        """
+        @param schema: A schema object
+        @type schema: L{xsd.schema.Schema}
+        @param xstq: The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates
+            that the I{xsi:type} attribute values should be qualified by
+            namespace.
+        @type xstq: bool
+        """
+        Core.__init__(self)
+        self.schema = schema
+        self.xstq = xstq
+        self.resolver = GraphResolver(self.schema)
+
+    def reset(self):
+        self.resolver.reset()
+
+    def start(self, content):
+        #
+        # Start marshalling the 'content' by ensuring that both the 'content'
+        # _and_ the resolver are primed with the XSD type information. The
+        # 'content' value is both translated and sorted based on the XSD type.
+        # Only values that are objects have their attributes sorted.
+        #
+        log.debug('starting content:\n%s', content)
+        if content.type is None:
+            name = content.tag
+            if name.startswith('_'):
+                name = '@' + name[1:]
+            content.type = self.resolver.find(name, content.value)
+            if content.type is None:
+                raise TypeNotFound(content.tag)
+        else:
+            known = None
+            if isinstance(content.value, Object):
+                known = self.resolver.known(content.value)
+                if known is None:
+                    log.debug('object %s has no type information',
+                        content.value)
+                    known = content.type
+            frame = Frame(content.type, resolved=known)
+            self.resolver.push(frame)
+        frame = self.resolver.top()
+        content.real = frame.resolved
+        content.ancestry = frame.ancestry
+        self.translate(content)
+        self.sort(content)
+        if self.skip(content):
+            log.debug('skipping (optional) content:\n%s', content)
+            self.resolver.pop()
+            return False
+        return True
+
+    def suspend(self, content):
+        #
+        # Suspend to process a list content. Primarily, this involves popping
+        # the 'list' content off the resolver's stack its list items can be
+        # marshalled.
+        #
+        self.resolver.pop()
+
+    def resume(self, content):
+        #
+        # Resume processing a list content. To do this, we really need to
+        # simply push the 'list' content back onto the resolver stack.
+        #
+        self.resolver.push(Frame(content.type))
+
+    def end(self, parent, content):
+        #
+        # End processing the content. Make sure the content ending matches the
+        # top of the resolver stack since for list processing we play games
+        # with the resolver stack.
+        #
+        log.debug('ending content:\n%s', content)
+        current = self.resolver.top().type
+        if current == content.type:
+            self.resolver.pop()
+        else:
+            raise Exception, \
+                'content (end) mismatch: top=(%s) cont=(%s)' % \
+                (current, content)
+
+    def node(self, content):
+        #
+        # Create an XML node and namespace qualify as defined by the schema
+        # (elementFormDefault).
+        #
+        ns = content.type.namespace()
+        if content.type.form_qualified:
+            node = Element(content.tag, ns=ns)
+            if ns[0]:
+                node.addPrefix(ns[0], ns[1])
+        else:
+            node = Element(content.tag)
+        self.encode(node, content)
+        log.debug('created - node:\n%s', node)
+        return node
+
+    def setnil(self, node, content):
+        #
+        # Set the 'node' nil only if the XSD type specifies that it is
+        # permitted.
+        #
+        if content.type.nillable:
+            node.setnil()
+
+    def setdefault(self, node, content):
+        #
+        # Set the node to the default value specified by the XSD type.
+        #
+        default = content.type.default
+        if default is not None:
+            node.setText(default)
+        return default
+
+    def optional(self, content):
+        if content.type.optional():
+            return True
+        for a in content.ancestry:
+            if a.optional():
+                return True
+        return False
+
+    def encode(self, node, content):
+        # Add (soap) encoding information only if the resolved type is derived
+        # by extension. Further, the xsi:type values is qualified by namespace
+        # only if the content (tag) and referenced type are in different
+        # namespaces.
+        if content.type.any():
+            return
+        if not content.real.extension():
+            return
+        if content.type.resolve() == content.real:
+            return
+        ns = None
+        name = content.real.name
+        if self.xstq:
+            ns = content.real.namespace('ns1')
+        Typer.manual(node, name, ns)
+
+    def skip(self, content):
+        """
+        Get whether to skip this I{content}.
+        Should be skipped when the content is optional and either value=None or
+        the value is an empty list.
+        @param content: The content to skip.
+        @type content: L{Object}
+        @return: True if content is to be skipped.
+        @rtype: bool
+        """
+        if self.optional(content):
+            v = content.value
+            if v is None:
+                return True
+            if isinstance(v, (list, tuple)) and len(v) == 0:
+                return True
+        return False
+
+    def optional(self, content):
+        if content.type.optional():
+            return True
+        for a in content.ancestry:
+            if a.optional():
+                return True
+        return False
+
+    def translate(self, content):
+        """
+        Translate using the XSD type information.
+        Python I{dict} is translated to a suds object. Most importantly,
+        primative values are translated from python types to XML types using
+        the XSD type.
+        @param content: The content to translate.
+        @type content: L{Object}
+        @return: self
+        @rtype: L{Typed}
+        """
+        v = content.value
+        if v is None:
+            return
+        if isinstance(v, dict):
+            cls = content.real.name
+            content.value = Factory.object(cls, v)
+            md = content.value.__metadata__
+            md.sxtype = content.type
+            return
+        v = content.real.translate(v, False)
+        content.value = v
+        return self
+
+    def sort(self, content):
+        """
+        Sort suds object attributes based on ordering defined in the XSD type
+        information.
+        @param content: The content to sort.
+        @type content: L{Object}
+        @return: self
+        @rtype: L{Typed}
+        """
+        v = content.value
+        if isinstance(v, Object):
+            md = v.__metadata__
+            md.ordering = self.ordering(content.real)
+        return self
+
+    def ordering(self, type):
+        """
+        Get the attribute ordering defined in the specified XSD type
+        information.
+        @param type: An XSD type object.
+        @type type: SchemaObject
+        @return: An ordered list of attribute names.
+        @rtype: list
+        """
+        result = []
+        for child, ancestry in type.resolve():
+            name = child.name
+            if child.name is None:
+                continue
+            if child.isattr():
+                name = '_%s' % child.name
+            result.append(name)
+        return result
+
+
+class Literal(Typed):
+    """
+    A I{literal} marshaller.
+    This marshaller is semi-typed as needed to support both I{document/literal}
+    and I{rpc/literal} soap message styles.
+    """
+    pass
diff --git a/suds/mx/typer.py b/suds/mx/typer.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc16fa53072dbe2240ed9e9e9a919f0f639a50c5
--- /dev/null
+++ b/suds/mx/typer.py
@@ -0,0 +1,119 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides sx typing classes.
+"""
+
+from suds import *
+from suds.mx import *
+from suds.sax import Namespace as NS
+from suds.sax.text import Text
+
+
+class Typer:
+    """
+    Provides XML node typing as either automatic or manual.
+    @cvar types:  A dict of class to xs type mapping.
+    @type types: dict
+    """
+
+    types = {
+        int : ('int', NS.xsdns),
+        long : ('long', NS.xsdns),
+        float : ('float', NS.xsdns),
+        str : ('string', NS.xsdns),
+        unicode : ('string', NS.xsdns),
+        Text : ('string', NS.xsdns),
+        bool : ('boolean', NS.xsdns),
+     }
+
+    @classmethod
+    def auto(cls, node, value=None):
+        """
+        Automatically set the node's xsi:type attribute based on either I{value}'s
+        class or the class of the node's text.  When I{value} is an unmapped class,
+        the default type (xs:any) is set.
+        @param node: An XML node
+        @type node: L{sax.element.Element}
+        @param value: An object that is or would be the node's text.
+        @type value: I{any}
+        @return: The specified node.
+        @rtype: L{sax.element.Element}
+        """
+        if value is None:
+            value = node.getText()
+        if isinstance(value, Object):
+            known = cls.known(value)
+            if known.name is None:
+                return node
+            tm = (known.name, known.namespace())
+        else:
+            tm = cls.types.get(value.__class__, cls.types.get(str))
+        cls.manual(node, *tm)
+        return node
+
+    @classmethod
+    def manual(cls, node, tval, ns=None):
+        """
+        Set the node's xsi:type attribute based on either I{value}'s
+        class or the class of the node's text.  Then adds the referenced
+        prefix(s) to the node's prefix mapping.
+        @param node: An XML node
+        @type node: L{sax.element.Element}
+        @param tval: The name of the schema type.
+        @type tval: str
+        @param ns: The XML namespace of I{tval}.
+        @type ns: (prefix, uri)
+        @return: The specified node.
+        @rtype: L{sax.element.Element}
+        """
+        xta = ':'.join((NS.xsins[0], 'type'))
+        node.addPrefix(NS.xsins[0], NS.xsins[1])
+        if ns is None:
+            node.set(xta, tval)
+        else:
+            ns = cls.genprefix(node, ns)
+            qname = ':'.join((ns[0], tval))
+            node.set(xta, qname)
+            node.addPrefix(ns[0], ns[1])
+        return node
+
+    @classmethod
+    def genprefix(cls, node, ns):
+        """
+        Generate a prefix.
+        @param node: An XML node on which the prefix will be used.
+        @type node: L{sax.element.Element}
+        @param ns: A namespace needing an unique prefix.
+        @type ns: (prefix, uri)
+        @return: The I{ns} with a new prefix.
+        """
+        for n in range(1, 1024):
+            p = 'ns%d' % n
+            u = node.resolvePrefix(p, default=None)
+            if u is None or u == ns[1]:
+                return (p, ns[1])
+        raise Exception('auto prefix, exhausted')
+
+    @classmethod
+    def known(cls, object):
+        try:
+            md = object.__metadata__
+            known = md.sxtype
+            return known
+        except:
+            pass
diff --git a/suds/options.py b/suds/options.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b2d48966b6d82a04fdc324d33676d31c845de2d
--- /dev/null
+++ b/suds/options.py
@@ -0,0 +1,150 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Suds basic options classes.
+"""
+
+from suds.cache import Cache, NoCache
+from suds.properties import *
+from suds.store import DocumentStore, defaultDocumentStore
+from suds.transport import Transport
+from suds.wsse import Security
+from suds.xsd.doctor import Doctor
+
+
+class TpLinker(AutoLinker):
+    """
+    Transport (auto) linker used to manage linkage between
+    transport objects Properties and those Properties that contain them.
+    """
+
+    def updated(self, properties, prev, next):
+        if isinstance(prev, Transport):
+            tp = Unskin(prev.options)
+            properties.unlink(tp)
+        if isinstance(next, Transport):
+            tp = Unskin(next.options)
+            properties.link(tp)
+
+
+class Options(Skin):
+    """
+    Options:
+        - B{cache} - The XML document cache. May be set to None for no caching.
+                - type: L{Cache}
+                - default: L{NoCache()}
+        - B{documentStore} - The XML document store used to access locally
+            stored documents without having to download them from an external
+            location. May be set to None for no internal suds library document
+            store.
+                - type: L{DocumentStore}
+                - default: L{defaultDocumentStore}
+        - B{extraArgumentErrors} - Raise exceptions when extra arguments are
+            detected when invoking a web service operation, compared to the
+            operation's WSDL schema definition.
+                - type: I{bool}
+                - default: True
+        - B{faults} - Raise faults raised by server, else return tuple from
+            service method invocation as (httpcode, object).
+                - type: I{bool}
+                - default: True
+        - B{service} - The default service name.
+                - type: I{str}
+                - default: None
+        - B{port} - The default service port name, not tcp port.
+                - type: I{str}
+                - default: None
+        - B{location} - This overrides the service port address I{URL} defined
+            in the WSDL.
+                - type: I{str}
+                - default: None
+        - B{transport} - The message transport.
+                - type: L{Transport}
+                - default: None
+        - B{soapheaders} - The soap headers to be included in the soap message.
+                - type: I{any}
+                - default: None
+        - B{wsse} - The web services I{security} provider object.
+                - type: L{Security}
+                - default: None
+        - B{doctor} - A schema I{doctor} object.
+                - type: L{Doctor}
+                - default: None
+        - B{xstq} - The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates
+            that the I{xsi:type} attribute values should be qualified by
+            namespace.
+                - type: I{bool}
+                - default: True
+        - B{prefixes} - Elements of the soap message should be qualified (when
+            needed) using XML prefixes as opposed to xmlns="" syntax.
+                - type: I{bool}
+                - default: True
+        - B{retxml} - Flag that causes the I{raw} soap envelope to be returned
+            instead of the python object graph.
+                - type: I{bool}
+                - default: False
+        - B{prettyxml} - Flag that causes I{pretty} xml to be rendered when
+            generating the outbound soap envelope.
+                - type: I{bool}
+                - default: False
+        - B{autoblend} - Flag that ensures that the schema(s) defined within
+            the WSDL import each other.
+                - type: I{bool}
+                - default: False
+        - B{cachingpolicy} - The caching policy.
+                - type: I{int}
+                  - 0 = Cache XML documents.
+                  - 1 = Cache WSDL (pickled) object.
+                - default: 0
+        - B{plugins} - A plugin container.
+                - type: I{list}
+                - default: I{list()}
+        - B{nosend} - Create the soap envelope but do not send.
+            When specified, method invocation returns a I{RequestContext}
+            instead of sending it.
+                - type: I{bool}
+                - default: False
+        - B{unwrap} - Enable automatic parameter unwrapping when possible.
+            Enabled by default. If disabled, no input or output parameters are
+            ever automatically unwrapped.
+                - type: I{bool}
+                - default: True
+    """
+    def __init__(self, **kwargs):
+        domain = __name__
+        definitions = [
+            Definition('cache', Cache, NoCache()),
+            Definition('documentStore', DocumentStore, defaultDocumentStore),
+            Definition('extraArgumentErrors', bool, True),
+            Definition('faults', bool, True),
+            Definition('transport', Transport, None, TpLinker()),
+            Definition('service', (int, basestring), None),
+            Definition('port', (int, basestring), None),
+            Definition('location', basestring, None),
+            Definition('soapheaders', (), ()),
+            Definition('wsse', Security, None),
+            Definition('doctor', Doctor, None),
+            Definition('xstq', bool, True),
+            Definition('prefixes', bool, True),
+            Definition('retxml', bool, False),
+            Definition('prettyxml', bool, False),
+            Definition('autoblend', bool, False),
+            Definition('cachingpolicy', int, 0),
+            Definition('plugins', (list, tuple), []),
+            Definition('nosend', bool, False),
+            Definition('unwrap', bool, True)]
+        Skin.__init__(self, domain, definitions, kwargs)
diff --git a/suds/plugin.py b/suds/plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..3579f5c4e78d6833d94bea5bce2d9f1c99e57d1c
--- /dev/null
+++ b/suds/plugin.py
@@ -0,0 +1,257 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The plugin module provides classes for implementation
+of suds plugins.
+"""
+
+from suds import *
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Context(object):
+    """
+    Plugin context.
+    """
+    pass
+
+
+class InitContext(Context):
+    """
+    Init Context.
+    @ivar wsdl: The wsdl.
+    @type wsdl: L{wsdl.Definitions}
+    """
+    pass
+
+
+class DocumentContext(Context):
+    """
+    The XML document load context.
+    @ivar url: The URL.
+    @type url: str
+    @ivar document: Either the XML text or the B{parsed} document root.
+    @type document: (str|L{sax.element.Element})
+    """
+    pass
+
+
+class MessageContext(Context):
+    """
+    The context for sending the SOAP envelope.
+    @ivar envelope: The SOAP envelope to be sent.
+    @type envelope: (str|L{sax.element.Element})
+    @ivar reply: The reply.
+    @type reply: (str|L{sax.element.Element}|object)
+    """
+    pass
+
+
+class Plugin:
+    """
+    Plugin base.
+    """
+    pass
+
+
+class InitPlugin(Plugin):
+    """
+    The base class for suds I{init} plugins.
+    """
+
+    def initialized(self, context):
+        """
+        Suds client initialization.
+        Called after wsdl the has been loaded.  Provides the plugin
+        with the opportunity to inspect/modify the WSDL.
+        @param context: The init context.
+        @type context: L{InitContext}
+        """
+        pass
+
+
+class DocumentPlugin(Plugin):
+    """
+    The base class for suds I{document} plugins.
+    """
+
+    def loaded(self, context):
+        """
+        Suds has loaded a WSDL/XSD document.  Provides the plugin
+        with an opportunity to inspect/modify the unparsed document.
+        Called after each WSDL/XSD document is loaded.
+        @param context: The document context.
+        @type context: L{DocumentContext}
+        """
+        pass
+
+    def parsed(self, context):
+        """
+        Suds has parsed a WSDL/XSD document.  Provides the plugin
+        with an opportunity to inspect/modify the parsed document.
+        Called after each WSDL/XSD document is parsed.
+        @param context: The document context.
+        @type context: L{DocumentContext}
+        """
+        pass
+
+
+class MessagePlugin(Plugin):
+    """
+    The base class for suds I{SOAP message} plugins.
+    """
+
+    def marshalled(self, context):
+        """
+        Suds will send the specified soap envelope.
+        Provides the plugin with the opportunity to inspect/modify
+        the envelope Document before it is sent.
+        @param context: The send context.
+            The I{envelope} is the envelope document.
+        @type context: L{MessageContext}
+        """
+        pass
+
+    def sending(self, context):
+        """
+        Suds will send the specified SOAP envelope.
+        Provides the plugin with the opportunity to inspect/modify
+        the message text it is sent.
+        @param context: The send context.
+            The I{envelope} is the envelope text.
+        @type context: L{MessageContext}
+        """
+        pass
+
+    def received(self, context):
+        """
+        Suds has received the specified reply.
+        Provides the plugin with the opportunity to inspect/modify
+        the received XML text before it is SAX parsed.
+        @param context: The reply context.
+            The I{reply} is the raw text.
+        @type context: L{MessageContext}
+        """
+        pass
+
+    def parsed(self, context):
+        """
+        Suds has sax parsed the received reply.
+        Provides the plugin with the opportunity to inspect/modify
+        the sax parsed DOM tree for the reply before it is unmarshalled.
+        @param context: The reply context.
+            The I{reply} is DOM tree.
+        @type context: L{MessageContext}
+        """
+        pass
+
+    def unmarshalled(self, context):
+        """
+        Suds has unmarshalled the received reply.
+        Provides the plugin with the opportunity to inspect/modify
+        the unmarshalled reply object before it is returned.
+        @param context: The reply context.
+            The I{reply} is unmarshalled suds object.
+        @type context: L{MessageContext}
+        """
+        pass
+
+
+class PluginContainer:
+    """
+    Plugin container provides easy method invocation.
+    @ivar plugins: A list of plugin objects.
+    @type plugins: [L{Plugin},]
+    @cvar ctxclass: A dict of plugin method / context classes.
+    @type ctxclass: dict
+    """
+
+    domains = {\
+        'init': (InitContext, InitPlugin),
+        'document': (DocumentContext, DocumentPlugin),
+        'message': (MessageContext, MessagePlugin ),
+    }
+
+    def __init__(self, plugins):
+        """
+        @param plugins: A list of plugin objects.
+        @type plugins: [L{Plugin},]
+        """
+        self.plugins = plugins
+
+    def __getattr__(self, name):
+        domain = self.domains.get(name)
+        if domain:
+            plugins = []
+            ctx, pclass = domain
+            for p in self.plugins:
+                if isinstance(p, pclass):
+                    plugins.append(p)
+            return PluginDomain(ctx, plugins)
+        else:
+            raise Exception, 'plugin domain (%s), invalid' % name
+
+
+class PluginDomain:
+    """
+    The plugin domain.
+    @ivar ctx: A context.
+    @type ctx: L{Context}
+    @ivar plugins: A list of plugins (targets).
+    @type plugins: list
+    """
+
+    def __init__(self, ctx, plugins):
+        self.ctx = ctx
+        self.plugins = plugins
+
+    def __getattr__(self, name):
+        return Method(name, self)
+
+
+class Method:
+    """
+    Plugin method.
+    @ivar name: The method name.
+    @type name: str
+    @ivar domain: The plugin domain.
+    @type domain: L{PluginDomain}
+    """
+
+    def __init__(self, name, domain):
+        """
+        @param name: The method name.
+        @type name: str
+        @param domain: A plugin domain.
+        @type domain: L{PluginDomain}
+        """
+        self.name = name
+        self.domain = domain
+
+    def __call__(self, **kwargs):
+        ctx = self.domain.ctx()
+        ctx.__dict__.update(kwargs)
+        for plugin in self.domain.plugins:
+            try:
+                method = getattr(plugin, self.name, None)
+                if method and callable(method):
+                    method(ctx)
+            except Exception, pe:
+                log.exception(pe)
+        return ctx
diff --git a/suds/properties.py b/suds/properties.py
new file mode 100644
index 0000000000000000000000000000000000000000..5907d943df4e69abbd3523ac4b5d40a438e8336e
--- /dev/null
+++ b/suds/properties.py
@@ -0,0 +1,539 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Properties classes.
+"""
+
+
+class AutoLinker(object):
+    """
+    Base class, provides interface for I{automatic} link
+    management between a L{Properties} object and the L{Properties}
+    contained within I{values}.
+    """
+    def updated(self, properties, prev, next):
+        """
+        Notification that a values was updated and the linkage
+        between the I{properties} contained with I{prev} need to
+        be relinked to the L{Properties} contained within the
+        I{next} value.
+        """
+        pass
+
+
+class Link(object):
+    """
+    Property link object.
+    @ivar endpoints: A tuple of the (2) endpoints of the link.
+    @type endpoints: tuple(2)
+    """
+    def __init__(self, a, b):
+        """
+        @param a: Property (A) to link.
+        @type a: L{Property}
+        @param b: Property (B) to link.
+        @type b: L{Property}
+        """
+        pA = Endpoint(self, a)
+        pB = Endpoint(self, b)
+        self.endpoints = (pA, pB)
+        self.validate(a, b)
+        a.links.append(pB)
+        b.links.append(pA)
+
+    def validate(self, pA, pB):
+        """
+        Validate that the two properties may be linked.
+        @param pA: Endpoint (A) to link.
+        @type pA: L{Endpoint}
+        @param pB: Endpoint (B) to link.
+        @type pB: L{Endpoint}
+        @return: self
+        @rtype: L{Link}
+        """
+        if pA in pB.links or \
+           pB in pA.links:
+            raise Exception, 'Already linked'
+        dA = pA.domains()
+        dB = pB.domains()
+        for d in dA:
+            if d in dB:
+                raise Exception, 'Duplicate domain "%s" found' % d
+        for d in dB:
+            if d in dA:
+                raise Exception, 'Duplicate domain "%s" found' % d
+        kA = pA.keys()
+        kB = pB.keys()
+        for k in kA:
+            if k in kB:
+                raise Exception, 'Duplicate key %s found' % k
+        for k in kB:
+            if k in kA:
+                raise Exception, 'Duplicate key %s found' % k
+        return self
+
+    def teardown(self):
+        """
+        Teardown the link.
+        Removes endpoints from properties I{links} collection.
+        @return: self
+        @rtype: L{Link}
+        """
+        pA, pB = self.endpoints
+        if pA in pB.links:
+            pB.links.remove(pA)
+        if pB in pA.links:
+            pA.links.remove(pB)
+        return self
+
+
+class Endpoint(object):
+    """
+    Link endpoint (wrapper).
+    @ivar link: The associated link.
+    @type link: L{Link}
+    @ivar target: The properties object.
+    @type target: L{Property}
+    """
+    def __init__(self, link, target):
+        self.link = link
+        self.target = target
+
+    def teardown(self):
+        return self.link.teardown()
+
+    def __eq__(self, rhs):
+        return ( self.target == rhs )
+
+    def __hash__(self):
+        return hash(self.target)
+
+    def __getattr__(self, name):
+        return getattr(self.target, name)
+
+
+class Definition:
+    """
+    Property definition.
+    @ivar name: The property name.
+    @type name: str
+    @ivar classes: The (class) list of permitted values
+    @type classes: tuple
+    @ivar default: The default value.
+    @ivar type: any
+    """
+    def __init__(self, name, classes, default, linker=AutoLinker()):
+        """
+        @param name: The property name.
+        @type name: str
+        @param classes: The (class) list of permitted values
+        @type classes: tuple
+        @param default: The default value.
+        @type default: any
+        """
+        if not isinstance(classes, (list, tuple)):
+            classes = (classes,)
+        self.name = name
+        self.classes = classes
+        self.default = default
+        self.linker = linker
+
+    def nvl(self, value=None):
+        """
+        Convert the I{value} into the default when I{None}.
+        @param value: The proposed value.
+        @type value: any
+        @return: The I{default} when I{value} is I{None}, else I{value}.
+        @rtype: any
+        """
+        if value is None:
+            return self.default
+        else:
+            return value
+
+    def validate(self, value):
+        """
+        Validate the I{value} is of the correct class.
+        @param value: The value to validate.
+        @type value: any
+        @raise AttributeError: When I{value} is invalid.
+        """
+        if value is None:
+            return
+        if len(self.classes) and \
+            not isinstance(value, self.classes):
+                msg = '"%s" must be: %s' % (self.name, self.classes)
+                raise AttributeError,msg
+
+
+    def __repr__(self):
+        return '%s: %s' % (self.name, str(self))
+
+    def __str__(self):
+        s = []
+        if len(self.classes):
+            s.append('classes=%s' % str(self.classes))
+        else:
+            s.append('classes=*')
+        s.append("default=%s" % str(self.default))
+        return ', '.join(s)
+
+
+class Properties:
+    """
+    Represents basic application properties.
+    Provides basic type validation, default values and
+    link/synchronization behavior.
+    @ivar domain: The domain name.
+    @type domain: str
+    @ivar definitions: A table of property definitions.
+    @type definitions: {name: L{Definition}}
+    @ivar links: A list of linked property objects used to create
+        a network of properties.
+    @type links: [L{Property},..]
+    @ivar defined: A dict of property values.
+    @type defined: dict
+    """
+    def __init__(self, domain, definitions, kwargs):
+        """
+        @param domain: The property domain name.
+        @type domain: str
+        @param definitions: A table of property definitions.
+        @type definitions: {name: L{Definition}}
+        @param kwargs: A list of property name/values to set.
+        @type kwargs: dict
+        """
+        self.definitions = {}
+        for d in definitions:
+            self.definitions[d.name] = d
+        self.domain = domain
+        self.links = []
+        self.defined = {}
+        self.modified = set()
+        self.prime()
+        self.update(kwargs)
+
+    def definition(self, name):
+        """
+        Get the definition for the property I{name}.
+        @param name: The property I{name} to find the definition for.
+        @type name: str
+        @return: The property definition
+        @rtype: L{Definition}
+        @raise AttributeError: On not found.
+        """
+        d = self.definitions.get(name)
+        if d is None:
+            raise AttributeError(name)
+        return d
+
+    def update(self, other):
+        """
+        Update the property values as specified by keyword/value.
+        @param other: An object to update from.
+        @type other: (dict|L{Properties})
+        @return: self
+        @rtype: L{Properties}
+        """
+        if isinstance(other, Properties):
+            other = other.defined
+        for n,v in other.items():
+            self.set(n, v)
+        return self
+
+    def notset(self, name):
+        """
+        Get whether a property has never been set by I{name}.
+        @param name: A property name.
+        @type name: str
+        @return: True if never been set.
+        @rtype: bool
+        """
+        self.provider(name).__notset(name)
+
+    def set(self, name, value):
+        """
+        Set the I{value} of a property by I{name}.
+        The value is validated against the definition and set
+        to the default when I{value} is None.
+        @param name: The property name.
+        @type name: str
+        @param value: The new property value.
+        @type value: any
+        @return: self
+        @rtype: L{Properties}
+        """
+        self.provider(name).__set(name, value)
+        return self
+
+    def unset(self, name):
+        """
+        Unset a property by I{name}.
+        @param name: A property name.
+        @type name: str
+        @return: self
+        @rtype: L{Properties}
+        """
+        self.provider(name).__set(name, None)
+        return self
+
+    def get(self, name, *df):
+        """
+        Get the value of a property by I{name}.
+        @param name: The property name.
+        @type name: str
+        @param df: An optional value to be returned when the value
+            is not set
+        @type df: [1].
+        @return: The stored value, or I{df[0]} if not set.
+        @rtype: any
+        """
+        return self.provider(name).__get(name, *df)
+
+    def link(self, other):
+        """
+        Link (associate) this object with anI{other} properties object
+        to create a network of properties.  Links are bidirectional.
+        @param other: The object to link.
+        @type other: L{Properties}
+        @return: self
+        @rtype: L{Properties}
+        """
+        Link(self, other)
+        return self
+
+    def unlink(self, *others):
+        """
+        Unlink (disassociate) the specified properties object.
+        @param others: The list object to unlink.  Unspecified means unlink all.
+        @type others: [L{Properties},..]
+        @return: self
+        @rtype: L{Properties}
+        """
+        if not len(others):
+            others = self.links[:]
+        for p in self.links[:]:
+            if p in others:
+                p.teardown()
+        return self
+
+    def provider(self, name, history=None):
+        """
+        Find the provider of the property by I{name}.
+        @param name: The property name.
+        @type name: str
+        @param history: A history of nodes checked to prevent
+            circular hunting.
+        @type history: [L{Properties},..]
+        @return: The provider when found.  Otherwise, None (when nested)
+            and I{self} when not nested.
+        @rtype: L{Properties}
+        """
+        if history is None:
+            history = []
+        history.append(self)
+        if name in self.definitions:
+            return self
+        for x in self.links:
+            if x in history:
+                continue
+            provider = x.provider(name, history)
+            if provider is not None:
+                return provider
+        history.remove(self)
+        if len(history):
+            return None
+        return self
+
+    def keys(self, history=None):
+        """
+        Get the set of I{all} property names.
+        @param history: A history of nodes checked to prevent
+            circular hunting.
+        @type history: [L{Properties},..]
+        @return: A set of property names.
+        @rtype: list
+        """
+        if history is None:
+            history = []
+        history.append(self)
+        keys = set()
+        keys.update(self.definitions.keys())
+        for x in self.links:
+            if x in history:
+                continue
+            keys.update(x.keys(history))
+        history.remove(self)
+        return keys
+
+    def domains(self, history=None):
+        """
+        Get the set of I{all} domain names.
+        @param history: A history of nodes checked to prevent
+            circular hunting.
+        @type history: [L{Properties},..]
+        @return: A set of domain names.
+        @rtype: list
+        """
+        if history is None:
+            history = []
+        history.append(self)
+        domains = set()
+        domains.add(self.domain)
+        for x in self.links:
+            if x in history:
+                continue
+            domains.update(x.domains(history))
+        history.remove(self)
+        return domains
+
+    def prime(self):
+        """
+        Prime the stored values based on default values
+        found in property definitions.
+        @return: self
+        @rtype: L{Properties}
+        """
+        for d in self.definitions.values():
+            self.defined[d.name] = d.default
+        return self
+
+    def __notset(self, name):
+        return not (name in self.modified)
+
+    def __set(self, name, value):
+        d = self.definition(name)
+        d.validate(value)
+        value = d.nvl(value)
+        prev = self.defined[name]
+        self.defined[name] = value
+        self.modified.add(name)
+        d.linker.updated(self, prev, value)
+
+    def __get(self, name, *df):
+        d = self.definition(name)
+        value = self.defined.get(name)
+        if value == d.default and len(df):
+            value = df[0]
+        return value
+
+    def str(self, history):
+        s = []
+        s.append('Definitions:')
+        for d in self.definitions.values():
+            s.append('\t%s' % repr(d))
+        s.append('Content:')
+        for d in self.defined.items():
+            s.append('\t%s' % str(d))
+        if self not in history:
+            history.append(self)
+            s.append('Linked:')
+            for x in self.links:
+                s.append(x.str(history))
+            history.remove(self)
+        return '\n'.join(s)
+
+    def __repr__(self):
+        return str(self)
+
+    def __str__(self):
+        return self.str([])
+
+
+class Skin(object):
+    """
+    The meta-programming I{skin} around the L{Properties} object.
+    @ivar __pts__: The wrapped object.
+    @type __pts__: L{Properties}.
+    """
+    def __init__(self, domain, definitions, kwargs):
+        self.__pts__ = Properties(domain, definitions, kwargs)
+
+    def __setattr__(self, name, value):
+        builtin = name.startswith('__') and name.endswith('__')
+        if builtin:
+            self.__dict__[name] = value
+            return
+        self.__pts__.set(name, value)
+
+    def __getattr__(self, name):
+        return self.__pts__.get(name)
+
+    def __repr__(self):
+        return str(self)
+
+    def __str__(self):
+        return str(self.__pts__)
+
+
+class Unskin(object):
+    def __new__(self, *args, **kwargs):
+        return args[0].__pts__
+
+
+class Inspector:
+    """
+    Wrapper inspector.
+    """
+    def __init__(self, options):
+        self.properties = options.__pts__
+
+    def get(self, name, *df):
+        """
+        Get the value of a property by I{name}.
+        @param name: The property name.
+        @type name: str
+        @param df: An optional value to be returned when the value
+            is not set
+        @type df: [1].
+        @return: The stored value, or I{df[0]} if not set.
+        @rtype: any
+        """
+        return self.properties.get(name, *df)
+
+    def update(self, **kwargs):
+        """
+        Update the property values as specified by keyword/value.
+        @param kwargs: A list of property name/values to set.
+        @type kwargs: dict
+        @return: self
+        @rtype: L{Properties}
+        """
+        return self.properties.update(**kwargs)
+
+    def link(self, other):
+        """
+        Link (associate) this object with anI{other} properties object
+        to create a network of properties.  Links are bidirectional.
+        @param other: The object to link.
+        @type other: L{Properties}
+        @return: self
+        @rtype: L{Properties}
+        """
+        p = other.__pts__
+        return self.properties.link(p)
+
+    def unlink(self, other):
+        """
+        Unlink (disassociate) the specified properties object.
+        @param other: The object to unlink.
+        @type other: L{Properties}
+        @return: self
+        @rtype: L{Properties}
+        """
+        p = other.__pts__
+        return self.properties.unlink(p)
diff --git a/suds/reader.py b/suds/reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad71642ab3ddb6b143fdb3a7d9033d397acd2741
--- /dev/null
+++ b/suds/reader.py
@@ -0,0 +1,166 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+  XML document reader classes providing integration with the suds library's
+caching system.
+"""
+
+
+from suds.cache import Cache, NoCache
+from suds.plugin import PluginContainer
+from suds.sax.parser import Parser
+from suds.store import DocumentStore
+from suds.transport import Request
+
+
+class Reader:
+    """
+    Provides integration with the cache.
+    @ivar options: An options object.
+    @type options: I{Options}
+    """
+
+    def __init__(self, options):
+        """
+        @param options: An options object.
+        @type options: I{Options}
+        """
+        self.options = options
+        self.plugins = PluginContainer(options.plugins)
+
+    def mangle(self, name, x):
+        """
+        Mangle the name by hashing the I{name} and appending I{x}.
+        @return: the mangled name.
+        """
+        h = abs(hash(name))
+        return '%s-%s' % (h, x)
+
+
+class DocumentReader(Reader):
+    """
+    Provides integration between the SAX L{Parser} and the document cache.
+    """
+
+    def open(self, url):
+        """
+        Open an XML document at the specified I{URL}.
+        First, the document attempted to be retrieved from the I{object cache}.
+        If not found, it is downloaded and parsed using the SAX parser. The
+        result is added to the cache for the next open().
+        @param url: A document URL.
+        @type url: str.
+        @return: The specified XML document.
+        @rtype: I{Document}
+        """
+        cache = self.cache()
+        id = self.mangle(url, 'document')
+        d = cache.get(id)
+        if d is None:
+            d = self.download(url)
+            cache.put(id, d)
+        self.plugins.document.parsed(url=url, document=d.root())
+        return d
+
+    def download(self, url):
+        """
+        Download the document.
+        @param url: A document URL.
+        @type url: str.
+        @return: A file pointer to the document.
+        @rtype: file-like
+        """
+        content = None
+        store = self.options.documentStore
+        if store is not None:
+            content = store.open(url)
+        if content is None:
+            fp = self.options.transport.open(Request(url))
+            try:
+                content = fp.read()
+            finally:
+                fp.close()
+        ctx = self.plugins.document.loaded(url=url, document=content)
+        content = ctx.document
+        sax = Parser()
+        return sax.parse(string=content)
+
+    def cache(self):
+        """
+        Get the cache.
+        @return: The I{cache} when I{cachingpolicy} = B{0}.
+        @rtype: L{Cache}
+        """
+        if self.options.cachingpolicy == 0:
+            return self.options.cache
+        return NoCache()
+
+
+class DefinitionsReader(Reader):
+    """
+    Provides integration between the WSDL Definitions object and the object
+    cache.
+    @ivar fn: A factory function (constructor) used to
+        create the object not found in the cache.
+    @type fn: I{Constructor}
+    """
+
+    def __init__(self, options, fn):
+        """
+        @param options: An options object.
+        @type options: I{Options}
+        @param fn: A factory function (constructor) used to create the object
+            not found in the cache.
+        @type fn: I{Constructor}
+        """
+        Reader.__init__(self, options)
+        self.fn = fn
+
+    def open(self, url):
+        """
+        Open a WSDL at the specified I{URL}.
+        First, the WSDL attempted to be retrieved from
+        the I{object cache}.  After unpickled from the cache, the
+        I{options} attribute is restored.
+        If not found, it is downloaded and instantiated using the
+        I{fn} constructor and added to the cache for the next open().
+        @param url: A WSDL URL.
+        @type url: str.
+        @return: The WSDL object.
+        @rtype: I{Definitions}
+        """
+        cache = self.cache()
+        id = self.mangle(url, 'wsdl')
+        d = cache.get(id)
+        if d is None:
+            d = self.fn(url, self.options)
+            cache.put(id, d)
+        else:
+            d.options = self.options
+            for imp in d.imports:
+                imp.imported.options = self.options
+        return d
+
+    def cache(self):
+        """
+        Get the cache.
+        @return: The I{cache} when I{cachingpolicy} = B{1}.
+        @rtype: L{Cache}
+        """
+        if self.options.cachingpolicy == 1:
+            return self.options.cache
+        return NoCache()
diff --git a/suds/resolver.py b/suds/resolver.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a3016962eae7347b23c0fde2acf8562e9055c05
--- /dev/null
+++ b/suds/resolver.py
@@ -0,0 +1,493 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{resolver} module provides a collection of classes that
+provide wsdl/xsd named type resolution.
+"""
+
+from suds import *
+from suds.sax import splitPrefix, Namespace
+from suds.sudsobject import Object
+from suds.xsd.query import BlindQuery, TypeQuery, qualify
+
+import re
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Resolver:
+    """
+    An I{abstract} schema-type resolver.
+    @ivar schema: A schema object.
+    @type schema: L{xsd.schema.Schema}
+    """
+
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        self.schema = schema
+
+    def find(self, name, resolved=True):
+        """
+        Get the definition object for the schema object by name.
+        @param name: The name of a schema object.
+        @type name: basestring
+        @param resolved: A flag indicating that the fully resolved type
+            should be returned.
+        @type resolved: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        log.debug('searching schema for (%s)', name)
+        qref = qualify(name, self.schema.root, self.schema.tns)
+        query = BlindQuery(qref)
+        result = query.execute(self.schema)
+        if result is None:
+            log.error('(%s) not-found', name)
+            return None
+        log.debug('found (%s) as (%s)', name, Repr(result))
+        if resolved:
+            result = result.resolve()
+        return result
+
+
+class PathResolver(Resolver):
+    """
+    Resolves the definition object for the schema type located at a given path.
+    The path may contain (.) dot notation to specify nested types.
+    @ivar wsdl: A wsdl object.
+    @type wsdl: L{wsdl.Definitions}
+    """
+
+    def __init__(self, wsdl, ps='.'):
+        """
+        @param wsdl: A schema object.
+        @type wsdl: L{wsdl.Definitions}
+        @param ps: The path separator character
+        @type ps: char
+        """
+        Resolver.__init__(self, wsdl.schema)
+        self.wsdl = wsdl
+        self.altp = re.compile('({)(.+)(})(.+)')
+        self.splitp = re.compile('({.+})*[^\%s]+' % ps[0])
+
+    def find(self, path, resolved=True):
+        """
+        Get the definition object for the schema type located at the specified path.
+        The path may contain (.) dot notation to specify nested types.
+        Actually, the path separator is usually a (.) but can be redefined
+        during contruction.
+        @param path: A (.) separated path to a schema type.
+        @type path: basestring
+        @param resolved: A flag indicating that the fully resolved type
+            should be returned.
+        @type resolved: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        result = None
+        parts = self.split(path)
+        try:
+            result = self.root(parts)
+            if len(parts) > 1:
+                result = result.resolve(nobuiltin=True)
+                result = self.branch(result, parts)
+                result = self.leaf(result, parts)
+            if resolved:
+                result = result.resolve(nobuiltin=True)
+        except PathResolver.BadPath:
+            log.error('path: "%s", not-found' % path)
+        return result
+
+    def root(self, parts):
+        """
+        Find the path root.
+        @param parts: A list of path parts.
+        @type parts: [str,..]
+        @return: The root.
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        result = None
+        name = parts[0]
+        log.debug('searching schema for (%s)', name)
+        qref = self.qualify(parts[0])
+        query = BlindQuery(qref)
+        result = query.execute(self.schema)
+        if result is None:
+            log.error('(%s) not-found', name)
+            raise PathResolver.BadPath(name)
+        log.debug('found (%s) as (%s)', name, Repr(result))
+        return result
+
+    def branch(self, root, parts):
+        """
+        Traverse the path until a leaf is reached.
+        @param parts: A list of path parts.
+        @type parts: [str,..]
+        @param root: The root.
+        @type root: L{xsd.sxbase.SchemaObject}
+        @return: The end of the branch.
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        result = root
+        for part in parts[1:-1]:
+            name = splitPrefix(part)[1]
+            log.debug('searching parent (%s) for (%s)', Repr(result), name)
+            result, ancestry = result.get_child(name)
+            if result is None:
+                log.error('(%s) not-found', name)
+                raise PathResolver.BadPath(name)
+            result = result.resolve(nobuiltin=True)
+            log.debug('found (%s) as (%s)', name, Repr(result))
+        return result
+
+    def leaf(self, parent, parts):
+        """
+        Find the leaf.
+        @param parts: A list of path parts.
+        @type parts: [str,..]
+        @param parent: The leaf's parent.
+        @type parent: L{xsd.sxbase.SchemaObject}
+        @return: The leaf.
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        name = splitPrefix(parts[-1])[1]
+        if name.startswith('@'):
+            result, path = parent.get_attribute(name[1:])
+        else:
+            result, ancestry = parent.get_child(name)
+        if result is None:
+            raise PathResolver.BadPath(name)
+        return result
+
+    def qualify(self, name):
+        """
+        Qualify the name as either:
+          - plain name
+          - ns prefixed name (eg: ns0:Person)
+          - fully ns qualified name (eg: {http://myns-uri}Person)
+        @param name: The name of an object in the schema.
+        @type name: str
+        @return: A qualified name.
+        @rtype: qname
+        """
+        m = self.altp.match(name)
+        if m is None:
+            return qualify(name, self.wsdl.root, self.wsdl.tns)
+        else:
+            return (m.group(4), m.group(2))
+
+    def split(self, s):
+        """
+        Split the string on (.) while preserving any (.) inside the
+        '{}' alternalte syntax for full ns qualification.
+        @param s: A plain or qualified name.
+        @type s: str
+        @return: A list of the name's parts.
+        @rtype: [str,..]
+        """
+        parts = []
+        b = 0
+        while 1:
+            m = self.splitp.match(s, b)
+            if m is None:
+                break
+            b,e = m.span()
+            parts.append(s[b:e])
+            b = e+1
+        return parts
+
+    class BadPath(Exception): pass
+
+
+class TreeResolver(Resolver):
+    """
+    The tree resolver is a I{stateful} tree resolver
+    used to resolve each node in a tree.  As such, it mirrors
+    the tree structure to ensure that nodes are resolved in
+    context.
+    @ivar stack: The context stack.
+    @type stack: list
+    """
+
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        Resolver.__init__(self, schema)
+        self.stack = Stack()
+
+    def reset(self):
+        """
+        Reset the resolver's state.
+        """
+        self.stack = Stack()
+
+    def push(self, x):
+        """
+        Push an I{object} onto the stack.
+        @param x: An object to push.
+        @type x: L{Frame}
+        @return: The pushed frame.
+        @rtype: L{Frame}
+        """
+        if isinstance(x, Frame):
+            frame = x
+        else:
+            frame = Frame(x)
+        self.stack.append(frame)
+        log.debug('push: (%s)\n%s', Repr(frame), Repr(self.stack))
+        return frame
+
+    def top(self):
+        """
+        Get the I{frame} at the top of the stack.
+        @return: The top I{frame}, else None.
+        @rtype: L{Frame}
+        """
+        if len(self.stack):
+            return self.stack[-1]
+        else:
+            return Frame.Empty()
+
+    def pop(self):
+        """
+        Pop the frame at the top of the stack.
+        @return: The popped frame, else None.
+        @rtype: L{Frame}
+        """
+        if len(self.stack):
+            popped = self.stack.pop()
+            log.debug('pop: (%s)\n%s', Repr(popped), Repr(self.stack))
+            return popped
+        log.debug('stack empty, not-popped')
+        return None
+
+    def depth(self):
+        """
+        Get the current stack depth.
+        @return: The current stack depth.
+        @rtype: int
+        """
+        return len(self.stack)
+
+    def getchild(self, name, parent):
+        """Get a child by name."""
+        log.debug('searching parent (%s) for (%s)', Repr(parent), name)
+        if name.startswith('@'):
+            return parent.get_attribute(name[1:])
+        return parent.get_child(name)
+
+
+class NodeResolver(TreeResolver):
+    """
+    The node resolver is a I{stateful} XML document resolver
+    used to resolve each node in a tree.  As such, it mirrors
+    the tree structure to ensure that nodes are resolved in
+    context.
+    """
+
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        TreeResolver.__init__(self, schema)
+
+    def find(self, node, resolved=False, push=True):
+        """
+        @param node: An xml node to be resolved.
+        @type node: L{sax.element.Element}
+        @param resolved: A flag indicating that the fully resolved type should be
+            returned.
+        @type resolved: boolean
+        @param push: Indicates that the resolved type should be
+            pushed onto the stack.
+        @type push: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        name = node.name
+        parent = self.top().resolved
+        if parent is None:
+            result, ancestry = self.query(name, node)
+        else:
+            result, ancestry = self.getchild(name, parent)
+        known = self.known(node)
+        if result is None:
+            return result
+        if push:
+            frame = Frame(result, resolved=known, ancestry=ancestry)
+            pushed = self.push(frame)
+        if resolved:
+            result = result.resolve()
+        return result
+
+    def findattr(self, name, resolved=True):
+        """
+        Find an attribute type definition.
+        @param name: An attribute name.
+        @type name: basestring
+        @param resolved: A flag indicating that the fully resolved type should be
+            returned.
+        @type resolved: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        name = '@%s'%name
+        parent = self.top().resolved
+        if parent is None:
+            result, ancestry = self.query(name, node)
+        else:
+            result, ancestry = self.getchild(name, parent)
+        if result is None:
+            return result
+        if resolved:
+            result = result.resolve()
+        return result
+
+    def query(self, name, node):
+        """Blindly query the schema by name."""
+        log.debug('searching schema for (%s)', name)
+        qref = qualify(name, node, node.namespace())
+        query = BlindQuery(qref)
+        result = query.execute(self.schema)
+        return (result, [])
+
+    def known(self, node):
+        """Resolve type referenced by @xsi:type."""
+        ref = node.get('type', Namespace.xsins)
+        if ref is None:
+            return None
+        qref = qualify(ref, node, node.namespace())
+        query = BlindQuery(qref)
+        return query.execute(self.schema)
+
+
+class GraphResolver(TreeResolver):
+    """
+    The graph resolver is a I{stateful} L{Object} graph resolver
+    used to resolve each node in a tree.  As such, it mirrors
+    the tree structure to ensure that nodes are resolved in
+    context.
+    """
+
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        TreeResolver.__init__(self, schema)
+
+    def find(self, name, object, resolved=False, push=True):
+        """
+        @param name: The name of the object to be resolved.
+        @type name: basestring
+        @param object: The name's value.
+        @type object: (any|L{Object})
+        @param resolved: A flag indicating that the fully resolved type
+            should be returned.
+        @type resolved: boolean
+        @param push: Indicates that the resolved type should be
+            pushed onto the stack.
+        @type push: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        known = None
+        parent = self.top().resolved
+        if parent is None:
+            result, ancestry = self.query(name)
+        else:
+            result, ancestry = self.getchild(name, parent)
+        if result is None:
+            return None
+        if isinstance(object, Object):
+            known = self.known(object)
+        if push:
+            frame = Frame(result, resolved=known, ancestry=ancestry)
+            pushed = self.push(frame)
+        if resolved:
+            if known is None:
+                result = result.resolve()
+            else:
+                result = known
+        return result
+
+    def query(self, name):
+        """Blindly query the schema by name."""
+        log.debug('searching schema for (%s)', name)
+        schema = self.schema
+        wsdl = self.wsdl()
+        if wsdl is None:
+            qref = qualify(name, schema.root, schema.tns)
+        else:
+            qref = qualify(name, wsdl.root, wsdl.tns)
+        query = BlindQuery(qref)
+        result = query.execute(schema)
+        return (result, [])
+
+    def wsdl(self):
+        """Get the wsdl."""
+        container = self.schema.container
+        if container is None:
+            return None
+        else:
+            return container.wsdl
+
+    def known(self, object):
+        """Get the type specified in the object's metadata."""
+        try:
+            md = object.__metadata__
+            known = md.sxtype
+            return known
+        except:
+            pass
+
+
+class Frame:
+    def __init__(self, type, resolved=None, ancestry=()):
+        self.type = type
+        if resolved is None:
+            resolved = type.resolve()
+        self.resolved = resolved.resolve()
+        self.ancestry = ancestry
+
+    def __str__(self):
+        return '%s\n%s\n%s' % \
+            (Repr(self.type),
+            Repr(self.resolved),
+            [Repr(t) for t in self.ancestry])
+
+    class Empty:
+        def __getattr__(self, name):
+            if name == 'ancestry':
+                return ()
+            else:
+                return None
+
+
+class Stack(list):
+    def __repr__(self):
+        result = []
+        for item in self:
+            result.append(repr(item))
+        return '\n'.join(result)
diff --git a/suds/sax/__init__.py b/suds/sax/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..be138239eb1a13ae1087576b9df8df5934e60b48
--- /dev/null
+++ b/suds/sax/__init__.py
@@ -0,0 +1,106 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The sax module contains a collection of classes that provide a (D)ocument
+(O)bject (M)odel representation of an XML document. The goal is to provide an
+easy, intuitive interface for managing XML documents. Although, the term, DOM,
+is used above, this model is B{far} better.
+
+XML namespaces in suds are represented using a (2) element tuple containing the
+prefix and the URI, e.g. I{('tns', 'http://myns')}
+
+@var encoder: A I{pluggable} XML special character processor used to encode/
+    decode strings.
+@type encoder: L{Encoder}
+"""
+
+from suds.sax.enc import Encoder
+
+#
+# pluggable XML special character encoder.
+#
+encoder = Encoder()
+
+
+def splitPrefix(name):
+    """
+    Split the name into a tuple (I{prefix}, I{name}). The first element in the
+    tuple is I{None} when the name does not have a prefix.
+    @param name: A node name containing an optional prefix.
+    @type name: basestring
+    @return: A tuple containing the (2) parts of I{name}
+    @rtype: (I{prefix}, I{name})
+    """
+    if isinstance(name, basestring) and ':' in name:
+        return tuple(name.split(':', 1))
+    return None, name
+
+
+class Namespace:
+    """
+    The namespace class represents XML namespaces.
+    """
+
+    default = (None, None)
+    xmlns = ('xml', 'http://www.w3.org/XML/1998/namespace')
+    xsdns = ('xs', 'http://www.w3.org/2001/XMLSchema')
+    xsins = ('xsi', 'http://www.w3.org/2001/XMLSchema-instance')
+    all = (xsdns, xsins)
+
+    @classmethod
+    def create(cls, p=None, u=None):
+        return p, u
+
+    @classmethod
+    def none(cls, ns):
+        return ns == cls.default
+
+    @classmethod
+    def xsd(cls, ns):
+        try:
+            return cls.w3(ns) and ns[1].endswith('XMLSchema')
+        except:
+            pass
+        return False
+
+    @classmethod
+    def xsi(cls, ns):
+        try:
+            return cls.w3(ns) and ns[1].endswith('XMLSchema-instance')
+        except:
+            pass
+        return False
+
+    @classmethod
+    def xs(cls, ns):
+        return cls.xsd(ns) or cls.xsi(ns)
+
+    @classmethod
+    def w3(cls, ns):
+        try:
+            return ns[1].startswith('http://www.w3.org')
+        except:
+            pass
+        return False
+
+    @classmethod
+    def isns(cls, ns):
+        try:
+            return isinstance(ns, tuple) and len(ns) == len(cls.default)
+        except:
+            pass
+        return False
diff --git a/suds/sax/attribute.py b/suds/sax/attribute.py
new file mode 100644
index 0000000000000000000000000000000000000000..61c5ad7a61e12fbb6ba8e2d9f5a4bb8bf6e1a5c6
--- /dev/null
+++ b/suds/sax/attribute.py
@@ -0,0 +1,174 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides XML I{attribute} classes.
+"""
+
+from suds import *
+from suds.sax import *
+from suds.sax.text import Text
+
+
+class Attribute(UnicodeMixin):
+    """
+    An XML attribute object.
+    @ivar parent: The node containing this attribute
+    @type parent: L{element.Element}
+    @ivar prefix: The I{optional} namespace prefix.
+    @type prefix: basestring
+    @ivar name: The I{unqualified} name of the attribute
+    @type name: basestring
+    @ivar value: The attribute's value
+    @type value: basestring
+    """
+    def __init__(self, name, value=None):
+        """
+        @param name: The attribute's name with I{optional} namespace prefix.
+        @type name: basestring
+        @param value: The attribute's value
+        @type value: basestring
+        """
+        self.parent = None
+        self.prefix, self.name = splitPrefix(name)
+        self.setValue(value)
+
+    def clone(self, parent=None):
+        """
+        Clone this object.
+        @param parent: The parent for the clone.
+        @type parent: L{element.Element}
+        @return: A copy of this object assigned to the new parent.
+        @rtype: L{Attribute}
+        """
+        a = Attribute(self.qname(), self.value)
+        a.parent = parent
+        return a
+
+    def qname(self):
+        """
+        Get the B{fully} qualified name of this attribute
+        @return: The fully qualified name.
+        @rtype: basestring
+        """
+        if self.prefix is None:
+            return self.name
+        else:
+            return ':'.join((self.prefix, self.name))
+
+    def setValue(self, value):
+        """
+        Set the attributes value
+        @param value: The new value (may be None)
+        @type value: basestring
+        @return: self
+        @rtype: L{Attribute}
+        """
+        if isinstance(value, Text):
+            self.value = value
+        else:
+            self.value = Text(value)
+        return self
+
+    def getValue(self, default=Text('')):
+        """
+        Get the attributes value with optional default.
+        @param default: An optional value to be return when the
+            attribute's has not been set.
+        @type default: basestring
+        @return: The attribute's value, or I{default}
+        @rtype: L{Text}
+        """
+        if self.hasText():
+            return self.value
+        else:
+            return default
+
+    def hasText(self):
+        """
+        Get whether the attribute has I{text} and that it is not an empty
+        (zero length) string.
+        @return: True when has I{text}.
+        @rtype: boolean
+        """
+        return ( self.value is not None and len(self.value) )
+
+    def namespace(self):
+        """
+        Get the attributes namespace.  This may either be the namespace
+        defined by an optional prefix, or its parent's namespace.
+        @return: The attribute's namespace
+        @rtype: (I{prefix}, I{name})
+        """
+        if self.prefix is None:
+            return Namespace.default
+        else:
+            return self.resolvePrefix(self.prefix)
+
+    def resolvePrefix(self, prefix):
+        """
+        Resolve the specified prefix to a known namespace.
+        @param prefix: A declared prefix
+        @type prefix: basestring
+        @return: The namespace that has been mapped to I{prefix}
+        @rtype: (I{prefix}, I{name})
+        """
+        ns = Namespace.default
+        if self.parent is not None:
+            ns = self.parent.resolvePrefix(prefix)
+        return ns
+
+    def match(self, name=None, ns=None):
+        """
+        Match by (optional) name and/or (optional) namespace.
+        @param name: The optional attribute tag name.
+        @type name: str
+        @param ns: An optional namespace.
+        @type ns: (I{prefix}, I{name})
+        @return: True if matched.
+        @rtype: boolean
+        """
+        if name is None:
+            byname = True
+        else:
+            byname = ( self.name == name )
+        if ns is None:
+            byns = True
+        else:
+            byns = ( self.namespace()[1] == ns[1] )
+        return ( byname and byns )
+
+    def __eq__(self, rhs):
+        """ equals operator """
+        return rhs is not None and \
+            isinstance(rhs, Attribute) and \
+            self.prefix == rhs.name and \
+            self.name == rhs.name
+
+    def __repr__(self):
+        """ get a string representation """
+        return \
+            'attr (prefix=%s, name=%s, value=(%s))' %\
+                (self.prefix, self.name, self.value)
+
+    def __unicode__(self):
+        """ get an xml string representation """
+        n = self.qname()
+        if self.hasText():
+            v = self.value.escape()
+        else:
+            v = self.value
+        return u'%s="%s"' % (n, v)
diff --git a/suds/sax/date.py b/suds/sax/date.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5e5e93dd0b0d0248add803858f3c633f69ea1e7
--- /dev/null
+++ b/suds/sax/date.py
@@ -0,0 +1,458 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr )
+# based on code by: Glen Walker
+# based on code by: Nathan Van Gheem ( vangheem@gmail.com )
+
+"""Classes for conversion between XML dates and Python objects."""
+
+from suds import UnicodeMixin
+
+import datetime
+import re
+import time
+
+
+_SNIPPET_DATE =  \
+    r"(?P<year>\d{1,})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
+_SNIPPET_TIME =  \
+    r"(?P<hour>\d{1,2}):(?P<minute>[0-5]?[0-9]):(?P<second>[0-5]?[0-9])"  \
+    r"(?:\.(?P<subsecond>\d+))?"
+_SNIPPET_ZONE =  \
+    r"(?:(?P<tz_sign>[-+])(?P<tz_hour>\d{1,2})"  \
+    r"(?::(?P<tz_minute>[0-5]?[0-9]))?)"  \
+    r"|(?P<tz_utc>[Zz])"
+
+_PATTERN_DATE = r"^%s(?:%s)?$" % (_SNIPPET_DATE, _SNIPPET_ZONE)
+_PATTERN_TIME = r"^%s(?:%s)?$" % (_SNIPPET_TIME, _SNIPPET_ZONE)
+_PATTERN_DATETIME = r"^%s[T ]%s(?:%s)?$" % (_SNIPPET_DATE, _SNIPPET_TIME,
+                                            _SNIPPET_ZONE)
+
+_RE_DATE = re.compile(_PATTERN_DATE)
+_RE_TIME = re.compile(_PATTERN_TIME)
+_RE_DATETIME = re.compile(_PATTERN_DATETIME)
+
+
+class Date(UnicodeMixin):
+    """
+    An XML date object supporting the xsd:date datatype.
+
+    @ivar value: The object value.
+    @type value: B{datetime}.I{date}
+
+    """
+
+    def __init__(self, value):
+        """
+        @param value: The date value of the object.
+        @type value: (datetime.date|str)
+        @raise ValueError: When I{value} is invalid.
+
+        """
+        if isinstance(value, datetime.datetime):
+            self.value = value.date()
+        elif isinstance(value, datetime.date):
+            self.value = value
+        elif isinstance(value, basestring):
+            self.value = self.__parse(value)
+        else:
+            raise ValueError("invalid type for Date(): %s" % type(value))
+
+    @staticmethod
+    def __parse(value):
+        """
+        Parse the string date.
+
+        Supports the subset of ISO8601 used by xsd:date, but is lenient with
+        what is accepted, handling most reasonable syntax.
+
+        Any timezone is parsed but ignored because a) it is meaningless without
+        a time and b) B{datetime}.I{date} does not support timezone
+        information.
+
+        @param value: A date string.
+        @type value: str
+        @return: A date object.
+        @rtype: B{datetime}.I{date}
+
+        """
+        match_result = _RE_DATE.match(value)
+        if match_result is None:
+            raise ValueError("date data has invalid format '%s'" % (value,))
+        return _date_from_match(match_result)
+
+    def __unicode__(self):
+        return self.value.isoformat()
+
+
+class DateTime(UnicodeMixin):
+    """
+    An XML datetime object supporting the xsd:dateTime datatype.
+
+    @ivar value: The object value.
+    @type value: B{datetime}.I{datetime}
+
+    """
+
+    def __init__(self, value):
+        """
+        @param value: The datetime value of the object.
+        @type value: (datetime.datetime|str)
+        @raise ValueError: When I{value} is invalid.
+
+        """
+        if isinstance(value, datetime.datetime):
+            self.value = value
+        elif isinstance(value, basestring):
+            self.value = self.__parse(value)
+        else:
+            raise ValueError("invalid type for DateTime(): %s" % type(value))
+
+    @staticmethod
+    def __parse(value):
+        """
+        Parse the string datetime.
+
+        Supports the subset of ISO8601 used by xsd:dateTime, but is lenient
+        with what is accepted, handling most reasonable syntax.
+
+        Subsecond information is rounded to microseconds due to a restriction
+        in the python datetime.datetime/time implementation.
+
+        @param value: A datetime string.
+        @type value: str
+        @return: A datetime object.
+        @rtype: B{datetime}.I{datetime}
+
+        """
+        match_result = _RE_DATETIME.match(value)
+        if match_result is None:
+           raise ValueError("date data has invalid format '%s'" % (value,))
+
+        date = _date_from_match(match_result)
+        time, round_up = _time_from_match(match_result)
+        tzinfo = _tzinfo_from_match(match_result)
+
+        value = datetime.datetime.combine(date, time)
+        value = value.replace(tzinfo=tzinfo)
+        if round_up:
+            value += datetime.timedelta(microseconds=1)
+        return value
+
+    def __unicode__(self):
+        return self.value.isoformat()
+
+
+class Time(UnicodeMixin):
+    """
+    An XML time object supporting the xsd:time datatype.
+
+    @ivar value: The object value.
+    @type value: B{datetime}.I{time}
+
+    """
+
+    def __init__(self, value):
+        """
+        @param value: The time value of the object.
+        @type value: (datetime.time|str)
+        @raise ValueError: When I{value} is invalid.
+
+        """
+        if isinstance(value, datetime.time):
+            self.value = value
+        elif isinstance(value, basestring):
+            self.value = self.__parse(value)
+        else:
+            raise ValueError("invalid type for Time(): %s" % type(value))
+
+    @staticmethod
+    def __parse(value):
+        """
+        Parse the string date.
+
+        Supports the subset of ISO8601 used by xsd:time, but is lenient with
+        what is accepted, handling most reasonable syntax.
+
+        Subsecond information is rounded to microseconds due to a restriction
+        in the python datetime.time implementation.
+
+        @param value: A time string.
+        @type value: str
+        @return: A time object.
+        @rtype: B{datetime}.I{time}
+
+        """
+        match_result = _RE_TIME.match(value)
+        if match_result is None:
+           raise ValueError("date data has invalid format '%s'" % (value,))
+
+        time, round_up = _time_from_match(match_result)
+        tzinfo = _tzinfo_from_match(match_result)
+        if round_up:
+            time = _bump_up_time_by_microsecond(time)
+        return time.replace(tzinfo=tzinfo)
+
+    def __unicode__(self):
+        return self.value.isoformat()
+
+
+class FixedOffsetTimezone(datetime.tzinfo, UnicodeMixin):
+    """
+    A timezone with a fixed offset and no daylight savings adjustment.
+
+    http://docs.python.org/library/datetime.html#datetime.tzinfo
+
+    """
+
+    def __init__(self, offset):
+        """
+        @param offset: The fixed offset of the timezone.
+        @type offset: I{int} or B{datetime}.I{timedelta}
+
+        """
+        if type(offset) == int:
+            offset = datetime.timedelta(hours=offset)
+        elif type(offset) != datetime.timedelta:
+            raise TypeError("timezone offset must be an int or "
+                "datetime.timedelta")
+        if offset.microseconds or (offset.seconds % 60 != 0):
+            raise ValueError("timezone offset must have minute precision")
+        self.__offset = offset
+
+    def dst(self, dt):
+        """
+        http://docs.python.org/library/datetime.html#datetime.tzinfo.dst
+
+        """
+        return datetime.timedelta(0)
+
+    def utcoffset(self, dt):
+        """
+        http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset
+
+        """
+        return self.__offset
+
+    def tzname(self, dt):
+        """
+        http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
+
+        """
+        # total_seconds was introduced in Python 2.7
+        if hasattr(self.__offset, "total_seconds"):
+            total_seconds = self.__offset.total_seconds()
+        else:
+            total_seconds = (self.__offset.days * 24 * 60 * 60) + \
+                            (self.__offset.seconds)
+
+        hours = total_seconds // (60 * 60)
+        total_seconds -= hours * 60 * 60
+
+        minutes = total_seconds // 60
+        total_seconds -= minutes * 60
+
+        seconds = total_seconds // 1
+        total_seconds -= seconds
+
+        if seconds:
+            return "%+03d:%02d:%02d" % (hours, minutes, seconds)
+        return "%+03d:%02d" % (hours, minutes)
+
+    def __unicode__(self):
+        return "FixedOffsetTimezone %s" % (self.tzname(None),)
+
+
+class UtcTimezone(FixedOffsetTimezone):
+    """
+    The UTC timezone.
+
+    http://docs.python.org/library/datetime.html#datetime.tzinfo
+
+    """
+
+    def __init__(self):
+        FixedOffsetTimezone.__init__(self, datetime.timedelta(0))
+
+    def tzname(self, dt):
+        """
+        http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
+
+        """
+        return "UTC"
+
+    def __unicode__(self):
+        return "UtcTimezone"
+
+
+class LocalTimezone(datetime.tzinfo):
+    """
+    The local timezone of the operating system.
+
+    http://docs.python.org/library/datetime.html#datetime.tzinfo
+
+    """
+
+    def __init__(self):
+        self.__offset = datetime.timedelta(seconds=-time.timezone)
+        self.__dst_offset = None
+        if time.daylight:
+            self.__dst_offset = datetime.timedelta(seconds=-time.altzone)
+
+    def dst(self, dt):
+        """
+        http://docs.python.org/library/datetime.html#datetime.tzinfo.dst
+
+        """
+        if self.__is_daylight_time(dt):
+            return self.__dst_offset - self.__offset
+        return datetime.timedelta(0)
+
+    def tzname(self, dt):
+        """
+        http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
+
+        """
+        if self.__is_daylight_time(dt):
+            return time.tzname[1]
+        return time.tzname[0]
+
+    def utcoffset(self, dt):
+        """
+        http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset
+
+        """
+        if self.__is_daylight_time(dt):
+            return self.__dst_offset
+        return self.__offset
+
+    def __is_daylight_time(self, dt):
+        if not time.daylight:
+            return False
+        time_tuple = dt.replace(tzinfo=None).timetuple()
+        time_tuple = time.localtime(time.mktime(time_tuple))
+        return time_tuple.tm_isdst > 0
+
+    def __unicode__(self):
+        dt = datetime.datetime.now()
+        return "LocalTimezone %s offset: %s dst: %s" % (self.tzname(dt),
+            self.utcoffset(dt), self.dst(dt))
+
+
+def _bump_up_time_by_microsecond(time):
+    """
+    Helper function bumping up the given datetime.time by a microsecond,
+    cycling around silently to 00:00:00.0 in case of an overflow.
+
+    @param time: Time object.
+    @type value: B{datetime}.I{time}
+    @return: Time object.
+    @rtype: B{datetime}.I{time}
+
+    """
+    dt = datetime.datetime(2000, 1, 1, time.hour, time.minute,
+        time.second, time.microsecond)
+    dt += datetime.timedelta(microseconds=1)
+    return dt.time()
+
+
+def _date_from_match(match_object):
+    """
+    Create a date object from a regular expression match.
+
+    The regular expression match is expected to be from _RE_DATE or
+    _RE_DATETIME.
+
+    @param match_object: The regular expression match.
+    @type value: B{re}.I{MatchObject}
+    @return: A date object.
+    @rtype: B{datetime}.I{date}
+
+    """
+    year = int(match_object.group("year"))
+    month = int(match_object.group("month"))
+    day = int(match_object.group("day"))
+    return datetime.date(year, month, day)
+
+
+def _time_from_match(match_object):
+    """
+    Create a time object from a regular expression match.
+
+    Returns the time object and information whether the resulting time should
+    be bumped up by one microsecond due to microsecond rounding.
+
+    Subsecond information is rounded to microseconds due to a restriction in
+    the python datetime.datetime/time implementation.
+
+    The regular expression match is expected to be from _RE_DATETIME or
+    _RE_TIME.
+
+    @param match_object: The regular expression match.
+    @type value: B{re}.I{MatchObject}
+    @return: Time object + rounding flag.
+    @rtype: tuple of B{datetime}.I{time} and bool
+
+    """
+    hour = int(match_object.group('hour'))
+    minute = int(match_object.group('minute'))
+    second = int(match_object.group('second'))
+    subsecond = match_object.group('subsecond')
+
+    round_up = False
+    microsecond = 0
+    if subsecond:
+        round_up = len(subsecond) > 6 and int(subsecond[6]) >= 5
+        subsecond = subsecond[:6]
+        microsecond = int(subsecond + "0" * (6 - len(subsecond)))
+    return datetime.time(hour, minute, second, microsecond), round_up
+
+
+def _tzinfo_from_match(match_object):
+    """
+    Create a timezone information object from a regular expression match.
+
+    The regular expression match is expected to be from _RE_DATE, _RE_DATETIME
+    or _RE_TIME.
+
+    @param match_object: The regular expression match.
+    @type value: B{re}.I{MatchObject}
+    @return: A timezone information object.
+    @rtype: B{datetime}.I{tzinfo}
+
+    """
+    tz_utc = match_object.group("tz_utc")
+    if tz_utc:
+        return UtcTimezone()
+
+    tz_sign = match_object.group("tz_sign")
+    if not tz_sign:
+        return
+
+    h = int(match_object.group("tz_hour") or 0)
+    m = int(match_object.group("tz_minute") or 0)
+    if h == 0 and m == 0:
+        return UtcTimezone()
+
+    # Python limitation - timezone offsets larger than one day (in absolute)
+    # will cause operations depending on tzinfo.utcoffset() to fail, e.g.
+    # comparing two timezone aware datetime.datetime/time objects.
+    if h >= 24:
+        raise ValueError("timezone indicator too large")
+
+    tz_delta = datetime.timedelta(hours=h, minutes=m)
+    if tz_sign == "-":
+        tz_delta *= -1
+    return FixedOffsetTimezone(tz_delta)
diff --git a/suds/sax/document.py b/suds/sax/document.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a4a615d49ec1f10790d9c736c5cf5726929d6bf
--- /dev/null
+++ b/suds/sax/document.py
@@ -0,0 +1,176 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides XML I{document} classes.
+"""
+
+from suds import *
+from suds.sax import *
+from suds.sax.element import Element
+
+
+class Document:
+    """ An XML Document """
+
+    DECL = '<?xml version="1.0" encoding="UTF-8"?>'
+
+    def __init__(self, root=None):
+        """
+        @param root: A root L{Element} or name used to build
+            the document root element.
+        @type root: (L{Element}|str|None)
+        """
+        self.__root = None
+        self.append(root)
+
+    def root(self):
+        """
+        Get the document root element (can be None)
+        @return: The document root.
+        @rtype: L{Element}
+        """
+        return self.__root
+
+    def append(self, node):
+        """
+        Append (set) the document root.
+        @param node: A root L{Element} or name used to build
+            the document root element.
+        @type node: (L{Element}|str|None)
+        """
+        if isinstance(node, basestring):
+            self.__root = Element(node)
+            return
+        if isinstance(node, Element):
+            self.__root = node
+            return
+
+    def getChild(self, name, ns=None, default=None):
+        """
+        Get a child by (optional) name and/or (optional) namespace.
+        @param name: The name of a child element (may contain prefix).
+        @type name: basestring
+        @param ns: An optional namespace used to match the child.
+        @type ns: (I{prefix}, I{name})
+        @param default: Returned when child not-found.
+        @type default: L{Element}
+        @return: The requested child, or I{default} when not-found.
+        @rtype: L{Element}
+        """
+        if self.__root is None:
+            return default
+        if ns is None:
+            prefix, name = splitPrefix(name)
+            if prefix is None:
+                ns = None
+            else:
+                ns = self.__root.resolvePrefix(prefix)
+        if self.__root.match(name, ns):
+            return self.__root
+        else:
+            return default
+
+    def childAtPath(self, path):
+        """
+        Get a child at I{path} where I{path} is a (/) separated
+        list of element names that are expected to be children.
+        @param path: A (/) separated list of element names.
+        @type path: basestring
+        @return: The leaf node at the end of I{path}
+        @rtype: L{Element}
+        """
+        if self.__root is None:
+            return None
+        if path[0] == '/':
+            path = path[1:]
+        path = path.split('/',1)
+        if self.getChild(path[0]) is None:
+            return None
+        if len(path) > 1:
+            return self.__root.childAtPath(path[1])
+        else:
+            return self.__root
+
+    def childrenAtPath(self, path):
+        """
+        Get a list of children at I{path} where I{path} is a (/) separated
+        list of element names that are expected to be children.
+        @param path: A (/) separated list of element names.
+        @type path: basestring
+        @return: The collection leaf nodes at the end of I{path}
+        @rtype: [L{Element},...]
+        """
+        if self.__root is None:
+            return []
+        if path[0] == '/':
+            path = path[1:]
+        path = path.split('/',1)
+        if self.getChild(path[0]) is None:
+            return []
+        if len(path) > 1:
+            return self.__root.childrenAtPath(path[1])
+        else:
+            return [self.__root,]
+
+    def getChildren(self, name=None, ns=None):
+        """
+        Get a list of children by (optional) name and/or (optional) namespace.
+        @param name: The name of a child element (may contain prefix).
+        @type name: basestring
+        @param ns: An optional namespace used to match the child.
+        @type ns: (I{prefix}, I{name})
+        @return: The list of matching children.
+        @rtype: [L{Element},...]
+        """
+        if name is None:
+            matched = self.__root
+        else:
+            matched = self.getChild(name, ns)
+        if matched is None:
+            return []
+        else:
+            return [matched,]
+
+    def str(self):
+        """
+        Get a string representation of this XML document.
+        @return: A I{pretty} string.
+        @rtype: basestring
+        """
+        s = []
+        s.append(self.DECL)
+        root = self.root()
+        if root is not None:
+            s.append('\n')
+            s.append(root.str())
+        return ''.join(s)
+
+    def plain(self):
+        """
+        Get a string representation of this XML document.
+        @return: A I{plain} string.
+        @rtype: basestring
+        """
+        s = []
+        s.append(self.DECL)
+        root = self.root()
+        if root is not None:
+            s.append(root.plain())
+        return ''.join(s)
+
+    def __unicode__(self):
+        return self.str()
diff --git a/suds/sax/element.py b/suds/sax/element.py
new file mode 100644
index 0000000000000000000000000000000000000000..084ea2b05a0d49edfd9183138f0d452c5408a4ad
--- /dev/null
+++ b/suds/sax/element.py
@@ -0,0 +1,1106 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides XML I{element} classes.
+"""
+
+from suds import *
+from suds.sax import *
+from suds.sax.text import Text
+from suds.sax.attribute import Attribute
+
+
+class Element(UnicodeMixin):
+    """
+    An XML element object.
+    @ivar parent: The node containing this attribute
+    @type parent: L{Element}
+    @ivar prefix: The I{optional} namespace prefix.
+    @type prefix: basestring
+    @ivar name: The I{unqualified} name of the attribute
+    @type name: basestring
+    @ivar expns: An explicit namespace (xmlns="...").
+    @type expns: (I{prefix}, I{name})
+    @ivar nsprefixes: A mapping of prefixes to namespaces.
+    @type nsprefixes: dict
+    @ivar attributes: A list of XML attributes.
+    @type attributes: [I{Attribute},]
+    @ivar text: The element's I{text} content.
+    @type text: basestring
+    @ivar children: A list of child elements.
+    @type children: [I{Element},]
+    @cvar matcher: A collection of I{lambda} for string matching.
+    @cvar specialprefixes: A dictionary of builtin-special prefixes.
+    """
+
+    matcher = {
+        'eq': lambda a,b: a == b,
+        'startswith' : lambda a,b: a.startswith(b),
+        'endswith' : lambda a,b: a.endswith(b),
+        'contains' : lambda a,b: b in a}
+
+    specialprefixes = {Namespace.xmlns[0] : Namespace.xmlns[1]}
+
+    @classmethod
+    def buildPath(self, parent, path):
+        """Build the specifed path as a/b/c.
+
+        Any missing intermediate nodes are built automatically.
+        @param parent: A parent element on which the path is built.
+        @type parent: I{Element}
+        @param path: A simple path separated by (/).
+        @type path: basestring
+        @return: The leaf node of I{path}.
+        @rtype: L{Element}
+        """
+        for tag in path.split('/'):
+            child = parent.getChild(tag)
+            if child is None:
+                child = Element(tag, parent)
+            parent = child
+        return child
+
+    def __init__(self, name, parent=None, ns=None):
+        """
+        @param name: The element's (tag) name. May contain a prefix.
+        @type name: basestring
+        @param parent: An optional parent element.
+        @type parent: I{Element}
+        @param ns: An optional namespace.
+        @type ns: (I{prefix}, I{name})
+        """
+        self.rename(name)
+        self.expns = None
+        self.nsprefixes = {}
+        self.attributes = []
+        self.text = None
+        if parent is not None:
+            if isinstance(parent, Element):
+                self.parent = parent
+            else:
+                raise Exception('parent (%s) not-valid', parent.__class__.__name__)
+        else:
+            self.parent = None
+        self.children = []
+        self.applyns(ns)
+
+    def rename(self, name):
+        """
+        Rename the element.
+        @param name: A new name for the element.
+        @type name: basestring
+        """
+        if name is None:
+            raise Exception('name (%s) not-valid' % name)
+        else:
+            self.prefix, self.name = splitPrefix(name)
+
+    def setPrefix(self, p, u=None):
+        """
+        Set the element namespace prefix.
+        @param p: A new prefix for the element.
+        @type p: basestring
+        @param u: A namespace URI to be mapped to the prefix.
+        @type u: basestring
+        @return: self
+        @rtype: L{Element}
+        """
+        self.prefix = p
+        if p is not None and u is not None:
+            self.expns = None
+            self.addPrefix(p, u)
+        return self
+
+    def qname(self):
+        """
+        Get the B{fully} qualified name of this element
+        @return: The fully qualified name.
+        @rtype: basestring
+        """
+        if self.prefix is None:
+            return self.name
+        return '%s:%s' % (self.prefix, self.name)
+
+    def getRoot(self):
+        """
+        Get the root (top) node of the tree.
+        @return: The I{top} node of this tree.
+        @rtype: I{Element}
+        """
+        if self.parent is None:
+            return self
+        return self.parent.getRoot()
+
+    def clone(self, parent=None):
+        """
+        Deep clone of this element and children.
+        @param parent: An optional parent for the copied fragment.
+        @type parent: I{Element}
+        @return: A deep copy parented by I{parent}
+        @rtype: I{Element}
+        """
+        root = Element(self.qname(), parent, self.namespace())
+        for a in self.attributes:
+            root.append(a.clone(self))
+        for c in self.children:
+            root.append(c.clone(self))
+        for item in self.nsprefixes.items():
+            root.addPrefix(item[0], item[1])
+        return root
+
+    def detach(self):
+        """
+        Detach from parent.
+        @return: This element removed from its parent's
+            child list and I{parent}=I{None}
+        @rtype: L{Element}
+        """
+        if self.parent is not None:
+            if self in self.parent.children:
+                self.parent.children.remove(self)
+            self.parent = None
+        return self
+
+    def set(self, name, value):
+        """
+        Set an attribute's value.
+        @param name: The name of the attribute.
+        @type name: basestring
+        @param value: The attribute value.
+        @type value: basestring
+        @see: __setitem__()
+        """
+        attr = self.getAttribute(name)
+        if attr is None:
+            attr = Attribute(name, value)
+            self.append(attr)
+        else:
+            attr.setValue(value)
+
+    def unset(self, name):
+        """
+        Unset (remove) an attribute.
+        @param name: The attribute name.
+        @type name: str
+        @return: self
+        @rtype: L{Element}
+        """
+        try:
+            attr = self.getAttribute(name)
+            self.attributes.remove(attr)
+        except:
+            pass
+        return self
+
+    def get(self, name, ns=None, default=None):
+        """
+        Get the value of an attribute by name.
+        @param name: The name of the attribute.
+        @type name: basestring
+        @param ns: The optional attribute's namespace.
+        @type ns: (I{prefix}, I{name})
+        @param default: An optional value to be returned when either
+            the attribute does not exist of has not value.
+        @type default: basestring
+        @return: The attribute's value or I{default}
+        @rtype: basestring
+        @see: __getitem__()
+        """
+        attr = self.getAttribute(name, ns)
+        if attr is None or attr.value is None:
+            return default
+        return attr.getValue()
+
+    def setText(self, value):
+        """
+        Set the element's L{Text} content.
+        @param value: The element's text value.
+        @type value: basestring
+        @return: self
+        @rtype: I{Element}
+        """
+        if isinstance(value, Text):
+            self.text = value
+        else:
+            self.text = Text(value)
+        return self
+
+    def getText(self, default=None):
+        """
+        Get the element's L{Text} content with optional default
+        @param default: A value to be returned when no text content exists.
+        @type default: basestring
+        @return: The text content, or I{default}
+        @rtype: L{Text}
+        """
+        if self.hasText():
+            return self.text
+        return default
+
+    def trim(self):
+        """
+        Trim leading and trailing whitespace.
+        @return: self
+        @rtype: L{Element}
+        """
+        if self.hasText():
+            self.text = self.text.trim()
+        return self
+
+    def hasText(self):
+        """
+        Get whether the element has I{text} and that it is not an empty
+        (zero length) string.
+        @return: True when has I{text}.
+        @rtype: boolean
+        """
+        return self.text is not None and len(self.text)
+
+    def namespace(self):
+        """
+        Get the element's namespace.
+        @return: The element's namespace by resolving the prefix, the explicit
+            namespace or the inherited namespace.
+        @rtype: (I{prefix}, I{name})
+        """
+        if self.prefix is None:
+            return self.defaultNamespace()
+        return self.resolvePrefix(self.prefix)
+
+    def defaultNamespace(self):
+        """
+        Get the default (unqualified namespace).
+        This is the expns of the first node (looking up the tree)
+        that has it set.
+        @return: The namespace of a node when not qualified.
+        @rtype: (I{prefix}, I{name})
+        """
+        p = self
+        while p is not None:
+            if p.expns is not None:
+                return None, p.expns
+            p = p.parent
+        return Namespace.default
+
+    def append(self, objects):
+        """
+        Append the specified child based on whether it is an element or an
+        attribute.
+        @param objects: A (single|collection) of attribute(s) or element(s)
+            to be added as children.
+        @type objects: (L{Element}|L{Attribute})
+        @return: self
+        @rtype: L{Element}
+        """
+        if not isinstance(objects, (list, tuple)):
+            objects = (objects,)
+        for child in objects:
+            if isinstance(child, Element):
+                self.children.append(child)
+                child.parent = self
+                continue
+            if isinstance(child, Attribute):
+                self.attributes.append(child)
+                child.parent = self
+                continue
+            raise Exception('append %s not-valid' % child.__class__.__name__)
+        return self
+
+    def insert(self, objects, index=0):
+        """
+        Insert an L{Element} content at the specified index.
+        @param objects: A (single|collection) of attribute(s) or element(s) to
+            be added as children.
+        @type objects: (L{Element}|L{Attribute})
+        @param index: The position in the list of children to insert.
+        @type index: int
+        @return: self
+        @rtype: L{Element}
+        """
+        objects = (objects,)
+        for child in objects:
+            if isinstance(child, Element):
+                self.children.insert(index, child)
+                child.parent = self
+            else:
+                raise Exception('append %s not-valid' % child.__class__.__name__)
+        return self
+
+    def remove(self, child):
+        """
+        Remove the specified child element or attribute.
+        @param child: A child to remove.
+        @type child: L{Element}|L{Attribute}
+        @return: The detached I{child} when I{child} is an element, else None.
+        @rtype: L{Element}|None
+        """
+        if isinstance(child, Element):
+            return child.detach()
+        if isinstance(child, Attribute):
+            self.attributes.remove(child)
+        return None
+
+    def replaceChild(self, child, content):
+        """
+        Replace I{child} with the specified I{content}.
+        @param child: A child element.
+        @type child: L{Element}
+        @param content: An element or collection of elements.
+        @type content: L{Element} or [L{Element},]
+        """
+        if child not in self.children:
+            raise Exception('child not-found')
+        index = self.children.index(child)
+        self.remove(child)
+        if not isinstance(content, (list, tuple)):
+            content = (content,)
+        for node in content:
+            self.children.insert(index, node.detach())
+            node.parent = self
+            index += 1
+
+    def getAttribute(self, name, ns=None, default=None):
+        """
+        Get an attribute by name and (optional) namespace
+        @param name: The name of a contained attribute (may contain prefix).
+        @type name: basestring
+        @param ns: An optional namespace
+        @type ns: (I{prefix}, I{name})
+        @param default: Returned when attribute not-found.
+        @type default: L{Attribute}
+        @return: The requested attribute object.
+        @rtype: L{Attribute}
+        """
+        if ns is None:
+            prefix, name = splitPrefix(name)
+            if prefix is None:
+                ns = None
+            else:
+                ns = self.resolvePrefix(prefix)
+        for a in self.attributes:
+            if a.match(name, ns):
+                return a
+        return default
+
+    def getChild(self, name, ns=None, default=None):
+        """
+        Get a child by (optional) name and/or (optional) namespace.
+        @param name: The name of a child element (may contain prefix).
+        @type name: basestring
+        @param ns: An optional namespace used to match the child.
+        @type ns: (I{prefix}, I{name})
+        @param default: Returned when child not-found.
+        @type default: L{Element}
+        @return: The requested child, or I{default} when not-found.
+        @rtype: L{Element}
+        """
+        if ns is None:
+            prefix, name = splitPrefix(name)
+            if prefix is None:
+                ns = None
+            else:
+                ns = self.resolvePrefix(prefix)
+        for c in self.children:
+            if c.match(name, ns):
+                return c
+        return default
+
+    def childAtPath(self, path):
+        """
+        Get a child at I{path} where I{path} is a (/) separated
+        list of element names that are expected to be children.
+        @param path: A (/) separated list of element names.
+        @type path: basestring
+        @return: The leaf node at the end of I{path}
+        @rtype: L{Element}
+        """
+        result = None
+        node = self
+        for name in [p for p in path.split('/') if len(p) > 0]:
+            ns = None
+            prefix, name = splitPrefix(name)
+            if prefix is not None:
+                ns = node.resolvePrefix(prefix)
+            result = node.getChild(name, ns)
+            if result is None:
+                break;
+            else:
+                node = result
+        return result
+
+    def childrenAtPath(self, path):
+        """
+        Get a list of children at I{path} where I{path} is a (/) separated
+        list of element names that are expected to be children.
+        @param path: A (/) separated list of element names.
+        @type path: basestring
+        @return: The collection leaf nodes at the end of I{path}
+        @rtype: [L{Element},...]
+        """
+        parts = [p for p in path.split('/') if len(p) > 0]
+        if len(parts) == 1:
+            result = self.getChildren(path)
+        else:
+            result = self.__childrenAtPath(parts)
+        return result
+
+    def getChildren(self, name=None, ns=None):
+        """
+        Get a list of children by (optional) name and/or (optional) namespace.
+        @param name: The name of a child element (may contain prefix).
+        @type name: basestring
+        @param ns: An optional namespace used to match the child.
+        @type ns: (I{prefix}, I{name})
+        @return: The list of matching children.
+        @rtype: [L{Element},...]
+        """
+        if ns is None:
+            if name is None:
+                return self.children
+            prefix, name = splitPrefix(name)
+            if prefix is None:
+                ns = None
+            else:
+                ns = self.resolvePrefix(prefix)
+        return [c for c in self.children if c.match(name, ns)]
+
+    def detachChildren(self):
+        """
+        Detach and return this element's children.
+        @return: The element's children (detached).
+        @rtype: [L{Element},...]
+        """
+        detached = self.children
+        self.children = []
+        for child in detached:
+            child.parent = None
+        return detached
+
+    def resolvePrefix(self, prefix, default=Namespace.default):
+        """
+        Resolve the specified prefix to a namespace.  The I{nsprefixes} is
+        searched.  If not found, it walks up the tree until either resolved or
+        the top of the tree is reached.  Searching up the tree provides for
+        inherited mappings.
+        @param prefix: A namespace prefix to resolve.
+        @type prefix: basestring
+        @param default: An optional value to be returned when the prefix
+            cannot be resolved.
+        @type default: (I{prefix},I{URI})
+        @return: The namespace that is mapped to I{prefix} in this context.
+        @rtype: (I{prefix},I{URI})
+        """
+        n = self
+        while n is not None:
+            if prefix in n.nsprefixes:
+                return prefix, n.nsprefixes[prefix]
+            if prefix in self.specialprefixes:
+                return prefix, self.specialprefixes[prefix]
+            n = n.parent
+        return default
+
+    def addPrefix(self, p, u):
+        """
+        Add or update a prefix mapping.
+        @param p: A prefix.
+        @type p: basestring
+        @param u: A namespace URI.
+        @type u: basestring
+        @return: self
+        @rtype: L{Element}
+        """
+        self.nsprefixes[p] = u
+        return self
+
+    def updatePrefix(self, p, u):
+        """
+        Update (redefine) a prefix mapping for the branch.
+        @param p: A prefix.
+        @type p: basestring
+        @param u: A namespace URI.
+        @type u: basestring
+        @return: self
+        @rtype: L{Element}
+        @note: This method traverses down the entire branch!
+        """
+        if p in self.nsprefixes:
+            self.nsprefixes[p] = u
+        for c in self.children:
+            c.updatePrefix(p, u)
+        return self
+
+    def clearPrefix(self, prefix):
+        """
+        Clear the specified prefix from the prefix mappings.
+        @param prefix: A prefix to clear.
+        @type prefix: basestring
+        @return: self
+        @rtype: L{Element}
+        """
+        if prefix in self.nsprefixes:
+            del self.nsprefixes[prefix]
+        return self
+
+    def findPrefix(self, uri, default=None):
+        """
+        Find the first prefix that has been mapped to a namespace URI.
+        The local mapping is searched, then it walks up the tree until
+        it reaches the top or finds a match.
+        @param uri: A namespace URI.
+        @type uri: basestring
+        @param default: A default prefix when not found.
+        @type default: basestring
+        @return: A mapped prefix.
+        @rtype: basestring
+        """
+        for item in self.nsprefixes.items():
+            if item[1] == uri:
+                prefix = item[0]
+                return prefix
+        for item in self.specialprefixes.items():
+            if item[1] == uri:
+                prefix = item[0]
+                return prefix
+        if self.parent is not None:
+            return self.parent.findPrefix(uri, default)
+        return default
+
+    def findPrefixes(self, uri, match='eq'):
+        """
+        Find all prefixes that have been mapped to a namespace URI.
+        The local mapping is searched, then it walks up the tree until it
+        reaches the top, collecting all matches.
+        @param uri: A namespace URI.
+        @type uri: basestring
+        @param match: A matching function L{Element.matcher}.
+        @type match: basestring
+        @return: A list of mapped prefixes.
+        @rtype: [basestring,...]
+        """
+        result = []
+        for item in self.nsprefixes.items():
+            if self.matcher[match](item[1], uri):
+                prefix = item[0]
+                result.append(prefix)
+        for item in self.specialprefixes.items():
+            if self.matcher[match](item[1], uri):
+                prefix = item[0]
+                result.append(prefix)
+        if self.parent is not None:
+            result += self.parent.findPrefixes(uri, match)
+        return result
+
+    def promotePrefixes(self):
+        """
+        Push prefix declarations up the tree as far as possible.  Prefix
+        mapping are pushed to its parent unless the parent has the
+        prefix mapped to another URI or the parent has the prefix.
+        This is propagated up the tree until the top is reached.
+        @return: self
+        @rtype: L{Element}
+        """
+        for c in self.children:
+            c.promotePrefixes()
+        if self.parent is None:
+            return
+        for p,u in self.nsprefixes.items():
+            if p in self.parent.nsprefixes:
+                pu = self.parent.nsprefixes[p]
+                if pu == u:
+                    del self.nsprefixes[p]
+                continue
+            if p != self.parent.prefix:
+                self.parent.nsprefixes[p] = u
+                del self.nsprefixes[p]
+        return self
+
+    def refitPrefixes(self):
+        """
+        Refit namespace qualification by replacing prefixes
+        with explicit namespaces. Also purges prefix mapping table.
+        @return: self
+        @rtype: L{Element}
+        """
+        for c in self.children:
+            c.refitPrefixes()
+        if self.prefix is not None:
+            ns = self.resolvePrefix(self.prefix)
+            if ns[1] is not None:
+                self.expns = ns[1]
+        self.prefix = None
+        self.nsprefixes = {}
+        return self
+
+    def normalizePrefixes(self):
+        """
+        Normalize the namespace prefixes.
+        This generates unique prefixes for all namespaces.  Then retrofits all
+        prefixes and prefix mappings.  Further, it will retrofix attribute values
+        that have values containing (:).
+        @return: self
+        @rtype: L{Element}
+        """
+        PrefixNormalizer.apply(self)
+        return self
+
+    def isempty(self, content=True):
+        """
+        Get whether the element has no children.
+        @param content: Test content (children & text) only.
+        @type content: boolean
+        @return: True when element has not children.
+        @rtype: boolean
+        """
+        noattrs = not len(self.attributes)
+        nochildren = not len(self.children)
+        notext = ( self.text is None )
+        nocontent = ( nochildren and notext )
+        if content:
+            return nocontent
+        return nocontent and noattrs
+
+    def isnil(self):
+        """
+        Get whether the element is I{nil} as defined by having
+        an attribute in the I{xsi:nil="true"}
+        @return: True if I{nil}, else False
+        @rtype: boolean
+        """
+        nilattr = self.getAttribute('nil', ns=Namespace.xsins)
+        return nilattr is not None and ( nilattr.getValue().lower() == 'true' )
+
+    def setnil(self, flag=True):
+        """
+        Set this node to I{nil} as defined by having an
+        attribute I{xsi:nil}=I{flag}.
+        @param flag: A flag indicating how I{xsi:nil} will be set.
+        @type flag: boolean
+        @return: self
+        @rtype: L{Element}
+        """
+        p, u = Namespace.xsins
+        name  = ':'.join((p, 'nil'))
+        self.set(name, str(flag).lower())
+        self.addPrefix(p, u)
+        if flag:
+            self.text = None
+        return self
+
+    def applyns(self, ns):
+        """
+        Apply the namespace to this node.  If the prefix is I{None} then
+        this element's explicit namespace I{expns} is set to the
+        URI defined by I{ns}.  Otherwise, the I{ns} is simply mapped.
+        @param ns: A namespace.
+        @type ns: (I{prefix},I{URI})
+        """
+        if ns is None:
+            return
+        if not isinstance(ns, (tuple, list)):
+            raise Exception('namespace must be tuple')
+        if ns[0] is None:
+            self.expns = ns[1]
+        else:
+            self.prefix = ns[0]
+            self.nsprefixes[ns[0]] = ns[1]
+
+    def str(self, indent=0):
+        """
+        Get a string representation of this XML fragment.
+        @param indent: The indent to be used in formatting the output.
+        @type indent: int
+        @return: A I{pretty} string.
+        @rtype: basestring
+        """
+        tab = '%*s'%(indent*3,'')
+        result = []
+        result.append('%s<%s' % (tab, self.qname()))
+        result.append(self.nsdeclarations())
+        for a in [unicode(a) for a in self.attributes]:
+            result.append(' %s' % a)
+        if self.isempty():
+            result.append('/>')
+            return ''.join(result)
+        result.append('>')
+        if self.hasText():
+            result.append(self.text.escape())
+        for c in self.children:
+            result.append('\n')
+            result.append(c.str(indent+1))
+        if len(self.children):
+            result.append('\n%s' % tab)
+        result.append('</%s>' % self.qname())
+        return ''.join(result)
+
+    def plain(self):
+        """
+        Get a string representation of this XML fragment.
+        @return: A I{plain} string.
+        @rtype: basestring
+        """
+        result = []
+        result.append('<%s' % self.qname())
+        result.append(self.nsdeclarations())
+        for a in [unicode(a) for a in self.attributes]:
+            result.append(' %s' % a)
+        if self.isempty():
+            result.append('/>')
+            return ''.join(result)
+        result.append('>')
+        if self.hasText():
+            result.append(self.text.escape())
+        for c in self.children:
+            result.append(c.plain())
+        result.append('</%s>' % self.qname())
+        return ''.join(result)
+
+    def nsdeclarations(self):
+        """
+        Get a string representation for all namespace declarations
+        as xmlns="" and xmlns:p="".
+        @return: A separated list of declarations.
+        @rtype: basestring
+        """
+        s = []
+        myns = (None, self.expns)
+        if self.parent is None:
+            pns = Namespace.default
+        else:
+            pns = (None, self.parent.expns)
+        if myns[1] != pns[1]:
+            if self.expns is not None:
+                d = ' xmlns="%s"' % self.expns
+                s.append(d)
+        for item in self.nsprefixes.items():
+            (p,u) = item
+            if self.parent is not None:
+                ns = self.parent.resolvePrefix(p)
+                if ns[1] == u: continue
+            d = ' xmlns:%s="%s"' % (p, u)
+            s.append(d)
+        return ''.join(s)
+
+    def match(self, name=None, ns=None):
+        """
+        Match by (optional) name and/or (optional) namespace.
+        @param name: The optional element tag name.
+        @type name: str
+        @param ns: An optional namespace.
+        @type ns: (I{prefix}, I{name})
+        @return: True if matched.
+        @rtype: boolean
+        """
+        byname = name is None or ( self.name == name )
+        byns = ns is None or ( self.namespace()[1] == ns[1] )
+        return byname and byns
+
+    def branch(self):
+        """
+        Get a flattened representation of the branch.
+        @return: A flat list of nodes.
+        @rtype: [L{Element},..]
+        """
+        branch = [self]
+        for c in self.children:
+            branch += c.branch()
+        return branch
+
+    def ancestors(self):
+        """
+        Get a list of ancestors.
+        @return: A list of ancestors.
+        @rtype: [L{Element},..]
+        """
+        ancestors = []
+        p = self.parent
+        while p is not None:
+            ancestors.append(p)
+            p = p.parent
+        return ancestors
+
+    def walk(self, visitor):
+        """
+        Walk the branch and call the visitor function on each node.
+        @param visitor: A function.
+        @return: self
+        @rtype: L{Element}
+        """
+        visitor(self)
+        for c in self.children:
+            c.walk(visitor)
+        return self
+
+    def prune(self):
+        """
+        Prune the branch of empty nodes.
+        """
+        pruned = []
+        for c in self.children:
+            c.prune()
+            if c.isempty(False):
+                pruned.append(c)
+        for p in pruned:
+            self.children.remove(p)
+
+    def __childrenAtPath(self, parts):
+        result = []
+        node = self
+        last = len(parts)-1
+        ancestors = parts[:last]
+        leaf = parts[last]
+        for name in ancestors:
+            ns = None
+            prefix, name = splitPrefix(name)
+            if prefix is not None:
+                ns = node.resolvePrefix(prefix)
+            child = node.getChild(name, ns)
+            if child is None:
+                break
+            else:
+                node = child
+        if child is not None:
+            ns = None
+            prefix, leaf = splitPrefix(leaf)
+            if prefix is not None:
+                ns = node.resolvePrefix(prefix)
+            result = child.getChildren(leaf)
+        return result
+
+    def __len__(self):
+        return len(self.children)
+
+    def __getitem__(self, index):
+        if isinstance(index, basestring):
+            return self.get(index)
+        if index < len(self.children):
+            return self.children[index]
+
+    def __setitem__(self, index, value):
+        if isinstance(index, basestring):
+            self.set(index, value)
+        else:
+            if index < len(self.children) and isinstance(value, Element):
+                self.children.insert(index, value)
+
+    def __eq__(self, rhs):
+        return isinstance(rhs, Element) and  \
+            self.match(rhs.name, rhs.namespace())
+
+    def __repr__(self):
+        return 'Element (prefix=%s, name=%s)' % (self.prefix, self.name)
+
+    def __unicode__(self):
+        return self.str()
+
+    def __iter__(self):
+        return NodeIterator(self)
+
+
+class NodeIterator:
+    """
+    The L{Element} child node iterator.
+    @ivar pos: The current position
+    @type pos: int
+    @ivar children: A list of a child nodes.
+    @type children: [L{Element},..]
+    """
+
+    def __init__(self, parent):
+        """
+        @param parent: An element to iterate.
+        @type parent: L{Element}
+        """
+        self.pos = 0
+        self.children = parent.children
+
+    def next(self):
+        """
+        Get the next child.
+        @return: The next child.
+        @rtype: L{Element}
+        @raise StopIterator: At the end.
+        """
+        try:
+            child = self.children[self.pos]
+            self.pos += 1
+            return child
+        except:
+            raise StopIteration()
+
+
+class PrefixNormalizer:
+    """
+    The prefix normalizer provides namespace prefix normalization.
+    @ivar node: A node to normalize.
+    @type node: L{Element}
+    @ivar branch: The nodes flattened branch.
+    @type branch: [L{Element},..]
+    @ivar namespaces: A unique list of namespaces (URI).
+    @type namespaces: [str,]
+    @ivar prefixes: A reverse dict of prefixes.
+    @type prefixes: {u, p}
+    """
+
+    @classmethod
+    def apply(cls, node):
+        """
+        Normalize the specified node.
+        @param node: A node to normalize.
+        @type node: L{Element}
+        @return: The normalized node.
+        @rtype: L{Element}
+        """
+        pn = PrefixNormalizer(node)
+        return pn.refit()
+
+    def __init__(self, node):
+        """
+        @param node: A node to normalize.
+        @type node: L{Element}
+        """
+        self.node = node
+        self.branch = node.branch()
+        self.namespaces = self.getNamespaces()
+        self.prefixes = self.genPrefixes()
+
+    def getNamespaces(self):
+        """
+        Get the I{unique} set of namespaces referenced in the branch.
+        @return: A set of namespaces.
+        @rtype: set
+        """
+        s = set()
+        for n in self.branch + self.node.ancestors():
+            if self.permit(n.expns):
+                s.add(n.expns)
+            s = s.union(self.pset(n))
+        return s
+
+    def pset(self, n):
+        """
+        Convert the nodes nsprefixes into a set.
+        @param n: A node.
+        @type n: L{Element}
+        @return: A set of namespaces.
+        @rtype: set
+        """
+        s = set()
+        for ns in n.nsprefixes.items():
+            if self.permit(ns):
+                s.add(ns[1])
+        return s
+
+    def genPrefixes(self):
+        """
+        Generate a I{reverse} mapping of unique prefixes for all namespaces.
+        @return: A referse dict of prefixes.
+        @rtype: {u, p}
+        """
+        prefixes = {}
+        n = 0
+        for u in self.namespaces:
+            p = 'ns%d' % n
+            prefixes[u] = p
+            n += 1
+        return prefixes
+
+    def refit(self):
+        """
+        Refit (normalize) the prefixes in the node.
+        """
+        self.refitNodes()
+        self.refitMappings()
+
+    def refitNodes(self):
+        """
+        Refit (normalize) all of the nodes in the branch.
+        """
+        for n in self.branch:
+            if n.prefix is not None:
+                ns = n.namespace()
+                if self.permit(ns):
+                    n.prefix = self.prefixes[ns[1]]
+            self.refitAttrs(n)
+
+    def refitAttrs(self, n):
+        """
+        Refit (normalize) all of the attributes in the node.
+        @param n: A node.
+        @type n: L{Element}
+        """
+        for a in n.attributes:
+            self.refitAddr(a)
+
+    def refitAddr(self, a):
+        """
+        Refit (normalize) the attribute.
+        @param a: An attribute.
+        @type a: L{Attribute}
+        """
+        if a.prefix is not None:
+            ns = a.namespace()
+            if self.permit(ns):
+                a.prefix = self.prefixes[ns[1]]
+        self.refitValue(a)
+
+    def refitValue(self, a):
+        """
+        Refit (normalize) the attribute's value.
+        @param a: An attribute.
+        @type a: L{Attribute}
+        """
+        p,name = splitPrefix(a.getValue())
+        if p is None: return
+        ns = a.resolvePrefix(p)
+        if self.permit(ns):
+            u = ns[1]
+            p = self.prefixes[u]
+            a.setValue(':'.join((p, name)))
+
+    def refitMappings(self):
+        """
+        Refit (normalize) all of the nsprefix mappings.
+        """
+        for n in self.branch:
+            n.nsprefixes = {}
+        n = self.node
+        for u, p in self.prefixes.items():
+            n.addPrefix(p, u)
+
+    def permit(self, ns):
+        """
+        Get whether the I{ns} is to be normalized.
+        @param ns: A namespace.
+        @type ns: (p,u)
+        @return: True if to be included.
+        @rtype: boolean
+        """
+        return not self.skip(ns)
+
+    def skip(self, ns):
+        """
+        Get whether the I{ns} is to B{not} be normalized.
+        @param ns: A namespace.
+        @type ns: (p,u)
+        @return: True if to be skipped.
+        @rtype: boolean
+        """
+        return (ns is None or
+            ns == Namespace.default or
+            ns == Namespace.xsdns or
+            ns == Namespace.xsins or
+            ns == Namespace.xmlns)
diff --git a/suds/sax/enc.py b/suds/sax/enc.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d3219c83a05ee6aa297b3b6d0c589ac5ae55aec
--- /dev/null
+++ b/suds/sax/enc.py
@@ -0,0 +1,79 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides XML I{special character} encoder classes.
+"""
+
+import re
+
+class Encoder:
+    """
+    An XML special character encoder/decoder.
+    @cvar encodings: A mapping of special characters encoding.
+    @type encodings: [(str,str)]
+    @cvar decodings: A mapping of special characters decoding.
+    @type decodings: [(str,str)]
+    @cvar special: A list of special characters
+    @type special: [char]
+    """
+
+    encodings = \
+        (( '&(?!(amp|lt|gt|quot|apos);)', '&amp;' ),( '<', '&lt;' ),( '>', '&gt;' ),( '"', '&quot;' ),("'", '&apos;' ))
+    decodings = \
+        (( '&lt;', '<' ),( '&gt;', '>' ),( '&quot;', '"' ),( '&apos;', "'" ),( '&amp;', '&' ))
+    special = \
+        ('&', '<', '>', '"', "'")
+
+    def needsEncoding(self, s):
+        """
+        Get whether string I{s} contains special characters.
+        @param s: A string to check.
+        @type s: str
+        @return: True if needs encoding.
+        @rtype: boolean
+        """
+        if isinstance(s, basestring):
+            for c in self.special:
+                if c in s:
+                    return True
+        return False
+
+    def encode(self, s):
+        """
+        Encode special characters found in string I{s}.
+        @param s: A string to encode.
+        @type s: str
+        @return: The encoded string.
+        @rtype: str
+        """
+        if isinstance(s, basestring) and self.needsEncoding(s):
+            for x in self.encodings:
+                s = re.sub(x[0], x[1], s)
+        return s
+
+    def decode(self, s):
+        """
+        Decode special characters encodings found in string I{s}.
+        @param s: A string to decode.
+        @type s: str
+        @return: The decoded string.
+        @rtype: str
+        """
+        if isinstance(s, basestring) and '&' in s:
+            for x in self.decodings:
+                s = s.replace(x[0], x[1])
+        return s
diff --git a/suds/sax/parser.py b/suds/sax/parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..a82583a0dcf5a593094ad1b03ac2e9bb9d5dea83
--- /dev/null
+++ b/suds/sax/parser.py
@@ -0,0 +1,136 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The sax module contains a collection of classes that provide a
+(D)ocument (O)bject (M)odel representation of an XML document.
+The goal is to provide an easy, intuitive interface for managing XML
+documents.  Although, the term, DOM, is used above, this model is
+B{far} better.
+
+XML namespaces in suds are represented using a (2) element tuple
+containing the prefix and the URI.  Eg: I{('tns', 'http://myns')}
+
+"""
+
+import suds
+from suds import *
+from suds.sax import *
+from suds.sax.attribute import Attribute
+from suds.sax.document import Document
+from suds.sax.element import Element
+from suds.sax.text import Text
+
+import sys
+from xml.sax import make_parser, InputSource, ContentHandler
+from xml.sax.handler import feature_external_ges
+
+
+class Handler(ContentHandler):
+    """ sax hanlder """
+
+    def __init__(self):
+        self.nodes = [Document()]
+
+    def startElement(self, name, attrs):
+        top = self.top()
+        node = Element(unicode(name))
+        for a in attrs.getNames():
+            n = unicode(a)
+            v = unicode(attrs.getValue(a))
+            attribute = Attribute(n,v)
+            if self.mapPrefix(node, attribute):
+                continue
+            node.append(attribute)
+        node.charbuffer = []
+        top.append(node)
+        self.push(node)
+
+    def mapPrefix(self, node, attribute):
+        skip = False
+        if attribute.name == 'xmlns':
+            if len(attribute.value):
+                node.expns = unicode(attribute.value)
+            skip = True
+        elif attribute.prefix == 'xmlns':
+            prefix = attribute.name
+            node.nsprefixes[prefix] = unicode(attribute.value)
+            skip = True
+        return skip
+
+    def endElement(self, name):
+        name = unicode(name)
+        current = self.top()
+        if len(current.charbuffer):
+            current.text = Text(u''.join(current.charbuffer))
+        del current.charbuffer
+        if len(current):
+            current.trim()
+        if name == current.qname():
+            self.pop()
+        else:
+            raise Exception('malformed document')
+
+    def characters(self, content):
+        text = unicode(content)
+        node = self.top()
+        node.charbuffer.append(text)
+
+    def push(self, node):
+        self.nodes.append(node)
+        return node
+
+    def pop(self):
+        return self.nodes.pop()
+
+    def top(self):
+        return self.nodes[len(self.nodes)-1]
+
+
+class Parser:
+    """ SAX Parser """
+
+    @classmethod
+    def saxparser(cls):
+        p = make_parser()
+        p.setFeature(feature_external_ges, 0)
+        h = Handler()
+        p.setContentHandler(h)
+        return (p, h)
+
+    def parse(self, file=None, string=None):
+        """
+        SAX parse XML text.
+        @param file: Parse a python I{file-like} object.
+        @type file: I{file-like} object.
+        @param string: Parse string XML.
+        @type string: str
+        """
+        timer = suds.metrics.Timer()
+        timer.start()
+        sax, handler = self.saxparser()
+        if file is not None:
+            sax.parse(file)
+            timer.stop()
+            suds.metrics.log.debug('sax (%s) duration: %s', file, timer)
+            return handler.nodes[0]
+        if string is not None:
+            source = InputSource(None)
+            source.setByteStream(suds.BytesIO(string))
+            sax.parse(source)
+            timer.stop()
+            suds.metrics.log.debug('%s\nsax duration: %s', string, timer)
+            return handler.nodes[0]
diff --git a/suds/sax/text.py b/suds/sax/text.py
new file mode 100644
index 0000000000000000000000000000000000000000..985386e44e442f59acba0bed7be18770830e8c97
--- /dev/null
+++ b/suds/sax/text.py
@@ -0,0 +1,116 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Contains XML text classes.
+"""
+
+from suds import *
+from suds.sax import *
+
+
+class Text(unicode):
+    """
+    An XML text object used to represent text content.
+    @ivar lang: The (optional) language flag.
+    @type lang: bool
+    @ivar escaped: The (optional) XML special character escaped flag.
+    @type escaped: bool
+    """
+    __slots__ = ('lang', 'escaped')
+
+    @classmethod
+    def __valid(cls, *args):
+        return len(args) and args[0] is not None
+
+    def __new__(cls, *args, **kwargs):
+        if cls.__valid(*args):
+            lang = kwargs.pop('lang', None)
+            escaped = kwargs.pop('escaped', False)
+            result = super(Text, cls).__new__(cls, *args, **kwargs)
+            result.lang = lang
+            result.escaped = escaped
+        else:
+            result = None
+        return result
+
+    def escape(self):
+        """
+        Encode (escape) special XML characters.
+        @return: The text with XML special characters escaped.
+        @rtype: L{Text}
+        """
+        if not self.escaped:
+            post = sax.encoder.encode(self)
+            escaped = ( post != self )
+            return Text(post, lang=self.lang, escaped=escaped)
+        return self
+
+    def unescape(self):
+        """
+        Decode (unescape) special XML characters.
+        @return: The text with escaped XML special characters decoded.
+        @rtype: L{Text}
+        """
+        if self.escaped:
+            post = sax.encoder.decode(self)
+            return Text(post, lang=self.lang)
+        return self
+
+    def trim(self):
+        post = self.strip()
+        return Text(post, lang=self.lang, escaped=self.escaped)
+
+    def __add__(self, other):
+        joined = u''.join((self, other))
+        result = Text(joined, lang=self.lang, escaped=self.escaped)
+        if isinstance(other, Text):
+            result.escaped = self.escaped or other.escaped
+        return result
+
+    def __repr__(self):
+        s = [self]
+        if self.lang is not None:
+            s.append(' [%s]' % self.lang)
+        if self.escaped:
+            s.append(' <escaped>')
+        return ''.join(s)
+
+    def __getstate__(self):
+        state = {}
+        for k in self.__slots__:
+            state[k] = getattr(self, k)
+        return state
+
+    def __setstate__(self, state):
+        for k in self.__slots__:
+            setattr(self, k, state[k])
+
+
+class Raw(Text):
+    """
+    Raw text which is not XML escaped.
+    This may include I{string} XML.
+    """
+    def escape(self):
+        return self
+
+    def unescape(self):
+        return self
+
+    def __add__(self, other):
+        joined = u''.join((self, other))
+        return Raw(joined, lang=self.lang)
diff --git a/suds/servicedefinition.py b/suds/servicedefinition.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b0e72f87f277c7d87856e1ade204e20a8f36a40
--- /dev/null
+++ b/suds/servicedefinition.py
@@ -0,0 +1,240 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{service definition} provides a textual representation of a service.
+"""
+
+from suds import *
+import suds.metrics as metrics
+from suds.sax import Namespace
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class ServiceDefinition(UnicodeMixin):
+    """
+    A service definition provides an object used to generate a textual description
+    of a service.
+    @ivar wsdl: A wsdl.
+    @type wsdl: L{wsdl.Definitions}
+    @ivar service: The service object.
+    @type service: L{suds.wsdl.Service}
+    @ivar ports: A list of port-tuple: (port, [(method-name, pdef)])
+    @type ports: [port-tuple,..]
+    @ivar prefixes: A list of remapped prefixes.
+    @type prefixes: [(prefix,uri),..]
+    @ivar types: A list of type definitions
+    @type types: [I{Type},..]
+    """
+
+    def __init__(self, wsdl, service):
+        """
+        @param wsdl: A WSDL object
+        @type wsdl: L{Definitions}
+        @param service: A service B{name}.
+        @type service: str
+        """
+        self.wsdl = wsdl
+        self.service = service
+        self.ports = []
+        self.params = []
+        self.types = []
+        self.prefixes = []
+        self.addports()
+        self.paramtypes()
+        self.publictypes()
+        self.getprefixes()
+        self.pushprefixes()
+
+    def pushprefixes(self):
+        """
+        Add our prefixes to the WSDL so that when users invoke methods
+        and reference the prefixes, they will resolve properly.
+        """
+        for ns in self.prefixes:
+            self.wsdl.root.addPrefix(ns[0], ns[1])
+
+    def addports(self):
+        """
+        Look through the list of service ports and construct a list of tuples
+        where each tuple is used to describe a port and its list of methods as:
+        (port, [method]).  Each method is a tuple: (name, [pdef,..]) where each
+        pdef is a tuple: (param-name, type).
+        """
+        timer = metrics.Timer()
+        timer.start()
+        for port in self.service.ports:
+            p = self.findport(port)
+            for op in port.binding.operations.values():
+                m = p[0].method(op.name)
+                binding = m.binding.input
+                method = (m.name, binding.param_defs(m))
+                p[1].append(method)
+                metrics.log.debug("method '%s' created: %s", m.name, timer)
+            p[1].sort()
+        timer.stop()
+
+    def findport(self, port):
+        """
+        Find and return a port tuple for the specified port.
+        Created and added when not found.
+        @param port: A port.
+        @type port: I{service.Port}
+        @return: A port tuple.
+        @rtype: (port, [method])
+        """
+        for p in self.ports:
+            if p[0] == p: return p
+        p = (port, [])
+        self.ports.append(p)
+        return p
+
+    def getprefixes(self):
+        """Add prefixes for each namespace referenced by parameter types."""
+        namespaces = []
+        for l in (self.params, self.types):
+            for t,r in l:
+                ns = r.namespace()
+                if ns[1] is None: continue
+                if ns[1] in namespaces: continue
+                if Namespace.xs(ns) or Namespace.xsd(ns):
+                    continue
+                namespaces.append(ns[1])
+                if t == r: continue
+                ns = t.namespace()
+                if ns[1] is None: continue
+                if ns[1] in namespaces: continue
+                namespaces.append(ns[1])
+        i = 0
+        namespaces.sort()
+        for u in namespaces:
+            p = self.nextprefix()
+            ns = (p, u)
+            self.prefixes.append(ns)
+
+    def paramtypes(self):
+        """Get all parameter types."""
+        for m in [p[1] for p in self.ports]:
+            for p in [p[1] for p in m]:
+                for pd in p:
+                    if pd[1] in self.params: continue
+                    item = (pd[1], pd[1].resolve())
+                    self.params.append(item)
+
+    def publictypes(self):
+        """Get all public types."""
+        for t in self.wsdl.schema.types.values():
+            if t in self.params: continue
+            if t in self.types: continue
+            item = (t, t)
+            self.types.append(item)
+        self.types.sort(key=lambda x: x[0].name)
+
+    def nextprefix(self):
+        """
+        Get the next available prefix.  This means a prefix starting with 'ns' with
+        a number appended as (ns0, ns1, ..) that is not already defined in the
+        WSDL document.
+        """
+        used = [ns[0] for ns in self.prefixes]
+        used += [ns[0] for ns in self.wsdl.root.nsprefixes.items()]
+        for n in range(0,1024):
+            p = 'ns%d'%n
+            if p not in used:
+                return p
+        raise Exception('prefixes exhausted')
+
+    def getprefix(self, u):
+        """
+        Get the prefix for the specified namespace (URI)
+        @param u: A namespace URI.
+        @type u: str
+        @return: The namspace.
+        @rtype: (prefix, uri).
+        """
+        for ns in Namespace.all:
+            if u == ns[1]: return ns[0]
+        for ns in self.prefixes:
+            if u == ns[1]: return ns[0]
+        raise Exception('ns (%s) not mapped'  % u)
+
+    def xlate(self, type):
+        """
+        Get a (namespace) translated I{qualified} name for specified type.
+        @param type: A schema type.
+        @type type: I{suds.xsd.sxbasic.SchemaObject}
+        @return: A translated I{qualified} name.
+        @rtype: str
+        """
+        resolved = type.resolve()
+        name = resolved.name
+        if type.multi_occurrence():
+            name += '[]'
+        ns = resolved.namespace()
+        if ns[1] == self.wsdl.tns[1]:
+            return name
+        prefix = self.getprefix(ns[1])
+        return ':'.join((prefix, name))
+
+    def description(self):
+        """
+        Get a textual description of the service for which this object represents.
+        @return: A textual description.
+        @rtype: str
+        """
+        s = []
+        indent = (lambda n :  '\n%*s'%(n*3,' '))
+        s.append('Service ( %s ) tns="%s"' % (self.service.name, self.wsdl.tns[1]))
+        s.append(indent(1))
+        s.append('Prefixes (%d)' % len(self.prefixes))
+        for p in self.prefixes:
+            s.append(indent(2))
+            s.append('%s = "%s"' % p)
+        s.append(indent(1))
+        s.append('Ports (%d):' % len(self.ports))
+        for p in self.ports:
+            s.append(indent(2))
+            s.append('(%s)' % p[0].name)
+            s.append(indent(3))
+            s.append('Methods (%d):' % len(p[1]))
+            for m in p[1]:
+                sig = []
+                s.append(indent(4))
+                sig.append(m[0])
+                sig.append('(')
+                sig.append(', '.join("%s %s" % (self.xlate(p[1]), p[0]) for p
+                    in m[1]))
+                sig.append(')')
+                try:
+                    s.append(''.join(sig))
+                except:
+                    pass
+            s.append(indent(3))
+            s.append('Types (%d):' % len(self.types))
+            for t in self.types:
+                s.append(indent(4))
+                s.append(self.xlate(t[0]))
+        s.append('\n\n')
+        return ''.join(s)
+
+    def __unicode__(self):
+        try:
+            return self.description()
+        except Exception, e:
+            log.exception(e)
+        return tostr(e)
diff --git a/suds/serviceproxy.py b/suds/serviceproxy.py
new file mode 100644
index 0000000000000000000000000000000000000000..278c18967515867816ab3e859e9501f14e129305
--- /dev/null
+++ b/suds/serviceproxy.py
@@ -0,0 +1,80 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The service proxy provides access to web services.
+
+Replaced by: L{client.Client}
+"""
+
+from suds import *
+from suds.client import Client
+
+
+class ServiceProxy(UnicodeMixin):
+
+    """
+    A lightweight soap based web service proxy.
+    @ivar __client__: A client.
+        Everything is delegated to the 2nd generation API.
+    @type __client__: L{Client}
+    @note:  Deprecated, replaced by L{Client}.
+    """
+
+    def __init__(self, url, **kwargs):
+        """
+        @param url: The URL for the WSDL.
+        @type url: str
+        @param kwargs: keyword arguments.
+        @keyword faults: Raise faults raised by server (default:True),
+                else return tuple from service method invocation as (http code, object).
+        @type faults: boolean
+        @keyword proxy: An http proxy to be specified on requests (default:{}).
+                           The proxy is defined as {protocol:proxy,}
+        @type proxy: dict
+        """
+        client = Client(url, **kwargs)
+        self.__client__ = client
+
+    def get_instance(self, name):
+        """
+        Get an instance of a WSDL type by name
+        @param name: The name of a type defined in the WSDL.
+        @type name: str
+        @return: An instance on success, else None
+        @rtype: L{sudsobject.Object}
+        """
+        return self.__client__.factory.create(name)
+
+    def get_enum(self, name):
+        """
+        Get an instance of an enumeration defined in the WSDL by name.
+        @param name: The name of a enumeration defined in the WSDL.
+        @type name: str
+        @return: An instance on success, else None
+        @rtype: L{sudsobject.Object}
+        """
+        return self.__client__.factory.create(name)
+
+    def __unicode__(self):
+        return unicode(self.__client__)
+
+    def __getattr__(self, name):
+        builtin = name.startswith('__') and name.endswith('__')
+        if builtin:
+            return self.__dict__[name]
+        else:
+            return getattr(self.__client__.service, name)
diff --git a/suds/soaparray.py b/suds/soaparray.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea04fa7add0d7595b10ae4cdd9e09ea26ef05fc0
--- /dev/null
+++ b/suds/soaparray.py
@@ -0,0 +1,71 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{soaparray} module provides XSD extensions for handling
+soap (section 5) encoded arrays.
+"""
+
+from suds import *
+from logging import getLogger
+from suds.xsd.sxbasic import Factory as SXFactory
+from suds.xsd.sxbasic import Attribute as SXAttribute
+
+
+class Attribute(SXAttribute):
+    """
+    Represents an XSD <attribute/> that handles special
+    attributes that are extensions for WSDLs.
+    @ivar aty: Array type information.
+    @type aty: The value of wsdl:arrayType.
+    """
+
+    def __init__(self, schema, root, aty):
+        """
+        @param aty: Array type information.
+        @type aty: The value of wsdl:arrayType.
+        """
+        SXAttribute.__init__(self, schema, root)
+        if aty.endswith('[]'):
+            self.aty = aty[:-2]
+        else:
+            self.aty = aty
+
+    def autoqualified(self):
+        aqs = SXAttribute.autoqualified(self)
+        aqs.append('aty')
+        return aqs
+
+    def description(self):
+        d = SXAttribute.description(self)
+        d = d+('aty',)
+        return d
+
+#
+# Builder function, only builds Attribute when arrayType
+# attribute is defined on root.
+#
+def __fn(x, y):
+    ns = (None, "http://schemas.xmlsoap.org/wsdl/")
+    aty = y.get('arrayType', ns=ns)
+    if aty is None:
+        return SXAttribute(x, y)
+    return Attribute(x, y, aty)
+
+#
+# Remap <xs:attribute/> tags to __fn() builder.
+#
+SXFactory.maptag('attribute', __fn)
diff --git a/suds/store.py b/suds/store.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5931aa5d20233d66b296ba8ee6df91e467ac172
--- /dev/null
+++ b/suds/store.py
@@ -0,0 +1,596 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Support for holding XML document texts that may then be accessed internally by
+suds without having to download them from an external source. Also contains XML
+document content to be distributed alongside the suds library.
+
+"""
+
+import suds
+
+
+soap5_encoding_schema = suds.byte_str("""\
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+    xmlns:tns="http://schemas.xmlsoap.org/soap/encoding/"
+    targetNamespace="http://schemas.xmlsoap.org/soap/encoding/">
+
+ <xs:attribute name="root">
+   <xs:annotation>
+     <xs:documentation>
+       'root' can be used to distinguish serialization roots from other
+       elements that are present in a serialization but are not roots of
+       a serialized value graph
+     </xs:documentation>
+   </xs:annotation>
+   <xs:simpleType>
+     <xs:restriction base="xs:boolean">
+       <xs:pattern value="0|1"/>
+     </xs:restriction>
+   </xs:simpleType>
+ </xs:attribute>
+
+  <xs:attributeGroup name="commonAttributes">
+    <xs:annotation>
+      <xs:documentation>
+        Attributes common to all elements that function as accessors or
+        represent independent (multi-ref) values.  The href attribute is
+        intended to be used in a manner like CONREF.  That is, the element
+        content should be empty iff the href attribute appears
+      </xs:documentation>
+    </xs:annotation>
+    <xs:attribute name="id" type="xs:ID"/>
+    <xs:attribute name="href" type="xs:anyURI"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+  </xs:attributeGroup>
+
+  <!-- Global Attributes.  The following attributes are intended to be usable via qualified attribute names on any complex type referencing them. -->
+
+  <!-- Array attributes. Needed to give the type and dimensions of an array's contents, and the offset for partially-transmitted arrays. -->
+
+  <xs:simpleType name="arrayCoordinate">
+    <xs:restriction base="xs:string"/>
+  </xs:simpleType>
+
+  <xs:attribute name="arrayType" type="xs:string"/>
+  <xs:attribute name="offset" type="tns:arrayCoordinate"/>
+
+  <xs:attributeGroup name="arrayAttributes">
+    <xs:attribute ref="tns:arrayType"/>
+    <xs:attribute ref="tns:offset"/>
+  </xs:attributeGroup>
+
+  <xs:attribute name="position" type="tns:arrayCoordinate"/>
+
+  <xs:attributeGroup name="arrayMemberAttributes">
+    <xs:attribute ref="tns:position"/>
+  </xs:attributeGroup>
+
+  <xs:group name="Array">
+    <xs:sequence>
+      <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+    </xs:sequence>
+  </xs:group>
+
+  <xs:element name="Array" type="tns:Array"/>
+  <xs:complexType name="Array">
+    <xs:annotation>
+      <xs:documentation>
+       'Array' is a complex type for accessors identified by position
+      </xs:documentation>
+    </xs:annotation>
+    <xs:group ref="tns:Array" minOccurs="0"/>
+    <xs:attributeGroup ref="tns:arrayAttributes"/>
+    <xs:attributeGroup ref="tns:commonAttributes"/>
+  </xs:complexType>
+
+  <!-- 'Struct' is a complex type for accessors identified by name.
+       Constraint: No element may be have the same name as any other,
+       nor may any element have a maxOccurs > 1. -->
+
+  <xs:element name="Struct" type="tns:Struct"/>
+
+  <xs:group name="Struct">
+    <xs:sequence>
+      <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+    </xs:sequence>
+  </xs:group>
+
+  <xs:complexType name="Struct">
+    <xs:group ref="tns:Struct" minOccurs="0"/>
+    <xs:attributeGroup ref="tns:commonAttributes"/>
+  </xs:complexType>
+
+  <!-- 'Base64' can be used to serialize binary data using base64 encoding
+       as defined in RFC2045 but without the MIME line length limitation. -->
+
+  <xs:simpleType name="base64">
+    <xs:restriction base="xs:base64Binary"/>
+  </xs:simpleType>
+
+ <!-- Element declarations corresponding to each of the simple types in the
+      XML Schemas Specification. -->
+
+  <xs:element name="duration" type="tns:duration"/>
+  <xs:complexType name="duration">
+    <xs:simpleContent>
+      <xs:extension base="xs:duration">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="dateTime" type="tns:dateTime"/>
+  <xs:complexType name="dateTime">
+    <xs:simpleContent>
+      <xs:extension base="xs:dateTime">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="NOTATION" type="tns:NOTATION"/>
+  <xs:complexType name="NOTATION">
+    <xs:simpleContent>
+      <xs:extension base="xs:QName">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="time" type="tns:time"/>
+  <xs:complexType name="time">
+    <xs:simpleContent>
+      <xs:extension base="xs:time">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="date" type="tns:date"/>
+  <xs:complexType name="date">
+    <xs:simpleContent>
+      <xs:extension base="xs:date">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gYearMonth" type="tns:gYearMonth"/>
+  <xs:complexType name="gYearMonth">
+    <xs:simpleContent>
+      <xs:extension base="xs:gYearMonth">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gYear" type="tns:gYear"/>
+  <xs:complexType name="gYear">
+    <xs:simpleContent>
+      <xs:extension base="xs:gYear">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gMonthDay" type="tns:gMonthDay"/>
+  <xs:complexType name="gMonthDay">
+    <xs:simpleContent>
+      <xs:extension base="xs:gMonthDay">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gDay" type="tns:gDay"/>
+  <xs:complexType name="gDay">
+    <xs:simpleContent>
+      <xs:extension base="xs:gDay">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gMonth" type="tns:gMonth"/>
+  <xs:complexType name="gMonth">
+    <xs:simpleContent>
+      <xs:extension base="xs:gMonth">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="boolean" type="tns:boolean"/>
+  <xs:complexType name="boolean">
+    <xs:simpleContent>
+      <xs:extension base="xs:boolean">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="base64Binary" type="tns:base64Binary"/>
+  <xs:complexType name="base64Binary">
+    <xs:simpleContent>
+      <xs:extension base="xs:base64Binary">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="hexBinary" type="tns:hexBinary"/>
+  <xs:complexType name="hexBinary">
+    <xs:simpleContent>
+     <xs:extension base="xs:hexBinary">
+       <xs:attributeGroup ref="tns:commonAttributes"/>
+     </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="float" type="tns:float"/>
+  <xs:complexType name="float">
+    <xs:simpleContent>
+      <xs:extension base="xs:float">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="double" type="tns:double"/>
+  <xs:complexType name="double">
+    <xs:simpleContent>
+      <xs:extension base="xs:double">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="anyURI" type="tns:anyURI"/>
+  <xs:complexType name="anyURI">
+    <xs:simpleContent>
+      <xs:extension base="xs:anyURI">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="QName" type="tns:QName"/>
+  <xs:complexType name="QName">
+    <xs:simpleContent>
+      <xs:extension base="xs:QName">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="string" type="tns:string"/>
+  <xs:complexType name="string">
+    <xs:simpleContent>
+      <xs:extension base="xs:string">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="normalizedString" type="tns:normalizedString"/>
+  <xs:complexType name="normalizedString">
+    <xs:simpleContent>
+      <xs:extension base="xs:normalizedString">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="token" type="tns:token"/>
+  <xs:complexType name="token">
+    <xs:simpleContent>
+      <xs:extension base="xs:token">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="language" type="tns:language"/>
+  <xs:complexType name="language">
+    <xs:simpleContent>
+      <xs:extension base="xs:language">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="Name" type="tns:Name"/>
+  <xs:complexType name="Name">
+    <xs:simpleContent>
+      <xs:extension base="xs:Name">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="NMTOKEN" type="tns:NMTOKEN"/>
+  <xs:complexType name="NMTOKEN">
+    <xs:simpleContent>
+      <xs:extension base="xs:NMTOKEN">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="NCName" type="tns:NCName"/>
+  <xs:complexType name="NCName">
+    <xs:simpleContent>
+      <xs:extension base="xs:NCName">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="NMTOKENS" type="tns:NMTOKENS"/>
+  <xs:complexType name="NMTOKENS">
+    <xs:simpleContent>
+      <xs:extension base="xs:NMTOKENS">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="ID" type="tns:ID"/>
+  <xs:complexType name="ID">
+    <xs:simpleContent>
+      <xs:extension base="xs:ID">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="IDREF" type="tns:IDREF"/>
+  <xs:complexType name="IDREF">
+    <xs:simpleContent>
+      <xs:extension base="xs:IDREF">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="ENTITY" type="tns:ENTITY"/>
+  <xs:complexType name="ENTITY">
+    <xs:simpleContent>
+      <xs:extension base="xs:ENTITY">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="IDREFS" type="tns:IDREFS"/>
+  <xs:complexType name="IDREFS">
+    <xs:simpleContent>
+      <xs:extension base="xs:IDREFS">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="ENTITIES" type="tns:ENTITIES"/>
+  <xs:complexType name="ENTITIES">
+    <xs:simpleContent>
+      <xs:extension base="xs:ENTITIES">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="decimal" type="tns:decimal"/>
+  <xs:complexType name="decimal">
+    <xs:simpleContent>
+      <xs:extension base="xs:decimal">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="integer" type="tns:integer"/>
+  <xs:complexType name="integer">
+    <xs:simpleContent>
+      <xs:extension base="xs:integer">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="nonPositiveInteger" type="tns:nonPositiveInteger"/>
+  <xs:complexType name="nonPositiveInteger">
+    <xs:simpleContent>
+      <xs:extension base="xs:nonPositiveInteger">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="negativeInteger" type="tns:negativeInteger"/>
+  <xs:complexType name="negativeInteger">
+    <xs:simpleContent>
+      <xs:extension base="xs:negativeInteger">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="long" type="tns:long"/>
+  <xs:complexType name="long">
+    <xs:simpleContent>
+      <xs:extension base="xs:long">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="int" type="tns:int"/>
+  <xs:complexType name="int">
+    <xs:simpleContent>
+      <xs:extension base="xs:int">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="short" type="tns:short"/>
+  <xs:complexType name="short">
+    <xs:simpleContent>
+      <xs:extension base="xs:short">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="byte" type="tns:byte"/>
+  <xs:complexType name="byte">
+    <xs:simpleContent>
+      <xs:extension base="xs:byte">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="nonNegativeInteger" type="tns:nonNegativeInteger"/>
+  <xs:complexType name="nonNegativeInteger">
+    <xs:simpleContent>
+      <xs:extension base="xs:nonNegativeInteger">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="unsignedLong" type="tns:unsignedLong"/>
+  <xs:complexType name="unsignedLong">
+    <xs:simpleContent>
+      <xs:extension base="xs:unsignedLong">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="unsignedInt" type="tns:unsignedInt"/>
+  <xs:complexType name="unsignedInt">
+    <xs:simpleContent>
+      <xs:extension base="xs:unsignedInt">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="unsignedShort" type="tns:unsignedShort"/>
+  <xs:complexType name="unsignedShort">
+    <xs:simpleContent>
+      <xs:extension base="xs:unsignedShort">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="unsignedByte" type="tns:unsignedByte"/>
+  <xs:complexType name="unsignedByte">
+    <xs:simpleContent>
+      <xs:extension base="xs:unsignedByte">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="positiveInteger" type="tns:positiveInteger"/>
+  <xs:complexType name="positiveInteger">
+    <xs:simpleContent>
+      <xs:extension base="xs:positiveInteger">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="anyType"/>
+</xs:schema>
+""")
+
+
+class DocumentStore:
+    """
+    The I{suds} document store provides a local repository for XML documents.
+
+    @cvar protocol: The URL protocol for the store.
+    @type protocol: str
+    @cvar store: The mapping of URL location to documents.
+    @type store: dict
+    """
+
+    def __init__(self, *args, **kwargs):
+        self.__store = {
+            'schemas.xmlsoap.org/soap/encoding/':soap5_encoding_schema}
+        self.update = self.__store.update
+        self.update(*args, **kwargs)
+
+    def __len__(self):
+        # Implementation note:
+        #   We can not implement '__len__' as simply self.__store.__len__, as
+        # we do for 'update' because that causes py2to3 conversion to fail.
+        #                                            (08.05.2013.) (Jurko)
+        return len(self.__store)
+
+    def open(self, url):
+        """
+        Open a document at the specified URL.
+
+        Missing documents referenced using the internal 'suds' protocol are
+        reported by raising an exception. For other protocols, None is returned
+        instead.
+
+        @param url: A document URL.
+        @type url: str
+        @return: Document content or None if not found.
+        @rtype: bytes
+        """
+        protocol, location = self.__split(url)
+        content = self.__find(location)
+        if protocol == 'suds' and content is None:
+            raise Exception, 'location "%s" not in document store' % location
+        return content
+
+    def __find(self, location):
+        """
+        Find the specified location in the store.
+        @param location: The I{location} part of a URL.
+        @type location: str
+        @return: Document content or None if not found.
+        @rtype: bytes
+        """
+        return self.__store.get(location)
+
+    def __split(self, url):
+        """
+        Split the URL into I{protocol} and I{location}
+        @param url: A URL.
+        @param url: str
+        @return: (I{url}, I{location})
+        @rtype: tuple
+        """
+        parts = url.split('://', 1)
+        if len(parts) == 2:
+            return parts
+        return None, url
+
+
+defaultDocumentStore = DocumentStore()
diff --git a/suds/sudsobject.py b/suds/sudsobject.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c18d5a7c03e0ea39f7169e6facdd905dee50278
--- /dev/null
+++ b/suds/sudsobject.py
@@ -0,0 +1,391 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides a collection of suds objects primarily used for highly dynamic
+interactions with WSDL/XSD defined types.
+
+"""
+
+from suds import *
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+def items(sobject):
+    """
+    Extract the I{items} from a suds object.
+
+    Much like the items() method works on I{dict}.
+
+    @param sobject: A suds object
+    @type sobject: L{Object}
+    @return: A list of items contained in I{sobject}.
+    @rtype: [(key, value),...]
+
+    """
+    for item in sobject:
+        yield item
+
+
+def asdict(sobject):
+    """
+    Convert a sudsobject into a dictionary.
+
+    @param sobject: A suds object
+    @type sobject: L{Object}
+    @return: A python dictionary containing the items contained in I{sobject}.
+    @rtype: dict
+
+    """
+    return dict(items(sobject))
+
+def merge(a, b):
+    """
+    Merge all attributes and metadata from I{a} to I{b}.
+
+    @param a: A I{source} object
+    @type a: L{Object}
+    @param b: A I{destination} object
+    @type b: L{Object}
+
+    """
+    for item in a:
+        setattr(b, item[0], item[1])
+        b.__metadata__ = b.__metadata__
+    return b
+
+def footprint(sobject):
+    """
+    Get the I{virtual footprint} of the object.
+
+    This is really a count of all the significant value attributes in the
+    branch.
+
+    @param sobject: A suds object.
+    @type sobject: L{Object}
+    @return: The branch footprint.
+    @rtype: int
+
+    """
+    n = 0
+    for a in sobject.__keylist__:
+        v = getattr(sobject, a)
+        if v is None:
+            continue
+        if isinstance(v, Object):
+            n += footprint(v)
+            continue
+        if hasattr(v, "__len__"):
+            if len(v):
+                n += 1
+            continue
+        n += 1
+    return n
+
+
+class Factory:
+
+    cache = {}
+
+    @classmethod
+    def subclass(cls, name, bases, dict={}):
+        if not isinstance(bases, tuple):
+            bases = (bases,)
+        # name is of type unicode in python 2 -> not accepted by type()
+        name = str(name)
+        key = ".".join((name, str(bases)))
+        subclass = cls.cache.get(key)
+        if subclass is None:
+            subclass = type(name, bases, dict)
+            cls.cache[key] = subclass
+        return subclass
+
+    @classmethod
+    def object(cls, classname=None, dict={}):
+        if classname is not None:
+            subclass = cls.subclass(classname, Object)
+            inst = subclass()
+        else:
+            inst = Object()
+        for a in dict.items():
+            setattr(inst, a[0], a[1])
+        return inst
+
+    @classmethod
+    def metadata(cls):
+        return Metadata()
+
+    @classmethod
+    def property(cls, name, value=None):
+        subclass = cls.subclass(name, Property)
+        return subclass(value)
+
+
+class Object(UnicodeMixin):
+
+    def __init__(self):
+        self.__keylist__ = []
+        self.__printer__ = Printer()
+        self.__metadata__ = Metadata()
+
+    def __setattr__(self, name, value):
+        builtin = name.startswith("__") and name.endswith("__")
+        if not builtin and name not in self.__keylist__:
+            self.__keylist__.append(name)
+        self.__dict__[name] = value
+
+    def __delattr__(self, name):
+        try:
+            del self.__dict__[name]
+            builtin = name.startswith("__") and name.endswith("__")
+            if not builtin:
+                self.__keylist__.remove(name)
+        except Exception:
+            cls = self.__class__.__name__
+            raise AttributeError, "%s has no attribute '%s'" % (cls, name)
+
+    def __getitem__(self, name):
+        if isinstance(name, int):
+            name = self.__keylist__[int(name)]
+        return getattr(self, name)
+
+    def __setitem__(self, name, value):
+        setattr(self, name, value)
+
+    def __iter__(self):
+        return Iter(self)
+
+    def __len__(self):
+        return len(self.__keylist__)
+
+    def __contains__(self, name):
+        return name in self.__keylist__
+
+    def __repr__(self):
+        return str(self)
+
+    def __unicode__(self):
+        return self.__printer__.tostr(self)
+
+
+class Iter:
+
+    def __init__(self, sobject):
+        self.sobject = sobject
+        self.keylist = self.__keylist(sobject)
+        self.index = 0
+
+    def next(self):
+        keylist = self.keylist
+        nkeys = len(self.keylist)
+        while self.index < nkeys:
+            k = keylist[self.index]
+            self.index += 1
+            if hasattr(self.sobject, k):
+                v = getattr(self.sobject, k)
+                return (k, v)
+        raise StopIteration()
+
+    def __keylist(self, sobject):
+        keylist = sobject.__keylist__
+        try:
+            keyset = set(keylist)
+            ordering = sobject.__metadata__.ordering
+            ordered = set(ordering)
+            if not ordered.issuperset(keyset):
+                log.debug("%s must be superset of %s, ordering ignored",
+                    keylist, ordering)
+                raise KeyError()
+            return ordering
+        except Exception:
+            return keylist
+
+    def __iter__(self):
+        return self
+
+
+class Metadata(Object):
+    def __init__(self):
+        self.__keylist__ = []
+        self.__printer__ = Printer()
+
+
+class Facade(Object):
+    def __init__(self, name):
+        Object.__init__(self)
+        md = self.__metadata__
+        md.facade = name
+
+
+class Property(Object):
+
+    def __init__(self, value):
+        Object.__init__(self)
+        self.value = value
+
+    def items(self):
+        for item in self:
+            if item[0] != "value":
+                yield item
+
+    def get(self):
+        return self.value
+
+    def set(self, value):
+        self.value = value
+        return self
+
+
+class Printer:
+    """Pretty printing of a Object object."""
+
+    @classmethod
+    def indent(cls, n):
+        return "%*s" % (n * 3, " ")
+
+    def tostr(self, object, indent=-2):
+        """Get s string representation of object."""
+        history = []
+        return self.process(object, history, indent)
+
+    def process(self, object, h, n=0, nl=False):
+        """Print object using the specified indent (n) and newline (nl)."""
+        if object is None:
+            return "None"
+        if isinstance(object, Object):
+            if len(object) == 0:
+                return "<empty>"
+            return self.print_object(object, h, n + 2, nl)
+        if isinstance(object, dict):
+            if len(object) == 0:
+                return "<empty>"
+            return self.print_dictionary(object, h, n + 2, nl)
+        if isinstance(object, (list, tuple)):
+            if len(object) == 0:
+                return "<empty>"
+            return self.print_collection(object, h, n + 2)
+        if isinstance(object, basestring):
+            return '"%s"' % (tostr(object),)
+        return "%s" % (tostr(object),)
+
+    def print_object(self, d, h, n, nl=False):
+        """Print complex using the specified indent (n) and newline (nl)."""
+        s = []
+        cls = d.__class__
+        if d in h:
+            s.append("(")
+            s.append(cls.__name__)
+            s.append(")")
+            s.append("...")
+            return "".join(s)
+        h.append(d)
+        if nl:
+            s.append("\n")
+            s.append(self.indent(n))
+        if cls != Object:
+            s.append("(")
+            if isinstance(d, Facade):
+                s.append(d.__metadata__.facade)
+            else:
+                s.append(cls.__name__)
+            s.append(")")
+        s.append("{")
+        for item in d:
+            if self.exclude(d, item):
+                continue
+            item = self.unwrap(d, item)
+            s.append("\n")
+            s.append(self.indent(n+1))
+            if isinstance(item[1], (list,tuple)):
+                s.append(item[0])
+                s.append("[]")
+            else:
+                s.append(item[0])
+            s.append(" = ")
+            s.append(self.process(item[1], h, n, True))
+        s.append("\n")
+        s.append(self.indent(n))
+        s.append("}")
+        h.pop()
+        return "".join(s)
+
+    def print_dictionary(self, d, h, n, nl=False):
+        """Print complex using the specified indent (n) and newline (nl)."""
+        if d in h:
+            return "{}..."
+        h.append(d)
+        s = []
+        if nl:
+            s.append("\n")
+            s.append(self.indent(n))
+        s.append("{")
+        for item in d.items():
+            s.append("\n")
+            s.append(self.indent(n+1))
+            if isinstance(item[1], (list,tuple)):
+                s.append(tostr(item[0]))
+                s.append("[]")
+            else:
+                s.append(tostr(item[0]))
+            s.append(" = ")
+            s.append(self.process(item[1], h, n, True))
+        s.append("\n")
+        s.append(self.indent(n))
+        s.append("}")
+        h.pop()
+        return "".join(s)
+
+    def print_collection(self, c, h, n):
+        """Print collection using the specified indent (n) and newline (nl)."""
+        if c in h:
+            return "[]..."
+        h.append(c)
+        s = []
+        for item in c:
+            s.append("\n")
+            s.append(self.indent(n))
+            s.append(self.process(item, h, n - 2))
+            s.append(",")
+        h.pop()
+        return "".join(s)
+
+    def unwrap(self, d, item):
+        """Translate (unwrap) using an optional wrapper function."""
+        try:
+            md = d.__metadata__
+            pmd = getattr(md, "__print__", None)
+            if pmd is None:
+                return item
+            wrappers = getattr(pmd, "wrappers", {})
+            fn = wrappers.get(item[0], lambda x: x)
+            return (item[0], fn(item[1]))
+        except Exception:
+            pass
+        return item
+
+    def exclude(self, d, item):
+        """Check metadata for excluded items."""
+        try:
+            md = d.__metadata__
+            pmd = getattr(md, "__print__", None)
+            if pmd is None:
+                return False
+            excludes = getattr(pmd, "excludes", [])
+            return item[0] in excludes
+        except Exception:
+            pass
+        return False
diff --git a/suds/transport/__init__.py b/suds/transport/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b193aead6edfc2c84c6d4070a64b6d8cdcc5ea89
--- /dev/null
+++ b/suds/transport/__init__.py
@@ -0,0 +1,135 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Contains transport interface (classes).
+
+"""
+
+from suds import UnicodeMixin
+
+
+class TransportError(Exception):
+    def __init__(self, reason, httpcode, fp=None):
+        Exception.__init__(self, reason)
+        self.httpcode = httpcode
+        self.fp = fp
+
+
+class Request(UnicodeMixin):
+    """
+    A transport request.
+
+    @ivar url: The URL for the request.
+    @type url: str
+    @ivar message: The message to be sent in a POST request.
+    @type message: str
+    @ivar headers: The HTTP headers to be used for the request.
+    @type headers: dict
+
+    """
+
+    def __init__(self, url, message=None):
+        """
+        @param url: The URL for the request.
+        @type url: str
+        @param message: The (optional) message to be sent in the request.
+        @type message: str
+
+        """
+        self.url = url
+        self.headers = {}
+        self.message = message
+
+    def __unicode__(self):
+        return u"""\
+URL: %s
+HEADERS: %s
+MESSAGE:
+%s""" % (self.url, self.headers, self.message)
+
+
+class Reply(UnicodeMixin):
+    """
+    A transport reply.
+
+    @ivar code: The HTTP code returned.
+    @type code: int
+    @ivar message: The message to be sent in a POST request.
+    @type message: str
+    @ivar headers: The HTTP headers to be used for the request.
+    @type headers: dict
+
+    """
+
+    def __init__(self, code, headers, message):
+        """
+        @param code: The HTTP code returned.
+        @type code: int
+        @param headers: The HTTP returned headers.
+        @type headers: dict
+        @param message: The (optional) reply message received.
+        @type message: str
+
+        """
+        self.code = code
+        self.headers = headers
+        self.message = message
+
+    def __unicode__(self):
+        return u"""\
+CODE: %s
+HEADERS: %s
+MESSAGE:
+%s""" % (self.code, self.headers, self.message)
+
+
+class Transport:
+    """The transport I{interface}."""
+
+    def __init__(self):
+        from suds.transport.options import Options
+        self.options = Options()
+
+    def open(self, request):
+        """
+        Open the URL in the specified request.
+
+        @param request: A transport request.
+        @type request: L{Request}
+        @return: An input stream.
+        @rtype: stream
+        @raise TransportError: On all transport errors.
+
+        """
+        raise Exception('not-implemented')
+
+    def send(self, request):
+        """
+        Send soap message. Implementations are expected to handle:
+            - proxies
+            - I{HTTP} headers
+            - cookies
+            - sending message
+            - brokering exceptions into L{TransportError}
+
+        @param request: A transport request.
+        @type request: L{Request}
+        @return: The reply
+        @rtype: L{Reply}
+        @raise TransportError: On all transport errors.
+
+        """
+        raise Exception('not-implemented')
diff --git a/suds/transport/http.py b/suds/transport/http.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3d042bec4ca54a71bb183bf20fa3ab28dfced41
--- /dev/null
+++ b/suds/transport/http.py
@@ -0,0 +1,254 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Contains classes for basic HTTP transport implementations.
+
+"""
+
+from suds.properties import Unskin
+from suds.transport import *
+
+import base64
+from cookielib import CookieJar
+import httplib
+import socket
+import sys
+import urllib2
+from urlparse import urlparse
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class HttpTransport(Transport):
+    """
+    Basic HTTP transport implemented using using urllib2, that provides for
+    cookies & proxies but no authentication.
+
+    """
+
+    def __init__(self, **kwargs):
+        """
+        @param kwargs: Keyword arguments.
+            - B{proxy} - An http proxy to be specified on requests.
+                 The proxy is defined as {protocol:proxy,}
+                    - type: I{dict}
+                    - default: {}
+            - B{timeout} - Set the url open timeout (seconds).
+                    - type: I{float}
+                    - default: 90
+
+        """
+        Transport.__init__(self)
+        Unskin(self.options).update(kwargs)
+        self.cookiejar = CookieJar()
+        self.proxy = {}
+        self.urlopener = None
+
+    def open(self, request):
+        try:
+            url = self.__get_request_url(request)
+            log.debug('opening (%s)', url)
+            u2request = urllib2.Request(url)
+            self.proxy = self.options.proxy
+            return self.u2open(u2request)
+        except urllib2.HTTPError, e:
+            raise TransportError(str(e), e.code, e.fp)
+
+    def send(self, request):
+        result = None
+        url = self.__get_request_url(request)
+        msg = request.message
+        headers = request.headers
+        try:
+            u2request = urllib2.Request(url, msg, headers)
+            self.addcookies(u2request)
+            self.proxy = self.options.proxy
+            request.headers.update(u2request.headers)
+            log.debug('sending:\n%s', request)
+            fp = self.u2open(u2request)
+            self.getcookies(fp, u2request)
+            if sys.version_info < (3, 0):
+                headers = fp.headers.dict
+            else:
+                headers = fp.headers
+            result = Reply(httplib.OK, headers, fp.read())
+            log.debug('received:\n%s', result)
+        except urllib2.HTTPError, e:
+            if e.code in (httplib.ACCEPTED, httplib.NO_CONTENT):
+                result = None
+            else:
+                raise TransportError(e.msg, e.code, e.fp)
+        return result
+
+    def addcookies(self, u2request):
+        """
+        Add cookies in the cookiejar to the request.
+
+        @param u2request: A urllib2 request.
+        @rtype: u2request: urllib2.Request.
+
+        """
+        self.cookiejar.add_cookie_header(u2request)
+
+    def getcookies(self, fp, u2request):
+        """
+        Add cookies in the request to the cookiejar.
+
+        @param u2request: A urllib2 request.
+        @rtype: u2request: urllib2.Request.
+
+        """
+        self.cookiejar.extract_cookies(fp, u2request)
+
+    def u2open(self, u2request):
+        """
+        Open a connection.
+
+        @param u2request: A urllib2 request.
+        @type u2request: urllib2.Request.
+        @return: The opened file-like urllib2 object.
+        @rtype: fp
+
+        """
+        tm = self.options.timeout
+        url = self.u2opener()
+        if (sys.version_info < (3, 0)) and (self.u2ver() < 2.6):
+            socket.setdefaulttimeout(tm)
+            return url.open(u2request)
+        return url.open(u2request, timeout=tm)
+
+    def u2opener(self):
+        """
+        Create a urllib opener.
+
+        @return: An opener.
+        @rtype: I{OpenerDirector}
+
+        """
+        if self.urlopener is None:
+            return urllib2.build_opener(*self.u2handlers())
+        return self.urlopener
+
+    def u2handlers(self):
+        """
+        Get a collection of urllib handlers.
+
+        @return: A list of handlers to be installed in the opener.
+        @rtype: [Handler,...]
+
+        """
+        handlers = []
+        handlers.append(urllib2.ProxyHandler(self.proxy))
+        return handlers
+
+    def u2ver(self):
+        """
+        Get the major/minor version of the urllib2 lib.
+
+        @return: The urllib2 version.
+        @rtype: float
+        """
+        try:
+            part = urllib2.__version__.split('.', 1)
+            return float('.'.join(part))
+        except Exception, e:
+            log.exception(e)
+            return 0
+
+    def __deepcopy__(self, memo={}):
+        clone = self.__class__()
+        p = Unskin(self.options)
+        cp = Unskin(clone.options)
+        cp.update(p)
+        return clone
+
+    @staticmethod
+    def __get_request_url(request):
+        """
+        Returns the given request's URL, properly encoded for use with urllib.
+
+        URLs are allowed to be:
+            under Python 2.x: unicode strings, single-byte strings;
+            under Python 3.x: unicode strings.
+        In any case, they are allowed to contain ASCII characters only. We
+        raise a UnicodeError derived exception if they contain any non-ASCII
+        characters (UnicodeEncodeError or UnicodeDecodeError depending on
+        whether the URL was specified as a unicode or a single-byte string).
+
+        Python 3.x httplib.client implementation must be given a unicode string
+        and not a bytes object and the given string is internally converted to
+        a bytes object using an explicitly specified ASCII encoding.
+
+        Python 2.7 httplib implementation expects the URL passed to it to not
+        be a unicode string. If it is, then passing it to the underlying
+        httplib Request object will cause that object to forcefully convert all
+        of its data to unicode, assuming that data contains ASCII data only and
+        raising a UnicodeDecodeError exception if it does not (caused by simple
+        unicode + string concatenation).
+
+        Python 2.4 httplib implementation does not really care about this as it
+        does not use the internal optimization present in the Python 2.7
+        implementation causing all the requested data to be converted to
+        unicode.
+
+        """
+        url = request.url
+        py2 = sys.version_info < (3, 0)
+        if py2 and isinstance(url, str):
+            encodedURL = url
+            decodedURL = url.decode("ascii")
+        else:
+            # On Python3, calling encode() on a bytes or a bytearray object
+            # raises an AttributeError exception.
+            assert py2 or not isinstance(url, bytes)
+            assert py2 or not isinstance(url, bytearray)
+            decodedURL = url
+            encodedURL = url.encode("ascii")
+        if py2:
+            return encodedURL  # Python 2 urllib - single-byte URL string.
+        return decodedURL  # Python 3 urllib - unicode URL string.
+
+
+class HttpAuthenticated(HttpTransport):
+    """
+    Provides basic HTTP authentication for servers that do not follow the
+    specified challenge/response model. Appends the I{Authorization} HTTP
+    header with base64 encoded credentials on every HTTP request.
+    """
+
+    def open(self, request):
+        self.addcredentials(request)
+        return HttpTransport.open(self, request)
+
+    def send(self, request):
+        self.addcredentials(request)
+        return HttpTransport.send(self, request)
+
+    def addcredentials(self, request):
+        credentials = self.credentials()
+        if not (None in credentials):
+            credentials = ':'.join(credentials)
+            if sys.version_info < (3,0):
+                basic = 'Basic %s' % base64.b64encode(credentials)
+            else:
+                encodedBytes = base64.urlsafe_b64encode(credentials.encode())
+                encodedString = encodedBytes.decode()
+                basic = 'Basic %s' % encodedString
+            request.headers['Authorization'] = basic
+
+    def credentials(self):
+        return self.options.username, self.options.password
diff --git a/suds/transport/https.py b/suds/transport/https.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdff55ab96f4e80671a2f61653842bfad4a52a27
--- /dev/null
+++ b/suds/transport/https.py
@@ -0,0 +1,99 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Contains classes for authenticated HTTP transport implementations.
+
+"""
+
+from suds.transport import *
+from suds.transport.http import HttpTransport
+
+import urllib2
+
+
+class HttpAuthenticated(HttpTransport):
+    """
+    Provides basic HTTP authentication that follows the RFC-2617 specification.
+
+    As defined by specifications, credentials are provided to the server upon
+    request (HTTP/1.0 401 Authorization Required) by the server only.
+
+    @ivar pm: The password manager.
+    @ivar handler: The authentication handler.
+
+    """
+
+    def __init__(self, **kwargs):
+        """
+        @param kwargs: Keyword arguments.
+            - B{proxy} - An HTTP proxy to be specified on requests.
+                 The proxy is defined as {protocol:proxy,}
+                    - type: I{dict}
+                    - default: {}
+            - B{timeout} - Set the URL open timeout (seconds).
+                    - type: I{float}
+                    - default: 90
+            - B{username} - The username used for HTTP authentication.
+                    - type: I{str}
+                    - default: None
+            - B{password} - The password used for HTTP authentication.
+                    - type: I{str}
+                    - default: None
+
+        """
+        HttpTransport.__init__(self, **kwargs)
+        self.pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
+
+    def open(self, request):
+        self.addcredentials(request)
+        return HttpTransport.open(self, request)
+
+    def send(self, request):
+        self.addcredentials(request)
+        return HttpTransport.send(self, request)
+
+    def addcredentials(self, request):
+        credentials = self.credentials()
+        if None not in credentials:
+            u = credentials[0]
+            p = credentials[1]
+            self.pm.add_password(None, request.url, u, p)
+
+    def credentials(self):
+        return self.options.username, self.options.password
+
+    def u2handlers(self):
+        handlers = HttpTransport.u2handlers(self)
+        handlers.append(urllib2.HTTPBasicAuthHandler(self.pm))
+        return handlers
+
+
+class WindowsHttpAuthenticated(HttpAuthenticated):
+    """
+    Provides Windows (NTLM) based HTTP authentication.
+
+    @author: Christopher Bess
+
+    """
+
+    def u2handlers(self):
+        try:
+            from ntlm import HTTPNtlmAuthHandler
+        except ImportError:
+            raise Exception("Cannot import python-ntlm module")
+        handlers = HttpTransport.u2handlers(self)
+        handlers.append(HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(self.pm))
+        return handlers
diff --git a/suds/transport/options.py b/suds/transport/options.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6a071e02ce9fb8f253d16f449dfeb0f0a8f6c5f
--- /dev/null
+++ b/suds/transport/options.py
@@ -0,0 +1,58 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the (LGPL) GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
+# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Classes modeling transport options.
+
+"""
+
+
+from suds.transport import *
+from suds.properties import *
+
+
+class Options(Skin):
+    """
+    Options:
+        - B{proxy} - An HTTP proxy to be specified on requests, defined as
+            {protocol:proxy, ...}.
+                - type: I{dict}
+                - default: {}
+        - B{timeout} - Set the URL open timeout (seconds).
+                - type: I{float}
+                - default: 90
+        - B{headers} - Extra HTTP headers.
+                - type: I{dict}
+                    - I{str} B{http} - The I{HTTP} protocol proxy URL.
+                    - I{str} B{https} - The I{HTTPS} protocol proxy URL.
+                - default: {}
+        - B{username} - The username used for HTTP authentication.
+                - type: I{str}
+                - default: None
+        - B{password} - The password used for HTTP authentication.
+                - type: I{str}
+                - default: None
+
+    """
+
+    def __init__(self, **kwargs):
+        domain = __name__
+        definitions = [
+            Definition('proxy', dict, {}),
+            Definition('timeout', (int,float), 90),
+            Definition('headers', dict, {}),
+            Definition('username', basestring, None),
+            Definition('password', basestring, None)]
+        Skin.__init__(self, domain, definitions, kwargs)
diff --git a/suds/umx/__init__.py b/suds/umx/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca65cad1544365c28a317f966b0414eafdfea058
--- /dev/null
+++ b/suds/umx/__init__.py
@@ -0,0 +1,56 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides modules containing classes to support
+unmarshalling (XML).
+"""
+
+from suds.sudsobject import Object
+
+
+
+class Content(Object):
+    """
+    @ivar node: The content source node.
+    @type node: L{sax.element.Element}
+    @ivar data: The (optional) content data.
+    @type data: L{Object}
+    @ivar text: The (optional) content (xml) text.
+    @type text: basestring
+    """
+
+    extensions = []
+
+    def __init__(self, node, **kwargs):
+        Object.__init__(self)
+        self.node = node
+        self.data = None
+        self.text = None
+        for k,v in kwargs.items():
+            setattr(self, k, v)
+
+    def __getattr__(self, name):
+        if name not in self.__dict__:
+            if name in self.extensions:
+                v = None
+                setattr(self, name, v)
+            else:
+                raise AttributeError, \
+                    'Content has no attribute %s' % name
+        else:
+            v = self.__dict__[name]
+        return v
diff --git a/suds/umx/attrlist.py b/suds/umx/attrlist.py
new file mode 100644
index 0000000000000000000000000000000000000000..df8da0bf32c33d702d6f68ff08311ed6fab292c4
--- /dev/null
+++ b/suds/umx/attrlist.py
@@ -0,0 +1,88 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides filtered attribute list classes.
+"""
+
+from suds import *
+from suds.umx import *
+from suds.sax import Namespace
+
+
+class AttrList:
+    """
+    A filtered attribute list.
+    Items are included during iteration if they are in either the (xs) or
+    (xml) namespaces.
+    @ivar raw: The I{raw} attribute list.
+    @type raw: list
+    """
+    def __init__(self, attributes):
+        """
+        @param attributes: A list of attributes
+        @type attributes: list
+        """
+        self.raw = attributes
+
+    def real(self):
+        """
+        Get list of I{real} attributes which exclude xs and xml attributes.
+        @return: A list of I{real} attributes.
+        @rtype: I{generator}
+        """
+        for a in self.raw:
+            if self.skip(a): continue
+            yield a
+
+    def rlen(self):
+        """
+        Get the number of I{real} attributes which exclude xs and xml attributes.
+        @return: A count of I{real} attributes.
+        @rtype: L{int}
+        """
+        n = 0
+        for a in self.real():
+            n += 1
+        return n
+
+    def lang(self):
+        """
+        Get list of I{filtered} attributes which exclude xs.
+        @return: A list of I{filtered} attributes.
+        @rtype: I{generator}
+        """
+        for a in self.raw:
+            if a.qname() == 'xml:lang':
+                return a.value
+            return None
+
+    def skip(self, attr):
+        """
+        Get whether to skip (filter-out) the specified attribute.
+        @param attr: An attribute.
+        @type attr: I{Attribute}
+        @return: True if should be skipped.
+        @rtype: bool
+        """
+        ns = attr.namespace()
+        skip = (
+            Namespace.xmlns[1],
+            'http://schemas.xmlsoap.org/soap/encoding/',
+            'http://schemas.xmlsoap.org/soap/envelope/',
+            'http://www.w3.org/2003/05/soap-envelope',
+        )
+        return ( Namespace.xs(ns) or ns[1] in skip )
diff --git a/suds/umx/basic.py b/suds/umx/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..888a21221f3e87626e6705c424380f30eaa38c5e
--- /dev/null
+++ b/suds/umx/basic.py
@@ -0,0 +1,41 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides basic unmarshaller classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.umx import *
+from suds.umx.core import Core
+
+
+class Basic(Core):
+    """
+    A object builder (unmarshaller).
+    """
+
+    def process(self, node):
+        """
+        Process an object graph representation of the xml I{node}.
+        @param node: An XML tree.
+        @type node: L{sax.element.Element}
+        @return: A suds object.
+        @rtype: L{Object}
+        """
+        content = Content(node)
+        return Core.process(self, content)
diff --git a/suds/umx/core.py b/suds/umx/core.py
new file mode 100644
index 0000000000000000000000000000000000000000..4db8eaa7723d5b6526a40817f9048c1bd9b7c5ba
--- /dev/null
+++ b/suds/umx/core.py
@@ -0,0 +1,214 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides base classes for XML->object I{unmarshalling}.
+"""
+
+from suds import *
+from suds.umx import *
+from suds.umx.attrlist import AttrList
+from suds.sax.text import Text
+from suds.sudsobject import Factory, merge
+
+
+reserved = {'class':'cls', 'def':'dfn'}
+
+
+class Core:
+    """
+    The abstract XML I{node} unmarshaller.  This class provides the
+    I{core} unmarshalling functionality.
+    """
+
+    def process(self, content):
+        """
+        Process an object graph representation of the xml I{node}.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: A suds object.
+        @rtype: L{Object}
+        """
+        self.reset()
+        return self.append(content)
+
+    def append(self, content):
+        """
+        Process the specified node and convert the XML document into
+        a I{suds} L{object}.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: A I{append-result} tuple as: (L{Object}, I{value})
+        @rtype: I{append-result}
+        @note: This is not the proper entry point.
+        @see: L{process()}
+        """
+        self.start(content)
+        self.append_attributes(content)
+        self.append_children(content)
+        self.append_text(content)
+        self.end(content)
+        return self.postprocess(content)
+
+    def postprocess(self, content):
+        """
+        Perform final processing of the resulting data structure as follows:
+          - Mixed values (children and text) will have a result of the I{content.node}.
+          - Simi-simple values (attributes, no-children and text) will have a result of a
+             property object.
+          - Simple values (no-attributes, no-children with text nodes) will have a string
+             result equal to the value of the content.node.getText().
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: The post-processed result.
+        @rtype: I{any}
+        """
+        node = content.node
+        if len(node.children) and node.hasText():
+            return node
+        attributes = AttrList(node.attributes)
+        if attributes.rlen() and \
+            not len(node.children) and \
+            node.hasText():
+                p = Factory.property(node.name, node.getText())
+                return merge(content.data, p)
+        if len(content.data):
+            return content.data
+        lang = attributes.lang()
+        if content.node.isnil():
+            return None
+        if not len(node.children) and content.text is None:
+            if self.nillable(content):
+                return None
+            else:
+                return Text('', lang=lang)
+        if isinstance(content.text, basestring):
+            return Text(content.text, lang=lang)
+        else:
+            return content.text
+
+    def append_attributes(self, content):
+        """
+        Append attribute nodes into L{Content.data}.
+        Attributes in the I{schema} or I{xml} namespaces are skipped.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        attributes = AttrList(content.node.attributes)
+        for attr in attributes.real():
+            name = attr.name
+            value = attr.value
+            self.append_attribute(name, value, content)
+
+    def append_attribute(self, name, value, content):
+        """
+        Append an attribute name/value into L{Content.data}.
+        @param name: The attribute name
+        @type name: basestring
+        @param value: The attribute's value
+        @type value: basestring
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        key = name
+        key = '_%s' % reserved.get(key, key)
+        setattr(content.data, key, value)
+
+    def append_children(self, content):
+        """
+        Append child nodes into L{Content.data}
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        for child in content.node:
+            cont = Content(child)
+            cval = self.append(cont)
+            key = reserved.get(child.name, child.name)
+            if key in content.data:
+                v = getattr(content.data, key)
+                if isinstance(v, list):
+                    v.append(cval)
+                else:
+                    setattr(content.data, key, [v, cval])
+                continue
+            if self.multi_occurrence(cont):
+                if cval is None:
+                    setattr(content.data, key, [])
+                else:
+                    setattr(content.data, key, [cval,])
+            else:
+                setattr(content.data, key, cval)
+
+    def append_text(self, content):
+        """
+        Append text nodes into L{Content.data}
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        if content.node.hasText():
+            content.text = content.node.getText()
+
+    def reset(self):
+        pass
+
+    def start(self, content):
+        """
+        Processing on I{node} has started.  Build and return
+        the proper object.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: A subclass of Object.
+        @rtype: L{Object}
+        """
+        content.data = Factory.object(content.node.name)
+
+    def end(self, content):
+        """
+        Processing on I{node} has ended.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        pass
+
+    def single_occurrence(self, content):
+        """
+        Get whether the content has at most a single occurrence (not a list).
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: True if content has at most a single occurrence, else False.
+        @rtype: boolean
+        '"""
+        return not self.multi_occurrence(content)
+
+    def multi_occurrence(self, content):
+        """
+        Get whether the content has more than one occurrence (a list).
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: True if content has more than one occurrence, else False.
+        @rtype: boolean
+        '"""
+        return False
+
+    def nillable(self, content):
+        """
+        Get whether the object is nillable.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: True if nillable, else False
+        @rtype: boolean
+        '"""
+        return False
diff --git a/suds/umx/encoded.py b/suds/umx/encoded.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb454e1cc84213e55e6c4730bfa5142bb2b82897
--- /dev/null
+++ b/suds/umx/encoded.py
@@ -0,0 +1,126 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides soap encoded unmarshaller classes.
+"""
+
+from suds import *
+from suds.umx import *
+from suds.umx.typed import Typed
+from suds.sax import Namespace
+
+
+#
+# Add encoded extensions
+# aty = The soap (section 5) encoded array type.
+#
+Content.extensions.append('aty')
+
+
+class Encoded(Typed):
+    """
+    A SOAP section (5) encoding unmarshaller.
+    This marshaller supports rpc/encoded soap styles.
+    """
+
+    def start(self, content):
+        #
+        # Grab the array type and continue
+        #
+        self.setaty(content)
+        Typed.start(self, content)
+
+    def end(self, content):
+        #
+        # Squash soap encoded arrays into python lists.  This is
+        # also where we insure that empty arrays are represented
+        # as empty python lists.
+        #
+        aty = content.aty
+        if aty is not None:
+            self.promote(content)
+        return Typed.end(self, content)
+
+    def postprocess(self, content):
+        #
+        # Ensure proper rendering of empty arrays.
+        #
+        if content.aty is None:
+            return Typed.postprocess(self, content)
+        else:
+            return content.data
+
+    def setaty(self, content):
+        """
+        Grab the (aty) soap-enc:arrayType and attach it to the
+        content for proper array processing later in end().
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: self
+        @rtype: L{Encoded}
+        """
+        name = 'arrayType'
+        ns = (None, 'http://schemas.xmlsoap.org/soap/encoding/')
+        aty = content.node.get(name, ns)
+        if aty is not None:
+            content.aty = aty
+            parts = aty.split('[')
+            ref = parts[0]
+            if len(parts) == 2:
+                self.applyaty(content, ref)
+            else:
+                pass # (2) dimensional array
+        return self
+
+    def applyaty(self, content, xty):
+        """
+        Apply the type referenced in the I{arrayType} to the content
+        (child nodes) of the array.  Each element (node) in the array
+        that does not have an explicit xsi:type attribute is given one
+        based on the I{arrayType}.
+        @param content: An array content.
+        @type content: L{Content}
+        @param xty: The XSI type reference.
+        @type xty: str
+        @return: self
+        @rtype: L{Encoded}
+        """
+        name = 'type'
+        ns = Namespace.xsins
+        parent = content.node
+        for child in parent.getChildren():
+            ref = child.get(name, ns)
+            if ref is None:
+                parent.addPrefix(ns[0], ns[1])
+                attr = ':'.join((ns[0], name))
+                child.set(attr, xty)
+        return self
+
+    def promote(self, content):
+        """
+        Promote (replace) the content.data with the first attribute
+        of the current content.data that is a I{list}.  Note: the
+        content.data may be empty or contain only _x attributes.
+        In either case, the content.data is assigned an empty list.
+        @param content: An array content.
+        @type content: L{Content}
+        """
+        for n,v in content.data:
+            if isinstance(v, list):
+                content.data = v
+                return
+        content.data = []
diff --git a/suds/umx/typed.py b/suds/umx/typed.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ab832980f858484d69b3be5b29a01a29f66d6a0
--- /dev/null
+++ b/suds/umx/typed.py
@@ -0,0 +1,140 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+Provides typed unmarshaller classes.
+"""
+
+from suds import *
+from suds.umx import *
+from suds.umx.core import Core
+from suds.resolver import NodeResolver, Frame
+from suds.sudsobject import Factory
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+#
+# Add typed extensions
+# type = The expected xsd type
+# real = The 'true' XSD type
+#
+Content.extensions.append('type')
+Content.extensions.append('real')
+
+
+class Typed(Core):
+    """
+    A I{typed} XML unmarshaller
+    @ivar resolver: A schema type resolver.
+    @type resolver: L{NodeResolver}
+    """
+
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        self.resolver = NodeResolver(schema)
+
+    def process(self, node, type):
+        """
+        Process an object graph representation of the xml L{node}.
+        @param node: An XML tree.
+        @type node: L{sax.element.Element}
+        @param type: The I{optional} schema type.
+        @type type: L{xsd.sxbase.SchemaObject}
+        @return: A suds object.
+        @rtype: L{Object}
+        """
+        content = Content(node)
+        content.type = type
+        return Core.process(self, content)
+
+    def reset(self):
+        log.debug('reset')
+        self.resolver.reset()
+
+    def start(self, content):
+        #
+        # Resolve to the schema type; build an object and setup metadata.
+        #
+        if content.type is None:
+            found = self.resolver.find(content.node)
+            if found is None:
+                log.error(self.resolver.schema)
+                raise TypeNotFound(content.node.qname())
+            content.type = found
+        else:
+            known = self.resolver.known(content.node)
+            frame = Frame(content.type, resolved=known)
+            self.resolver.push(frame)
+        real = self.resolver.top().resolved
+        content.real = real
+        cls_name = real.name
+        if cls_name is None:
+            cls_name = content.node.name
+        content.data = Factory.object(cls_name)
+        md = content.data.__metadata__
+        md.sxtype = real
+
+    def end(self, content):
+        self.resolver.pop()
+
+    def multi_occurrence(self, content):
+        return content.type.multi_occurrence()
+
+    def nillable(self, content):
+        resolved = content.type.resolve()
+        return ( content.type.nillable or \
+            (resolved.builtin() and resolved.nillable ) )
+
+    def append_attribute(self, name, value, content):
+        """
+        Append an attribute name/value into L{Content.data}.
+        @param name: The attribute name
+        @type name: basestring
+        @param value: The attribute's value
+        @type value: basestring
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        type = self.resolver.findattr(name)
+        if type is None:
+            log.warn('attribute (%s) type, not-found', name)
+        else:
+            value = self.translated(value, type)
+        Core.append_attribute(self, name, value, content)
+
+    def append_text(self, content):
+        """
+        Append text nodes into L{Content.data}
+        Here is where the I{true} type is used to translate the value
+        into the proper python type.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        Core.append_text(self, content)
+        known = self.resolver.top().resolved
+        content.text = self.translated(content.text, known)
+
+    def translated(self, value, type):
+        """ translate using the schema type """
+        if value is not None:
+            resolved = type.resolve()
+            return resolved.translate(value)
+        return value
diff --git a/suds/version.py b/suds/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..28f5a90a2fd75efecffa1cadc91f94cefff39696
--- /dev/null
+++ b/suds/version.py
@@ -0,0 +1,26 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""
+Module containing the library's version information.
+
+  This version information has been extracted into a separate file so it can be
+read from the setup.py script without having to import the suds package itself.
+See the setup.py script for more detailed information.
+
+"""
+
+__version__ = "0.6"
+__build__ = ""
diff --git a/suds/wsdl.py b/suds/wsdl.py
new file mode 100644
index 0000000000000000000000000000000000000000..987dbc3fadf017a78e890b8771643e323f48093c
--- /dev/null
+++ b/suds/wsdl.py
@@ -0,0 +1,917 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{wsdl} module provides an objectification of the WSDL.
+The primary class is I{Definitions} as it represents the root element
+found in the document.
+"""
+
+from suds import *
+from suds.sax.element import Element
+from suds.bindings.document import Document
+from suds.bindings.rpc import RPC, Encoded
+from suds.xsd import qualify, Namespace
+from suds.xsd.schema import Schema, SchemaCollection
+from suds.xsd.query import ElementQuery
+from suds.sudsobject import Object, Facade, Metadata
+from suds.reader import DocumentReader
+
+import re
+import soaparray
+from urlparse import urljoin
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+wsdlns = (None, "http://schemas.xmlsoap.org/wsdl/")
+soapns = (None, 'http://schemas.xmlsoap.org/wsdl/soap/')
+soap12ns = (None, 'http://schemas.xmlsoap.org/wsdl/soap12/')
+
+
+class WObject(Object):
+    """
+    Base object for WSDL types.
+    @ivar root: The XML I{root} element.
+    @type root: L{Element}
+    """
+
+    def __init__(self, root):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        """
+        Object.__init__(self)
+        self.root = root
+        pmd = Metadata()
+        pmd.excludes = ['root']
+        pmd.wrappers = dict(qname=repr)
+        self.__metadata__.__print__ = pmd
+
+    def resolve(self, definitions):
+        """
+        Resolve named references to other WSDL objects.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        pass
+
+
+class NamedObject(WObject):
+    """
+    A B{named} WSDL object.
+    @ivar name: The name of the object.
+    @type name: str
+    @ivar qname: The I{qualified} name of the object.
+    @type qname: (name, I{namespace-uri}).
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        WObject.__init__(self, root)
+        self.name = root.get('name')
+        self.qname = (self.name, definitions.tns[1])
+        pmd = self.__metadata__.__print__
+        pmd.wrappers['qname'] = repr
+
+
+class Definitions(WObject):
+    """
+    I{root} container for all the WSDL objects as defined by
+    <wsdl:definitions/>
+    @ivar id: The object id.
+    @type id: str
+    @ivar options: An options dictionary.
+    @type options: L{options.Options}
+    @ivar url: The URL used to load the object.
+    @type url: str
+    @ivar tns: The target namespace for the WSDL.
+    @type tns: str
+    @ivar schema: The collective WSDL schema object.
+    @type schema: L{SchemaCollection}
+    @ivar children: The raw list of child objects.
+    @type children: [L{WObject},...]
+    @ivar imports: The list of L{Import} children.
+    @type imports: [L{Import},...]
+    @ivar messages: The dictionary of L{Message} children key'd by I{qname}
+    @type messages: [L{Message},...]
+    @ivar port_types: The dictionary of L{PortType} children key'd by I{qname}
+    @type port_types: [L{PortType},...]
+    @ivar bindings: The dictionary of L{Binding} children key'd by I{qname}
+    @type bindings: [L{Binding},...]
+    @ivar service: The service object.
+    @type service: L{Service}
+    """
+
+    Tag = 'definitions'
+
+    def __init__(self, url, options):
+        """
+        @param url: A URL to the WSDL.
+        @type url: str
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        """
+        log.debug('reading WSDL at: %s ...', url)
+        reader = DocumentReader(options)
+        d = reader.open(url)
+        root = d.root()
+        WObject.__init__(self, root)
+        self.id = objid(self)
+        self.options = options
+        self.url = url
+        self.tns = self.mktns(root)
+        self.types = []
+        self.schema = None
+        self.children = []
+        self.imports = []
+        self.messages = {}
+        self.port_types = {}
+        self.bindings = {}
+        self.services = []
+        self.add_children(self.root)
+        self.children.sort()
+        pmd = self.__metadata__.__print__
+        pmd.excludes.append('children')
+        pmd.excludes.append('wsdl')
+        pmd.wrappers['schema'] = repr
+        self.open_imports()
+        self.resolve()
+        self.build_schema()
+        self.set_wrapped()
+        for s in self.services:
+            self.add_methods(s)
+        log.debug("WSDL at '%s' loaded:\n%s", url, self)
+
+    def mktns(self, root):
+        """ Get/create the target namespace """
+        tns = root.get('targetNamespace')
+        prefix = root.findPrefix(tns)
+        if prefix is None:
+            log.debug('warning: tns (%s), not mapped to prefix', tns)
+            prefix = 'tns'
+        return (prefix, tns)
+
+    def add_children(self, root):
+        """ Add child objects using the factory """
+        for c in root.getChildren(ns=wsdlns):
+            child = Factory.create(c, self)
+            if child is None: continue
+            self.children.append(child)
+            if isinstance(child, Import):
+                self.imports.append(child)
+                continue
+            if isinstance(child, Types):
+                self.types.append(child)
+                continue
+            if isinstance(child, Message):
+                self.messages[child.qname] = child
+                continue
+            if isinstance(child, PortType):
+                self.port_types[child.qname] = child
+                continue
+            if isinstance(child, Binding):
+                self.bindings[child.qname] = child
+                continue
+            if isinstance(child, Service):
+                self.services.append(child)
+                continue
+
+    def open_imports(self):
+        """ Import the I{imported} WSDLs. """
+        for imp in self.imports:
+            imp.load(self)
+
+    def resolve(self):
+        """ Tell all children to resolve themselves """
+        for c in self.children:
+            c.resolve(self)
+
+    def build_schema(self):
+        """ Process L{Types} objects and create the schema collection """
+        container = SchemaCollection(self)
+        for t in [t for t in self.types if t.local()]:
+            for root in t.contents():
+                schema = Schema(root, self.url, self.options, container)
+                container.add(schema)
+        if not len(container):
+            root = Element.buildPath(self.root, 'types/schema')
+            schema = Schema(root, self.url, self.options, container)
+            container.add(schema)
+        self.schema = container.load(self.options)
+        for s in [t.schema() for t in self.types if t.imported()]:
+            self.schema.merge(s)
+        return self.schema
+
+    def add_methods(self, service):
+        """ Build method view for service """
+        bindings = {
+            'document/literal' : Document(self),
+            'rpc/literal' : RPC(self),
+            'rpc/encoded' : Encoded(self)
+        }
+        for p in service.ports:
+            binding = p.binding
+            ptype = p.binding.type
+            operations = p.binding.type.operations.values()
+            for name in [op.name for op in operations]:
+                m = Facade('Method')
+                m.name = name
+                m.location = p.location
+                m.binding = Facade('binding')
+                op = binding.operation(name)
+                m.soap = op.soap
+                key = '/'.join((op.soap.style, op.soap.input.body.use))
+                m.binding.input = bindings.get(key)
+                key = '/'.join((op.soap.style, op.soap.output.body.use))
+                m.binding.output = bindings.get(key)
+                op = ptype.operation(name)
+                p.methods[name] = m
+
+    def set_wrapped(self):
+        """ set (wrapped|bare) flag on messages """
+        for b in self.bindings.values():
+            for op in b.operations.values():
+                for body in (op.soap.input.body, op.soap.output.body):
+                    body.wrapped = False
+                    if not self.options.unwrap:
+                        continue
+                    if len(body.parts) != 1:
+                        continue
+                    for p in body.parts:
+                        if p.element is None:
+                            continue
+                        query = ElementQuery(p.element)
+                        pt = query.execute(self.schema)
+                        if pt is None:
+                            raise TypeNotFound(query.ref)
+                        resolved = pt.resolve()
+                        if resolved.builtin():
+                            continue
+                        body.wrapped = True
+
+    def __getstate__(self):
+        nopickle = ('options',)
+        state = self.__dict__.copy()
+        for k in nopickle:
+            if k in state:
+                del state[k]
+        return state
+
+    def __repr__(self):
+        return 'Definitions (id=%s)' % self.id
+
+
+class Import(WObject):
+    """
+    Represents the <wsdl:import/>.
+    @ivar location: The value of the I{location} attribute.
+    @type location: str
+    @ivar ns: The value of the I{namespace} attribute.
+    @type ns: str
+    @ivar imported: The imported object.
+    @type imported: L{Definitions}
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        WObject.__init__(self, root)
+        self.location = root.get('location')
+        self.ns = root.get('namespace')
+        self.imported = None
+        pmd = self.__metadata__.__print__
+        pmd.wrappers['imported'] = repr
+
+    def load(self, definitions):
+        """ Load the object by opening the URL """
+        url = self.location
+        log.debug('importing (%s)', url)
+        if '://' not in url:
+            url = urljoin(definitions.url, url)
+        options = definitions.options
+        d = Definitions(url, options)
+        if d.root.match(Definitions.Tag, wsdlns):
+            self.import_definitions(definitions, d)
+            return
+        if d.root.match(Schema.Tag, Namespace.xsdns):
+            self.import_schema(definitions, d)
+            return
+        raise Exception('document at "%s" is unknown' % url)
+
+    def import_definitions(self, definitions, d):
+        """ import/merge WSDL definitions """
+        definitions.types += d.types
+        definitions.messages.update(d.messages)
+        definitions.port_types.update(d.port_types)
+        definitions.bindings.update(d.bindings)
+        self.imported = d
+        log.debug('imported (WSDL):\n%s', d)
+
+    def import_schema(self, definitions, d):
+        """ import schema as <types/> content """
+        if not len(definitions.types):
+            root = Element('types', ns=wsdlns)
+            definitions.root.insert(root)
+            types = Types(root, definitions)
+            definitions.types.append(types)
+        else:
+            types = definitions.types[-1]
+        types.root.append(d.root)
+        log.debug('imported (XSD):\n%s', d.root)
+
+    def __gt__(self, other):
+        return False
+
+
+class Types(WObject):
+    """
+    Represents <types><schema/></types>.
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        WObject.__init__(self, root)
+        self.definitions = definitions
+
+    def contents(self):
+        return self.root.getChildren('schema', Namespace.xsdns)
+
+    def schema(self):
+        return self.definitions.schema
+
+    def local(self):
+        return ( self.definitions.schema is None )
+
+    def imported(self):
+        return ( not self.local() )
+
+    def __gt__(self, other):
+        return isinstance(other, Import)
+
+
+class Part(NamedObject):
+    """
+    Represents <message><part/></message>.
+    @ivar element: The value of the {element} attribute.
+        Stored as a I{qref} as converted by L{suds.xsd.qualify}.
+    @type element: str
+    @ivar type: The value of the {type} attribute.
+        Stored as a I{qref} as converted by L{suds.xsd.qualify}.
+    @type type: str
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        pmd = Metadata()
+        pmd.wrappers = dict(element=repr, type=repr)
+        self.__metadata__.__print__ = pmd
+        tns = definitions.tns
+        self.element = self.__getref('element', tns)
+        self.type = self.__getref('type', tns)
+
+    def __getref(self, a, tns):
+        """ Get the qualified value of attribute named 'a'."""
+        s = self.root.get(a)
+        if s is None:
+            return s
+        else:
+            return qualify(s, self.root, tns)
+
+
+class Message(NamedObject):
+    """
+    Represents <message/>.
+    @ivar parts: A list of message parts.
+    @type parts: [I{Part},...]
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.parts = []
+        for p in root.getChildren('part'):
+            part = Part(p, definitions)
+            self.parts.append(part)
+
+    def __gt__(self, other):
+        return isinstance(other, (Import, Types))
+
+
+class PortType(NamedObject):
+    """
+    Represents <portType/>.
+    @ivar operations: A list of contained operations.
+    @type operations: list
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.operations = {}
+        for c in root.getChildren('operation'):
+            op = Facade('Operation')
+            op.name = c.get('name')
+            op.tns = definitions.tns
+            input = c.getChild('input')
+            if input is None:
+                op.input = None
+            else:
+                op.input = input.get('message')
+            output = c.getChild('output')
+            if output is None:
+                op.output = None
+            else:
+                op.output = output.get('message')
+            faults = []
+            for fault in c.getChildren('fault'):
+                f = Facade('Fault')
+                f.name = fault.get('name')
+                f.message = fault.get('message')
+                faults.append(f)
+            op.faults = faults
+            self.operations[op.name] = op
+
+    def resolve(self, definitions):
+        """
+        Resolve named references to other WSDL objects.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        for op in self.operations.values():
+            if op.input is None:
+                op.input = Message(Element('no-input'), definitions)
+            else:
+                qref = qualify(op.input, self.root, definitions.tns)
+                msg = definitions.messages.get(qref)
+                if msg is None:
+                    raise Exception("msg '%s', not-found" % op.input)
+                else:
+                    op.input = msg
+            if op.output is None:
+                op.output = Message(Element('no-output'), definitions)
+            else:
+                qref = qualify(op.output, self.root, definitions.tns)
+                msg = definitions.messages.get(qref)
+                if msg is None:
+                    raise Exception("msg '%s', not-found" % op.output)
+                else:
+                    op.output = msg
+            for f in op.faults:
+                qref = qualify(f.message, self.root, definitions.tns)
+                msg = definitions.messages.get(qref)
+                if msg is None:
+                    raise Exception, "msg '%s', not-found" % f.message
+                f.message = msg
+
+    def operation(self, name):
+        """
+        Shortcut used to get a contained operation by name.
+        @param name: An operation name.
+        @type name: str
+        @return: The named operation.
+        @rtype: Operation
+        @raise L{MethodNotFound}: When not found.
+        """
+        try:
+            return self.operations[name]
+        except Exception, e:
+            raise MethodNotFound(name)
+
+    def __gt__(self, other):
+        return isinstance(other, (Import, Types, Message))
+
+
+class Binding(NamedObject):
+    """
+    Represents <binding/>
+    @ivar operations: A list of contained operations.
+    @type operations: list
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.operations = {}
+        self.type = root.get('type')
+        sr = self.soaproot()
+        if sr is None:
+            self.soap = None
+            log.debug('binding: "%s" not a SOAP binding', self.name)
+            return
+        soap = Facade('soap')
+        self.soap = soap
+        self.soap.style = sr.get('style', default='document')
+        self.add_operations(self.root, definitions)
+
+    def soaproot(self):
+        """ get the soap:binding """
+        for ns in (soapns, soap12ns):
+            sr = self.root.getChild('binding', ns=ns)
+            if sr is not None:
+                return sr
+        return None
+
+    def add_operations(self, root, definitions):
+        """ Add <operation/> children """
+        dsop = Element('operation', ns=soapns)
+        for c in root.getChildren('operation'):
+            op = Facade('Operation')
+            op.name = c.get('name')
+            sop = c.getChild('operation', default=dsop)
+            soap = Facade('soap')
+            soap.action = '"%s"' % sop.get('soapAction', default='')
+            soap.style = sop.get('style', default=self.soap.style)
+            soap.input = Facade('Input')
+            soap.input.body = Facade('Body')
+            soap.input.headers = []
+            soap.output = Facade('Output')
+            soap.output.body = Facade('Body')
+            soap.output.headers = []
+            op.soap = soap
+            input = c.getChild('input')
+            if input is None:
+                input = Element('input', ns=wsdlns)
+            body = input.getChild('body')
+            self.body(definitions, soap.input.body, body)
+            for header in input.getChildren('header'):
+                self.header(definitions, soap.input, header)
+            output = c.getChild('output')
+            if output is None:
+                output = Element('output', ns=wsdlns)
+            body = output.getChild('body')
+            self.body(definitions, soap.output.body, body)
+            for header in output.getChildren('header'):
+                self.header(definitions, soap.output, header)
+            faults = []
+            for fault in c.getChildren('fault'):
+                sf = fault.getChild('fault')
+                if sf is None:
+                    continue
+                fn = fault.get('name')
+                f = Facade('Fault')
+                f.name = sf.get('name', default=fn)
+                f.use = sf.get('use', default='literal')
+                faults.append(f)
+            soap.faults = faults
+            self.operations[op.name] = op
+
+    def body(self, definitions, body, root):
+        """ add the input/output body properties """
+        if root is None:
+            body.use = 'literal'
+            body.namespace = definitions.tns
+            body.parts = ()
+            return
+        parts = root.get('parts')
+        if parts is None:
+            body.parts = ()
+        else:
+            body.parts = re.split('[\s,]', parts)
+        body.use = root.get('use', default='literal')
+        ns = root.get('namespace')
+        if ns is None:
+            body.namespace = definitions.tns
+        else:
+            prefix = root.findPrefix(ns, 'b0')
+            body.namespace = (prefix, ns)
+
+    def header(self, definitions, parent, root):
+        """ add the input/output header properties """
+        if root is None:
+            return
+        header = Facade('Header')
+        parent.headers.append(header)
+        header.use = root.get('use', default='literal')
+        ns = root.get('namespace')
+        if ns is None:
+            header.namespace = definitions.tns
+        else:
+            prefix = root.findPrefix(ns, 'h0')
+            header.namespace = (prefix, ns)
+        msg = root.get('message')
+        if msg is not None:
+            header.message = msg
+        part = root.get('part')
+        if part is not None:
+            header.part = part
+
+    def resolve(self, definitions):
+        """
+        Resolve named references to other WSDL objects.  This includes
+        cross-linking information (from) the portType (to) the I{SOAP}
+        protocol information on the binding for each operation.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        self.resolveport(definitions)
+        for op in self.operations.values():
+            self.resolvesoapbody(definitions, op)
+            self.resolveheaders(definitions, op)
+            self.resolvefaults(definitions, op)
+
+    def resolveport(self, definitions):
+        """
+        Resolve port_type reference.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        ref = qualify(self.type, self.root, definitions.tns)
+        port_type = definitions.port_types.get(ref)
+        if port_type is None:
+            raise Exception("portType '%s', not-found" % self.type)
+        else:
+            self.type = port_type
+
+    def resolvesoapbody(self, definitions, op):
+        """
+        Resolve SOAP body I{message} parts by
+        cross-referencing with operation defined in port type.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @param op: An I{operation} object.
+        @type op: I{operation}
+        """
+        ptop = self.type.operation(op.name)
+        if ptop is None:
+            raise Exception, \
+                "operation '%s' not defined in portType" % op.name
+        soap = op.soap
+        parts = soap.input.body.parts
+        if len(parts):
+            pts = []
+            for p in ptop.input.parts:
+                if p.name in parts:
+                    pts.append(p)
+            soap.input.body.parts = pts
+        else:
+            soap.input.body.parts = ptop.input.parts
+        parts = soap.output.body.parts
+        if len(parts):
+            pts = []
+            for p in ptop.output.parts:
+                if p.name in parts:
+                    pts.append(p)
+            soap.output.body.parts = pts
+        else:
+            soap.output.body.parts = ptop.output.parts
+
+    def resolveheaders(self, definitions, op):
+        """
+        Resolve SOAP header I{message} references.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @param op: An I{operation} object.
+        @type op: I{operation}
+        """
+        soap = op.soap
+        headers = soap.input.headers + soap.output.headers
+        for header in headers:
+            mn = header.message
+            ref = qualify(mn, self.root, definitions.tns)
+            message = definitions.messages.get(ref)
+            if message is None:
+                raise Exception, "message'%s', not-found" % mn
+            pn = header.part
+            for p in message.parts:
+                if p.name == pn:
+                    header.part = p
+                    break
+            if pn == header.part:
+                raise Exception, \
+                    "message '%s' has not part named '%s'" % (ref, pn)
+
+    def resolvefaults(self, definitions, op):
+        """
+        Resolve SOAP fault I{message} references by
+        cross-referencing with operations defined in the port type.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @param op: An I{operation} object.
+        @type op: I{operation}
+        """
+        ptop = self.type.operation(op.name)
+        if ptop is None:
+            raise Exception, \
+                "operation '%s' not defined in portType" % op.name
+        soap = op.soap
+        for fault in soap.faults:
+            for f in ptop.faults:
+                if f.name == fault.name:
+                    fault.parts = f.message.parts
+                    continue
+            if hasattr(fault, 'parts'):
+                continue
+            raise Exception, \
+                "fault '%s' not defined in portType '%s'" % (fault.name, self.type.name)
+
+    def operation(self, name):
+        """
+        Shortcut used to get a contained operation by name.
+        @param name: An operation name.
+        @type name: str
+        @return: The named operation.
+        @rtype: Operation
+        @raise L{MethodNotFound}: When not found.
+        """
+        try:
+            return self.operations[name]
+        except:
+            raise MethodNotFound(name)
+
+    def __gt__(self, other):
+        return ( not isinstance(other, Service) )
+
+
+class Port(NamedObject):
+    """
+    Represents a service port.
+    @ivar service: A service.
+    @type service: L{Service}
+    @ivar binding: A binding name.
+    @type binding: str
+    @ivar location: The service location (URL).
+    @type location: str
+    """
+
+    def __init__(self, root, definitions, service):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @param service: A service object.
+        @type service: L{Service}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.__service = service
+        self.binding = root.get('binding')
+        address = root.getChild('address')
+        self.location = address is not None and address.get('location')
+        self.methods = {}
+
+    def method(self, name):
+        """
+        Get a method defined in this portType by name.
+        @param name: A method name.
+        @type name: str
+        @return: The requested method object.
+        @rtype: I{Method}
+        """
+        return self.methods.get(name)
+
+
+class Service(NamedObject):
+    """
+    Represents <service/>.
+    @ivar port: The contained ports.
+    @type port: [Port,..]
+    @ivar methods: The contained methods for all ports.
+    @type methods: [Method,..]
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.ports = []
+        for p in root.getChildren('port'):
+            port = Port(p, definitions, self)
+            self.ports.append(port)
+
+    def port(self, name):
+        """
+        Locate a port by name.
+        @param name: A port name.
+        @type name: str
+        @return: The port object.
+        @rtype: L{Port}
+        """
+        for p in self.ports:
+            if p.name == name:
+                return p
+        return None
+
+    def setlocation(self, url, names=None):
+        """
+        Override the invocation location (URL) for service method.
+        @param url: A URL location.
+        @type url: A URL.
+        @param names:  A list of method names.  None=ALL
+        @type names: [str,..]
+        """
+        for p in self.ports:
+            for m in p.methods.values():
+                if names is None or m.name in names:
+                    m.location = url
+
+    def resolve(self, definitions):
+        """
+        Resolve named references to other WSDL objects.
+        Ports without SOAP bindings are discarded.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        filtered = []
+        for p in self.ports:
+            ref = qualify(p.binding, self.root, definitions.tns)
+            binding = definitions.bindings.get(ref)
+            if binding is None:
+                raise Exception("binding '%s', not-found" % p.binding)
+            if binding.soap is None:
+                log.debug('binding "%s" - not a SOAP binding, discarded', binding.name)
+                continue
+            p.binding = binding
+            filtered.append(p)
+        self.ports = filtered
+
+    def __gt__(self, other):
+        return True
+
+
+class Factory:
+    """
+    Simple WSDL object factory.
+    @cvar tags: Dictionary of tag->constructor mappings.
+    @type tags: dict
+    """
+
+    tags =\
+    {
+        'import' : Import,
+        'types' : Types,
+        'message' : Message,
+        'portType' : PortType,
+        'binding' : Binding,
+        'service' : Service,
+    }
+
+    @classmethod
+    def create(cls, root, definitions):
+        """
+        Create an object based on the root tag name.
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @return: The created object.
+        @rtype: L{WObject}
+        """
+        fn = cls.tags.get(root.name)
+        if fn is not None:
+            return fn(root, definitions)
+        else:
+            return None
diff --git a/suds/wsse.py b/suds/wsse.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2f7f524297fae37322d4297115181ccb3268a2e
--- /dev/null
+++ b/suds/wsse.py
@@ -0,0 +1,212 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{wsse} module provides WS-Security.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sudsobject import Object
+from suds.sax.element import Element
+from suds.sax.date import DateTime, UtcTimezone
+from datetime import datetime, timedelta
+
+try:
+    from hashlib import md5
+except ImportError:
+    # Python 2.4 compatibility
+    from md5 import md5
+
+
+dsns = \
+    ('ds',
+     'http://www.w3.org/2000/09/xmldsig#')
+wssens = \
+    ('wsse',
+     'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd')
+wsuns = \
+    ('wsu',
+     'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd')
+wsencns = \
+    ('wsenc',
+     'http://www.w3.org/2001/04/xmlenc#')
+
+
+class Security(Object):
+    """
+    WS-Security object.
+    @ivar tokens: A list of security tokens
+    @type tokens: [L{Token},...]
+    @ivar signatures: A list of signatures.
+    @type signatures: TBD
+    @ivar references: A list of references.
+    @type references: TBD
+    @ivar keys: A list of encryption keys.
+    @type keys: TBD
+    """
+
+    def __init__(self):
+        """ """
+        Object.__init__(self)
+        self.mustUnderstand = True
+        self.tokens = []
+        self.signatures = []
+        self.references = []
+        self.keys = []
+
+    def xml(self):
+        """
+        Get xml representation of the object.
+        @return: The root node.
+        @rtype: L{Element}
+        """
+        root = Element('Security', ns=wssens)
+        root.set('mustUnderstand', str(self.mustUnderstand).lower())
+        for t in self.tokens:
+            root.append(t.xml())
+        return root
+
+
+class Token(Object):
+    """ I{Abstract} security token. """
+
+    @classmethod
+    def now(cls):
+        return datetime.now()
+
+    @classmethod
+    def utc(cls):
+        return datetime.utcnow().replace(tzinfo=UtcTimezone())
+
+    @classmethod
+    def sysdate(cls):
+        utc = DateTime(self.utc())
+        return str(utc)
+
+    def __init__(self):
+            Object.__init__(self)
+
+
+class UsernameToken(Token):
+    """
+    Represents a basic I{UsernameToken} WS-Secuirty token.
+    @ivar username: A username.
+    @type username: str
+    @ivar password: A password.
+    @type password: str
+    @ivar nonce: A set of bytes to prevent replay attacks.
+    @type nonce: str
+    @ivar created: The token created.
+    @type created: L{datetime}
+    """
+
+    def __init__(self, username=None, password=None):
+        """
+        @param username: A username.
+        @type username: str
+        @param password: A password.
+        @type password: str
+        """
+        Token.__init__(self)
+        self.username = username
+        self.password = password
+        self.nonce = None
+        self.created = None
+
+    def setnonce(self, text=None):
+        """
+        Set I{nonce} which is an arbitrary set of bytes to prevent replay
+        attacks.
+        @param text: The nonce text value.
+            Generated when I{None}.
+        @type text: str
+        """
+        if text is None:
+            s = []
+            s.append(self.username)
+            s.append(self.password)
+            s.append(Token.sysdate())
+            m = md5()
+            m.update(':'.join(s))
+            self.nonce = m.hexdigest()
+        else:
+            self.nonce = text
+
+    def setcreated(self, dt=None):
+        """
+        Set I{created}.
+        @param dt: The created date & time.
+            Set as datetime.utc() when I{None}.
+        @type dt: L{datetime}
+        """
+        if dt is None:
+            self.created = Token.utc()
+        else:
+            self.created = dt
+
+
+    def xml(self):
+        """
+        Get xml representation of the object.
+        @return: The root node.
+        @rtype: L{Element}
+        """
+        root = Element('UsernameToken', ns=wssens)
+        u = Element('Username', ns=wssens)
+        u.setText(self.username)
+        root.append(u)
+        p = Element('Password', ns=wssens)
+        p.setText(self.password)
+        root.append(p)
+        if self.nonce is not None:
+            n = Element('Nonce', ns=wssens)
+            n.setText(self.nonce)
+            root.append(n)
+        if self.created is not None:
+            n = Element('Created', ns=wsuns)
+            n.setText(str(DateTime(self.created)))
+            root.append(n)
+        return root
+
+
+class Timestamp(Token):
+    """
+    Represents the I{Timestamp} WS-Secuirty token.
+    @ivar created: The token created.
+    @type created: L{datetime}
+    @ivar expires: The token expires.
+    @type expires: L{datetime}
+    """
+
+    def __init__(self, validity=90):
+        """
+        @param validity: The time in seconds.
+        @type validity: int
+        """
+        Token.__init__(self)
+        self.created = Token.utc()
+        self.expires = self.created + timedelta(seconds=validity)
+
+    def xml(self):
+        root = Element("Timestamp", ns=wsuns)
+        created = Element('Created', ns=wsuns)
+        created.setText(str(DateTime(self.created)))
+        expires = Element('Expires', ns=wsuns)
+        expires.setText(str(DateTime(self.expires)))
+        root.append(created)
+        root.append(expires)
+        return root
diff --git a/suds/xsd/__init__.py b/suds/xsd/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5d801543bcc04b24b908eb1e096bb351bae4458
--- /dev/null
+++ b/suds/xsd/__init__.py
@@ -0,0 +1,75 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+
+from suds import *
+from suds.sax import Namespace, splitPrefix
+
+
+def qualify(ref, resolvers, defns=Namespace.default):
+    """
+    Get a reference that is I{qualified} by namespace.
+    @param ref: A referenced schema type name.
+    @type ref: str
+    @param resolvers: A list of objects to be used to resolve types.
+    @type resolvers: [L{sax.element.Element},]
+    @param defns: An optional target namespace used to qualify references
+        when no prefix is specified.
+    @type defns: A default namespace I{tuple: (prefix,uri)} used when ref not prefixed.
+    @return: A qualified reference.
+    @rtype: (name, namespace-uri)
+    """
+    ns = None
+    p, n = splitPrefix(ref)
+    if p is not None:
+        if not isinstance(resolvers, (list, tuple)):
+            resolvers = (resolvers,)
+        for r in resolvers:
+            resolved = r.resolvePrefix(p)
+            if resolved[1] is not None:
+                ns = resolved
+                break
+        if ns is None:
+            raise Exception('prefix (%s) not resolved' % p)
+    else:
+        ns = defns
+    return (n, ns[1])
+
+def isqref(object):
+    """
+    Get whether the object is a I{qualified reference}.
+    @param object: An object to be tested.
+    @type object: I{any}
+    @rtype: boolean
+    @see: L{qualify}
+    """
+    return (\
+        isinstance(object, tuple) and \
+        len(object) == 2 and \
+        isinstance(object[0], basestring) and \
+        isinstance(object[1], basestring))
+
+
+class Filter:
+    def __init__(self, inclusive=False, *items):
+        self.inclusive = inclusive
+        self.items = items
+    def __contains__(self, x):
+        if self.inclusive:
+            result = ( x in self.items )
+        else:
+            result = ( x not in self.items )
+        return result
diff --git a/suds/xsd/deplist.py b/suds/xsd/deplist.py
new file mode 100644
index 0000000000000000000000000000000000000000..b813f80f1affacfaca63f1573c58793051235c1d
--- /dev/null
+++ b/suds/xsd/deplist.py
@@ -0,0 +1,140 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{depsolve} module defines a class for performing dependency solving.
+"""
+
+from suds import *
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class DepList:
+    """
+    Dependency solving list.
+    Items are tuples: (object, (deps,))
+    @ivar raw: The raw (unsorted) items.
+    @type raw: list
+    @ivar index: The index of (unsorted) items.
+    @type index: list
+    @ivar stack: The sorting stack.
+    @type stack: list
+    @ivar pushed: The I{pushed} set tracks items that have been
+        processed.
+    @type pushed: set
+    @ivar sorted: The sorted list of items.
+    @type sorted: list
+    """
+
+    def __init__(self):
+        """ """
+        self.unsorted = []
+        self.index = {}
+        self.stack = []
+        self.pushed = set()
+        self.sorted = None
+
+    def add(self, *items):
+        """
+        Add items to be sorted.
+        @param items: One or more items to be added.
+        @type items: I{item}
+        @return: self
+        @rtype: L{DepList}
+        """
+        for item in items:
+            self.unsorted.append(item)
+            key = item[0]
+            self.index[key] = item
+        return self
+
+    def sort(self):
+        """
+        Sort the list based on dependencies.
+        @return: The sorted items.
+        @rtype: list
+        """
+        self.sorted = list()
+        self.pushed = set()
+        for item in self.unsorted:
+            popped = []
+            self.push(item)
+            while len(self.stack):
+                try:
+                    top = self.top()
+                    ref = top[1].next()
+                    refd = self.index.get(ref)
+                    if refd is None:
+                        log.debug('"%s" not found, skipped', Repr(ref))
+                        continue
+                    self.push(refd)
+                except StopIteration:
+                    popped.append(self.pop())
+                    continue
+            for p in popped:
+                self.sorted.append(p)
+        self.unsorted = self.sorted
+        return self.sorted
+
+    def top(self):
+        """
+        Get the item at the top of the stack.
+        @return: The top item.
+        @rtype: (item, iter)
+        """
+        return self.stack[-1]
+
+    def push(self, item):
+        """
+        Push and item onto the sorting stack.
+        @param item: An item to push.
+        @type item: I{item}
+        @return: The number of items pushed.
+        @rtype: int
+        """
+        if item in self.pushed:
+            return
+        frame = (item, iter(item[1]))
+        self.stack.append(frame)
+        self.pushed.add(item)
+
+    def pop(self):
+        """
+        Pop the top item off the stack and append
+        it to the sorted list.
+        @return: The popped item.
+        @rtype: I{item}
+        """
+        try:
+            frame = self.stack.pop()
+            return frame[0]
+        except:
+            pass
+
+
+if __name__ == '__main__':
+    a = ('a', ('x',))
+    b = ('b', ('a',))
+    c = ('c', ('a','b'))
+    d = ('d', ('c',))
+    e = ('e', ('d','a'))
+    f = ('f', ('e','c','d','a'))
+    x = ('x', ())
+    L = DepList()
+    L.add(c, e, d, b, f, a, x)
+    print [x[0] for x in L.sort()]
diff --git a/suds/xsd/doctor.py b/suds/xsd/doctor.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a52e76c66258688fec1dcba49ddce2e49bfe304
--- /dev/null
+++ b/suds/xsd/doctor.py
@@ -0,0 +1,223 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{doctor} module provides classes for fixing broken (sick)
+schema(s).
+"""
+
+from suds.sax import Namespace
+from suds.sax.element import Element
+from suds.plugin import DocumentPlugin, DocumentContext
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Doctor:
+    """
+    Schema Doctor.
+    """
+    def examine(self, root):
+        """
+        Examine and repair the schema (if necessary).
+        @param root: A schema root element.
+        @type root: L{Element}
+        """
+        pass
+
+
+class Practice(Doctor):
+    """
+    A collection of doctors.
+    @ivar doctors: A list of doctors.
+    @type doctors: list
+    """
+
+    def __init__(self):
+        self.doctors = []
+
+    def add(self, doctor):
+        """
+        Add a doctor to the practice
+        @param doctor: A doctor to add.
+        @type doctor: L{Doctor}
+        """
+        self.doctors.append(doctor)
+
+    def examine(self, root):
+        for d in self.doctors:
+            d.examine(root)
+        return root
+
+
+class TnsFilter:
+    """
+    Target Namespace filter.
+    @ivar tns: A list of target namespaces.
+    @type tns: [str,...]
+    """
+
+    def __init__(self, *tns):
+        """
+        @param tns: A list of target namespaces.
+        @type tns: [str,...]
+        """
+        self.tns = []
+        self.add(*tns)
+
+    def add(self, *tns):
+        """
+        Add I{targetNamespaces} to be added.
+        @param tns: A list of target namespaces.
+        @type tns: [str,...]
+        """
+        self.tns += tns
+
+    def match(self, root, ns):
+        """
+        Match by I{targetNamespace} excluding those that
+        are equal to the specified namespace to prevent
+        adding an import to itself.
+        @param root: A schema root.
+        @type root: L{Element}
+        """
+        tns = root.get('targetNamespace')
+        if len(self.tns):
+            matched = ( tns in self.tns )
+        else:
+            matched = 1
+        itself = ( ns == tns )
+        return ( matched and not itself )
+
+
+class Import:
+    """
+    An <xs:import/> to be applied.
+    @cvar xsdns: The XSD namespace.
+    @type xsdns: (p,u)
+    @ivar ns: An import namespace.
+    @type ns: str
+    @ivar location: An optional I{schemaLocation}.
+    @type location: str
+    @ivar filter: A filter used to restrict application to
+        a particular schema.
+    @type filter: L{TnsFilter}
+    """
+
+    xsdns = Namespace.xsdns
+
+    def __init__(self, ns, location=None):
+        """
+        @param ns: An import namespace.
+        @type ns: str
+        @param location: An optional I{schemaLocation}.
+        @type location: str
+        """
+        self.ns = ns
+        self.location = location
+        self.filter = TnsFilter()
+
+    def setfilter(self, filter):
+        """
+        Set the filter.
+        @param filter: A filter to set.
+        @type filter: L{TnsFilter}
+        """
+        self.filter = filter
+
+    def apply(self, root):
+        """
+        Apply the import (rule) to the specified schema.
+        If the schema does not already contain an import for the
+        I{namespace} specified here, it is added.
+        @param root: A schema root.
+        @type root: L{Element}
+        """
+        if not self.filter.match(root, self.ns):
+            return
+        if self.exists(root):
+            return
+        node = Element('import', ns=self.xsdns)
+        node.set('namespace', self.ns)
+        if self.location is not None:
+            node.set('schemaLocation', self.location)
+        log.debug('inserting: %s', node)
+        root.insert(node)
+
+    def add(self, root):
+        """
+        Add an <xs:import/> to the specified schema root.
+        @param root: A schema root.
+        @type root: L{Element}
+        """
+        node = Element('import', ns=self.xsdns)
+        node.set('namespace', self.ns)
+        if self.location is not None:
+            node.set('schemaLocation', self.location)
+        log.debug('%s inserted', node)
+        root.insert(node)
+
+    def exists(self, root):
+        """
+        Check to see if the <xs:import/> already exists
+        in the specified schema root by matching I{namespace}.
+        @param root: A schema root.
+        @type root: L{Element}
+        """
+        for node in root.children:
+            if node.name != 'import':
+                continue
+            ns = node.get('namespace')
+            if self.ns == ns:
+                return 1
+        return 0
+
+
+class ImportDoctor(Doctor, DocumentPlugin):
+    """
+    Doctor used to fix missing imports.
+    @ivar imports: A list of imports to apply.
+    @type imports: [L{Import},...]
+    """
+
+    def __init__(self, *imports):
+        self.imports = []
+        self.add(*imports)
+
+    def add(self, *imports):
+        """
+        Add a namespace to be checked.
+        @param imports: A list of L{Import} objects.
+        @type imports: [L{Import},..]
+        """
+        self.imports += imports
+
+    def examine(self, node):
+        for imp in self.imports:
+            imp.apply(node)
+
+    def parsed(self, context):
+        node = context.document
+        # xsd root
+        if node.name == 'schema' and Namespace.xsd(node.namespace()):
+            self.examine(node)
+            return
+        # look deeper
+        context = DocumentContext()
+        for child in node:
+            context.document = child
+            self.parsed(context)
diff --git a/suds/xsd/query.py b/suds/xsd/query.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f2266b1b7c60d824e8487e63e09b60c9398fbcf
--- /dev/null
+++ b/suds/xsd/query.py
@@ -0,0 +1,208 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{query} module defines a class for performing schema queries.
+"""
+
+from suds import *
+from suds.sudsobject import *
+from suds.xsd import qualify, isqref
+from suds.xsd.sxbuiltin import Factory
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class Query(Object):
+    """
+    Schema query base class.
+
+    """
+    def __init__(self, ref=None):
+        """
+        @param ref: The schema reference being queried.
+        @type ref: qref
+        """
+        Object.__init__(self)
+        self.id = objid(self)
+        self.ref = ref
+        self.history = []
+        self.resolved = False
+        if not isqref(self.ref):
+            raise Exception('%s, must be qref' % tostr(self.ref))
+
+    def execute(self, schema):
+        """
+        Execute this query using the specified schema.
+        @param schema: The schema associated with the query. The schema is used
+            by the query to search for items.
+        @type schema: L{schema.Schema}
+        @return: The item matching the search criteria.
+        @rtype: L{sxbase.SchemaObject}
+        """
+        raise Exception, 'not-implemented by subclass'
+
+    def filter(self, result):
+        """
+        Filter the specified result based on query criteria.
+        @param result: A potential result.
+        @type result: L{sxbase.SchemaObject}
+        @return: True if result should be excluded.
+        @rtype: boolean
+        """
+        if result is None:
+            return True
+        reject = ( result in self.history )
+        if reject:
+            log.debug('result %s, rejected by\n%s', Repr(result), self)
+        return reject
+
+    def result(self, result):
+        """
+        Query result post processing.
+        @param result: A query result.
+        @type result: L{sxbase.SchemaObject}
+        """
+        if result is None:
+            log.debug('%s, not-found', self.ref)
+            return
+        if self.resolved:
+            result = result.resolve()
+        log.debug('%s, found as: %s', self.ref, Repr(result))
+        self.history.append(result)
+        return result
+
+
+class BlindQuery(Query):
+    """
+    Schema query class that I{blindly} searches for a reference in the
+    specified schema. It may be used to find Elements and Types but will match
+    on an Element first. This query will also find builtins.
+
+    """
+    def execute(self, schema):
+        if schema.builtin(self.ref):
+            name = self.ref[0]
+            b = Factory.create(schema, name)
+            log.debug('%s, found builtin (%s)', self.id, name)
+            return b
+        result = None
+        for d in (schema.elements, schema.types):
+            result = d.get(self.ref)
+            if self.filter(result):
+                result = None
+            else:
+                break
+        if result is None:
+            eq = ElementQuery(self.ref)
+            eq.history = self.history
+            result = eq.execute(schema)
+        return self.result(result)
+
+
+class TypeQuery(Query):
+    """
+    Schema query class that searches for Type references in the specified
+    schema. Matches on root types only.
+
+    """
+    def execute(self, schema):
+        if schema.builtin(self.ref):
+            name = self.ref[0]
+            b = Factory.create(schema, name)
+            log.debug('%s, found builtin (%s)', self.id, name)
+            return b
+        result = schema.types.get(self.ref)
+        if self.filter(result):
+            result = None
+        return self.result(result)
+
+
+class GroupQuery(Query):
+    """
+    Schema query class that searches for Group references in the specified
+    schema.
+
+    """
+    def execute(self, schema):
+        result = schema.groups.get(self.ref)
+        if self.filter(result):
+            result = None
+        return self.result(result)
+
+
+class AttrQuery(Query):
+    """
+    Schema query class that searches for Attribute references in the specified
+    schema. Matches on root Attribute by qname first, then searches deeper into
+    the document.
+
+    """
+    def execute(self, schema):
+        result = schema.attributes.get(self.ref)
+        if self.filter(result):
+            result = self.__deepsearch(schema)
+        return self.result(result)
+
+    def __deepsearch(self, schema):
+        from suds.xsd.sxbasic import Attribute
+        result = None
+        for e in schema.all:
+            result = e.find(self.ref, (Attribute,))
+            if self.filter(result):
+                result = None
+            else:
+                break
+        return result
+
+
+class AttrGroupQuery(Query):
+    """
+    Schema query class that searches for attributeGroup references in the
+    specified schema.
+
+    """
+    def execute(self, schema):
+        result = schema.agrps.get(self.ref)
+        if self.filter(result):
+            result = None
+        return self.result(result)
+
+
+class ElementQuery(Query):
+    """
+    Schema query class that searches for Element references in the specified
+    schema. Matches on root Elements by qname first, then searches deeper into
+    the document.
+
+    """
+    def execute(self, schema):
+        result = schema.elements.get(self.ref)
+        if self.filter(result):
+            result = self.__deepsearch(schema)
+        return self.result(result)
+
+    def __deepsearch(self, schema):
+        from suds.xsd.sxbasic import Element
+        result = None
+        for e in schema.all:
+            result = e.find(self.ref, (Element,))
+            if self.filter(result):
+                result = None
+            else:
+                break
+        return result
diff --git a/suds/xsd/schema.py b/suds/xsd/schema.py
new file mode 100644
index 0000000000000000000000000000000000000000..00e217ffc332cbc656c8062fa879c1e4945ce71b
--- /dev/null
+++ b/suds/xsd/schema.py
@@ -0,0 +1,411 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{schema} module provides an intelligent representation of
+an XSD schema.  The I{raw} model is the XML tree and the I{model}
+is the denormalized, objectified and intelligent view of the schema.
+Most of the I{value-add} provided by the model is centered around
+transparent referenced type resolution and targeted denormalization.
+"""
+
+
+from suds import *
+from suds.xsd import *
+from suds.xsd.sxbuiltin import *
+from suds.xsd.sxbasic import Factory as BasicFactory
+from suds.xsd.sxbuiltin import Factory as BuiltinFactory
+from suds.xsd.sxbase import SchemaObject
+from suds.xsd.deplist import DepList
+from suds.sax.element import Element
+from suds.sax import splitPrefix, Namespace
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class SchemaCollection(UnicodeMixin):
+    """
+    A collection of schema objects.  This class is needed because WSDLs
+    may contain more then one <schema/> node.
+    @ivar wsdl: A wsdl object.
+    @type wsdl: L{suds.wsdl.Definitions}
+    @ivar children: A list contained schemas.
+    @type children: [L{Schema},...]
+    @ivar namespaces: A dictionary of contained schemas by namespace.
+    @type namespaces: {str:L{Schema}}
+    """
+
+    def __init__(self, wsdl):
+        """
+        @param wsdl: A wsdl object.
+        @type wsdl: L{suds.wsdl.Definitions}
+        """
+        self.wsdl = wsdl
+        self.children = []
+        self.namespaces = {}
+
+    def add(self, schema):
+        """
+        Add a schema node to the collection.  Schema(s) within the same target
+        namespace are consolidated.
+        @param schema: A schema object.
+        @type schema: (L{Schema})
+        """
+        key = schema.tns[1]
+        existing = self.namespaces.get(key)
+        if existing is None:
+            self.children.append(schema)
+            self.namespaces[key] = schema
+        else:
+            existing.root.children += schema.root.children
+            existing.root.nsprefixes.update(schema.root.nsprefixes)
+
+    def load(self, options):
+        """
+        Load the schema objects for the root nodes.
+            - de-references schemas
+            - merge schemas
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @return: The merged schema.
+        @rtype: L{Schema}
+        """
+        if options.autoblend:
+            self.autoblend()
+        for child in self.children:
+            child.build()
+        for child in self.children:
+            child.open_imports(options)
+        for child in self.children:
+            child.dereference()
+        log.debug('loaded:\n%s', self)
+        merged = self.merge()
+        log.debug('MERGED:\n%s', merged)
+        return merged
+
+    def autoblend(self):
+        """
+        Ensure that all schemas within the collection
+        import each other which has a blending effect.
+        @return: self
+        @rtype: L{SchemaCollection}
+        """
+        namespaces = self.namespaces.keys()
+        for s in self.children:
+            for ns in namespaces:
+                tns = s.root.get('targetNamespace')
+                if tns == ns:
+                    continue
+                for imp in s.root.getChildren('import'):
+                    if imp.get('namespace') == ns:
+                        continue
+                imp = Element('import', ns=Namespace.xsdns)
+                imp.set('namespace', ns)
+                s.root.append(imp)
+        return self
+
+    def locate(self, ns):
+        """
+        Find a schema by namespace.  Only the URI portion of
+        the namespace is compared to each schema's I{targetNamespace}
+        @param ns: A namespace.
+        @type ns: (prefix,URI)
+        @return: The schema matching the namespace, else None.
+        @rtype: L{Schema}
+        """
+        return self.namespaces.get(ns[1])
+
+    def merge(self):
+        """
+        Merge the contained schemas into one.
+        @return: The merged schema.
+        @rtype: L{Schema}
+        """
+        if len(self):
+            schema = self.children[0]
+            for s in self.children[1:]:
+                schema.merge(s)
+            return schema
+        else:
+            return None
+
+    def __len__(self):
+        return len(self.children)
+
+    def __unicode__(self):
+        result = ['\nschema collection']
+        for s in self.children:
+            result.append(s.str(1))
+        return '\n'.join(result)
+
+
+class Schema(UnicodeMixin):
+    """
+    The schema is an objectification of a <schema/> (xsd) definition.
+    It provides inspection, lookup and type resolution.
+    @ivar root: The root node.
+    @type root: L{sax.element.Element}
+    @ivar baseurl: The I{base} URL for this schema.
+    @type baseurl: str
+    @ivar container: A schema collection containing this schema.
+    @type container: L{SchemaCollection}
+    @ivar children: A list of direct top level children.
+    @type children: [L{SchemaObject},...]
+    @ivar all: A list of all (includes imported) top level children.
+    @type all: [L{SchemaObject},...]
+    @ivar types: A schema types cache.
+    @type types: {name:L{SchemaObject}}
+    @ivar imports: A list of import objects.
+    @type imports: [L{SchemaObject},...]
+    @ivar elements: A list of <element/> objects.
+    @type elements: [L{SchemaObject},...]
+    @ivar attributes: A list of <attribute/> objects.
+    @type attributes: [L{SchemaObject},...]
+    @ivar groups: A list of group objects.
+    @type groups: [L{SchemaObject},...]
+    @ivar agrps: A list of attribute group objects.
+    @type agrps: [L{SchemaObject},...]
+    @ivar form_qualified: The flag indicating:
+        (@elementFormDefault).
+    @type form_qualified: bool
+    """
+
+    Tag = 'schema'
+
+    def __init__(self, root, baseurl, options, container=None):
+        """
+        @param root: The xml root.
+        @type root: L{sax.element.Element}
+        @param baseurl: The base url used for importing.
+        @type baseurl: basestring
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @param container: An optional container.
+        @type container: L{SchemaCollection}
+        """
+        self.root = root
+        self.id = objid(self)
+        self.tns = self.mktns()
+        self.baseurl = baseurl
+        self.container = container
+        self.children = []
+        self.all = []
+        self.types = {}
+        self.imports = []
+        self.elements = {}
+        self.attributes = {}
+        self.groups = {}
+        self.agrps = {}
+        if options.doctor is not None:
+            options.doctor.examine(root)
+        form = self.root.get('elementFormDefault')
+        if form is None:
+            self.form_qualified = False
+        else:
+            self.form_qualified = ( form == 'qualified' )
+        if container is None:
+            self.build()
+            self.open_imports(options)
+            log.debug('built:\n%s', self)
+            self.dereference()
+            log.debug('dereferenced:\n%s', self)
+
+    def mktns(self):
+        """
+        Make the schema's target namespace.
+        @return: The namespace representation of the schema's
+            targetNamespace value.
+        @rtype: (prefix, uri)
+        """
+        tns = [None, self.root.get('targetNamespace')]
+        if tns[1] is not None:
+            tns[0] = self.root.findPrefix(tns[1])
+        return tuple(tns)
+
+    def build(self):
+        """
+        Build the schema (object graph) using the root node
+        using the factory.
+            - Build the graph.
+            - Collate the children.
+        """
+        self.children = BasicFactory.build(self.root, self)
+        collated = BasicFactory.collate(self.children)
+        self.children = collated[0]
+        self.attributes = collated[2]
+        self.imports = collated[1]
+        self.elements = collated[3]
+        self.types = collated[4]
+        self.groups = collated[5]
+        self.agrps = collated[6]
+
+    def merge(self, schema):
+        """
+        Merge the contents from the schema.  Only objects not already contained
+        in this schema's collections are merged.  This is to provide for bidirectional
+        import which produce cyclic includes.
+        @returns: self
+        @rtype: L{Schema}
+        """
+        for item in schema.attributes.items():
+            if item[0] in self.attributes:
+                continue
+            self.all.append(item[1])
+            self.attributes[item[0]] = item[1]
+        for item in schema.elements.items():
+            if item[0] in self.elements:
+                continue
+            self.all.append(item[1])
+            self.elements[item[0]] = item[1]
+        for item in schema.types.items():
+            if item[0] in self.types:
+                continue
+            self.all.append(item[1])
+            self.types[item[0]] = item[1]
+        for item in schema.groups.items():
+            if item[0] in self.groups:
+                continue
+            self.all.append(item[1])
+            self.groups[item[0]] = item[1]
+        for item in schema.agrps.items():
+            if item[0] in self.agrps:
+                continue
+            self.all.append(item[1])
+            self.agrps[item[0]] = item[1]
+        schema.merged = True
+        return self
+
+    def open_imports(self, options):
+        """
+        Instruct all contained L{sxbasic.Import} children to import
+        the schema's which they reference.  The contents of the
+        imported schema are I{merged} in.
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        """
+        for imp in self.imports:
+            imported = imp.open(options)
+            if imported is None:
+                continue
+            imported.open_imports(options)
+            log.debug('imported:\n%s', imported)
+            self.merge(imported)
+
+    def dereference(self):
+        """
+        Instruct all children to perform dereferencing.
+        """
+        all = []
+        indexes = {}
+        for child in self.children:
+            child.content(all)
+        deplist = DepList()
+        for x in all:
+            x.qualify()
+            midx, deps = x.dependencies()
+            item = (x, tuple(deps))
+            deplist.add(item)
+            indexes[x] = midx
+        for x, deps in deplist.sort():
+            midx = indexes.get(x)
+            if midx is None: continue
+            d = deps[midx]
+            log.debug('(%s) merging %s <== %s', self.tns[1], Repr(x), Repr(d))
+            x.merge(d)
+
+    def locate(self, ns):
+        """
+        Find a schema by namespace.  Only the URI portion of
+        the namespace is compared to each schema's I{targetNamespace}.
+        The request is passed to the container.
+        @param ns: A namespace.
+        @type ns: (prefix,URI)
+        @return: The schema matching the namespace, else None.
+        @rtype: L{Schema}
+        """
+        if self.container is not None:
+            return self.container.locate(ns)
+        else:
+            return None
+
+    def custom(self, ref, context=None):
+        """
+        Get whether the specified reference is B{not} an (xs) builtin.
+        @param ref: A str or qref.
+        @type ref: (str|qref)
+        @return: True if B{not} a builtin, else False.
+        @rtype: bool
+        """
+        if ref is None:
+            return True
+        else:
+            return ( not self.builtin(ref, context) )
+
+    def builtin(self, ref, context=None):
+        """
+        Get whether the specified reference is an (xs) builtin.
+        @param ref: A str or qref.
+        @type ref: (str|qref)
+        @return: True if builtin, else False.
+        @rtype: bool
+        """
+        w3 = 'http://www.w3.org'
+        try:
+            if isqref(ref):
+                ns = ref[1]
+                return ( ref[0] in Factory.tags and ns.startswith(w3) )
+            if context is None:
+                context = self.root
+            prefix = splitPrefix(ref)[0]
+            prefixes = context.findPrefixes(w3, 'startswith')
+            return ( prefix in prefixes and ref[0] in Factory.tags )
+        except:
+            return False
+
+    def instance(self, root, baseurl, options):
+        """
+        Create and return an new schema object using the
+        specified I{root} and I{url}.
+        @param root: A schema root node.
+        @type root: L{sax.element.Element}
+        @param baseurl: A base URL.
+        @type baseurl: str
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @return: The newly created schema object.
+        @rtype: L{Schema}
+        @note: This is only used by Import children.
+        """
+        return Schema(root, baseurl, options)
+
+    def str(self, indent=0):
+        tab = '%*s'%(indent*3, '')
+        result = []
+        result.append('%s%s' % (tab, self.id))
+        result.append('%s(raw)' % tab)
+        result.append(self.root.str(indent+1))
+        result.append('%s(model)' % tab)
+        for c in self.children:
+            result.append(c.str(indent+1))
+        result.append('')
+        return '\n'.join(result)
+
+    def __repr__(self):
+        return '<%s tns="%s"/>' % (self.id, self.tns[1])
+
+    def __unicode__(self):
+        return self.str()
diff --git a/suds/xsd/sxbase.py b/suds/xsd/sxbase.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ceb82363e69856d9aa9f0eff35d7551ce7b56ac
--- /dev/null
+++ b/suds/xsd/sxbase.py
@@ -0,0 +1,661 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{sxbase} module provides I{base} classes representing schema objects.
+"""
+
+from suds import *
+from suds.xsd import *
+from suds.sax.element import Element
+from suds.sax import Namespace
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class SchemaObject(UnicodeMixin):
+    """
+    A schema object is an extension to object with schema awareness.
+    @ivar root: The XML root element.
+    @type root: L{Element}
+    @ivar schema: The schema containing this object.
+    @type schema: L{schema.Schema}
+    @ivar form_qualified: A flag that indicates that @elementFormDefault
+        has a value of I{qualified}.
+    @type form_qualified: boolean
+    @ivar nillable: A flag that indicates that @nillable
+        has a value of I{true}.
+    @type nillable: boolean
+    @ivar default: The default value.
+    @type default: object
+    @ivar rawchildren: A list raw of all children.
+    @type rawchildren: [L{SchemaObject},...]
+    """
+
+    @classmethod
+    def prepend(cls, d, s, filter=Filter()):
+        """
+        Prepend schema objects from B{s}ource list to
+        the B{d}estination list while applying the filter.
+        @param d: The destination list.
+        @type d: list
+        @param s: The source list.
+        @type s: list
+        @param filter: A filter that allows items to be prepended.
+        @type filter: L{Filter}
+        """
+        i = 0
+        for x in s:
+            if x in filter:
+                d.insert(i, x)
+                i += 1
+
+    @classmethod
+    def append(cls, d, s, filter=Filter()):
+        """
+        Append schema objects from B{s}ource list to
+        the B{d}estination list while applying the filter.
+        @param d: The destination list.
+        @type d: list
+        @param s: The source list.
+        @type s: list
+        @param filter: A filter that allows items to be appended.
+        @type filter: L{Filter}
+        """
+        for item in s:
+            if item in filter:
+                d.append(item)
+
+    def __init__(self, schema, root):
+        """
+        @param schema: The containing schema.
+        @type schema: L{schema.Schema}
+        @param root: The XML root node.
+        @type root: L{Element}
+        """
+        self.schema = schema
+        self.root = root
+        self.id = objid(self)
+        self.name = root.get('name')
+        self.qname = (self.name, schema.tns[1])
+        self.min = root.get('minOccurs')
+        self.max = root.get('maxOccurs')
+        self.type = root.get('type')
+        self.ref = root.get('ref')
+        self.form_qualified = schema.form_qualified
+        self.nillable = False
+        self.default = root.get('default')
+        self.rawchildren = []
+
+    def attributes(self, filter=Filter()):
+        """
+        Get only the attribute content.
+        @param filter: A filter to constrain the result.
+        @type filter: L{Filter}
+        @return: A list of tuples (attr, ancestry)
+        @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
+        """
+        result = []
+        for child, ancestry in self:
+            if child.isattr() and child in filter:
+                result.append((child, ancestry))
+        return result
+
+    def children(self, filter=Filter()):
+        """
+        Get only the I{direct} or non-attribute content.
+        @param filter: A filter to constrain the result.
+        @type filter: L{Filter}
+        @return: A list tuples: (child, ancestry)
+        @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
+        """
+        result = []
+        for child, ancestry in self:
+            if not child.isattr() and child in filter:
+                result.append((child, ancestry))
+        return result
+
+    def get_attribute(self, name):
+        """
+        Get (find) an attribute by name.
+        @param name: A attribute name.
+        @type name: str
+        @return: A tuple: the requested (attribute, ancestry).
+        @rtype: (L{SchemaObject}, [L{SchemaObject},..])
+        """
+        for child, ancestry in self.attributes():
+            if child.name == name:
+                return child, ancestry
+        return None, []
+
+    def get_child(self, name):
+        """
+        Get (find) a I{non-attribute} child by name.
+        @param name: A child name.
+        @type name: str
+        @return: A tuple: the requested (child, ancestry).
+        @rtype: (L{SchemaObject}, [L{SchemaObject},..])
+        """
+        for child, ancestry in self.children():
+            if child.any() or child.name == name:
+                return child, ancestry
+        return None, []
+
+    def namespace(self, prefix=None):
+        """
+        Get this property's namespace.
+        @param prefix: The default prefix.
+        @type prefix: str
+        @return: The schema's target namespace
+        @rtype: (I{prefix},I{URI})
+        """
+        ns = self.schema.tns
+        if ns[0] is None:
+            ns = (prefix, ns[1])
+        return ns
+
+    def default_namespace(self):
+        return self.root.defaultNamespace()
+
+    def multi_occurrence(self):
+        """
+        Get whether the node has multiple occurrences, i.e. is a I{collection}.
+        @return: True if it has, False if it has 1 occurrence at most.
+        @rtype: boolean
+        """
+        max = self.max
+        if max is None:
+            return False
+        if max.isdigit():
+            return int(max) > 1
+        return max == 'unbounded'
+
+    def optional(self):
+        """
+        Get whether this type is optional.
+        @return: True if optional, else False
+        @rtype: boolean
+        """
+        return self.min == '0'
+
+    def required(self):
+        """
+        Get whether this type is required.
+        @return: True if required, else False
+        @rtype: boolean
+        """
+        return not self.optional()
+
+    def resolve(self, nobuiltin=False):
+        """
+        Resolve the node's type reference and return the referenced type node.
+
+        Only schema objects that actually support 'having a type' custom
+        implement this interface while others simply resolve as themselves.
+        @param nobuiltin: Flag indicating whether resolving to an external XSD
+            builtin type should not be allowed.
+        @return: The resolved (true) type.
+        @rtype: L{SchemaObject}
+        """
+        return self
+
+    def sequence(self):
+        """
+        Get whether this is a <xs:sequence/>.
+        @return: True if <xs:sequence/>, else False
+        @rtype: boolean
+        """
+        return False
+
+    def xslist(self):
+        """
+        Get whether this is a <xs:list/>.
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+
+    def all(self):
+        """
+        Get whether this is an <xs:all/>.
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+
+    def choice(self):
+        """
+        Get whether this is a <xs:choice/>.
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+
+    def any(self):
+        """
+        Get whether this is an <xs:any/>.
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+
+    def builtin(self):
+        """
+        Get whether this is a schema-instance (xs) type.
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+
+    def enum(self):
+        """
+        Get whether this is a simple-type containing an enumeration.
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+
+    def isattr(self):
+        """
+        Get whether the object is a schema I{attribute} definition.
+        @return: True if an attribute, else False.
+        @rtype: boolean
+        """
+        return False
+
+    def extension(self):
+        """
+        Get whether the object is an extension of another type.
+        @return: True if an extension, else False.
+        @rtype: boolean
+        """
+        return False
+
+    def restriction(self):
+        """
+        Get whether the object is an restriction of another type.
+        @return: True if an restriction, else False.
+        @rtype: boolean
+        """
+        return False
+
+    def mixed(self):
+        """
+        Get whether this I{mixed} content.
+        """
+        return False
+
+    def find(self, qref, classes=()):
+        """
+        Find a referenced type in self or children.
+        @param qref: A qualified reference.
+        @type qref: qref
+        @param classes: A list of classes used to qualify the match.
+        @type classes: [I{class},...]
+        @return: The referenced type.
+        @rtype: L{SchemaObject}
+        @see: L{qualify()}
+        """
+        if not len(classes):
+            classes = (self.__class__,)
+        if self.qname == qref and self.__class__ in classes:
+            return self
+        for c in self.rawchildren:
+            p = c.find(qref, classes)
+            if p is not None:
+                return p
+        return None
+
+    def translate(self, value, topython=True):
+        """
+        Translate a value (type) to/from a Python type.
+        @param value: A value to translate.
+        @return: The converted I{language} type.
+        """
+        return value
+
+    def childtags(self):
+        """
+        Get a list of valid child tag names.
+        @return: A list of child tag names.
+        @rtype: [str,...]
+        """
+        return ()
+
+    def dependencies(self):
+        """
+        Get a list of dependencies for dereferencing.
+        @return: A merge dependency index and a list of dependencies.
+        @rtype: (int, [L{SchemaObject},...])
+        """
+        return None, []
+
+    def autoqualified(self):
+        """
+        The list of I{auto} qualified attribute values.
+        Qualification means to convert values into I{qref}.
+        @return: A list of attibute names.
+        @rtype: list
+        """
+        return ['type', 'ref']
+
+    def qualify(self):
+        """
+        Convert attribute values, that are references to other
+        objects, into I{qref}.  Qualified using the default document namespace.
+        Since many WSDLs are written improperly: when the document does
+        not define its default namespace, the schema target namespace is used
+        to qualify references.
+        """
+        defns = self.root.defaultNamespace()
+        if Namespace.none(defns):
+            defns = self.schema.tns
+        for a in self.autoqualified():
+            ref = getattr(self, a)
+            if ref is None:
+                continue
+            if isqref(ref):
+                continue
+            qref = qualify(ref, self.root, defns)
+            log.debug('%s, convert %s="%s" to %s', self.id, a, ref, qref)
+            setattr(self, a, qref)
+
+    def merge(self, other):
+        """
+        Merge another object as needed.
+        """
+        other.qualify()
+        for n in ('name',
+                  'qname',
+                  'min',
+                  'max',
+                  'default',
+                  'type',
+                  'nillable',
+                  'form_qualified'):
+            if getattr(self, n) is not None:
+                continue
+            v = getattr(other, n)
+            if v is None:
+                continue
+            setattr(self, n, v)
+
+    def content(self, collection=None, filter=Filter(), history=None):
+        """
+        Get a I{flattened} list of this node's contents.
+        @param collection: A list to fill.
+        @type collection: list
+        @param filter: A filter used to constrain the result.
+        @type filter: L{Filter}
+        @param history: The history list used to prevent cyclic dependency.
+        @type history: list
+        @return: The filled list.
+        @rtype: list
+        """
+        if collection is None:
+            collection = []
+        if history is None:
+            history = []
+        if self in history:
+            return collection
+        history.append(self)
+        if self in filter:
+            collection.append(self)
+        for c in self.rawchildren:
+            c.content(collection, filter, history[:])
+        return collection
+
+    def str(self, indent=0, history=None):
+        """
+        Get a string representation of this object.
+        @param indent: The indent.
+        @type indent: int
+        @return: A string.
+        @rtype: str
+        """
+        if history is None:
+            history = []
+        if self in history:
+            return '%s ...' % Repr(self)
+        history.append(self)
+        tab = '%*s'%(indent*3, '')
+        result = ['%s<%s' % (tab, self.id)]
+        for n in self.description():
+            if not hasattr(self, n):
+                continue
+            v = getattr(self, n)
+            if v is None:
+                continue
+            result.append(' %s="%s"' % (n, v))
+        if len(self):
+            result.append('>')
+            for c in self.rawchildren:
+                result.append('\n')
+                result.append(c.str(indent+1, history[:]))
+                if c.isattr():
+                    result.append('@')
+            result.append('\n%s' % tab)
+            result.append('</%s>' % self.__class__.__name__)
+        else:
+            result.append(' />')
+        return ''.join(result)
+
+    def description(self):
+        """
+        Get the names used for str() and repr() description.
+        @return:  A dictionary of relevant attributes.
+        @rtype: [str,...]
+        """
+        return ()
+
+    def __unicode__(self):
+        return unicode(self.str())
+
+    def __repr__(self):
+        s = []
+        s.append('<%s' % self.id)
+        for n in self.description():
+            if not hasattr(self, n):
+                continue
+            v = getattr(self, n)
+            if v is None:
+                continue
+            s.append(' %s="%s"' % (n, v))
+        s.append(' />')
+        return ''.join(s)
+
+    def __len__(self):
+        n = 0
+        for x in self: n += 1
+        return n
+
+    def __iter__(self):
+        return Iter(self)
+
+    def __getitem__(self, index):
+        """Returns a contained schema object referenced by its 0-based index.
+
+        Returns None if such an object does not exist.
+
+        """
+        i = 0
+        for c in self:
+            if i == index:
+                return c
+            i += 1
+
+
+class Iter:
+    """
+    The content iterator - used to iterate the L{Content} children.  The
+    iterator provides a I{view} of the children that is free of container
+    elements such as <sequence/> and <choice/>.
+    @ivar stack: A stack used to control nesting.
+    @type stack: list
+    """
+
+    class Frame:
+        """ A content iterator frame. """
+
+        def __init__(self, sx):
+            """
+            @param sx: A schema object.
+            @type sx: L{SchemaObject}
+            """
+            self.sx = sx
+            self.items = sx.rawchildren
+            self.index = 0
+
+        def next(self):
+            """
+            Get the I{next} item in the frame's collection.
+            @return: The next item or None
+            @rtype: L{SchemaObject}
+            """
+            if self.index < len(self.items):
+                result = self.items[self.index]
+                self.index += 1
+                return result
+
+    def __init__(self, sx):
+        """
+        @param sx: A schema object.
+        @type sx: L{SchemaObject}
+        """
+        self.stack = []
+        self.push(sx)
+
+    def push(self, sx):
+        """
+        Create a frame and push the specified object.
+        @param sx: A schema object to push.
+        @type sx: L{SchemaObject}
+        """
+        self.stack.append(Iter.Frame(sx))
+
+    def pop(self):
+        """
+        Pop the I{top} frame.
+        @return: The popped frame.
+        @rtype: L{Frame}
+        @raise StopIteration: when stack is empty.
+        """
+        if len(self.stack):
+            return self.stack.pop()
+        else:
+            raise StopIteration()
+
+    def top(self):
+        """
+        Get the I{top} frame.
+        @return: The top frame.
+        @rtype: L{Frame}
+        @raise StopIteration: when stack is empty.
+        """
+        if len(self.stack):
+            return self.stack[-1]
+        else:
+            raise StopIteration()
+
+    def next(self):
+        """
+        Get the next item.
+        @return: A tuple: the next (child, ancestry).
+        @rtype: (L{SchemaObject}, [L{SchemaObject},..])
+        @raise StopIteration: A the end.
+        """
+        frame = self.top()
+        while True:
+            result = frame.next()
+            if result is None:
+                self.pop()
+                return self.next()
+            if isinstance(result, Content):
+                ancestry = [f.sx for f in self.stack]
+                return result, ancestry
+            self.push(result)
+            return self.next()
+
+    def __iter__(self):
+        return self
+
+
+class XBuiltin(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:*/> node.
+    """
+
+    def __init__(self, schema, name):
+        """
+        @param schema: The containing schema.
+        @type schema: L{schema.Schema}
+        """
+        root = Element(name)
+        SchemaObject.__init__(self, schema, root)
+        self.name = name
+        self.nillable = True
+
+    def namespace(self, prefix=None):
+        return Namespace.xsdns
+
+    def builtin(self):
+        return True
+
+
+class Content(SchemaObject):
+    """
+    This class represents those schema objects that represent
+    real XML document content.
+    """
+    pass
+
+
+class NodeFinder:
+    """
+    Find nodes based on flexable criteria.  The I{matcher}
+    may be any object that implements a match(n) method.
+    @ivar matcher: An object used as criteria for match.
+    @type matcher: I{any}.match(n)
+    @ivar limit: Limit the number of matches.  0=unlimited.
+    @type limit: int
+    """
+    def __init__(self, matcher, limit=0):
+        """
+        @param matcher: An object used as criteria for match.
+        @type matcher: I{any}.match(n)
+        @param limit: Limit the number of matches.  0=unlimited.
+        @type limit: int
+        """
+        self.matcher = matcher
+        self.limit = limit
+
+    def find(self, node, list):
+        """
+        Traverse the tree looking for matches.
+        @param node: A node to match on.
+        @type node: L{SchemaObject}
+        @param list: A list to fill.
+        @type list: list
+        """
+        if self.matcher.match(node):
+            list.append(node)
+            self.limit -= 1
+            if self.limit == 0:
+                return
+        for c in node.rawchildren:
+            self.find(c, list)
+        return self
diff --git a/suds/xsd/sxbasic.py b/suds/xsd/sxbasic.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6050885169475442aae8d2f283efefe2b9b9c27
--- /dev/null
+++ b/suds/xsd/sxbasic.py
@@ -0,0 +1,857 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{sxbasic} module provides classes that represent
+I{basic} schema objects.
+"""
+
+from suds import *
+from suds.xsd import *
+from suds.xsd.sxbase import *
+from suds.xsd.query import *
+from suds.sax import Namespace
+from suds.transport import TransportError
+from suds.reader import DocumentReader
+from urlparse import urljoin
+
+from logging import getLogger
+log = getLogger(__name__)
+
+
+class RestrictionMatcher:
+    """
+    For use with L{NodeFinder} to match restriction.
+    """
+    def match(self, n):
+        return isinstance(n, Restriction)
+
+
+class TypedContent(Content):
+    """
+    Represents any I{typed} content.
+    """
+
+    def __init__(self, *args, **kwargs):
+        Content.__init__(self, *args, **kwargs)
+        self.resolved_cache = {}
+
+    def resolve(self, nobuiltin=False):
+        """
+        Resolve the node's type reference and return the referenced type node.
+
+        Returns self if the type is defined locally, e.g. as a <complexType>
+        subnode. Otherwise returns the referenced external node.
+        @param nobuiltin: Flag indicating whether resolving to XSD builtin
+            types should not be allowed.
+        @return: The resolved (true) type.
+        @rtype: L{SchemaObject}
+        """
+        cached = self.resolved_cache.get(nobuiltin)
+        if cached is not None:
+            return cached
+        resolved = self.__resolve_type(nobuiltin)
+        self.resolved_cache[nobuiltin] = resolved
+        return resolved
+
+    def __resolve_type(self, nobuiltin=False):
+        """
+        Private resolve() worker without any result caching.
+        @param nobuiltin: Flag indicating whether resolving to XSD builtin
+            types should not be allowed.
+        @return: The resolved (true) type.
+        @rtype: L{SchemaObject}
+
+        Implementation note:
+          Note that there is no need for a recursive implementation here since
+        a node can reference an external type node but there is no way using
+        WSDL to then make that type node actually be a reference to a different
+        type node.
+        """
+        qref = self.qref()
+        if qref is None:
+            return self
+        query = TypeQuery(qref)
+        query.history = [self]
+        log.debug('%s, resolving: %s\n using:%s', self.id, qref, query)
+        resolved = query.execute(self.schema)
+        if resolved is None:
+            log.debug(self.schema)
+            raise TypeNotFound(qref)
+        if resolved.builtin() and nobuiltin:
+            return self
+        return resolved
+
+    def qref(self):
+        """
+        Get the I{type} qualified reference to the referenced XSD type.
+        This method takes into account simple types defined through
+        restriction which are detected by determining that self is simple
+        (len=0) and by finding a restriction child.
+        @return: The I{type} qualified reference.
+        @rtype: qref
+        """
+        qref = self.type
+        if qref is None and len(self) == 0:
+            ls = []
+            m = RestrictionMatcher()
+            finder = NodeFinder(m, 1)
+            finder.find(self, ls)
+            if len(ls):
+                return ls[0].ref
+        return qref
+
+
+class Complex(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:complexType/> node.
+    @cvar childtags: A list of valid child node names.
+    @type childtags: (I{str},...)
+    """
+
+    def childtags(self):
+        return 'attribute', 'attributeGroup', 'sequence', 'all', 'choice',  \
+            'complexContent', 'simpleContent', 'any', 'group'
+
+    def description(self):
+        return ('name',)
+
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+
+    def mixed(self):
+        for c in self.rawchildren:
+            if isinstance(c, SimpleContent) and c.mixed():
+                return True
+        return False
+
+
+class Group(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:group/> node.
+    @cvar childtags: A list of valid child node names.
+    @type childtags: (I{str},...)
+    """
+
+    def childtags(self):
+        return 'sequence', 'all', 'choice'
+
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = GroupQuery(self.ref)
+            g = query.execute(self.schema)
+            if g is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            deps.append(g)
+            midx = 0
+        return midx, deps
+
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        self.rawchildren = other.rawchildren
+
+    def description(self):
+        return 'name', 'ref'
+
+
+class AttributeGroup(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:attributeGroup/> node.
+    @cvar childtags: A list of valid child node names.
+    @type childtags: (I{str},...)
+    """
+
+    def childtags(self):
+        return 'attribute', 'attributeGroup'
+
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = AttrGroupQuery(self.ref)
+            ag = query.execute(self.schema)
+            if ag is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            deps.append(ag)
+            midx = 0
+        return midx, deps
+
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        self.rawchildren = other.rawchildren
+
+    def description(self):
+        return 'name', 'ref'
+
+
+class Simple(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:simpleType/> node.
+    """
+
+    def childtags(self):
+        return 'restriction', 'any', 'list'
+
+    def enum(self):
+        for child, ancestry in self.children():
+            if isinstance(child, Enumeration):
+                return True
+        return False
+
+    def mixed(self):
+        return len(self)
+
+    def description(self):
+        return ('name',)
+
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+
+    def restriction(self):
+        for c in self.rawchildren:
+            if c.restriction():
+                return True
+        return False
+
+
+class List(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:list/> node.
+    """
+
+    def childtags(self):
+        return ()
+
+    def description(self):
+        return ('name',)
+
+    def xslist(self):
+        return True
+
+
+class Restriction(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:restriction/> node.
+    """
+
+    def __init__(self, schema, root):
+        SchemaObject.__init__(self, schema, root)
+        self.ref = root.get('base')
+
+    def childtags(self):
+        return 'enumeration', 'attribute', 'attributeGroup'
+
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = TypeQuery(self.ref)
+            super = query.execute(self.schema)
+            if super is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            if not super.builtin():
+                deps.append(super)
+                midx = 0
+        return midx, deps
+
+    def restriction(self):
+        return True
+
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        filter = Filter(False, self.rawchildren)
+        self.prepend(self.rawchildren, other.rawchildren, filter)
+
+    def description(self):
+        return ('ref',)
+
+
+class Collection(SchemaObject):
+    """
+    Represents an (XSD) schema collection node:
+        - sequence
+        - choice
+        - all
+    """
+
+    def childtags(self):
+        return 'element', 'sequence', 'all', 'choice', 'any', 'group'
+
+
+class Sequence(Collection):
+    """
+    Represents an (XSD) schema <xs:sequence/> node.
+    """
+    def sequence(self):
+        return True
+
+
+class All(Collection):
+    """
+    Represents an (XSD) schema <xs:all/> node.
+    """
+    def all(self):
+        return True
+
+
+class Choice(Collection):
+    """
+    Represents an (XSD) schema <xs:choice/> node.
+    """
+    def choice(self):
+        return True
+
+
+class ComplexContent(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:complexContent/> node.
+    """
+
+    def childtags(self):
+        return 'attribute', 'attributeGroup', 'extension', 'restriction'
+
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+
+    def restriction(self):
+        for c in self.rawchildren:
+            if c.restriction():
+                return True
+        return False
+
+
+class SimpleContent(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:simpleContent/> node.
+    """
+
+    def childtags(self):
+        return 'extension', 'restriction'
+
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+
+    def restriction(self):
+        for c in self.rawchildren:
+            if c.restriction():
+                return True
+        return False
+
+    def mixed(self):
+        return len(self)
+
+
+class Enumeration(Content):
+    """
+    Represents an (XSD) schema <xs:enumeration/> node.
+    """
+
+    def __init__(self, schema, root):
+        Content.__init__(self, schema, root)
+        self.name = root.get('value')
+
+    def description(self):
+        return ('name',)
+
+    def enum(self):
+        return True
+
+
+class Element(TypedContent):
+    """
+    Represents an (XSD) schema <xs:element/> node.
+    """
+
+    def __init__(self, schema, root):
+        TypedContent.__init__(self, schema, root)
+        a = root.get('form')
+        if a is not None:
+            self.form_qualified = ( a == 'qualified' )
+        a = self.root.get('nillable')
+        if a is not None:
+            self.nillable = ( a in ('1', 'true') )
+        self.implany()
+
+    def implany(self):
+        """
+        Set the type as any when implicit.
+        An implicit <xs:any/> is when an element has not
+        body and no type defined.
+        @return: self
+        @rtype: L{Element}
+        """
+        if self.type is None and \
+            self.ref is None and \
+            self.root.isempty():
+                self.type = self.anytype()
+        return self
+
+    def childtags(self):
+        return 'attribute', 'simpleType', 'complexType', 'any'
+
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+
+    def restriction(self):
+        for c in self.rawchildren:
+            if c.restriction():
+                return True
+        return False
+
+    def dependencies(self):
+        deps = []
+        midx = None
+        e = self.__deref()
+        if e is not None:
+            deps.append(e)
+            midx = 0
+        return midx, deps
+
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        self.rawchildren = other.rawchildren
+
+    def description(self):
+        return 'name', 'ref', 'type'
+
+    def anytype(self):
+        """ create an xsd:anyType reference """
+        p, u = Namespace.xsdns
+        mp = self.root.findPrefix(u)
+        if mp is None:
+            mp = p
+            self.root.addPrefix(p, u)
+        return ':'.join((mp, 'anyType'))
+
+    def namespace(self, prefix=None):
+        """
+        Get this schema element's target namespace.
+
+        In case of reference elements, the target namespace is defined by the
+        referenced and not the referencing element node.
+
+        @param prefix: The default prefix.
+        @type prefix: str
+        @return: The schema element's target namespace
+        @rtype: (I{prefix},I{URI})
+        """
+        e = self.__deref()
+        if e is not None:
+            return e.namespace(prefix)
+        return super(Element, self).namespace()
+
+    def __deref(self):
+        if self.ref is None:
+            return
+        query = ElementQuery(self.ref)
+        e = query.execute(self.schema)
+        if e is None:
+            log.debug(self.schema)
+            raise TypeNotFound(self.ref)
+        return e
+
+
+class Extension(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:extension/> node.
+    """
+
+    def __init__(self, schema, root):
+        SchemaObject.__init__(self, schema, root)
+        self.ref = root.get('base')
+
+    def childtags(self):
+        return 'attribute', 'attributeGroup', 'sequence', 'all', 'choice',  \
+            'group'
+
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = TypeQuery(self.ref)
+            super = query.execute(self.schema)
+            if super is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            if not super.builtin():
+                deps.append(super)
+                midx = 0
+        return midx, deps
+
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        filter = Filter(False, self.rawchildren)
+        self.prepend(self.rawchildren, other.rawchildren, filter)
+
+    def extension(self):
+        return self.ref is not None
+
+    def description(self):
+        return ('ref',)
+
+
+class Import(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:import/> node.
+    @cvar locations: A dictionary of namespace locations.
+    @type locations: dict
+    @ivar ns: The imported namespace.
+    @type ns: str
+    @ivar location: The (optional) location.
+    @type location: namespace-uri
+    @ivar opened: Opened and I{imported} flag.
+    @type opened: boolean
+    """
+
+    locations = {}
+
+    @classmethod
+    def bind(cls, ns, location=None):
+        """
+        Bind a namespace to a schema location (URI).
+        This is used for imports that don't specify a schemaLocation.
+        @param ns: A namespace-uri.
+        @type ns: str
+        @param location: The (optional) schema location for the
+            namespace.  (default=ns).
+        @type location: str
+        """
+        if location is None:
+            location = ns
+        cls.locations[ns] = location
+
+    def __init__(self, schema, root):
+        SchemaObject.__init__(self, schema, root)
+        self.ns = (None, root.get('namespace'))
+        self.location = root.get('schemaLocation')
+        if self.location is None:
+            self.location = self.locations.get(self.ns[1])
+        self.opened = False
+
+    def open(self, options):
+        """
+        Open and import the refrenced schema.
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @return: The referenced schema.
+        @rtype: L{Schema}
+        """
+        if self.opened:
+            return
+        self.opened = True
+        log.debug('%s, importing ns="%s", location="%s"', self.id, self.ns[1], self.location)
+        result = self.locate()
+        if result is None:
+            if self.location is None:
+                log.debug('imported schema (%s) not-found', self.ns[1])
+            else:
+                result = self.download(options)
+        log.debug('imported:\n%s', result)
+        return result
+
+    def locate(self):
+        """ find the schema locally """
+        if self.ns[1] != self.schema.tns[1]:
+            return self.schema.locate(self.ns)
+
+    def download(self, options):
+        """ download the schema """
+        url = self.location
+        try:
+            if '://' not in url:
+                url = urljoin(self.schema.baseurl, url)
+            reader = DocumentReader(options)
+            d = reader.open(url)
+            root = d.root()
+            root.set('url', url)
+            return self.schema.instance(root, url, options)
+        except TransportError:
+            msg = 'imported schema (%s) at (%s), failed' % (self.ns[1], url)
+            log.error('%s, %s', self.id, msg, exc_info=True)
+            raise Exception(msg)
+
+    def description(self):
+        return 'ns', 'location'
+
+
+class Include(SchemaObject):
+    """
+    Represents an (XSD) schema <xs:include/> node.
+    @ivar location: The (optional) location.
+    @type location: namespace-uri
+    @ivar opened: Opened and I{imported} flag.
+    @type opened: boolean
+    """
+
+    locations = {}
+
+    def __init__(self, schema, root):
+        SchemaObject.__init__(self, schema, root)
+        self.location = root.get('schemaLocation')
+        if self.location is None:
+            self.location = self.locations.get(self.ns[1])
+        self.opened = False
+
+    def open(self, options):
+        """
+        Open and include the refrenced schema.
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @return: The referenced schema.
+        @rtype: L{Schema}
+        """
+        if self.opened:
+            return
+        self.opened = True
+        log.debug('%s, including location="%s"', self.id, self.location)
+        result = self.download(options)
+        log.debug('included:\n%s', result)
+        return result
+
+    def download(self, options):
+        """ download the schema """
+        url = self.location
+        try:
+            if '://' not in url:
+                url = urljoin(self.schema.baseurl, url)
+            reader = DocumentReader(options)
+            d = reader.open(url)
+            root = d.root()
+            root.set('url', url)
+            self.__applytns(root)
+            return self.schema.instance(root, url, options)
+        except TransportError:
+            msg = 'include schema at (%s), failed' % url
+            log.error('%s, %s', self.id, msg, exc_info=True)
+            raise Exception(msg)
+
+    def __applytns(self, root):
+        """ make sure included schema has same tns. """
+        TNS = 'targetNamespace'
+        tns = root.get(TNS)
+        if tns is None:
+            tns = self.schema.tns[1]
+            root.set(TNS, tns)
+        else:
+            if self.schema.tns[1] != tns:
+                raise Exception, '%s mismatch' % TNS
+
+
+    def description(self):
+        return 'location'
+
+
+class Attribute(TypedContent):
+    """
+    Represents an (XSD) <attribute/> node.
+    """
+
+    def __init__(self, schema, root):
+        TypedContent.__init__(self, schema, root)
+        self.use = root.get('use', default='')
+
+    def childtags(self):
+        return ('restriction',)
+
+    def isattr(self):
+        return True
+
+    def get_default(self):
+        """
+        Gets the <xs:attribute default=""/> attribute value.
+        @return: The default value for the attribute
+        @rtype: str
+        """
+        return self.root.get('default', default='')
+
+    def optional(self):
+        return self.use != 'required'
+
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = AttrQuery(self.ref)
+            a = query.execute(self.schema)
+            if a is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            deps.append(a)
+            midx = 0
+        return midx, deps
+
+    def description(self):
+        return 'name', 'ref', 'type'
+
+
+class Any(Content):
+    """
+    Represents an (XSD) <any/> node.
+    """
+
+    def get_child(self, name):
+        root = self.root.clone()
+        root.set('note', 'synthesized (any) child')
+        child = Any(self.schema, root)
+        return child, []
+
+    def get_attribute(self, name):
+        root = self.root.clone()
+        root.set('note', 'synthesized (any) attribute')
+        attribute = Any(self.schema, root)
+        return attribute, []
+
+    def any(self):
+        return True
+
+
+class Factory:
+    """
+    @cvar tags: A factory to create object objects based on tag.
+    @type tags: {tag:fn,}
+    """
+
+    tags = {
+        'import' : Import,
+        'include' : Include,
+        'complexType' : Complex,
+        'group' : Group,
+        'attributeGroup' : AttributeGroup,
+        'simpleType' : Simple,
+        'list' : List,
+        'element' : Element,
+        'attribute' : Attribute,
+        'sequence' : Sequence,
+        'all' : All,
+        'choice' : Choice,
+        'complexContent' : ComplexContent,
+        'simpleContent' : SimpleContent,
+        'restriction' : Restriction,
+        'enumeration' : Enumeration,
+        'extension' : Extension,
+        'any' : Any,
+    }
+
+    @classmethod
+    def maptag(cls, tag, fn):
+        """
+        Map (override) tag => I{class} mapping.
+        @param tag: An XSD tag name.
+        @type tag: str
+        @param fn: A function or class.
+        @type fn: fn|class.
+        """
+        cls.tags[tag] = fn
+
+    @classmethod
+    def create(cls, root, schema):
+        """
+        Create an object based on the root tag name.
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param schema: A schema object.
+        @type schema: L{schema.Schema}
+        @return: The created object.
+        @rtype: L{SchemaObject}
+        """
+        fn = cls.tags.get(root.name)
+        if fn is not None:
+            return fn(schema, root)
+
+    @classmethod
+    def build(cls, root, schema, filter=('*',)):
+        """
+        Build an xsobject representation.
+        @param root: An schema XML root.
+        @type root: L{sax.element.Element}
+        @param filter: A tag filter.
+        @type filter: [str,...]
+        @return: A schema object graph.
+        @rtype: L{sxbase.SchemaObject}
+        """
+        children = []
+        for node in root.getChildren(ns=Namespace.xsdns):
+            if '*' in filter or node.name in filter:
+                child = cls.create(node, schema)
+                if child is None:
+                    continue
+                children.append(child)
+                c = cls.build(node, schema, child.childtags())
+                child.rawchildren = c
+        return children
+
+    @classmethod
+    def collate(cls, children):
+        imports = []
+        elements = {}
+        attributes = {}
+        types = {}
+        groups = {}
+        agrps = {}
+        for c in children:
+            if isinstance(c, (Import, Include)):
+                imports.append(c)
+                continue
+            if isinstance(c, Attribute):
+                attributes[c.qname] = c
+                continue
+            if isinstance(c, Element):
+                elements[c.qname] = c
+                continue
+            if isinstance(c, Group):
+                groups[c.qname] = c
+                continue
+            if isinstance(c, AttributeGroup):
+                agrps[c.qname] = c
+                continue
+            types[c.qname] = c
+        for i in imports:
+            children.remove(i)
+        return children, imports, attributes, elements, types, groups, agrps
+
+
+#######################################################
+# Static Import Bindings :-(
+#######################################################
+Import.bind(
+    'http://schemas.xmlsoap.org/soap/encoding/',
+    'suds://schemas.xmlsoap.org/soap/encoding/')
+Import.bind(
+    'http://www.w3.org/XML/1998/namespace',
+    'http://www.w3.org/2001/xml.xsd')
+Import.bind(
+    'http://www.w3.org/2001/XMLSchema',
+    'http://www.w3.org/2001/XMLSchema.xsd')
diff --git a/suds/xsd/sxbuiltin.py b/suds/xsd/sxbuiltin.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d2c3dde96667b74ec221b837009f38af6faf830
--- /dev/null
+++ b/suds/xsd/sxbuiltin.py
@@ -0,0 +1,255 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel@redhat.com )
+
+"""
+The I{sxbuiltin} module provides classes that represent
+XSD I{builtin} schema objects.
+"""
+
+from suds import *
+from suds.xsd import *
+from suds.sax.date import *
+from suds.xsd.sxbase import XBuiltin
+
+import datetime as dt
+
+
+class XString(XBuiltin):
+    """
+    Represents an (xsd) <xs:string/> node
+    """
+    pass
+
+
+class XAny(XBuiltin):
+    """
+    Represents an (xsd) <any/> node
+    """
+
+    def __init__(self, schema, name):
+        XBuiltin.__init__(self, schema, name)
+        self.nillable = False
+
+    def get_child(self, name):
+        child = XAny(self.schema, name)
+        return child, []
+
+    def any(self):
+        return True
+
+
+class XBoolean(XBuiltin):
+    """
+    Represents an (xsd) boolean builtin type.
+    """
+
+    translation = ({'1':True, 'true':True, '0':False, 'false':False},
+        {True:'true', 1:'true', False:'false', 0:'false'})
+
+    @staticmethod
+    def translate(value, topython=True):
+        if topython:
+            if isinstance(value, basestring):
+                return XBoolean.translation[0].get(value)
+        else:
+            if isinstance(value, (bool, int)):
+                return XBoolean.translation[1].get(value)
+            return value
+
+
+class XInteger(XBuiltin):
+    """
+    Represents an (xsd) xs:int builtin type.
+    """
+
+    @staticmethod
+    def translate(value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return int(value)
+        else:
+            if isinstance(value, int):
+                return str(value)
+            return value
+
+
+class XLong(XBuiltin):
+    """
+    Represents an (xsd) xs:long builtin type.
+    """
+
+    @staticmethod
+    def translate(value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return long(value)
+        else:
+            if isinstance(value, (int, long)):
+                return str(value)
+            return value
+
+
+class XFloat(XBuiltin):
+    """
+    Represents an (xsd) xs:float builtin type.
+    """
+
+    @staticmethod
+    def translate(value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return float(value)
+        else:
+            if isinstance(value, float):
+                return str(value)
+            return value
+
+
+class XDate(XBuiltin):
+    """
+    Represents an (xsd) xs:date builtin type.
+    """
+
+    @staticmethod
+    def translate(value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return Date(value).value
+        else:
+            if isinstance(value, dt.date):
+                return str(Date(value))
+            return value
+
+
+class XTime(XBuiltin):
+    """
+    Represents an (xsd) xs:time builtin type.
+    """
+
+    @staticmethod
+    def translate(value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return Time(value).value
+        else:
+            if isinstance(value, dt.time):
+                return str(Time(value))
+            return value
+
+
+class XDateTime(XBuiltin):
+    """
+    Represents an (xsd) xs:datetime builtin type.
+    """
+
+    @staticmethod
+    def translate(value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return DateTime(value).value
+        else:
+            if isinstance(value, dt.datetime):
+                return str(DateTime(value))
+            return value
+
+
+class Factory:
+
+    tags =\
+    {
+        # any
+        'anyType' : XAny,
+        # strings
+        'string' : XString,
+        'normalizedString' : XString,
+        'ID' : XString,
+        'Name' : XString,
+        'QName' : XString,
+        'NCName' : XString,
+        'anySimpleType' : XString,
+        'anyURI' : XString,
+        'NOTATION' : XString,
+        'token' : XString,
+        'language' : XString,
+        'IDREFS' : XString,
+        'ENTITIES' : XString,
+        'IDREF' : XString,
+        'ENTITY' : XString,
+        'NMTOKEN' : XString,
+        'NMTOKENS' : XString,
+        # binary
+        'hexBinary' : XString,
+        'base64Binary' : XString,
+        # integers
+        'int' : XInteger,
+        'integer' : XInteger,
+        'unsignedInt' : XInteger,
+        'positiveInteger' : XInteger,
+        'negativeInteger' : XInteger,
+        'nonPositiveInteger' : XInteger,
+        'nonNegativeInteger' : XInteger,
+        # longs
+        'long' : XLong,
+        'unsignedLong' : XLong,
+        # shorts
+        'short' : XInteger,
+        'unsignedShort' : XInteger,
+        'byte' : XInteger,
+        'unsignedByte' : XInteger,
+        # floats
+        'float' : XFloat,
+        'double' : XFloat,
+        'decimal' : XFloat,
+        # dates & times
+        'date' : XDate,
+        'time' : XTime,
+        'dateTime': XDateTime,
+        'duration': XString,
+        'gYearMonth' : XString,
+        'gYear' : XString,
+        'gMonthDay' : XString,
+        'gDay' : XString,
+        'gMonth' : XString,
+        # boolean
+        'boolean' : XBoolean,
+    }
+
+    @classmethod
+    def maptag(cls, tag, fn):
+        """
+        Map (override) tag => I{class} mapping.
+        @param tag: An xsd tag name.
+        @type tag: str
+        @param fn: A function or class.
+        @type fn: fn|class.
+        """
+        cls.tags[tag] = fn
+
+    @classmethod
+    def create(cls, schema, name):
+        """
+        Create an object based on the root tag name.
+        @param schema: A schema object.
+        @type schema: L{schema.Schema}
+        @param name: The name.
+        @type name: str
+        @return: The created object.
+        @rtype: L{XBuiltin}
+        """
+        fn = cls.tags.get(name)
+        if fn is not None:
+            return fn(schema, name)
+        return XBuiltin(schema, name)