fixed error in assertion text
[p2pool.git] / wstools / Utility.py
1 # Copyright (c) 2003, The Regents of the University of California,
2 # through Lawrence Berkeley National Laboratory (subject to receipt of
3 # any required approvals from the U.S. Dept. of Energy).  All rights
4 # reserved. 
5 #
6 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
7 #
8 # This software is subject to the provisions of the Zope Public License,
9 # Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
10 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
11 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
12 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
13 # FOR A PARTICULAR PURPOSE.
14
15 ident = "$Id$"
16
17 import sys, types, httplib, urllib, socket, weakref
18 from os.path import isfile
19 from string import join, strip, split
20 from UserDict import UserDict
21 from cStringIO import StringIO
22 from TimeoutSocket import TimeoutSocket, TimeoutError
23 from urlparse import urlparse
24 from httplib import HTTPConnection, HTTPSConnection
25 from exceptions import Exception
26 try:
27     from ZSI import _get_idstr
28 except:
29     def _get_idstr(pyobj):
30         '''Python 2.3.x generates a FutureWarning for negative IDs, so
31         we use a different prefix character to ensure uniqueness, and
32         call abs() to avoid the warning.'''
33         x = id(pyobj)
34         if x < 0:
35             return 'x%x' % abs(x)
36         return 'o%x' % x
37
38 import xml.dom.minidom
39 from xml.dom import Node
40
41 import logging
42 from c14n import Canonicalize
43 from Namespaces import SCHEMA, SOAP, XMLNS, ZSI_SCHEMA_URI
44
45
46 try:
47     from xml.dom.ext import SplitQName
48 except:
49     def SplitQName(qname):
50         '''SplitQName(qname) -> (string, string)
51         
52            Split Qualified Name into a tuple of len 2, consisting 
53            of the prefix and the local name.  
54     
55            (prefix, localName)
56         
57            Special Cases:
58                xmlns -- (localName, 'xmlns')
59                None -- (None, localName)
60         '''
61         
62         l = qname.split(':')
63         if len(l) == 1:
64             l.insert(0, None)
65         elif len(l) == 2:
66             if l[0] == 'xmlns':
67                 l.reverse()
68         else:
69             return
70         return tuple(l)
71
72 #
73 # python2.3 urllib.basejoin does not remove current directory ./
74 # from path and this causes problems on subsequent basejoins.
75 #
76 basejoin = urllib.basejoin
77 if sys.version_info[0:2] < (2, 4, 0, 'final', 0)[0:2]:
78     #basejoin = lambda base,url: urllib.basejoin(base,url.lstrip('./'))
79     token = './'
80     def basejoin(base, url): 
81         if url.startswith(token) is True:
82             return urllib.basejoin(base,url[2:])
83         return urllib.basejoin(base,url)
84
85 class NamespaceError(Exception):
86     """Used to indicate a Namespace Error."""
87
88
89 class RecursionError(Exception):
90     """Used to indicate a HTTP redirect recursion."""
91
92
93 class ParseError(Exception):
94     """Used to indicate a XML parsing error."""
95
96
97 class DOMException(Exception):
98     """Used to indicate a problem processing DOM."""
99
100
101 class Base:
102     """Base class for instance level Logging"""
103     def __init__(self, module=__name__):
104         self.logger = logging.getLogger('%s-%s(%s)' %(module, self.__class__, _get_idstr(self)))
105
106
107 class HTTPResponse:
108     """Captures the information in an HTTP response message."""
109
110     def __init__(self, response):
111         self.status = response.status
112         self.reason = response.reason
113         self.headers = response.msg
114         self.body = response.read() or None
115         response.close()
116
117 class TimeoutHTTP(HTTPConnection):
118     """A custom http connection object that supports socket timeout."""
119     def __init__(self, host, port=None, timeout=20):
120         HTTPConnection.__init__(self, host, port)
121         self.timeout = timeout
122
123     def connect(self):
124         self.sock = TimeoutSocket(self.timeout)
125         self.sock.connect((self.host, self.port))
126
127
128 class TimeoutHTTPS(HTTPSConnection):
129     """A custom https object that supports socket timeout. Note that this
130        is not really complete. The builtin SSL support in the Python socket
131        module requires a real socket (type) to be passed in to be hooked to
132        SSL. That means our fake socket won't work and our timeout hacks are
133        bypassed for send and recv calls. Since our hack _is_ in place at
134        connect() time, it should at least provide some timeout protection."""
135     def __init__(self, host, port=None, timeout=20, **kwargs):
136         HTTPSConnection.__init__(self, str(host), port, **kwargs)
137         self.timeout = timeout
138
139     def connect(self):
140         sock = TimeoutSocket(self.timeout)
141         sock.connect((self.host, self.port))
142         realsock = getattr(sock.sock, '_sock', sock.sock)
143         ssl = socket.ssl(realsock, self.key_file, self.cert_file)
144         self.sock = httplib.FakeSocket(sock, ssl)
145
146
147 def urlopen(url, timeout=20, redirects=None):
148     """A minimal urlopen replacement hack that supports timeouts for http.
149        Note that this supports GET only."""
150     scheme, host, path, params, query, frag = urlparse(url)
151
152     if not scheme in ('http', 'https'):
153         return urllib.urlopen(url)
154     if params: path = '%s;%s' % (path, params)
155     if query:  path = '%s?%s' % (path, query)
156     if frag:   path = '%s#%s' % (path, frag)
157
158     if scheme == 'https':
159         # If ssl is not compiled into Python, you will not get an exception
160         # until a conn.endheaders() call.   We need to know sooner, so use
161         # getattr.
162         try:
163             import M2Crypto
164         except ImportError:
165             if not hasattr(socket, 'ssl'):
166                 raise RuntimeError, 'no built-in SSL Support'
167
168             conn = TimeoutHTTPS(host, None, timeout)
169         else:
170             ctx = M2Crypto.SSL.Context()
171             ctx.set_session_timeout(timeout)
172             conn = M2Crypto.httpslib.HTTPSConnection(host, ssl_context=ctx)
173             conn.set_debuglevel(1)
174
175     else:
176         conn = TimeoutHTTP(host, None, timeout)
177
178     conn.putrequest('GET', path)
179     conn.putheader('Connection', 'close')
180     conn.endheaders()
181     response = None
182     while 1:
183         response = conn.getresponse()
184         if response.status != 100:
185             break
186         conn._HTTPConnection__state = httplib._CS_REQ_SENT
187         conn._HTTPConnection__response = None
188
189     status = response.status
190
191     # If we get an HTTP redirect, we will follow it automatically.
192     if status >= 300 and status < 400:
193         location = response.msg.getheader('location')
194         if location is not None:
195             response.close()
196             if redirects is not None and redirects.has_key(location):
197                 raise RecursionError(
198                     'Circular HTTP redirection detected.'
199                     )
200             if redirects is None:
201                 redirects = {}
202             redirects[location] = 1
203             return urlopen(location, timeout, redirects)
204         raise HTTPResponse(response)
205
206     if not (status >= 200 and status < 300):
207         raise HTTPResponse(response)
208
209     body = StringIO(response.read())
210     response.close()
211     return body
212
213 class DOM:
214     """The DOM singleton defines a number of XML related constants and
215        provides a number of utility methods for DOM related tasks. It
216        also provides some basic abstractions so that the rest of the
217        package need not care about actual DOM implementation in use."""
218
219     # Namespace stuff related to the SOAP specification.
220
221     NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'
222     NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/'
223
224     NS_SOAP_ENV_1_2 = 'http://www.w3.org/2001/06/soap-envelope'
225     NS_SOAP_ENC_1_2 = 'http://www.w3.org/2001/06/soap-encoding'
226
227     NS_SOAP_ENV_ALL = (NS_SOAP_ENV_1_1, NS_SOAP_ENV_1_2)
228     NS_SOAP_ENC_ALL = (NS_SOAP_ENC_1_1, NS_SOAP_ENC_1_2)
229
230     NS_SOAP_ENV = NS_SOAP_ENV_1_1
231     NS_SOAP_ENC = NS_SOAP_ENC_1_1
232
233     _soap_uri_mapping = {
234         NS_SOAP_ENV_1_1 : '1.1',
235         NS_SOAP_ENV_1_2 : '1.2',
236     }
237
238     SOAP_ACTOR_NEXT_1_1 = 'http://schemas.xmlsoap.org/soap/actor/next'
239     SOAP_ACTOR_NEXT_1_2 = 'http://www.w3.org/2001/06/soap-envelope/actor/next'
240     SOAP_ACTOR_NEXT_ALL = (SOAP_ACTOR_NEXT_1_1, SOAP_ACTOR_NEXT_1_2)
241     
242     def SOAPUriToVersion(self, uri):
243         """Return the SOAP version related to an envelope uri."""
244         value = self._soap_uri_mapping.get(uri)
245         if value is not None:
246             return value
247         raise ValueError(
248             'Unsupported SOAP envelope uri: %s' % uri
249             )
250
251     def GetSOAPEnvUri(self, version):
252         """Return the appropriate SOAP envelope uri for a given
253            human-friendly SOAP version string (e.g. '1.1')."""
254         attrname = 'NS_SOAP_ENV_%s' % join(split(version, '.'), '_')
255         value = getattr(self, attrname, None)
256         if value is not None:
257             return value
258         raise ValueError(
259             'Unsupported SOAP version: %s' % version
260             )
261
262     def GetSOAPEncUri(self, version):
263         """Return the appropriate SOAP encoding uri for a given
264            human-friendly SOAP version string (e.g. '1.1')."""
265         attrname = 'NS_SOAP_ENC_%s' % join(split(version, '.'), '_')
266         value = getattr(self, attrname, None)
267         if value is not None:
268             return value
269         raise ValueError(
270             'Unsupported SOAP version: %s' % version
271             )
272
273     def GetSOAPActorNextUri(self, version):
274         """Return the right special next-actor uri for a given
275            human-friendly SOAP version string (e.g. '1.1')."""
276         attrname = 'SOAP_ACTOR_NEXT_%s' % join(split(version, '.'), '_')
277         value = getattr(self, attrname, None)
278         if value is not None:
279             return value
280         raise ValueError(
281             'Unsupported SOAP version: %s' % version
282             )
283
284
285     # Namespace stuff related to XML Schema.
286
287     NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema'
288     NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance'    
289
290     NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema'
291     NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance'    
292
293     NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema'
294     NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance'
295
296     NS_XSD_ALL = (NS_XSD_99, NS_XSD_00, NS_XSD_01)
297     NS_XSI_ALL = (NS_XSI_99, NS_XSI_00, NS_XSI_01)
298
299     NS_XSD = NS_XSD_01
300     NS_XSI = NS_XSI_01
301
302     _xsd_uri_mapping = {
303         NS_XSD_99 : NS_XSI_99,
304         NS_XSD_00 : NS_XSI_00,
305         NS_XSD_01 : NS_XSI_01,
306     }
307
308     for key, value in _xsd_uri_mapping.items():
309         _xsd_uri_mapping[value] = key
310
311
312     def InstanceUriForSchemaUri(self, uri):
313         """Return the appropriate matching XML Schema instance uri for
314            the given XML Schema namespace uri."""
315         return self._xsd_uri_mapping.get(uri)
316
317     def SchemaUriForInstanceUri(self, uri):
318         """Return the appropriate matching XML Schema namespace uri for
319            the given XML Schema instance namespace uri."""
320         return self._xsd_uri_mapping.get(uri)
321
322
323     # Namespace stuff related to WSDL.
324
325     NS_WSDL_1_1 = 'http://schemas.xmlsoap.org/wsdl/'
326     NS_WSDL_ALL = (NS_WSDL_1_1,)
327     NS_WSDL = NS_WSDL_1_1
328
329     NS_SOAP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/'
330     NS_HTTP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/http/'
331     NS_MIME_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/mime/'
332
333     NS_SOAP_BINDING_ALL = (NS_SOAP_BINDING_1_1,)
334     NS_HTTP_BINDING_ALL = (NS_HTTP_BINDING_1_1,)
335     NS_MIME_BINDING_ALL = (NS_MIME_BINDING_1_1,)
336
337     NS_SOAP_BINDING = NS_SOAP_BINDING_1_1
338     NS_HTTP_BINDING = NS_HTTP_BINDING_1_1
339     NS_MIME_BINDING = NS_MIME_BINDING_1_1
340
341     NS_SOAP_HTTP_1_1 = 'http://schemas.xmlsoap.org/soap/http'
342     NS_SOAP_HTTP_ALL = (NS_SOAP_HTTP_1_1,)
343     NS_SOAP_HTTP = NS_SOAP_HTTP_1_1
344     
345
346     _wsdl_uri_mapping = {
347         NS_WSDL_1_1 : '1.1',
348     }
349     
350     def WSDLUriToVersion(self, uri):
351         """Return the WSDL version related to a WSDL namespace uri."""
352         value = self._wsdl_uri_mapping.get(uri)
353         if value is not None:
354             return value
355         raise ValueError(
356             'Unsupported SOAP envelope uri: %s' % uri
357             )
358
359     def GetWSDLUri(self, version):
360         attr = 'NS_WSDL_%s' % join(split(version, '.'), '_')
361         value = getattr(self, attr, None)
362         if value is not None:
363             return value
364         raise ValueError(
365             'Unsupported WSDL version: %s' % version
366             )
367
368     def GetWSDLSoapBindingUri(self, version):
369         attr = 'NS_SOAP_BINDING_%s' % join(split(version, '.'), '_')
370         value = getattr(self, attr, None)
371         if value is not None:
372             return value
373         raise ValueError(
374             'Unsupported WSDL version: %s' % version
375             )
376
377     def GetWSDLHttpBindingUri(self, version):
378         attr = 'NS_HTTP_BINDING_%s' % join(split(version, '.'), '_')
379         value = getattr(self, attr, None)
380         if value is not None:
381             return value
382         raise ValueError(
383             'Unsupported WSDL version: %s' % version
384             )
385
386     def GetWSDLMimeBindingUri(self, version):
387         attr = 'NS_MIME_BINDING_%s' % join(split(version, '.'), '_')
388         value = getattr(self, attr, None)
389         if value is not None:
390             return value
391         raise ValueError(
392             'Unsupported WSDL version: %s' % version
393             )
394
395     def GetWSDLHttpTransportUri(self, version):
396         attr = 'NS_SOAP_HTTP_%s' % join(split(version, '.'), '_')
397         value = getattr(self, attr, None)
398         if value is not None:
399             return value
400         raise ValueError(
401             'Unsupported WSDL version: %s' % version
402             )
403
404
405     # Other xml namespace constants.
406     NS_XMLNS     = 'http://www.w3.org/2000/xmlns/'
407
408
409
410     def isElement(self, node, name, nsuri=None):
411         """Return true if the given node is an element with the given
412            name and optional namespace uri."""
413         if node.nodeType != node.ELEMENT_NODE:
414             return 0
415         return node.localName == name and \
416                (nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri))
417
418     def getElement(self, node, name, nsuri=None, default=join):
419         """Return the first child of node with a matching name and
420            namespace uri, or the default if one is provided."""
421         nsmatch = self.nsUriMatch
422         ELEMENT_NODE = node.ELEMENT_NODE
423         for child in node.childNodes:
424             if child.nodeType == ELEMENT_NODE:
425                 if ((child.localName == name or name is None) and
426                     (nsuri is None or nsmatch(child.namespaceURI, nsuri))
427                     ):
428                     return child
429         if default is not join:
430             return default
431         raise KeyError, name
432
433     def getElementById(self, node, id, default=join):
434         """Return the first child of node matching an id reference."""
435         attrget = self.getAttr
436         ELEMENT_NODE = node.ELEMENT_NODE
437         for child in node.childNodes:
438             if child.nodeType == ELEMENT_NODE:
439                 if attrget(child, 'id') == id:
440                     return child
441         if default is not join:
442             return default
443         raise KeyError, name
444
445     def getMappingById(self, document, depth=None, element=None,
446                        mapping=None, level=1):
447         """Create an id -> element mapping of those elements within a
448            document that define an id attribute. The depth of the search
449            may be controlled by using the (1-based) depth argument."""
450         if document is not None:
451             element = document.documentElement
452             mapping = {}
453         attr = element._attrs.get('id', None)
454         if attr is not None:
455             mapping[attr.value] = element
456         if depth is None or depth > level:
457             level = level + 1
458             ELEMENT_NODE = element.ELEMENT_NODE
459             for child in element.childNodes:
460                 if child.nodeType == ELEMENT_NODE:
461                     self.getMappingById(None, depth, child, mapping, level)
462         return mapping        
463
464     def getElements(self, node, name, nsuri=None):
465         """Return a sequence of the child elements of the given node that
466            match the given name and optional namespace uri."""
467         nsmatch = self.nsUriMatch
468         result = []
469         ELEMENT_NODE = node.ELEMENT_NODE
470         for child in node.childNodes:
471             if child.nodeType == ELEMENT_NODE:
472                 if ((child.localName == name or name is None) and (
473                     (nsuri is None) or nsmatch(child.namespaceURI, nsuri))):
474                     result.append(child)
475         return result
476
477     def hasAttr(self, node, name, nsuri=None):
478         """Return true if element has attribute with the given name and
479            optional nsuri. If nsuri is not specified, returns true if an
480            attribute exists with the given name with any namespace."""
481         if nsuri is None:
482             if node.hasAttribute(name):
483                 return True
484             return False
485         return node.hasAttributeNS(nsuri, name)
486
487     def getAttr(self, node, name, nsuri=None, default=join):
488         """Return the value of the attribute named 'name' with the
489            optional nsuri, or the default if one is specified. If
490            nsuri is not specified, an attribute that matches the
491            given name will be returned regardless of namespace."""
492         if nsuri is None:
493             result = node._attrs.get(name, None)
494             if result is None:
495                 for item in node._attrsNS.keys():
496                     if item[1] == name:
497                         result = node._attrsNS[item]
498                         break
499         else:
500             result = node._attrsNS.get((nsuri, name), None)
501         if result is not None:
502             return result.value
503         if default is not join:
504             return default
505         return ''
506
507     def getAttrs(self, node):
508         """Return a Collection of all attributes 
509         """
510         attrs = {}
511         for k,v in node._attrs.items():
512             attrs[k] = v.value
513         return attrs
514
515     def getElementText(self, node, preserve_ws=None):
516         """Return the text value of an xml element node. Leading and trailing
517            whitespace is stripped from the value unless the preserve_ws flag
518            is passed with a true value."""
519         result = []
520         for child in node.childNodes:
521             nodetype = child.nodeType
522             if nodetype == child.TEXT_NODE or \
523                nodetype == child.CDATA_SECTION_NODE:
524                 result.append(child.nodeValue)
525         value = join(result, '')
526         if preserve_ws is None:
527             value = strip(value)
528         return value
529
530     def findNamespaceURI(self, prefix, node):
531         """Find a namespace uri given a prefix and a context node."""
532         attrkey = (self.NS_XMLNS, prefix)
533         DOCUMENT_NODE = node.DOCUMENT_NODE
534         ELEMENT_NODE = node.ELEMENT_NODE
535         while 1:
536             if node is None:
537                 raise DOMException('Value for prefix %s not found.' % prefix)
538             if node.nodeType != ELEMENT_NODE:
539                 node = node.parentNode
540                 continue
541             result = node._attrsNS.get(attrkey, None)
542             if result is not None:
543                 return result.value
544             if hasattr(node, '__imported__'):
545                 raise DOMException('Value for prefix %s not found.' % prefix)
546             node = node.parentNode
547             if node.nodeType == DOCUMENT_NODE:
548                 raise DOMException('Value for prefix %s not found.' % prefix)
549
550     def findDefaultNS(self, node):
551         """Return the current default namespace uri for the given node."""
552         attrkey = (self.NS_XMLNS, 'xmlns')
553         DOCUMENT_NODE = node.DOCUMENT_NODE
554         ELEMENT_NODE = node.ELEMENT_NODE
555         while 1:
556             if node.nodeType != ELEMENT_NODE:
557                 node = node.parentNode
558                 continue
559             result = node._attrsNS.get(attrkey, None)
560             if result is not None:
561                 return result.value
562             if hasattr(node, '__imported__'):
563                 raise DOMException('Cannot determine default namespace.')
564             node = node.parentNode
565             if node.nodeType == DOCUMENT_NODE:
566                 raise DOMException('Cannot determine default namespace.')
567
568     def findTargetNS(self, node):
569         """Return the defined target namespace uri for the given node."""
570         attrget = self.getAttr
571         attrkey = (self.NS_XMLNS, 'xmlns')
572         DOCUMENT_NODE = node.DOCUMENT_NODE
573         ELEMENT_NODE = node.ELEMENT_NODE
574         while 1:
575             if node.nodeType != ELEMENT_NODE:
576                 node = node.parentNode
577                 continue
578             result = attrget(node, 'targetNamespace', default=None)
579             if result is not None:
580                 return result
581             node = node.parentNode
582             if node.nodeType == DOCUMENT_NODE:
583                 raise DOMException('Cannot determine target namespace.')
584
585     def getTypeRef(self, element):
586         """Return (namespaceURI, name) for a type attribue of the given
587            element, or None if the element does not have a type attribute."""
588         typeattr = self.getAttr(element, 'type', default=None)
589         if typeattr is None:
590             return None
591         parts = typeattr.split(':', 1)
592         if len(parts) == 2:
593             nsuri = self.findNamespaceURI(parts[0], element)
594         else:
595             nsuri = self.findDefaultNS(element)
596         return (nsuri, parts[1])
597
598     def importNode(self, document, node, deep=0):
599         """Implements (well enough for our purposes) DOM node import."""
600         nodetype = node.nodeType
601         if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE):
602             raise DOMException('Illegal node type for importNode')
603         if nodetype == node.ENTITY_REFERENCE_NODE:
604             deep = 0
605         clone = node.cloneNode(deep)
606         self._setOwnerDoc(document, clone)
607         clone.__imported__ = 1
608         return clone
609
610     def _setOwnerDoc(self, document, node):
611         node.ownerDocument = document
612         for child in node.childNodes:
613             self._setOwnerDoc(document, child)
614
615     def nsUriMatch(self, value, wanted, strict=0, tt=type(())):
616         """Return a true value if two namespace uri values match."""
617         if value == wanted or (type(wanted) is tt) and value in wanted:
618             return 1
619         if not strict and value is not None:
620             wanted = type(wanted) is tt and wanted or (wanted,)
621             value = value[-1:] != '/' and value or value[:-1]
622             for item in wanted:
623                 if item == value or item[:-1] == value:
624                     return 1
625         return 0
626
627     def createDocument(self, nsuri, qname, doctype=None):
628         """Create a new writable DOM document object."""
629         impl = xml.dom.minidom.getDOMImplementation()
630         return impl.createDocument(nsuri, qname, doctype)
631
632     def loadDocument(self, data):
633         """Load an xml file from a file-like object and return a DOM
634            document instance."""
635         return xml.dom.minidom.parse(data)
636
637     def loadFromURL(self, url):
638         """Load an xml file from a URL and return a DOM document."""
639         if isfile(url) is True:
640             file = open(url, 'r')
641         else:
642             file = urlopen(url)
643
644         try:     
645             result = self.loadDocument(file)
646         except Exception, ex:
647             file.close()
648             raise ParseError(('Failed to load document %s' %url,) + ex.args)
649         else:
650             file.close()
651         return result
652
653 DOM = DOM()
654
655
656 class MessageInterface:
657     '''Higher Level Interface, delegates to DOM singleton, must 
658     be subclassed and implement all methods that throw NotImplementedError.
659     '''
660     def __init__(self, sw):
661         '''Constructor, May be extended, do not override.
662             sw -- soapWriter instance
663         '''
664         self.sw = None
665         if type(sw) != weakref.ReferenceType and sw is not None:
666             self.sw = weakref.ref(sw)
667         else:
668             self.sw = sw
669
670     def AddCallback(self, func, *arglist):
671         self.sw().AddCallback(func, *arglist)
672
673     def Known(self, obj):
674         return self.sw().Known(obj)
675
676     def Forget(self, obj):
677         return self.sw().Forget(obj)
678
679     def canonicalize(self):
680         '''canonicalize the underlying DOM, and return as string.
681         '''
682         raise NotImplementedError, ''
683
684     def createDocument(self, namespaceURI=SOAP.ENV, localName='Envelope'):
685         '''create Document
686         '''
687         raise NotImplementedError, ''
688
689     def createAppendElement(self, namespaceURI, localName):
690         '''create and append element(namespaceURI,localName), and return
691         the node.
692         '''
693         raise NotImplementedError, ''
694
695     def findNamespaceURI(self, qualifiedName):
696         raise NotImplementedError, ''
697
698     def resolvePrefix(self, prefix):
699         raise NotImplementedError, ''
700
701     def setAttributeNS(self, namespaceURI, localName, value):
702         '''set attribute (namespaceURI, localName)=value
703         '''
704         raise NotImplementedError, ''
705
706     def setAttributeType(self, namespaceURI, localName):
707         '''set attribute xsi:type=(namespaceURI, localName)
708         '''
709         raise NotImplementedError, ''
710
711     def setNamespaceAttribute(self, namespaceURI, prefix):
712         '''set namespace attribute xmlns:prefix=namespaceURI 
713         '''
714         raise NotImplementedError, ''
715
716
717 class ElementProxy(Base, MessageInterface):
718     '''
719     '''
720     _soap_env_prefix = 'SOAP-ENV'
721     _soap_enc_prefix = 'SOAP-ENC'
722     _zsi_prefix = 'ZSI'
723     _xsd_prefix = 'xsd'
724     _xsi_prefix = 'xsi'
725     _xml_prefix = 'xml'
726     _xmlns_prefix = 'xmlns'
727
728     _soap_env_nsuri = SOAP.ENV
729     _soap_enc_nsuri = SOAP.ENC
730     _zsi_nsuri = ZSI_SCHEMA_URI
731     _xsd_nsuri =  SCHEMA.XSD3
732     _xsi_nsuri = SCHEMA.XSI3
733     _xml_nsuri = XMLNS.XML
734     _xmlns_nsuri = XMLNS.BASE
735
736     standard_ns = {\
737         _xml_prefix:_xml_nsuri,
738         _xmlns_prefix:_xmlns_nsuri
739     }
740     reserved_ns = {\
741         _soap_env_prefix:_soap_env_nsuri,
742         _soap_enc_prefix:_soap_enc_nsuri,
743         _zsi_prefix:_zsi_nsuri,
744         _xsd_prefix:_xsd_nsuri,
745         _xsi_prefix:_xsi_nsuri,
746     }
747     name = None
748     namespaceURI = None
749
750     def __init__(self, sw, message=None):
751         '''Initialize. 
752            sw -- SoapWriter
753         '''
754         self._indx = 0
755         MessageInterface.__init__(self, sw)
756         Base.__init__(self)
757         self._dom = DOM
758         self.node = None
759         if type(message) in (types.StringType,types.UnicodeType):
760             self.loadFromString(message)
761         elif isinstance(message, ElementProxy):
762             self.node = message._getNode()
763         else:
764             self.node = message
765         self.processorNss = self.standard_ns.copy()
766         self.processorNss.update(self.reserved_ns)
767
768     def __str__(self):
769         return self.toString()
770
771     def evaluate(self, expression, processorNss=None):
772         '''expression -- XPath compiled expression
773         '''
774         from Ft.Xml import XPath
775         if not processorNss:
776             context = XPath.Context.Context(self.node, processorNss=self.processorNss)
777         else:
778             context = XPath.Context.Context(self.node, processorNss=processorNss)
779         nodes = expression.evaluate(context)
780         return map(lambda node: ElementProxy(self.sw,node), nodes)
781
782     #############################################
783     # Methods for checking/setting the
784     # classes (namespaceURI,name) node. 
785     #############################################
786     def checkNode(self, namespaceURI=None, localName=None):
787         '''
788             namespaceURI -- namespace of element
789             localName -- local name of element
790         '''
791         namespaceURI = namespaceURI or self.namespaceURI
792         localName = localName or self.name
793         check = False
794         if localName and self.node:
795             check = self._dom.isElement(self.node, localName, namespaceURI)
796         if not check:
797             raise NamespaceError, 'unexpected node type %s, expecting %s' %(self.node, localName)
798
799     def setNode(self, node=None):
800         if node:
801             if isinstance(node, ElementProxy):
802                 self.node = node._getNode()
803             else:
804                 self.node = node
805         elif self.node:
806             node = self._dom.getElement(self.node, self.name, self.namespaceURI, default=None)
807             if not node:
808                 raise NamespaceError, 'cant find element (%s,%s)' %(self.namespaceURI,self.name)
809             self.node = node
810         else:
811             #self.node = self._dom.create(self.node, self.name, self.namespaceURI, default=None)
812             self.createDocument(self.namespaceURI, localName=self.name, doctype=None)
813         
814         self.checkNode()
815
816     #############################################
817     # Wrapper Methods for direct DOM Element Node access
818     #############################################
819     def _getNode(self):
820         return self.node
821
822     def _getElements(self):
823         return self._dom.getElements(self.node, name=None)
824
825     def _getOwnerDocument(self):
826         return self.node.ownerDocument or self.node
827
828     def _getUniquePrefix(self):
829         '''I guess we need to resolve all potential prefixes
830         because when the current node is attached it copies the 
831         namespaces into the parent node.
832         '''
833         while 1:
834             self._indx += 1
835             prefix = 'ns%d' %self._indx
836             try:
837                 self._dom.findNamespaceURI(prefix, self._getNode())
838             except DOMException, ex:
839                 break
840         return prefix
841
842     def _getPrefix(self, node, nsuri):
843         '''
844         Keyword arguments:
845             node -- DOM Element Node
846             nsuri -- namespace of attribute value
847         '''
848         try:
849             if node and (node.nodeType == node.ELEMENT_NODE) and \
850                 (nsuri == self._dom.findDefaultNS(node)):
851                 return None
852         except DOMException, ex:
853             pass
854         if nsuri == XMLNS.XML:
855             return self._xml_prefix
856         if node.nodeType == Node.ELEMENT_NODE:
857             for attr in node.attributes.values():
858                 if attr.namespaceURI == XMLNS.BASE \
859                    and nsuri == attr.value:
860                         return attr.localName
861             else:
862                 if node.parentNode:
863                     return self._getPrefix(node.parentNode, nsuri)
864         raise NamespaceError, 'namespaceURI "%s" is not defined' %nsuri
865
866     def _appendChild(self, node):
867         '''
868         Keyword arguments:
869             node -- DOM Element Node
870         '''
871         if node is None:
872             raise TypeError, 'node is None'
873         self.node.appendChild(node)
874
875     def _insertBefore(self, newChild, refChild):
876         '''
877         Keyword arguments:
878             child -- DOM Element Node to insert
879             refChild -- DOM Element Node 
880         '''
881         self.node.insertBefore(newChild, refChild)
882
883     def _setAttributeNS(self, namespaceURI, qualifiedName, value):
884         '''
885         Keyword arguments:
886             namespaceURI -- namespace of attribute
887             qualifiedName -- qualified name of new attribute value
888             value -- value of attribute
889         '''
890         self.node.setAttributeNS(namespaceURI, qualifiedName, value)
891
892     #############################################
893     #General Methods
894     #############################################
895     def isFault(self):
896         '''check to see if this is a soap:fault message.
897         '''
898         return False
899
900     def getPrefix(self, namespaceURI):
901         try:
902             prefix = self._getPrefix(node=self.node, nsuri=namespaceURI)
903         except NamespaceError, ex:
904             prefix = self._getUniquePrefix() 
905             self.setNamespaceAttribute(prefix, namespaceURI)
906         return prefix
907
908     def getDocument(self):
909         return self._getOwnerDocument()
910
911     def setDocument(self, document):
912         self.node = document
913
914     def importFromString(self, xmlString):
915         doc = self._dom.loadDocument(StringIO(xmlString))
916         node = self._dom.getElement(doc, name=None)
917         clone = self.importNode(node)
918         self._appendChild(clone)
919
920     def importNode(self, node):
921         if isinstance(node, ElementProxy):
922             node = node._getNode()
923         return self._dom.importNode(self._getOwnerDocument(), node, deep=1)
924
925     def loadFromString(self, data):
926         self.node = self._dom.loadDocument(StringIO(data))
927
928     def canonicalize(self):
929         return Canonicalize(self.node)
930
931     def toString(self):
932         return self.canonicalize()
933
934     def createDocument(self, namespaceURI, localName, doctype=None):
935         '''If specified must be a SOAP envelope, else may contruct an empty document.
936         '''
937         prefix = self._soap_env_prefix
938
939         if namespaceURI == self.reserved_ns[prefix]:
940             qualifiedName = '%s:%s' %(prefix,localName)
941         elif namespaceURI is localName is None:
942             self.node = self._dom.createDocument(None,None,None)
943             return
944         else:
945             raise KeyError, 'only support creation of document in %s' %self.reserved_ns[prefix]
946
947         document = self._dom.createDocument(nsuri=namespaceURI, qname=qualifiedName, doctype=doctype)
948         self.node = document.childNodes[0]
949
950         #set up reserved namespace attributes
951         for prefix,nsuri in self.reserved_ns.items():
952             self._setAttributeNS(namespaceURI=self._xmlns_nsuri, 
953                 qualifiedName='%s:%s' %(self._xmlns_prefix,prefix), 
954                 value=nsuri)
955
956     #############################################
957     #Methods for attributes
958     #############################################
959     def hasAttribute(self, namespaceURI, localName):
960         return self._dom.hasAttr(self._getNode(), name=localName, nsuri=namespaceURI)
961
962     def setAttributeType(self, namespaceURI, localName):
963         '''set xsi:type
964         Keyword arguments:
965             namespaceURI -- namespace of attribute value
966             localName -- name of new attribute value
967
968         '''
969         self.logger.debug('setAttributeType: (%s,%s)', namespaceURI, localName)
970         value = localName
971         if namespaceURI:
972             value = '%s:%s' %(self.getPrefix(namespaceURI),localName)
973
974         xsi_prefix = self.getPrefix(self._xsi_nsuri)
975         self._setAttributeNS(self._xsi_nsuri, '%s:type' %xsi_prefix, value)
976
977     def createAttributeNS(self, namespace, name, value):
978         document = self._getOwnerDocument()
979         ##this function doesn't exist!! it has only two arguments
980         attrNode = document.createAttributeNS(namespace, name, value)
981
982     def setAttributeNS(self, namespaceURI, localName, value):
983         '''
984         Keyword arguments:
985             namespaceURI -- namespace of attribute to create, None is for
986                 attributes in no namespace.
987             localName -- local name of new attribute
988             value -- value of new attribute
989         ''' 
990         prefix = None
991         if namespaceURI:
992             try:
993                 prefix = self.getPrefix(namespaceURI)
994             except KeyError, ex:
995                 prefix = 'ns2'
996                 self.setNamespaceAttribute(prefix, namespaceURI)
997         qualifiedName = localName
998         if prefix:
999             qualifiedName = '%s:%s' %(prefix, localName)
1000         self._setAttributeNS(namespaceURI, qualifiedName, value)
1001
1002     def setNamespaceAttribute(self, prefix, namespaceURI):
1003         '''
1004         Keyword arguments:
1005             prefix -- xmlns prefix
1006             namespaceURI -- value of prefix
1007         '''
1008         self._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI)
1009
1010     #############################################
1011     #Methods for elements
1012     #############################################
1013     def createElementNS(self, namespace, qname):
1014         '''
1015         Keyword arguments:
1016             namespace -- namespace of element to create
1017             qname -- qualified name of new element
1018         '''
1019         document = self._getOwnerDocument()
1020         node = document.createElementNS(namespace, qname)
1021         return ElementProxy(self.sw, node)
1022
1023     def createAppendSetElement(self, namespaceURI, localName, prefix=None):
1024         '''Create a new element (namespaceURI,name), append it
1025            to current node, then set it to be the current node.
1026         Keyword arguments:
1027             namespaceURI -- namespace of element to create
1028             localName -- local name of new element
1029             prefix -- if namespaceURI is not defined, declare prefix.  defaults
1030                 to 'ns1' if left unspecified.
1031         '''
1032         node = self.createAppendElement(namespaceURI, localName, prefix=None)
1033         node=node._getNode()
1034         self._setNode(node._getNode())
1035
1036     def createAppendElement(self, namespaceURI, localName, prefix=None):
1037         '''Create a new element (namespaceURI,name), append it
1038            to current node, and return the newly created node.
1039         Keyword arguments:
1040             namespaceURI -- namespace of element to create
1041             localName -- local name of new element
1042             prefix -- if namespaceURI is not defined, declare prefix.  defaults
1043                 to 'ns1' if left unspecified.
1044         '''
1045         declare = False
1046         qualifiedName = localName
1047         if namespaceURI:
1048             try:
1049                 prefix = self.getPrefix(namespaceURI)
1050             except:
1051                 declare = True
1052                 prefix = prefix or self._getUniquePrefix()
1053             if prefix: 
1054                 qualifiedName = '%s:%s' %(prefix, localName)
1055         node = self.createElementNS(namespaceURI, qualifiedName)
1056         if declare:
1057             node._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI)
1058         self._appendChild(node=node._getNode())
1059         return node
1060
1061     def createInsertBefore(self, namespaceURI, localName, refChild):
1062         qualifiedName = localName
1063         prefix = self.getPrefix(namespaceURI)
1064         if prefix: 
1065             qualifiedName = '%s:%s' %(prefix, localName)
1066         node = self.createElementNS(namespaceURI, qualifiedName)
1067         self._insertBefore(newChild=node._getNode(), refChild=refChild._getNode())
1068         return node
1069
1070     def getElement(self, namespaceURI, localName):
1071         '''
1072         Keyword arguments:
1073             namespaceURI -- namespace of element
1074             localName -- local name of element
1075         '''
1076         node = self._dom.getElement(self.node, localName, namespaceURI, default=None)
1077         if node:
1078             return ElementProxy(self.sw, node)
1079         return None
1080
1081     def getAttributeValue(self, namespaceURI, localName):
1082         '''
1083         Keyword arguments:
1084             namespaceURI -- namespace of attribute
1085             localName -- local name of attribute
1086         '''
1087         if self.hasAttribute(namespaceURI, localName):
1088             attr = self.node.getAttributeNodeNS(namespaceURI,localName)
1089             return attr.value
1090         return None
1091
1092     def getValue(self):
1093         return self._dom.getElementText(self.node, preserve_ws=True)    
1094
1095     #############################################
1096     #Methods for text nodes
1097     #############################################
1098     def createAppendTextNode(self, pyobj):
1099         node = self.createTextNode(pyobj)
1100         self._appendChild(node=node._getNode())
1101         return node
1102
1103     def createTextNode(self, pyobj):
1104         document = self._getOwnerDocument()
1105         node = document.createTextNode(pyobj)
1106         return ElementProxy(self.sw, node)
1107
1108     #############################################
1109     #Methods for retrieving namespaceURI's
1110     #############################################
1111     def findNamespaceURI(self, qualifiedName):
1112         parts = SplitQName(qualifiedName)
1113         element = self._getNode()
1114         if len(parts) == 1:
1115             return (self._dom.findTargetNS(element), value)
1116         return self._dom.findNamespaceURI(parts[0], element)
1117
1118     def resolvePrefix(self, prefix):
1119         element = self._getNode()
1120         return self._dom.findNamespaceURI(prefix, element)
1121
1122     def getSOAPEnvURI(self):
1123         return self._soap_env_nsuri
1124
1125     def isEmpty(self):
1126         return not self.node
1127
1128
1129
1130 class Collection(UserDict):
1131     """Helper class for maintaining ordered named collections."""
1132     default = lambda self,k: k.name
1133     def __init__(self, parent, key=None):
1134         UserDict.__init__(self)
1135         self.parent = weakref.ref(parent)
1136         self.list = []
1137         self._func = key or self.default
1138
1139     def __getitem__(self, key):
1140         if type(key) is type(1):
1141             return self.list[key]
1142         return self.data[key]
1143
1144     def __setitem__(self, key, item):
1145         item.parent = weakref.ref(self)
1146         self.list.append(item)
1147         self.data[key] = item
1148
1149     def keys(self):
1150         return map(lambda i: self._func(i), self.list)
1151
1152     def items(self):
1153         return map(lambda i: (self._func(i), i), self.list)
1154
1155     def values(self):
1156         return self.list
1157
1158
1159 class CollectionNS(UserDict):
1160     """Helper class for maintaining ordered named collections."""
1161     default = lambda self,k: k.name
1162     def __init__(self, parent, key=None):
1163         UserDict.__init__(self)
1164         self.parent = weakref.ref(parent)
1165         self.targetNamespace = None
1166         self.list = []
1167         self._func = key or self.default
1168
1169     def __getitem__(self, key):
1170         self.targetNamespace = self.parent().targetNamespace
1171         if type(key) is types.IntType:
1172             return self.list[key]
1173         elif self.__isSequence(key):
1174             nsuri,name = key
1175             return self.data[nsuri][name]
1176         return self.data[self.parent().targetNamespace][key]
1177
1178     def __setitem__(self, key, item):
1179         item.parent = weakref.ref(self)
1180         self.list.append(item)
1181         targetNamespace = getattr(item, 'targetNamespace', self.parent().targetNamespace)
1182         if not self.data.has_key(targetNamespace):
1183             self.data[targetNamespace] = {}
1184         self.data[targetNamespace][key] = item
1185
1186     def __isSequence(self, key):
1187         return (type(key) in (types.TupleType,types.ListType) and len(key) == 2)
1188
1189     def keys(self):
1190         keys = []
1191         for tns in self.data.keys():
1192             keys.append(map(lambda i: (tns,self._func(i)), self.data[tns].values()))
1193         return keys
1194
1195     def items(self):
1196         return map(lambda i: (self._func(i), i), self.list)
1197
1198     def values(self):
1199         return self.list
1200
1201
1202
1203 # This is a runtime guerilla patch for pulldom (used by minidom) so
1204 # that xml namespace declaration attributes are not lost in parsing.
1205 # We need them to do correct QName linking for XML Schema and WSDL.
1206 # The patch has been submitted to SF for the next Python version.
1207
1208 from xml.dom.pulldom import PullDOM, START_ELEMENT
1209 if 1:
1210     def startPrefixMapping(self, prefix, uri):
1211         if not hasattr(self, '_xmlns_attrs'):
1212             self._xmlns_attrs = []
1213         self._xmlns_attrs.append((prefix or 'xmlns', uri))
1214         self._ns_contexts.append(self._current_context.copy())
1215         self._current_context[uri] = prefix or ''
1216
1217     PullDOM.startPrefixMapping = startPrefixMapping
1218
1219     def startElementNS(self, name, tagName , attrs):
1220         # Retrieve xml namespace declaration attributes.
1221         xmlns_uri = 'http://www.w3.org/2000/xmlns/'
1222         xmlns_attrs = getattr(self, '_xmlns_attrs', None)
1223         if xmlns_attrs is not None:
1224             for aname, value in xmlns_attrs:
1225                 attrs._attrs[(xmlns_uri, aname)] = value
1226             self._xmlns_attrs = []
1227         uri, localname = name
1228         if uri:
1229             # When using namespaces, the reader may or may not
1230             # provide us with the original name. If not, create
1231             # *a* valid tagName from the current context.
1232             if tagName is None:
1233                 prefix = self._current_context[uri]
1234                 if prefix:
1235                     tagName = prefix + ":" + localname
1236                 else:
1237                     tagName = localname
1238             if self.document:
1239                 node = self.document.createElementNS(uri, tagName)
1240             else:
1241                 node = self.buildDocument(uri, tagName)
1242         else:
1243             # When the tagname is not prefixed, it just appears as
1244             # localname
1245             if self.document:
1246                 node = self.document.createElement(localname)
1247             else:
1248                 node = self.buildDocument(None, localname)
1249
1250         for aname,value in attrs.items():
1251             a_uri, a_localname = aname
1252             if a_uri == xmlns_uri:
1253                 if a_localname == 'xmlns':
1254                     qname = a_localname
1255                 else:
1256                     qname = 'xmlns:' + a_localname
1257                 attr = self.document.createAttributeNS(a_uri, qname)
1258                 node.setAttributeNodeNS(attr)
1259             elif a_uri:
1260                 prefix = self._current_context[a_uri]
1261                 if prefix:
1262                     qname = prefix + ":" + a_localname
1263                 else:
1264                     qname = a_localname
1265                 attr = self.document.createAttributeNS(a_uri, qname)
1266                 node.setAttributeNodeNS(attr)
1267             else:
1268                 attr = self.document.createAttribute(a_localname)
1269                 node.setAttributeNode(attr)
1270             attr.value = value
1271
1272         self.lastEvent[1] = [(START_ELEMENT, node), None]
1273         self.lastEvent = self.lastEvent[1]
1274         self.push(node)
1275
1276     PullDOM.startElementNS = startElementNS
1277
1278 #
1279 # This is a runtime guerilla patch for minidom so
1280 # that xmlns prefixed attributes dont raise AttributeErrors
1281 # during cloning.
1282 #
1283 # Namespace declarations can appear in any start-tag, must look for xmlns
1284 # prefixed attribute names during cloning.
1285 #
1286 # key (attr.namespaceURI, tag)
1287 # ('http://www.w3.org/2000/xmlns/', u'xsd')   <xml.dom.minidom.Attr instance at 0x82227c4>
1288 # ('http://www.w3.org/2000/xmlns/', 'xmlns')   <xml.dom.minidom.Attr instance at 0x8414b3c>
1289 #
1290 # xml.dom.minidom.Attr.nodeName = xmlns:xsd
1291 # xml.dom.minidom.Attr.value =  = http://www.w3.org/2001/XMLSchema 
1292
1293 if 1:
1294     def _clone_node(node, deep, newOwnerDocument):
1295         """
1296         Clone a node and give it the new owner document.
1297         Called by Node.cloneNode and Document.importNode
1298         """
1299         if node.ownerDocument.isSameNode(newOwnerDocument):
1300             operation = xml.dom.UserDataHandler.NODE_CLONED
1301         else:
1302             operation = xml.dom.UserDataHandler.NODE_IMPORTED
1303         if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
1304             clone = newOwnerDocument.createElementNS(node.namespaceURI,
1305                                                      node.nodeName)
1306             for attr in node.attributes.values():
1307                 clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
1308
1309                 prefix, tag = xml.dom.minidom._nssplit(attr.nodeName)
1310                 if prefix == 'xmlns':
1311                     a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
1312                 elif prefix:
1313                     a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
1314                 else:
1315                     a = clone.getAttributeNodeNS(attr.namespaceURI, attr.nodeName)
1316                 a.specified = attr.specified
1317
1318             if deep:
1319                 for child in node.childNodes:
1320                     c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
1321                     clone.appendChild(c)
1322         elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_FRAGMENT_NODE:
1323             clone = newOwnerDocument.createDocumentFragment()
1324             if deep:
1325                 for child in node.childNodes:
1326                     c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
1327                     clone.appendChild(c)
1328
1329         elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
1330             clone = newOwnerDocument.createTextNode(node.data)
1331         elif node.nodeType == xml.dom.minidom.Node.CDATA_SECTION_NODE:
1332             clone = newOwnerDocument.createCDATASection(node.data)
1333         elif node.nodeType == xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE:
1334             clone = newOwnerDocument.createProcessingInstruction(node.target,
1335                                                                  node.data)
1336         elif node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
1337             clone = newOwnerDocument.createComment(node.data)
1338         elif node.nodeType == xml.dom.minidom.Node.ATTRIBUTE_NODE:
1339             clone = newOwnerDocument.createAttributeNS(node.namespaceURI,
1340                                                        node.nodeName)
1341             clone.specified = True
1342             clone.value = node.value
1343         elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_TYPE_NODE:
1344             assert node.ownerDocument is not newOwnerDocument
1345             operation = xml.dom.UserDataHandler.NODE_IMPORTED
1346             clone = newOwnerDocument.implementation.createDocumentType(
1347                 node.name, node.publicId, node.systemId)
1348             clone.ownerDocument = newOwnerDocument
1349             if deep:
1350                 clone.entities._seq = []
1351                 clone.notations._seq = []
1352                 for n in node.notations._seq:
1353                     notation = xml.dom.minidom.Notation(n.nodeName, n.publicId, n.systemId)
1354                     notation.ownerDocument = newOwnerDocument
1355                     clone.notations._seq.append(notation)
1356                     if hasattr(n, '_call_user_data_handler'):
1357                         n._call_user_data_handler(operation, n, notation)
1358                 for e in node.entities._seq:
1359                     entity = xml.dom.minidom.Entity(e.nodeName, e.publicId, e.systemId,
1360                                     e.notationName)
1361                     entity.actualEncoding = e.actualEncoding
1362                     entity.encoding = e.encoding
1363                     entity.version = e.version
1364                     entity.ownerDocument = newOwnerDocument
1365                     clone.entities._seq.append(entity)
1366                     if hasattr(e, '_call_user_data_handler'):
1367                         e._call_user_data_handler(operation, n, entity)
1368         else:
1369             # Note the cloning of Document and DocumentType nodes is
1370             # implemenetation specific.  minidom handles those cases
1371             # directly in the cloneNode() methods.
1372             raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node))
1373
1374         # Check for _call_user_data_handler() since this could conceivably
1375         # used with other DOM implementations (one of the FourThought
1376         # DOMs, perhaps?).
1377         if hasattr(node, '_call_user_data_handler'):
1378             node._call_user_data_handler(operation, node, clone)
1379         return clone
1380
1381     xml.dom.minidom._clone_node = _clone_node
1382