1 #!/usr/bin/env python 2 3 """ 4 SOAP support using libxml2dom. 5 6 See: http://www.w3.org/TR/2007/REC-soap12-part0-20070427/ 7 8 Copyright (C) 2007 Paul Boddie <paul@boddie.org.uk> 9 10 This program is free software; you can redistribute it and/or modify it under 11 the terms of the GNU Lesser General Public License as published by the Free 12 Software Foundation; either version 3 of the License, or (at your option) any 13 later version. 14 15 This program is distributed in the hope that it will be useful, but WITHOUT 16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 18 details. 19 20 You should have received a copy of the GNU Lesser General Public License along 21 with this program. If not, see <http://www.gnu.org/licenses/>. 22 23 -------- 24 25 The sending and receiving of SOAP messages can be done using traditional HTTP 26 libraries. 27 28 See tests/soap_test.py for more details. 29 """ 30 31 import libxml2dom 32 from libxml2dom.macrolib import * 33 from libxml2dom.macrolib import \ 34 createDocument as Node_createDocument 35 36 # SOAP-related namespaces. 37 38 SOAP_ENVELOPE_NAMESPACE = "http://www.w3.org/2003/05/soap-envelope" 39 SOAP_ENCODING_NAMESPACE = "http://www.w3.org/2003/05/soap-encoding" 40 SOAP_RPC_NAMESPACE = "http://www.w3.org/2003/05/soap-rpc" 41 XS_NAMESPACE = "http://www.w3.org/2001/XMLSchema" 42 XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance" 43 44 # Default namespace bindings for XPath. 45 46 default_ns = { 47 "env" : SOAP_ENVELOPE_NAMESPACE, 48 "enc" : SOAP_ENCODING_NAMESPACE, 49 "rpc" : SOAP_RPC_NAMESPACE, 50 "xs" : XS_NAMESPACE, 51 "xsi" : XSI_NAMESPACE 52 } 53 54 class SOAPImplementation(libxml2dom.Implementation): 55 56 "Contains a SOAP-specific implementation." 57 58 # Wrapping of documents. 59 60 def adoptDocument(self, node): 61 return SOAPDocument(node, self) 62 63 # Factory functions. 64 65 def get_node(self, _node, context_node): 66 67 """ 68 Get a libxml2dom node for the given low-level '_node' and libxml2dom 69 'context_node'. 70 """ 71 72 if Node_nodeType(_node) == context_node.ELEMENT_NODE: 73 74 # Make special envelope elements. 75 76 if Node_namespaceURI(_node) == SOAP_ENVELOPE_NAMESPACE: 77 if Node_localName(_node) == "Envelope": 78 return SOAPEnvelopeElement(_node, self, context_node.ownerDocument) 79 elif Node_localName(_node) == "Header": 80 return SOAPHeaderElement(_node, self, context_node.ownerDocument) 81 elif Node_localName(_node) == "Body": 82 return SOAPBodyElement(_node, self, context_node.ownerDocument) 83 elif Node_localName(_node) == "Fault": 84 return SOAPFaultElement(_node, self, context_node.ownerDocument) 85 elif Node_localName(_node) == "Code": 86 return SOAPCodeElement(_node, self, context_node.ownerDocument) 87 elif Node_localName(_node) == "Subcode": 88 return SOAPSubcodeElement(_node, self, context_node.ownerDocument) 89 elif Node_localName(_node) == "Value": 90 return SOAPValueElement(_node, self, context_node.ownerDocument) 91 elif Node_localName(_node) == "Text": 92 return SOAPTextElement(_node, self, context_node.ownerDocument) 93 94 # Otherwise, make generic SOAP elements. 95 96 return SOAPElement(_node, self, context_node.ownerDocument) 97 98 else: 99 return libxml2dom.Implementation.get_node(self, _node, context_node) 100 101 # Convenience functions. 102 103 def createSOAPMessage(self, namespaceURI, localName): 104 105 "Create a new SOAP message document (fragment)." 106 107 return SOAPDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement 108 109 # Node classes. 110 111 class SOAPNode(libxml2dom.Node): 112 113 "Convenience modifications to nodes specific to libxml2dom.soap." 114 115 def xpath(self, expr, variables=None, namespaces=None): 116 117 """ 118 Evaluate the given 'expr' using the optional 'variables' and 119 'namespaces'. If not otherwise specified, the prefixes given in the 120 module global 'default_ns' will be bound as in that dictionary. 121 """ 122 123 ns = {} 124 ns.update(default_ns) 125 ns.update(namespaces or {}) 126 return libxml2dom.Node.xpath(self, expr, variables, ns) 127 128 class SOAPDocument(libxml2dom._Document, SOAPNode): 129 130 "A SOAP document fragment." 131 132 def _envelope(self): 133 return self.xpath("./env:Envelope")[0] 134 135 envelope = property(_envelope) 136 137 class SOAPElement(SOAPNode): 138 139 "A SOAP element." 140 141 pass 142 143 class SOAPEnvelopeElement(SOAPNode): 144 145 "A SOAP envelope element." 146 147 def _body(self): 148 return self.xpath("./env:Body")[0] 149 150 def _setBody(self, body): 151 self.appendChild(body) 152 153 def _delBody(self): 154 self.removeChild(self.body) 155 156 def createBody(self): 157 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Body") 158 159 body = property(_body, _setBody, _delBody) 160 161 class SOAPHeaderElement(SOAPNode): 162 163 "A SOAP header element." 164 165 pass 166 167 class SOAPBodyElement(SOAPNode): 168 169 "A SOAP body element." 170 171 def _fault(self): 172 return self.xpath("./env:Fault")[0] 173 174 def _method(self): 175 return self.xpath("./*[@env:encodingStyle = '%s']" % SOAP_ENCODING_NAMESPACE)[0] 176 177 def _methodName(self): 178 return self.method.localName 179 180 def _resultParameter(self): 181 return self.method.xpath(".//rpc:result")[0] 182 183 def _resultParameterValue(self): 184 name = self.resultParameter.textContent.strip() 185 return self.method.xpath(".//%s" % name, namespaces={self.method.prefix : self.method.namespaceURI})[0].textContent.strip() 186 187 def _parameters(self): 188 return self.method.xpath(".//*[not(./*)]") 189 190 def _parameterValues(self): 191 values = {} 192 for parameter in self.parameters: 193 values[(parameter.namespaceURI, parameter.localName)] = parameter.textContent.strip() 194 return values 195 196 def createFault(self): 197 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Fault") 198 199 fault = property(_fault) 200 method = property(_method) 201 methodName = property(_methodName) 202 resultParameter = property(_resultParameter) 203 resultParameterValue = property(_resultParameterValue) 204 parameters = property(_parameters) 205 parameterValues = property(_parameterValues) 206 207 class SOAPFaultElement(SOAPNode): 208 209 "A SOAP fault element." 210 211 def _code(self): 212 return self.xpath("./env:Code")[0] 213 214 def _reason(self): 215 return self.xpath("./env:Reason")[0] 216 217 def _detail(self): 218 return self.xpath("./env:Detail")[0] 219 220 def createCode(self): 221 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Code") 222 223 code = property(_code) 224 reason = property(_reason) 225 detail = property(_detail) 226 227 class SOAPSubcodeElement(SOAPNode): 228 229 "A SOAP subcode element." 230 231 def _value(self): 232 return self.xpath("./env:Value")[0] 233 234 def createValue(self, value=None): 235 code_value = self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Value") 236 if value is not None: 237 code_value.value = code 238 return code_value 239 240 value = property(_value) 241 242 class SOAPCodeElement(SOAPSubcodeElement): 243 244 "A SOAP code element." 245 246 def _subcode(self): 247 return self.xpath("./env:Subcode")[0] 248 249 def createSubcode(self): 250 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Subcode") 251 252 subcode = property(_subcode) 253 254 class SOAPValueElement(SOAPNode): 255 256 "A SOAP value element." 257 258 def _value(self): 259 return self.textContent 260 261 def _setValue(self, value): 262 for node in self.childNodes: 263 self.removeChild(node) 264 text = self.ownerDocument.createTextNode(value) 265 self.appendChild(text) 266 267 value = property(_value, _setValue) 268 269 class SOAPTextElement(SOAPValueElement): 270 271 "A SOAP text element." 272 273 def _lang(self): 274 return self.getAttributeNS(libxml2dom.XML_NAMESPACE, "lang") 275 276 def _setLang(self, value): 277 self.setAttributeNS(libxml2dom.XML_NAMESPACE, "xml:lang", value) 278 279 lang = property(_lang, _setLang) 280 281 # Utility functions. 282 283 createDocument = libxml2dom.createDocument 284 createDocumentType = libxml2dom.createDocumentType 285 286 def createSOAPMessage(namespaceURI, localName): 287 return default_impl.createSOAPMessage(namespaceURI, localName) 288 289 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 290 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 291 292 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 293 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 294 295 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 296 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 297 298 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 299 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 300 301 # Single instance of the implementation. 302 303 default_impl = SOAPImplementation() 304 305 # vim: tabstop=4 expandtab shiftwidth=4