bundled wstools-0.3
[p2pool.git] / wstools / Utility.py
diff --git a/wstools/Utility.py b/wstools/Utility.py
new file mode 100755 (executable)
index 0000000..df536b9
--- /dev/null
@@ -0,0 +1,1382 @@
+# Copyright (c) 2003, The Regents of the University of California,
+# through Lawrence Berkeley National Laboratory (subject to receipt of
+# any required approvals from the U.S. Dept. of Energy).  All rights
+# reserved. 
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+
+ident = "$Id$"
+
+import sys, types, httplib, urllib, socket, weakref
+from os.path import isfile
+from string import join, strip, split
+from UserDict import UserDict
+from cStringIO import StringIO
+from TimeoutSocket import TimeoutSocket, TimeoutError
+from urlparse import urlparse
+from httplib import HTTPConnection, HTTPSConnection
+from exceptions import Exception
+try:
+    from ZSI import _get_idstr
+except:
+    def _get_idstr(pyobj):
+        '''Python 2.3.x generates a FutureWarning for negative IDs, so
+        we use a different prefix character to ensure uniqueness, and
+        call abs() to avoid the warning.'''
+        x = id(pyobj)
+        if x < 0:
+            return 'x%x' % abs(x)
+        return 'o%x' % x
+
+import xml.dom.minidom
+from xml.dom import Node
+
+import logging
+from c14n import Canonicalize
+from Namespaces import SCHEMA, SOAP, XMLNS, ZSI_SCHEMA_URI
+
+
+try:
+    from xml.dom.ext import SplitQName
+except:
+    def SplitQName(qname):
+        '''SplitQName(qname) -> (string, string)
+        
+           Split Qualified Name into a tuple of len 2, consisting 
+           of the prefix and the local name.  
+    
+           (prefix, localName)
+        
+           Special Cases:
+               xmlns -- (localName, 'xmlns')
+               None -- (None, localName)
+        '''
+        
+        l = qname.split(':')
+        if len(l) == 1:
+            l.insert(0, None)
+        elif len(l) == 2:
+            if l[0] == 'xmlns':
+                l.reverse()
+        else:
+            return
+        return tuple(l)
+
+#
+# python2.3 urllib.basejoin does not remove current directory ./
+# from path and this causes problems on subsequent basejoins.
+#
+basejoin = urllib.basejoin
+if sys.version_info[0:2] < (2, 4, 0, 'final', 0)[0:2]:
+    #basejoin = lambda base,url: urllib.basejoin(base,url.lstrip('./'))
+    token = './'
+    def basejoin(base, url): 
+        if url.startswith(token) is True:
+            return urllib.basejoin(base,url[2:])
+        return urllib.basejoin(base,url)
+
+class NamespaceError(Exception):
+    """Used to indicate a Namespace Error."""
+
+
+class RecursionError(Exception):
+    """Used to indicate a HTTP redirect recursion."""
+
+
+class ParseError(Exception):
+    """Used to indicate a XML parsing error."""
+
+
+class DOMException(Exception):
+    """Used to indicate a problem processing DOM."""
+
+
+class Base:
+    """Base class for instance level Logging"""
+    def __init__(self, module=__name__):
+        self.logger = logging.getLogger('%s-%s(%s)' %(module, self.__class__, _get_idstr(self)))
+
+
+class HTTPResponse:
+    """Captures the information in an HTTP response message."""
+
+    def __init__(self, response):
+        self.status = response.status
+        self.reason = response.reason
+        self.headers = response.msg
+        self.body = response.read() or None
+        response.close()
+
+class TimeoutHTTP(HTTPConnection):
+    """A custom http connection object that supports socket timeout."""
+    def __init__(self, host, port=None, timeout=20):
+        HTTPConnection.__init__(self, host, port)
+        self.timeout = timeout
+
+    def connect(self):
+        self.sock = TimeoutSocket(self.timeout)
+        self.sock.connect((self.host, self.port))
+
+
+class TimeoutHTTPS(HTTPSConnection):
+    """A custom https object that supports socket timeout. Note that this
+       is not really complete. The builtin SSL support in the Python socket
+       module requires a real socket (type) to be passed in to be hooked to
+       SSL. That means our fake socket won't work and our timeout hacks are
+       bypassed for send and recv calls. Since our hack _is_ in place at
+       connect() time, it should at least provide some timeout protection."""
+    def __init__(self, host, port=None, timeout=20, **kwargs):
+        HTTPSConnection.__init__(self, str(host), port, **kwargs)
+        self.timeout = timeout
+
+    def connect(self):
+        sock = TimeoutSocket(self.timeout)
+        sock.connect((self.host, self.port))
+        realsock = getattr(sock.sock, '_sock', sock.sock)
+        ssl = socket.ssl(realsock, self.key_file, self.cert_file)
+        self.sock = httplib.FakeSocket(sock, ssl)
+
+
+def urlopen(url, timeout=20, redirects=None):
+    """A minimal urlopen replacement hack that supports timeouts for http.
+       Note that this supports GET only."""
+    scheme, host, path, params, query, frag = urlparse(url)
+
+    if not scheme in ('http', 'https'):
+        return urllib.urlopen(url)
+    if params: path = '%s;%s' % (path, params)
+    if query:  path = '%s?%s' % (path, query)
+    if frag:   path = '%s#%s' % (path, frag)
+
+    if scheme == 'https':
+        # If ssl is not compiled into Python, you will not get an exception
+        # until a conn.endheaders() call.   We need to know sooner, so use
+        # getattr.
+        try:
+            import M2Crypto
+        except ImportError:
+            if not hasattr(socket, 'ssl'):
+                raise RuntimeError, 'no built-in SSL Support'
+
+            conn = TimeoutHTTPS(host, None, timeout)
+        else:
+            ctx = M2Crypto.SSL.Context()
+            ctx.set_session_timeout(timeout)
+            conn = M2Crypto.httpslib.HTTPSConnection(host, ssl_context=ctx)
+            conn.set_debuglevel(1)
+
+    else:
+        conn = TimeoutHTTP(host, None, timeout)
+
+    conn.putrequest('GET', path)
+    conn.putheader('Connection', 'close')
+    conn.endheaders()
+    response = None
+    while 1:
+        response = conn.getresponse()
+        if response.status != 100:
+            break
+        conn._HTTPConnection__state = httplib._CS_REQ_SENT
+        conn._HTTPConnection__response = None
+
+    status = response.status
+
+    # If we get an HTTP redirect, we will follow it automatically.
+    if status >= 300 and status < 400:
+        location = response.msg.getheader('location')
+        if location is not None:
+            response.close()
+            if redirects is not None and redirects.has_key(location):
+                raise RecursionError(
+                    'Circular HTTP redirection detected.'
+                    )
+            if redirects is None:
+                redirects = {}
+            redirects[location] = 1
+            return urlopen(location, timeout, redirects)
+        raise HTTPResponse(response)
+
+    if not (status >= 200 and status < 300):
+        raise HTTPResponse(response)
+
+    body = StringIO(response.read())
+    response.close()
+    return body
+
+class DOM:
+    """The DOM singleton defines a number of XML related constants and
+       provides a number of utility methods for DOM related tasks. It
+       also provides some basic abstractions so that the rest of the
+       package need not care about actual DOM implementation in use."""
+
+    # Namespace stuff related to the SOAP specification.
+
+    NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'
+    NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/'
+
+    NS_SOAP_ENV_1_2 = 'http://www.w3.org/2001/06/soap-envelope'
+    NS_SOAP_ENC_1_2 = 'http://www.w3.org/2001/06/soap-encoding'
+
+    NS_SOAP_ENV_ALL = (NS_SOAP_ENV_1_1, NS_SOAP_ENV_1_2)
+    NS_SOAP_ENC_ALL = (NS_SOAP_ENC_1_1, NS_SOAP_ENC_1_2)
+
+    NS_SOAP_ENV = NS_SOAP_ENV_1_1
+    NS_SOAP_ENC = NS_SOAP_ENC_1_1
+
+    _soap_uri_mapping = {
+        NS_SOAP_ENV_1_1 : '1.1',
+        NS_SOAP_ENV_1_2 : '1.2',
+    }
+
+    SOAP_ACTOR_NEXT_1_1 = 'http://schemas.xmlsoap.org/soap/actor/next'
+    SOAP_ACTOR_NEXT_1_2 = 'http://www.w3.org/2001/06/soap-envelope/actor/next'
+    SOAP_ACTOR_NEXT_ALL = (SOAP_ACTOR_NEXT_1_1, SOAP_ACTOR_NEXT_1_2)
+    
+    def SOAPUriToVersion(self, uri):
+        """Return the SOAP version related to an envelope uri."""
+        value = self._soap_uri_mapping.get(uri)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported SOAP envelope uri: %s' % uri
+            )
+
+    def GetSOAPEnvUri(self, version):
+        """Return the appropriate SOAP envelope uri for a given
+           human-friendly SOAP version string (e.g. '1.1')."""
+        attrname = 'NS_SOAP_ENV_%s' % join(split(version, '.'), '_')
+        value = getattr(self, attrname, None)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported SOAP version: %s' % version
+            )
+
+    def GetSOAPEncUri(self, version):
+        """Return the appropriate SOAP encoding uri for a given
+           human-friendly SOAP version string (e.g. '1.1')."""
+        attrname = 'NS_SOAP_ENC_%s' % join(split(version, '.'), '_')
+        value = getattr(self, attrname, None)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported SOAP version: %s' % version
+            )
+
+    def GetSOAPActorNextUri(self, version):
+        """Return the right special next-actor uri for a given
+           human-friendly SOAP version string (e.g. '1.1')."""
+        attrname = 'SOAP_ACTOR_NEXT_%s' % join(split(version, '.'), '_')
+        value = getattr(self, attrname, None)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported SOAP version: %s' % version
+            )
+
+
+    # Namespace stuff related to XML Schema.
+
+    NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema'
+    NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance'    
+
+    NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema'
+    NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance'    
+
+    NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema'
+    NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance'
+
+    NS_XSD_ALL = (NS_XSD_99, NS_XSD_00, NS_XSD_01)
+    NS_XSI_ALL = (NS_XSI_99, NS_XSI_00, NS_XSI_01)
+
+    NS_XSD = NS_XSD_01
+    NS_XSI = NS_XSI_01
+
+    _xsd_uri_mapping = {
+        NS_XSD_99 : NS_XSI_99,
+        NS_XSD_00 : NS_XSI_00,
+        NS_XSD_01 : NS_XSI_01,
+    }
+
+    for key, value in _xsd_uri_mapping.items():
+        _xsd_uri_mapping[value] = key
+
+
+    def InstanceUriForSchemaUri(self, uri):
+        """Return the appropriate matching XML Schema instance uri for
+           the given XML Schema namespace uri."""
+        return self._xsd_uri_mapping.get(uri)
+
+    def SchemaUriForInstanceUri(self, uri):
+        """Return the appropriate matching XML Schema namespace uri for
+           the given XML Schema instance namespace uri."""
+        return self._xsd_uri_mapping.get(uri)
+
+
+    # Namespace stuff related to WSDL.
+
+    NS_WSDL_1_1 = 'http://schemas.xmlsoap.org/wsdl/'
+    NS_WSDL_ALL = (NS_WSDL_1_1,)
+    NS_WSDL = NS_WSDL_1_1
+
+    NS_SOAP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/'
+    NS_HTTP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/http/'
+    NS_MIME_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/mime/'
+
+    NS_SOAP_BINDING_ALL = (NS_SOAP_BINDING_1_1,)
+    NS_HTTP_BINDING_ALL = (NS_HTTP_BINDING_1_1,)
+    NS_MIME_BINDING_ALL = (NS_MIME_BINDING_1_1,)
+
+    NS_SOAP_BINDING = NS_SOAP_BINDING_1_1
+    NS_HTTP_BINDING = NS_HTTP_BINDING_1_1
+    NS_MIME_BINDING = NS_MIME_BINDING_1_1
+
+    NS_SOAP_HTTP_1_1 = 'http://schemas.xmlsoap.org/soap/http'
+    NS_SOAP_HTTP_ALL = (NS_SOAP_HTTP_1_1,)
+    NS_SOAP_HTTP = NS_SOAP_HTTP_1_1
+    
+
+    _wsdl_uri_mapping = {
+        NS_WSDL_1_1 : '1.1',
+    }
+    
+    def WSDLUriToVersion(self, uri):
+        """Return the WSDL version related to a WSDL namespace uri."""
+        value = self._wsdl_uri_mapping.get(uri)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported SOAP envelope uri: %s' % uri
+            )
+
+    def GetWSDLUri(self, version):
+        attr = 'NS_WSDL_%s' % join(split(version, '.'), '_')
+        value = getattr(self, attr, None)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported WSDL version: %s' % version
+            )
+
+    def GetWSDLSoapBindingUri(self, version):
+        attr = 'NS_SOAP_BINDING_%s' % join(split(version, '.'), '_')
+        value = getattr(self, attr, None)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported WSDL version: %s' % version
+            )
+
+    def GetWSDLHttpBindingUri(self, version):
+        attr = 'NS_HTTP_BINDING_%s' % join(split(version, '.'), '_')
+        value = getattr(self, attr, None)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported WSDL version: %s' % version
+            )
+
+    def GetWSDLMimeBindingUri(self, version):
+        attr = 'NS_MIME_BINDING_%s' % join(split(version, '.'), '_')
+        value = getattr(self, attr, None)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported WSDL version: %s' % version
+            )
+
+    def GetWSDLHttpTransportUri(self, version):
+        attr = 'NS_SOAP_HTTP_%s' % join(split(version, '.'), '_')
+        value = getattr(self, attr, None)
+        if value is not None:
+            return value
+        raise ValueError(
+            'Unsupported WSDL version: %s' % version
+            )
+
+
+    # Other xml namespace constants.
+    NS_XMLNS     = 'http://www.w3.org/2000/xmlns/'
+
+
+
+    def isElement(self, node, name, nsuri=None):
+        """Return true if the given node is an element with the given
+           name and optional namespace uri."""
+        if node.nodeType != node.ELEMENT_NODE:
+            return 0
+        return node.localName == name and \
+               (nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri))
+
+    def getElement(self, node, name, nsuri=None, default=join):
+        """Return the first child of node with a matching name and
+           namespace uri, or the default if one is provided."""
+        nsmatch = self.nsUriMatch
+        ELEMENT_NODE = node.ELEMENT_NODE
+        for child in node.childNodes:
+            if child.nodeType == ELEMENT_NODE:
+                if ((child.localName == name or name is None) and
+                    (nsuri is None or nsmatch(child.namespaceURI, nsuri))
+                    ):
+                    return child
+        if default is not join:
+            return default
+        raise KeyError, name
+
+    def getElementById(self, node, id, default=join):
+        """Return the first child of node matching an id reference."""
+        attrget = self.getAttr
+        ELEMENT_NODE = node.ELEMENT_NODE
+        for child in node.childNodes:
+            if child.nodeType == ELEMENT_NODE:
+                if attrget(child, 'id') == id:
+                    return child
+        if default is not join:
+            return default
+        raise KeyError, name
+
+    def getMappingById(self, document, depth=None, element=None,
+                       mapping=None, level=1):
+        """Create an id -> element mapping of those elements within a
+           document that define an id attribute. The depth of the search
+           may be controlled by using the (1-based) depth argument."""
+        if document is not None:
+            element = document.documentElement
+            mapping = {}
+        attr = element._attrs.get('id', None)
+        if attr is not None:
+            mapping[attr.value] = element
+        if depth is None or depth > level:
+            level = level + 1
+            ELEMENT_NODE = element.ELEMENT_NODE
+            for child in element.childNodes:
+                if child.nodeType == ELEMENT_NODE:
+                    self.getMappingById(None, depth, child, mapping, level)
+        return mapping        
+
+    def getElements(self, node, name, nsuri=None):
+        """Return a sequence of the child elements of the given node that
+           match the given name and optional namespace uri."""
+        nsmatch = self.nsUriMatch
+        result = []
+        ELEMENT_NODE = node.ELEMENT_NODE
+        for child in node.childNodes:
+            if child.nodeType == ELEMENT_NODE:
+                if ((child.localName == name or name is None) and (
+                    (nsuri is None) or nsmatch(child.namespaceURI, nsuri))):
+                    result.append(child)
+        return result
+
+    def hasAttr(self, node, name, nsuri=None):
+        """Return true if element has attribute with the given name and
+           optional nsuri. If nsuri is not specified, returns true if an
+           attribute exists with the given name with any namespace."""
+        if nsuri is None:
+            if node.hasAttribute(name):
+                return True
+            return False
+        return node.hasAttributeNS(nsuri, name)
+
+    def getAttr(self, node, name, nsuri=None, default=join):
+        """Return the value of the attribute named 'name' with the
+           optional nsuri, or the default if one is specified. If
+           nsuri is not specified, an attribute that matches the
+           given name will be returned regardless of namespace."""
+        if nsuri is None:
+            result = node._attrs.get(name, None)
+            if result is None:
+                for item in node._attrsNS.keys():
+                    if item[1] == name:
+                        result = node._attrsNS[item]
+                        break
+        else:
+            result = node._attrsNS.get((nsuri, name), None)
+        if result is not None:
+            return result.value
+        if default is not join:
+            return default
+        return ''
+
+    def getAttrs(self, node):
+        """Return a Collection of all attributes 
+        """
+        attrs = {}
+        for k,v in node._attrs.items():
+            attrs[k] = v.value
+        return attrs
+
+    def getElementText(self, node, preserve_ws=None):
+        """Return the text value of an xml element node. Leading and trailing
+           whitespace is stripped from the value unless the preserve_ws flag
+           is passed with a true value."""
+        result = []
+        for child in node.childNodes:
+            nodetype = child.nodeType
+            if nodetype == child.TEXT_NODE or \
+               nodetype == child.CDATA_SECTION_NODE:
+                result.append(child.nodeValue)
+        value = join(result, '')
+        if preserve_ws is None:
+            value = strip(value)
+        return value
+
+    def findNamespaceURI(self, prefix, node):
+        """Find a namespace uri given a prefix and a context node."""
+        attrkey = (self.NS_XMLNS, prefix)
+        DOCUMENT_NODE = node.DOCUMENT_NODE
+        ELEMENT_NODE = node.ELEMENT_NODE
+        while 1:
+            if node is None:
+                raise DOMException('Value for prefix %s not found.' % prefix)
+            if node.nodeType != ELEMENT_NODE:
+                node = node.parentNode
+                continue
+            result = node._attrsNS.get(attrkey, None)
+            if result is not None:
+                return result.value
+            if hasattr(node, '__imported__'):
+                raise DOMException('Value for prefix %s not found.' % prefix)
+            node = node.parentNode
+            if node.nodeType == DOCUMENT_NODE:
+                raise DOMException('Value for prefix %s not found.' % prefix)
+
+    def findDefaultNS(self, node):
+        """Return the current default namespace uri for the given node."""
+        attrkey = (self.NS_XMLNS, 'xmlns')
+        DOCUMENT_NODE = node.DOCUMENT_NODE
+        ELEMENT_NODE = node.ELEMENT_NODE
+        while 1:
+            if node.nodeType != ELEMENT_NODE:
+                node = node.parentNode
+                continue
+            result = node._attrsNS.get(attrkey, None)
+            if result is not None:
+                return result.value
+            if hasattr(node, '__imported__'):
+                raise DOMException('Cannot determine default namespace.')
+            node = node.parentNode
+            if node.nodeType == DOCUMENT_NODE:
+                raise DOMException('Cannot determine default namespace.')
+
+    def findTargetNS(self, node):
+        """Return the defined target namespace uri for the given node."""
+        attrget = self.getAttr
+        attrkey = (self.NS_XMLNS, 'xmlns')
+        DOCUMENT_NODE = node.DOCUMENT_NODE
+        ELEMENT_NODE = node.ELEMENT_NODE
+        while 1:
+            if node.nodeType != ELEMENT_NODE:
+                node = node.parentNode
+                continue
+            result = attrget(node, 'targetNamespace', default=None)
+            if result is not None:
+                return result
+            node = node.parentNode
+            if node.nodeType == DOCUMENT_NODE:
+                raise DOMException('Cannot determine target namespace.')
+
+    def getTypeRef(self, element):
+        """Return (namespaceURI, name) for a type attribue of the given
+           element, or None if the element does not have a type attribute."""
+        typeattr = self.getAttr(element, 'type', default=None)
+        if typeattr is None:
+            return None
+        parts = typeattr.split(':', 1)
+        if len(parts) == 2:
+            nsuri = self.findNamespaceURI(parts[0], element)
+        else:
+            nsuri = self.findDefaultNS(element)
+        return (nsuri, parts[1])
+
+    def importNode(self, document, node, deep=0):
+        """Implements (well enough for our purposes) DOM node import."""
+        nodetype = node.nodeType
+        if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE):
+            raise DOMException('Illegal node type for importNode')
+        if nodetype == node.ENTITY_REFERENCE_NODE:
+            deep = 0
+        clone = node.cloneNode(deep)
+        self._setOwnerDoc(document, clone)
+        clone.__imported__ = 1
+        return clone
+
+    def _setOwnerDoc(self, document, node):
+        node.ownerDocument = document
+        for child in node.childNodes:
+            self._setOwnerDoc(document, child)
+
+    def nsUriMatch(self, value, wanted, strict=0, tt=type(())):
+        """Return a true value if two namespace uri values match."""
+        if value == wanted or (type(wanted) is tt) and value in wanted:
+            return 1
+        if not strict and value is not None:
+            wanted = type(wanted) is tt and wanted or (wanted,)
+            value = value[-1:] != '/' and value or value[:-1]
+            for item in wanted:
+                if item == value or item[:-1] == value:
+                    return 1
+        return 0
+
+    def createDocument(self, nsuri, qname, doctype=None):
+        """Create a new writable DOM document object."""
+        impl = xml.dom.minidom.getDOMImplementation()
+        return impl.createDocument(nsuri, qname, doctype)
+
+    def loadDocument(self, data):
+        """Load an xml file from a file-like object and return a DOM
+           document instance."""
+        return xml.dom.minidom.parse(data)
+
+    def loadFromURL(self, url):
+        """Load an xml file from a URL and return a DOM document."""
+        if isfile(url) is True:
+            file = open(url, 'r')
+        else:
+            file = urlopen(url)
+
+        try:     
+            result = self.loadDocument(file)
+        except Exception, ex:
+            file.close()
+            raise ParseError(('Failed to load document %s' %url,) + ex.args)
+        else:
+            file.close()
+        return result
+
+DOM = DOM()
+
+
+class MessageInterface:
+    '''Higher Level Interface, delegates to DOM singleton, must 
+    be subclassed and implement all methods that throw NotImplementedError.
+    '''
+    def __init__(self, sw):
+        '''Constructor, May be extended, do not override.
+            sw -- soapWriter instance
+        '''
+        self.sw = None
+        if type(sw) != weakref.ReferenceType and sw is not None:
+            self.sw = weakref.ref(sw)
+        else:
+            self.sw = sw
+
+    def AddCallback(self, func, *arglist):
+        self.sw().AddCallback(func, *arglist)
+
+    def Known(self, obj):
+        return self.sw().Known(obj)
+
+    def Forget(self, obj):
+        return self.sw().Forget(obj)
+
+    def canonicalize(self):
+        '''canonicalize the underlying DOM, and return as string.
+        '''
+        raise NotImplementedError, ''
+
+    def createDocument(self, namespaceURI=SOAP.ENV, localName='Envelope'):
+        '''create Document
+        '''
+        raise NotImplementedError, ''
+
+    def createAppendElement(self, namespaceURI, localName):
+        '''create and append element(namespaceURI,localName), and return
+        the node.
+        '''
+        raise NotImplementedError, ''
+
+    def findNamespaceURI(self, qualifiedName):
+        raise NotImplementedError, ''
+
+    def resolvePrefix(self, prefix):
+        raise NotImplementedError, ''
+
+    def setAttributeNS(self, namespaceURI, localName, value):
+        '''set attribute (namespaceURI, localName)=value
+        '''
+        raise NotImplementedError, ''
+
+    def setAttributeType(self, namespaceURI, localName):
+        '''set attribute xsi:type=(namespaceURI, localName)
+        '''
+        raise NotImplementedError, ''
+
+    def setNamespaceAttribute(self, namespaceURI, prefix):
+        '''set namespace attribute xmlns:prefix=namespaceURI 
+        '''
+        raise NotImplementedError, ''
+
+
+class ElementProxy(Base, MessageInterface):
+    '''
+    '''
+    _soap_env_prefix = 'SOAP-ENV'
+    _soap_enc_prefix = 'SOAP-ENC'
+    _zsi_prefix = 'ZSI'
+    _xsd_prefix = 'xsd'
+    _xsi_prefix = 'xsi'
+    _xml_prefix = 'xml'
+    _xmlns_prefix = 'xmlns'
+
+    _soap_env_nsuri = SOAP.ENV
+    _soap_enc_nsuri = SOAP.ENC
+    _zsi_nsuri = ZSI_SCHEMA_URI
+    _xsd_nsuri =  SCHEMA.XSD3
+    _xsi_nsuri = SCHEMA.XSI3
+    _xml_nsuri = XMLNS.XML
+    _xmlns_nsuri = XMLNS.BASE
+
+    standard_ns = {\
+        _xml_prefix:_xml_nsuri,
+        _xmlns_prefix:_xmlns_nsuri
+    }
+    reserved_ns = {\
+        _soap_env_prefix:_soap_env_nsuri,
+        _soap_enc_prefix:_soap_enc_nsuri,
+        _zsi_prefix:_zsi_nsuri,
+        _xsd_prefix:_xsd_nsuri,
+        _xsi_prefix:_xsi_nsuri,
+    }
+    name = None
+    namespaceURI = None
+
+    def __init__(self, sw, message=None):
+        '''Initialize. 
+           sw -- SoapWriter
+        '''
+        self._indx = 0
+        MessageInterface.__init__(self, sw)
+        Base.__init__(self)
+        self._dom = DOM
+        self.node = None
+        if type(message) in (types.StringType,types.UnicodeType):
+            self.loadFromString(message)
+        elif isinstance(message, ElementProxy):
+            self.node = message._getNode()
+        else:
+            self.node = message
+        self.processorNss = self.standard_ns.copy()
+        self.processorNss.update(self.reserved_ns)
+
+    def __str__(self):
+        return self.toString()
+
+    def evaluate(self, expression, processorNss=None):
+        '''expression -- XPath compiled expression
+        '''
+        from Ft.Xml import XPath
+        if not processorNss:
+            context = XPath.Context.Context(self.node, processorNss=self.processorNss)
+        else:
+            context = XPath.Context.Context(self.node, processorNss=processorNss)
+        nodes = expression.evaluate(context)
+        return map(lambda node: ElementProxy(self.sw,node), nodes)
+
+    #############################################
+    # Methods for checking/setting the
+    # classes (namespaceURI,name) node. 
+    #############################################
+    def checkNode(self, namespaceURI=None, localName=None):
+        '''
+            namespaceURI -- namespace of element
+            localName -- local name of element
+        '''
+        namespaceURI = namespaceURI or self.namespaceURI
+        localName = localName or self.name
+        check = False
+        if localName and self.node:
+            check = self._dom.isElement(self.node, localName, namespaceURI)
+        if not check:
+            raise NamespaceError, 'unexpected node type %s, expecting %s' %(self.node, localName)
+
+    def setNode(self, node=None):
+        if node:
+            if isinstance(node, ElementProxy):
+                self.node = node._getNode()
+            else:
+                self.node = node
+        elif self.node:
+            node = self._dom.getElement(self.node, self.name, self.namespaceURI, default=None)
+            if not node:
+                raise NamespaceError, 'cant find element (%s,%s)' %(self.namespaceURI,self.name)
+            self.node = node
+        else:
+            #self.node = self._dom.create(self.node, self.name, self.namespaceURI, default=None)
+            self.createDocument(self.namespaceURI, localName=self.name, doctype=None)
+        
+        self.checkNode()
+
+    #############################################
+    # Wrapper Methods for direct DOM Element Node access
+    #############################################
+    def _getNode(self):
+        return self.node
+
+    def _getElements(self):
+        return self._dom.getElements(self.node, name=None)
+
+    def _getOwnerDocument(self):
+        return self.node.ownerDocument or self.node
+
+    def _getUniquePrefix(self):
+        '''I guess we need to resolve all potential prefixes
+        because when the current node is attached it copies the 
+        namespaces into the parent node.
+        '''
+        while 1:
+            self._indx += 1
+            prefix = 'ns%d' %self._indx
+            try:
+                self._dom.findNamespaceURI(prefix, self._getNode())
+            except DOMException, ex:
+                break
+        return prefix
+
+    def _getPrefix(self, node, nsuri):
+        '''
+        Keyword arguments:
+            node -- DOM Element Node
+            nsuri -- namespace of attribute value
+        '''
+        try:
+            if node and (node.nodeType == node.ELEMENT_NODE) and \
+                (nsuri == self._dom.findDefaultNS(node)):
+                return None
+        except DOMException, ex:
+            pass
+        if nsuri == XMLNS.XML:
+            return self._xml_prefix
+        if node.nodeType == Node.ELEMENT_NODE:
+            for attr in node.attributes.values():
+                if attr.namespaceURI == XMLNS.BASE \
+                   and nsuri == attr.value:
+                        return attr.localName
+            else:
+                if node.parentNode:
+                    return self._getPrefix(node.parentNode, nsuri)
+        raise NamespaceError, 'namespaceURI "%s" is not defined' %nsuri
+
+    def _appendChild(self, node):
+        '''
+        Keyword arguments:
+            node -- DOM Element Node
+        '''
+        if node is None:
+            raise TypeError, 'node is None'
+        self.node.appendChild(node)
+
+    def _insertBefore(self, newChild, refChild):
+        '''
+        Keyword arguments:
+            child -- DOM Element Node to insert
+            refChild -- DOM Element Node 
+        '''
+        self.node.insertBefore(newChild, refChild)
+
+    def _setAttributeNS(self, namespaceURI, qualifiedName, value):
+        '''
+        Keyword arguments:
+            namespaceURI -- namespace of attribute
+            qualifiedName -- qualified name of new attribute value
+            value -- value of attribute
+        '''
+        self.node.setAttributeNS(namespaceURI, qualifiedName, value)
+
+    #############################################
+    #General Methods
+    #############################################
+    def isFault(self):
+        '''check to see if this is a soap:fault message.
+        '''
+        return False
+
+    def getPrefix(self, namespaceURI):
+        try:
+            prefix = self._getPrefix(node=self.node, nsuri=namespaceURI)
+        except NamespaceError, ex:
+            prefix = self._getUniquePrefix() 
+            self.setNamespaceAttribute(prefix, namespaceURI)
+        return prefix
+
+    def getDocument(self):
+        return self._getOwnerDocument()
+
+    def setDocument(self, document):
+        self.node = document
+
+    def importFromString(self, xmlString):
+        doc = self._dom.loadDocument(StringIO(xmlString))
+        node = self._dom.getElement(doc, name=None)
+        clone = self.importNode(node)
+        self._appendChild(clone)
+
+    def importNode(self, node):
+        if isinstance(node, ElementProxy):
+            node = node._getNode()
+        return self._dom.importNode(self._getOwnerDocument(), node, deep=1)
+
+    def loadFromString(self, data):
+        self.node = self._dom.loadDocument(StringIO(data))
+
+    def canonicalize(self):
+        return Canonicalize(self.node)
+
+    def toString(self):
+        return self.canonicalize()
+
+    def createDocument(self, namespaceURI, localName, doctype=None):
+        '''If specified must be a SOAP envelope, else may contruct an empty document.
+        '''
+        prefix = self._soap_env_prefix
+
+        if namespaceURI == self.reserved_ns[prefix]:
+            qualifiedName = '%s:%s' %(prefix,localName)
+        elif namespaceURI is localName is None:
+            self.node = self._dom.createDocument(None,None,None)
+            return
+        else:
+            raise KeyError, 'only support creation of document in %s' %self.reserved_ns[prefix]
+
+        document = self._dom.createDocument(nsuri=namespaceURI, qname=qualifiedName, doctype=doctype)
+        self.node = document.childNodes[0]
+
+        #set up reserved namespace attributes
+        for prefix,nsuri in self.reserved_ns.items():
+            self._setAttributeNS(namespaceURI=self._xmlns_nsuri, 
+                qualifiedName='%s:%s' %(self._xmlns_prefix,prefix), 
+                value=nsuri)
+
+    #############################################
+    #Methods for attributes
+    #############################################
+    def hasAttribute(self, namespaceURI, localName):
+        return self._dom.hasAttr(self._getNode(), name=localName, nsuri=namespaceURI)
+
+    def setAttributeType(self, namespaceURI, localName):
+        '''set xsi:type
+        Keyword arguments:
+            namespaceURI -- namespace of attribute value
+            localName -- name of new attribute value
+
+        '''
+        self.logger.debug('setAttributeType: (%s,%s)', namespaceURI, localName)
+        value = localName
+        if namespaceURI:
+            value = '%s:%s' %(self.getPrefix(namespaceURI),localName)
+
+        xsi_prefix = self.getPrefix(self._xsi_nsuri)
+        self._setAttributeNS(self._xsi_nsuri, '%s:type' %xsi_prefix, value)
+
+    def createAttributeNS(self, namespace, name, value):
+        document = self._getOwnerDocument()
+        ##this function doesn't exist!! it has only two arguments
+        attrNode = document.createAttributeNS(namespace, name, value)
+
+    def setAttributeNS(self, namespaceURI, localName, value):
+        '''
+        Keyword arguments:
+            namespaceURI -- namespace of attribute to create, None is for
+                attributes in no namespace.
+            localName -- local name of new attribute
+            value -- value of new attribute
+        ''' 
+        prefix = None
+        if namespaceURI:
+            try:
+                prefix = self.getPrefix(namespaceURI)
+            except KeyError, ex:
+                prefix = 'ns2'
+                self.setNamespaceAttribute(prefix, namespaceURI)
+        qualifiedName = localName
+        if prefix:
+            qualifiedName = '%s:%s' %(prefix, localName)
+        self._setAttributeNS(namespaceURI, qualifiedName, value)
+
+    def setNamespaceAttribute(self, prefix, namespaceURI):
+        '''
+        Keyword arguments:
+            prefix -- xmlns prefix
+            namespaceURI -- value of prefix
+        '''
+        self._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI)
+
+    #############################################
+    #Methods for elements
+    #############################################
+    def createElementNS(self, namespace, qname):
+        '''
+        Keyword arguments:
+            namespace -- namespace of element to create
+            qname -- qualified name of new element
+        '''
+        document = self._getOwnerDocument()
+        node = document.createElementNS(namespace, qname)
+        return ElementProxy(self.sw, node)
+
+    def createAppendSetElement(self, namespaceURI, localName, prefix=None):
+        '''Create a new element (namespaceURI,name), append it
+           to current node, then set it to be the current node.
+        Keyword arguments:
+            namespaceURI -- namespace of element to create
+            localName -- local name of new element
+            prefix -- if namespaceURI is not defined, declare prefix.  defaults
+                to 'ns1' if left unspecified.
+        '''
+        node = self.createAppendElement(namespaceURI, localName, prefix=None)
+        node=node._getNode()
+        self._setNode(node._getNode())
+
+    def createAppendElement(self, namespaceURI, localName, prefix=None):
+        '''Create a new element (namespaceURI,name), append it
+           to current node, and return the newly created node.
+        Keyword arguments:
+            namespaceURI -- namespace of element to create
+            localName -- local name of new element
+            prefix -- if namespaceURI is not defined, declare prefix.  defaults
+                to 'ns1' if left unspecified.
+        '''
+        declare = False
+        qualifiedName = localName
+        if namespaceURI:
+            try:
+                prefix = self.getPrefix(namespaceURI)
+            except:
+                declare = True
+                prefix = prefix or self._getUniquePrefix()
+            if prefix: 
+                qualifiedName = '%s:%s' %(prefix, localName)
+        node = self.createElementNS(namespaceURI, qualifiedName)
+        if declare:
+            node._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI)
+        self._appendChild(node=node._getNode())
+        return node
+
+    def createInsertBefore(self, namespaceURI, localName, refChild):
+        qualifiedName = localName
+        prefix = self.getPrefix(namespaceURI)
+        if prefix: 
+            qualifiedName = '%s:%s' %(prefix, localName)
+        node = self.createElementNS(namespaceURI, qualifiedName)
+        self._insertBefore(newChild=node._getNode(), refChild=refChild._getNode())
+        return node
+
+    def getElement(self, namespaceURI, localName):
+        '''
+        Keyword arguments:
+            namespaceURI -- namespace of element
+            localName -- local name of element
+        '''
+        node = self._dom.getElement(self.node, localName, namespaceURI, default=None)
+        if node:
+            return ElementProxy(self.sw, node)
+        return None
+
+    def getAttributeValue(self, namespaceURI, localName):
+        '''
+        Keyword arguments:
+            namespaceURI -- namespace of attribute
+            localName -- local name of attribute
+        '''
+        if self.hasAttribute(namespaceURI, localName):
+            attr = self.node.getAttributeNodeNS(namespaceURI,localName)
+            return attr.value
+        return None
+
+    def getValue(self):
+        return self._dom.getElementText(self.node, preserve_ws=True)    
+
+    #############################################
+    #Methods for text nodes
+    #############################################
+    def createAppendTextNode(self, pyobj):
+        node = self.createTextNode(pyobj)
+        self._appendChild(node=node._getNode())
+        return node
+
+    def createTextNode(self, pyobj):
+        document = self._getOwnerDocument()
+        node = document.createTextNode(pyobj)
+        return ElementProxy(self.sw, node)
+
+    #############################################
+    #Methods for retrieving namespaceURI's
+    #############################################
+    def findNamespaceURI(self, qualifiedName):
+        parts = SplitQName(qualifiedName)
+        element = self._getNode()
+        if len(parts) == 1:
+            return (self._dom.findTargetNS(element), value)
+        return self._dom.findNamespaceURI(parts[0], element)
+
+    def resolvePrefix(self, prefix):
+        element = self._getNode()
+        return self._dom.findNamespaceURI(prefix, element)
+
+    def getSOAPEnvURI(self):
+        return self._soap_env_nsuri
+
+    def isEmpty(self):
+        return not self.node
+
+
+
+class Collection(UserDict):
+    """Helper class for maintaining ordered named collections."""
+    default = lambda self,k: k.name
+    def __init__(self, parent, key=None):
+        UserDict.__init__(self)
+        self.parent = weakref.ref(parent)
+        self.list = []
+        self._func = key or self.default
+
+    def __getitem__(self, key):
+        if type(key) is type(1):
+            return self.list[key]
+        return self.data[key]
+
+    def __setitem__(self, key, item):
+        item.parent = weakref.ref(self)
+        self.list.append(item)
+        self.data[key] = item
+
+    def keys(self):
+        return map(lambda i: self._func(i), self.list)
+
+    def items(self):
+        return map(lambda i: (self._func(i), i), self.list)
+
+    def values(self):
+        return self.list
+
+
+class CollectionNS(UserDict):
+    """Helper class for maintaining ordered named collections."""
+    default = lambda self,k: k.name
+    def __init__(self, parent, key=None):
+        UserDict.__init__(self)
+        self.parent = weakref.ref(parent)
+        self.targetNamespace = None
+        self.list = []
+        self._func = key or self.default
+
+    def __getitem__(self, key):
+        self.targetNamespace = self.parent().targetNamespace
+        if type(key) is types.IntType:
+            return self.list[key]
+        elif self.__isSequence(key):
+            nsuri,name = key
+            return self.data[nsuri][name]
+        return self.data[self.parent().targetNamespace][key]
+
+    def __setitem__(self, key, item):
+        item.parent = weakref.ref(self)
+        self.list.append(item)
+        targetNamespace = getattr(item, 'targetNamespace', self.parent().targetNamespace)
+        if not self.data.has_key(targetNamespace):
+            self.data[targetNamespace] = {}
+        self.data[targetNamespace][key] = item
+
+    def __isSequence(self, key):
+        return (type(key) in (types.TupleType,types.ListType) and len(key) == 2)
+
+    def keys(self):
+        keys = []
+        for tns in self.data.keys():
+            keys.append(map(lambda i: (tns,self._func(i)), self.data[tns].values()))
+        return keys
+
+    def items(self):
+        return map(lambda i: (self._func(i), i), self.list)
+
+    def values(self):
+        return self.list
+
+
+
+# This is a runtime guerilla patch for pulldom (used by minidom) so
+# that xml namespace declaration attributes are not lost in parsing.
+# We need them to do correct QName linking for XML Schema and WSDL.
+# The patch has been submitted to SF for the next Python version.
+
+from xml.dom.pulldom import PullDOM, START_ELEMENT
+if 1:
+    def startPrefixMapping(self, prefix, uri):
+        if not hasattr(self, '_xmlns_attrs'):
+            self._xmlns_attrs = []
+        self._xmlns_attrs.append((prefix or 'xmlns', uri))
+        self._ns_contexts.append(self._current_context.copy())
+        self._current_context[uri] = prefix or ''
+
+    PullDOM.startPrefixMapping = startPrefixMapping
+
+    def startElementNS(self, name, tagName , attrs):
+        # Retrieve xml namespace declaration attributes.
+        xmlns_uri = 'http://www.w3.org/2000/xmlns/'
+        xmlns_attrs = getattr(self, '_xmlns_attrs', None)
+        if xmlns_attrs is not None:
+            for aname, value in xmlns_attrs:
+                attrs._attrs[(xmlns_uri, aname)] = value
+            self._xmlns_attrs = []
+        uri, localname = name
+        if uri:
+            # When using namespaces, the reader may or may not
+            # provide us with the original name. If not, create
+            # *a* valid tagName from the current context.
+            if tagName is None:
+                prefix = self._current_context[uri]
+                if prefix:
+                    tagName = prefix + ":" + localname
+                else:
+                    tagName = localname
+            if self.document:
+                node = self.document.createElementNS(uri, tagName)
+            else:
+                node = self.buildDocument(uri, tagName)
+        else:
+            # When the tagname is not prefixed, it just appears as
+            # localname
+            if self.document:
+                node = self.document.createElement(localname)
+            else:
+                node = self.buildDocument(None, localname)
+
+        for aname,value in attrs.items():
+            a_uri, a_localname = aname
+            if a_uri == xmlns_uri:
+                if a_localname == 'xmlns':
+                    qname = a_localname
+                else:
+                    qname = 'xmlns:' + a_localname
+                attr = self.document.createAttributeNS(a_uri, qname)
+                node.setAttributeNodeNS(attr)
+            elif a_uri:
+                prefix = self._current_context[a_uri]
+                if prefix:
+                    qname = prefix + ":" + a_localname
+                else:
+                    qname = a_localname
+                attr = self.document.createAttributeNS(a_uri, qname)
+                node.setAttributeNodeNS(attr)
+            else:
+                attr = self.document.createAttribute(a_localname)
+                node.setAttributeNode(attr)
+            attr.value = value
+
+        self.lastEvent[1] = [(START_ELEMENT, node), None]
+        self.lastEvent = self.lastEvent[1]
+        self.push(node)
+
+    PullDOM.startElementNS = startElementNS
+
+#
+# This is a runtime guerilla patch for minidom so
+# that xmlns prefixed attributes dont raise AttributeErrors
+# during cloning.
+#
+# Namespace declarations can appear in any start-tag, must look for xmlns
+# prefixed attribute names during cloning.
+#
+# key (attr.namespaceURI, tag)
+# ('http://www.w3.org/2000/xmlns/', u'xsd')   <xml.dom.minidom.Attr instance at 0x82227c4>
+# ('http://www.w3.org/2000/xmlns/', 'xmlns')   <xml.dom.minidom.Attr instance at 0x8414b3c>
+#
+# xml.dom.minidom.Attr.nodeName = xmlns:xsd
+# xml.dom.minidom.Attr.value =  = http://www.w3.org/2001/XMLSchema 
+
+if 1:
+    def _clone_node(node, deep, newOwnerDocument):
+        """
+        Clone a node and give it the new owner document.
+        Called by Node.cloneNode and Document.importNode
+        """
+        if node.ownerDocument.isSameNode(newOwnerDocument):
+            operation = xml.dom.UserDataHandler.NODE_CLONED
+        else:
+            operation = xml.dom.UserDataHandler.NODE_IMPORTED
+        if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
+            clone = newOwnerDocument.createElementNS(node.namespaceURI,
+                                                     node.nodeName)
+            for attr in node.attributes.values():
+                clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
+
+                prefix, tag = xml.dom.minidom._nssplit(attr.nodeName)
+                if prefix == 'xmlns':
+                    a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
+                elif prefix:
+                    a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
+                else:
+                    a = clone.getAttributeNodeNS(attr.namespaceURI, attr.nodeName)
+                a.specified = attr.specified
+
+            if deep:
+                for child in node.childNodes:
+                    c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
+                    clone.appendChild(c)
+        elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_FRAGMENT_NODE:
+            clone = newOwnerDocument.createDocumentFragment()
+            if deep:
+                for child in node.childNodes:
+                    c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
+                    clone.appendChild(c)
+
+        elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
+            clone = newOwnerDocument.createTextNode(node.data)
+        elif node.nodeType == xml.dom.minidom.Node.CDATA_SECTION_NODE:
+            clone = newOwnerDocument.createCDATASection(node.data)
+        elif node.nodeType == xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE:
+            clone = newOwnerDocument.createProcessingInstruction(node.target,
+                                                                 node.data)
+        elif node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
+            clone = newOwnerDocument.createComment(node.data)
+        elif node.nodeType == xml.dom.minidom.Node.ATTRIBUTE_NODE:
+            clone = newOwnerDocument.createAttributeNS(node.namespaceURI,
+                                                       node.nodeName)
+            clone.specified = True
+            clone.value = node.value
+        elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_TYPE_NODE:
+            assert node.ownerDocument is not newOwnerDocument
+            operation = xml.dom.UserDataHandler.NODE_IMPORTED
+            clone = newOwnerDocument.implementation.createDocumentType(
+                node.name, node.publicId, node.systemId)
+            clone.ownerDocument = newOwnerDocument
+            if deep:
+                clone.entities._seq = []
+                clone.notations._seq = []
+                for n in node.notations._seq:
+                    notation = xml.dom.minidom.Notation(n.nodeName, n.publicId, n.systemId)
+                    notation.ownerDocument = newOwnerDocument
+                    clone.notations._seq.append(notation)
+                    if hasattr(n, '_call_user_data_handler'):
+                        n._call_user_data_handler(operation, n, notation)
+                for e in node.entities._seq:
+                    entity = xml.dom.minidom.Entity(e.nodeName, e.publicId, e.systemId,
+                                    e.notationName)
+                    entity.actualEncoding = e.actualEncoding
+                    entity.encoding = e.encoding
+                    entity.version = e.version
+                    entity.ownerDocument = newOwnerDocument
+                    clone.entities._seq.append(entity)
+                    if hasattr(e, '_call_user_data_handler'):
+                        e._call_user_data_handler(operation, n, entity)
+        else:
+            # Note the cloning of Document and DocumentType nodes is
+            # implemenetation specific.  minidom handles those cases
+            # directly in the cloneNode() methods.
+            raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node))
+
+        # Check for _call_user_data_handler() since this could conceivably
+        # used with other DOM implementations (one of the FourThought
+        # DOMs, perhaps?).
+        if hasattr(node, '_call_user_data_handler'):
+            node._call_user_data_handler(operation, node, clone)
+        return clone
+
+    xml.dom.minidom._clone_node = _clone_node
+