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
6 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
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.
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
27 from ZSI import _get_idstr
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.'''
38 import xml.dom.minidom
39 from xml.dom import Node
42 from c14n import Canonicalize
43 from Namespaces import SCHEMA, SOAP, XMLNS, ZSI_SCHEMA_URI
47 from xml.dom.ext import SplitQName
49 def SplitQName(qname):
50 '''SplitQName(qname) -> (string, string)
52 Split Qualified Name into a tuple of len 2, consisting
53 of the prefix and the local name.
58 xmlns -- (localName, 'xmlns')
59 None -- (None, localName)
73 # python2.3 urllib.basejoin does not remove current directory ./
74 # from path and this causes problems on subsequent basejoins.
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('./'))
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)
85 class NamespaceError(Exception):
86 """Used to indicate a Namespace Error."""
89 class RecursionError(Exception):
90 """Used to indicate a HTTP redirect recursion."""
93 class ParseError(Exception):
94 """Used to indicate a XML parsing error."""
97 class DOMException(Exception):
98 """Used to indicate a problem processing DOM."""
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)))
108 """Captures the information in an HTTP response message."""
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
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
124 self.sock = TimeoutSocket(self.timeout)
125 self.sock.connect((self.host, self.port))
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
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)
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)
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)
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
165 if not hasattr(socket, 'ssl'):
166 raise RuntimeError, 'no built-in SSL Support'
168 conn = TimeoutHTTPS(host, None, timeout)
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)
176 conn = TimeoutHTTP(host, None, timeout)
178 conn.putrequest('GET', path)
179 conn.putheader('Connection', 'close')
183 response = conn.getresponse()
184 if response.status != 100:
186 conn._HTTPConnection__state = httplib._CS_REQ_SENT
187 conn._HTTPConnection__response = None
189 status = response.status
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:
196 if redirects is not None and redirects.has_key(location):
197 raise RecursionError(
198 'Circular HTTP redirection detected.'
200 if redirects is None:
202 redirects[location] = 1
203 return urlopen(location, timeout, redirects)
204 raise HTTPResponse(response)
206 if not (status >= 200 and status < 300):
207 raise HTTPResponse(response)
209 body = StringIO(response.read())
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."""
219 # Namespace stuff related to the SOAP specification.
221 NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'
222 NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/'
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'
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)
230 NS_SOAP_ENV = NS_SOAP_ENV_1_1
231 NS_SOAP_ENC = NS_SOAP_ENC_1_1
233 _soap_uri_mapping = {
234 NS_SOAP_ENV_1_1 : '1.1',
235 NS_SOAP_ENV_1_2 : '1.2',
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)
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:
248 'Unsupported SOAP envelope uri: %s' % uri
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:
259 'Unsupported SOAP version: %s' % version
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:
270 'Unsupported SOAP version: %s' % version
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:
281 'Unsupported SOAP version: %s' % version
285 # Namespace stuff related to XML Schema.
287 NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema'
288 NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance'
290 NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema'
291 NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance'
293 NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema'
294 NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance'
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)
303 NS_XSD_99 : NS_XSI_99,
304 NS_XSD_00 : NS_XSI_00,
305 NS_XSD_01 : NS_XSI_01,
308 for key, value in _xsd_uri_mapping.items():
309 _xsd_uri_mapping[value] = key
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)
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)
323 # Namespace stuff related to WSDL.
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
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/'
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,)
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
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
346 _wsdl_uri_mapping = {
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:
356 'Unsupported SOAP envelope uri: %s' % uri
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:
365 'Unsupported WSDL version: %s' % version
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:
374 'Unsupported WSDL version: %s' % version
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:
383 'Unsupported WSDL version: %s' % version
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:
392 'Unsupported WSDL version: %s' % version
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:
401 'Unsupported WSDL version: %s' % version
405 # Other xml namespace constants.
406 NS_XMLNS = 'http://www.w3.org/2000/xmlns/'
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:
415 return node.localName == name and \
416 (nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri))
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))
429 if default is not join:
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:
441 if default is not join:
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
453 attr = element._attrs.get('id', None)
455 mapping[attr.value] = element
456 if depth is None or depth > level:
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)
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
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))):
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."""
482 if node.hasAttribute(name):
485 return node.hasAttributeNS(nsuri, name)
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."""
493 result = node._attrs.get(name, None)
495 for item in node._attrsNS.keys():
497 result = node._attrsNS[item]
500 result = node._attrsNS.get((nsuri, name), None)
501 if result is not None:
503 if default is not join:
507 def getAttrs(self, node):
508 """Return a Collection of all attributes
511 for k,v in node._attrs.items():
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."""
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:
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
537 raise DOMException('Value for prefix %s not found.' % prefix)
538 if node.nodeType != ELEMENT_NODE:
539 node = node.parentNode
541 result = node._attrsNS.get(attrkey, None)
542 if result is not None:
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)
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
556 if node.nodeType != ELEMENT_NODE:
557 node = node.parentNode
559 result = node._attrsNS.get(attrkey, None)
560 if result is not None:
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.')
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
575 if node.nodeType != ELEMENT_NODE:
576 node = node.parentNode
578 result = attrget(node, 'targetNamespace', default=None)
579 if result is not None:
581 node = node.parentNode
582 if node.nodeType == DOCUMENT_NODE:
583 raise DOMException('Cannot determine target namespace.')
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)
591 parts = typeattr.split(':', 1)
593 nsuri = self.findNamespaceURI(parts[0], element)
595 nsuri = self.findDefaultNS(element)
596 return (nsuri, parts[1])
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:
605 clone = node.cloneNode(deep)
606 self._setOwnerDoc(document, clone)
607 clone.__imported__ = 1
610 def _setOwnerDoc(self, document, node):
611 node.ownerDocument = document
612 for child in node.childNodes:
613 self._setOwnerDoc(document, child)
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:
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]
623 if item == value or item[:-1] == value:
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)
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)
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')
645 result = self.loadDocument(file)
646 except Exception, ex:
648 raise ParseError(('Failed to load document %s' %url,) + ex.args)
656 class MessageInterface:
657 '''Higher Level Interface, delegates to DOM singleton, must
658 be subclassed and implement all methods that throw NotImplementedError.
660 def __init__(self, sw):
661 '''Constructor, May be extended, do not override.
662 sw -- soapWriter instance
665 if type(sw) != weakref.ReferenceType and sw is not None:
666 self.sw = weakref.ref(sw)
670 def AddCallback(self, func, *arglist):
671 self.sw().AddCallback(func, *arglist)
673 def Known(self, obj):
674 return self.sw().Known(obj)
676 def Forget(self, obj):
677 return self.sw().Forget(obj)
679 def canonicalize(self):
680 '''canonicalize the underlying DOM, and return as string.
682 raise NotImplementedError, ''
684 def createDocument(self, namespaceURI=SOAP.ENV, localName='Envelope'):
687 raise NotImplementedError, ''
689 def createAppendElement(self, namespaceURI, localName):
690 '''create and append element(namespaceURI,localName), and return
693 raise NotImplementedError, ''
695 def findNamespaceURI(self, qualifiedName):
696 raise NotImplementedError, ''
698 def resolvePrefix(self, prefix):
699 raise NotImplementedError, ''
701 def setAttributeNS(self, namespaceURI, localName, value):
702 '''set attribute (namespaceURI, localName)=value
704 raise NotImplementedError, ''
706 def setAttributeType(self, namespaceURI, localName):
707 '''set attribute xsi:type=(namespaceURI, localName)
709 raise NotImplementedError, ''
711 def setNamespaceAttribute(self, namespaceURI, prefix):
712 '''set namespace attribute xmlns:prefix=namespaceURI
714 raise NotImplementedError, ''
717 class ElementProxy(Base, MessageInterface):
720 _soap_env_prefix = 'SOAP-ENV'
721 _soap_enc_prefix = 'SOAP-ENC'
726 _xmlns_prefix = 'xmlns'
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
737 _xml_prefix:_xml_nsuri,
738 _xmlns_prefix:_xmlns_nsuri
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,
750 def __init__(self, sw, message=None):
755 MessageInterface.__init__(self, sw)
759 if type(message) in (types.StringType,types.UnicodeType):
760 self.loadFromString(message)
761 elif isinstance(message, ElementProxy):
762 self.node = message._getNode()
765 self.processorNss = self.standard_ns.copy()
766 self.processorNss.update(self.reserved_ns)
769 return self.toString()
771 def evaluate(self, expression, processorNss=None):
772 '''expression -- XPath compiled expression
774 from Ft.Xml import XPath
776 context = XPath.Context.Context(self.node, processorNss=self.processorNss)
778 context = XPath.Context.Context(self.node, processorNss=processorNss)
779 nodes = expression.evaluate(context)
780 return map(lambda node: ElementProxy(self.sw,node), nodes)
782 #############################################
783 # Methods for checking/setting the
784 # classes (namespaceURI,name) node.
785 #############################################
786 def checkNode(self, namespaceURI=None, localName=None):
788 namespaceURI -- namespace of element
789 localName -- local name of element
791 namespaceURI = namespaceURI or self.namespaceURI
792 localName = localName or self.name
794 if localName and self.node:
795 check = self._dom.isElement(self.node, localName, namespaceURI)
797 raise NamespaceError, 'unexpected node type %s, expecting %s' %(self.node, localName)
799 def setNode(self, node=None):
801 if isinstance(node, ElementProxy):
802 self.node = node._getNode()
806 node = self._dom.getElement(self.node, self.name, self.namespaceURI, default=None)
808 raise NamespaceError, 'cant find element (%s,%s)' %(self.namespaceURI,self.name)
811 #self.node = self._dom.create(self.node, self.name, self.namespaceURI, default=None)
812 self.createDocument(self.namespaceURI, localName=self.name, doctype=None)
816 #############################################
817 # Wrapper Methods for direct DOM Element Node access
818 #############################################
822 def _getElements(self):
823 return self._dom.getElements(self.node, name=None)
825 def _getOwnerDocument(self):
826 return self.node.ownerDocument or self.node
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.
835 prefix = 'ns%d' %self._indx
837 self._dom.findNamespaceURI(prefix, self._getNode())
838 except DOMException, ex:
842 def _getPrefix(self, node, nsuri):
845 node -- DOM Element Node
846 nsuri -- namespace of attribute value
849 if node and (node.nodeType == node.ELEMENT_NODE) and \
850 (nsuri == self._dom.findDefaultNS(node)):
852 except DOMException, ex:
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
863 return self._getPrefix(node.parentNode, nsuri)
864 raise NamespaceError, 'namespaceURI "%s" is not defined' %nsuri
866 def _appendChild(self, node):
869 node -- DOM Element Node
872 raise TypeError, 'node is None'
873 self.node.appendChild(node)
875 def _insertBefore(self, newChild, refChild):
878 child -- DOM Element Node to insert
879 refChild -- DOM Element Node
881 self.node.insertBefore(newChild, refChild)
883 def _setAttributeNS(self, namespaceURI, qualifiedName, value):
886 namespaceURI -- namespace of attribute
887 qualifiedName -- qualified name of new attribute value
888 value -- value of attribute
890 self.node.setAttributeNS(namespaceURI, qualifiedName, value)
892 #############################################
894 #############################################
896 '''check to see if this is a soap:fault message.
900 def getPrefix(self, namespaceURI):
902 prefix = self._getPrefix(node=self.node, nsuri=namespaceURI)
903 except NamespaceError, ex:
904 prefix = self._getUniquePrefix()
905 self.setNamespaceAttribute(prefix, namespaceURI)
908 def getDocument(self):
909 return self._getOwnerDocument()
911 def setDocument(self, document):
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)
920 def importNode(self, node):
921 if isinstance(node, ElementProxy):
922 node = node._getNode()
923 return self._dom.importNode(self._getOwnerDocument(), node, deep=1)
925 def loadFromString(self, data):
926 self.node = self._dom.loadDocument(StringIO(data))
928 def canonicalize(self):
929 return Canonicalize(self.node)
932 return self.canonicalize()
934 def createDocument(self, namespaceURI, localName, doctype=None):
935 '''If specified must be a SOAP envelope, else may contruct an empty document.
937 prefix = self._soap_env_prefix
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)
945 raise KeyError, 'only support creation of document in %s' %self.reserved_ns[prefix]
947 document = self._dom.createDocument(nsuri=namespaceURI, qname=qualifiedName, doctype=doctype)
948 self.node = document.childNodes[0]
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),
956 #############################################
957 #Methods for attributes
958 #############################################
959 def hasAttribute(self, namespaceURI, localName):
960 return self._dom.hasAttr(self._getNode(), name=localName, nsuri=namespaceURI)
962 def setAttributeType(self, namespaceURI, localName):
965 namespaceURI -- namespace of attribute value
966 localName -- name of new attribute value
969 self.logger.debug('setAttributeType: (%s,%s)', namespaceURI, localName)
972 value = '%s:%s' %(self.getPrefix(namespaceURI),localName)
974 xsi_prefix = self.getPrefix(self._xsi_nsuri)
975 self._setAttributeNS(self._xsi_nsuri, '%s:type' %xsi_prefix, value)
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)
982 def setAttributeNS(self, namespaceURI, localName, value):
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
993 prefix = self.getPrefix(namespaceURI)
996 self.setNamespaceAttribute(prefix, namespaceURI)
997 qualifiedName = localName
999 qualifiedName = '%s:%s' %(prefix, localName)
1000 self._setAttributeNS(namespaceURI, qualifiedName, value)
1002 def setNamespaceAttribute(self, prefix, namespaceURI):
1005 prefix -- xmlns prefix
1006 namespaceURI -- value of prefix
1008 self._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI)
1010 #############################################
1011 #Methods for elements
1012 #############################################
1013 def createElementNS(self, namespace, qname):
1016 namespace -- namespace of element to create
1017 qname -- qualified name of new element
1019 document = self._getOwnerDocument()
1020 node = document.createElementNS(namespace, qname)
1021 return ElementProxy(self.sw, node)
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.
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.
1032 node = self.createAppendElement(namespaceURI, localName, prefix=None)
1033 node=node._getNode()
1034 self._setNode(node._getNode())
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.
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.
1046 qualifiedName = localName
1049 prefix = self.getPrefix(namespaceURI)
1052 prefix = prefix or self._getUniquePrefix()
1054 qualifiedName = '%s:%s' %(prefix, localName)
1055 node = self.createElementNS(namespaceURI, qualifiedName)
1057 node._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI)
1058 self._appendChild(node=node._getNode())
1061 def createInsertBefore(self, namespaceURI, localName, refChild):
1062 qualifiedName = localName
1063 prefix = self.getPrefix(namespaceURI)
1065 qualifiedName = '%s:%s' %(prefix, localName)
1066 node = self.createElementNS(namespaceURI, qualifiedName)
1067 self._insertBefore(newChild=node._getNode(), refChild=refChild._getNode())
1070 def getElement(self, namespaceURI, localName):
1073 namespaceURI -- namespace of element
1074 localName -- local name of element
1076 node = self._dom.getElement(self.node, localName, namespaceURI, default=None)
1078 return ElementProxy(self.sw, node)
1081 def getAttributeValue(self, namespaceURI, localName):
1084 namespaceURI -- namespace of attribute
1085 localName -- local name of attribute
1087 if self.hasAttribute(namespaceURI, localName):
1088 attr = self.node.getAttributeNodeNS(namespaceURI,localName)
1093 return self._dom.getElementText(self.node, preserve_ws=True)
1095 #############################################
1096 #Methods for text nodes
1097 #############################################
1098 def createAppendTextNode(self, pyobj):
1099 node = self.createTextNode(pyobj)
1100 self._appendChild(node=node._getNode())
1103 def createTextNode(self, pyobj):
1104 document = self._getOwnerDocument()
1105 node = document.createTextNode(pyobj)
1106 return ElementProxy(self.sw, node)
1108 #############################################
1109 #Methods for retrieving namespaceURI's
1110 #############################################
1111 def findNamespaceURI(self, qualifiedName):
1112 parts = SplitQName(qualifiedName)
1113 element = self._getNode()
1115 return (self._dom.findTargetNS(element), value)
1116 return self._dom.findNamespaceURI(parts[0], element)
1118 def resolvePrefix(self, prefix):
1119 element = self._getNode()
1120 return self._dom.findNamespaceURI(prefix, element)
1122 def getSOAPEnvURI(self):
1123 return self._soap_env_nsuri
1126 return not self.node
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)
1137 self._func = key or self.default
1139 def __getitem__(self, key):
1140 if type(key) is type(1):
1141 return self.list[key]
1142 return self.data[key]
1144 def __setitem__(self, key, item):
1145 item.parent = weakref.ref(self)
1146 self.list.append(item)
1147 self.data[key] = item
1150 return map(lambda i: self._func(i), self.list)
1153 return map(lambda i: (self._func(i), i), self.list)
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
1167 self._func = key or self.default
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):
1175 return self.data[nsuri][name]
1176 return self.data[self.parent().targetNamespace][key]
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
1186 def __isSequence(self, key):
1187 return (type(key) in (types.TupleType,types.ListType) and len(key) == 2)
1191 for tns in self.data.keys():
1192 keys.append(map(lambda i: (tns,self._func(i)), self.data[tns].values()))
1196 return map(lambda i: (self._func(i), i), self.list)
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.
1208 from xml.dom.pulldom import PullDOM, START_ELEMENT
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 ''
1217 PullDOM.startPrefixMapping = startPrefixMapping
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
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.
1233 prefix = self._current_context[uri]
1235 tagName = prefix + ":" + localname
1239 node = self.document.createElementNS(uri, tagName)
1241 node = self.buildDocument(uri, tagName)
1243 # When the tagname is not prefixed, it just appears as
1246 node = self.document.createElement(localname)
1248 node = self.buildDocument(None, localname)
1250 for aname,value in attrs.items():
1251 a_uri, a_localname = aname
1252 if a_uri == xmlns_uri:
1253 if a_localname == 'xmlns':
1256 qname = 'xmlns:' + a_localname
1257 attr = self.document.createAttributeNS(a_uri, qname)
1258 node.setAttributeNodeNS(attr)
1260 prefix = self._current_context[a_uri]
1262 qname = prefix + ":" + a_localname
1265 attr = self.document.createAttributeNS(a_uri, qname)
1266 node.setAttributeNodeNS(attr)
1268 attr = self.document.createAttribute(a_localname)
1269 node.setAttributeNode(attr)
1272 self.lastEvent[1] = [(START_ELEMENT, node), None]
1273 self.lastEvent = self.lastEvent[1]
1276 PullDOM.startElementNS = startElementNS
1279 # This is a runtime guerilla patch for minidom so
1280 # that xmlns prefixed attributes dont raise AttributeErrors
1283 # Namespace declarations can appear in any start-tag, must look for xmlns
1284 # prefixed attribute names during cloning.
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>
1290 # xml.dom.minidom.Attr.nodeName = xmlns:xsd
1291 # xml.dom.minidom.Attr.value = = http://www.w3.org/2001/XMLSchema
1294 def _clone_node(node, deep, newOwnerDocument):
1296 Clone a node and give it the new owner document.
1297 Called by Node.cloneNode and Document.importNode
1299 if node.ownerDocument.isSameNode(newOwnerDocument):
1300 operation = xml.dom.UserDataHandler.NODE_CLONED
1302 operation = xml.dom.UserDataHandler.NODE_IMPORTED
1303 if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
1304 clone = newOwnerDocument.createElementNS(node.namespaceURI,
1306 for attr in node.attributes.values():
1307 clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
1309 prefix, tag = xml.dom.minidom._nssplit(attr.nodeName)
1310 if prefix == 'xmlns':
1311 a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
1313 a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
1315 a = clone.getAttributeNodeNS(attr.namespaceURI, attr.nodeName)
1316 a.specified = attr.specified
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()
1325 for child in node.childNodes:
1326 c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
1327 clone.appendChild(c)
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,
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,
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
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,
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)
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))
1374 # Check for _call_user_data_handler() since this could conceivably
1375 # used with other DOM implementations (one of the FourThought
1377 if hasattr(node, '_call_user_data_handler'):
1378 node._call_user_data_handler(operation, node, clone)
1381 xml.dom.minidom._clone_node = _clone_node