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 from libxml2dom.rpc import ParameterName, ParameterValue 36 37 # SOAP-related namespaces. 38 39 SOAP_ENVELOPE_NAMESPACE = "http://www.w3.org/2003/05/soap-envelope" 40 SOAP_ENCODING_NAMESPACE = "http://www.w3.org/2003/05/soap-encoding" 41 SOAP_RPC_NAMESPACE = "http://www.w3.org/2003/05/soap-rpc" 42 XS_NAMESPACE = "http://www.w3.org/2001/XMLSchema" 43 XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance" 44 45 # Default namespace bindings for XPath. 46 47 default_ns = { 48 "env" : SOAP_ENVELOPE_NAMESPACE, 49 "enc" : SOAP_ENCODING_NAMESPACE, 50 "rpc" : SOAP_RPC_NAMESPACE, 51 "xs" : XS_NAMESPACE, 52 "xsi" : XSI_NAMESPACE 53 } 54 55 class SOAPImplementation(libxml2dom.Implementation): 56 57 "Contains a SOAP-specific implementation." 58 59 # Wrapping of documents. 60 61 def adoptDocument(self, node): 62 return SOAPDocument(node, self) 63 64 # Factory functions. 65 66 def get_node(self, _node, context_node): 67 68 """ 69 Get a libxml2dom node for the given low-level '_node' and libxml2dom 70 'context_node'. 71 """ 72 73 if Node_nodeType(_node) == context_node.ELEMENT_NODE: 74 75 # Make special envelope elements. 76 77 if Node_namespaceURI(_node) == SOAP_ENVELOPE_NAMESPACE: 78 if Node_localName(_node) == "Envelope": 79 return SOAPEnvelopeElement(_node, self, context_node.ownerDocument) 80 elif Node_localName(_node) == "Header": 81 return SOAPHeaderElement(_node, self, context_node.ownerDocument) 82 elif Node_localName(_node) == "Body": 83 return SOAPBodyElement(_node, self, context_node.ownerDocument) 84 elif Node_localName(_node) == "Fault": 85 return SOAPFaultElement(_node, self, context_node.ownerDocument) 86 elif Node_localName(_node) == "Code": 87 return SOAPCodeElement(_node, self, context_node.ownerDocument) 88 elif Node_localName(_node) == "Subcode": 89 return SOAPSubcodeElement(_node, self, context_node.ownerDocument) 90 elif Node_localName(_node) == "Value": 91 return SOAPValueElement(_node, self, context_node.ownerDocument) 92 elif Node_localName(_node) == "Text": 93 return SOAPTextElement(_node, self, context_node.ownerDocument) 94 95 # Detect the method element. 96 97 if Node_parentNode(_node) and Node_localName(Node_parentNode(_node)) == "Body" and \ 98 Node_namespaceURI(Node_parentNode(_node)) == SOAP_ENVELOPE_NAMESPACE: 99 100 return SOAPMethodElement(_node, self, context_node.ownerDocument) 101 102 # Otherwise, make generic SOAP elements. 103 104 return SOAPElement(_node, self, context_node.ownerDocument) 105 106 else: 107 return libxml2dom.Implementation.get_node(self, _node, context_node) 108 109 # Convenience functions. 110 111 def createSOAPMessage(self, namespaceURI, localName): 112 113 "Create a new SOAP message document (fragment)." 114 115 return SOAPDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement 116 117 # Node classes. 118 119 class SOAPNode(libxml2dom.Node): 120 121 "Convenience modifications to nodes specific to libxml2dom.soap." 122 123 def xpath(self, expr, variables=None, namespaces=None): 124 125 """ 126 Evaluate the given 'expr' using the optional 'variables' and 127 'namespaces'. If not otherwise specified, the prefixes given in the 128 module global 'default_ns' will be bound as in that dictionary. 129 """ 130 131 ns = {} 132 ns.update(default_ns) 133 ns.update(namespaces or {}) 134 return libxml2dom.Node.xpath(self, expr, variables, ns) 135 136 class SOAPDocument(libxml2dom._Document, SOAPNode): 137 138 "A SOAP document fragment." 139 140 def _envelope(self): 141 return self.xpath("./env:Envelope")[0] 142 143 envelope = property(_envelope) 144 145 # Convenience methods and properties. 146 147 def _fault(self): 148 return self.envelope.body.fault 149 150 def _method(self): 151 return self.envelope.body.method 152 153 fault = property(_fault) 154 method = property(_method) 155 156 class SOAPElement(SOAPNode): 157 158 "A SOAP element." 159 160 pass 161 162 class SOAPEnvelopeElement(SOAPNode): 163 164 "A SOAP envelope element." 165 166 def _body(self): 167 return self.xpath("./env:Body")[0] 168 169 def _setBody(self, body): 170 self.appendChild(body) 171 172 def _delBody(self): 173 self.removeChild(self.body) 174 175 def createBody(self): 176 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Body") 177 178 body = property(_body, _setBody, _delBody) 179 180 class SOAPHeaderElement(SOAPNode): 181 182 "A SOAP header element." 183 184 pass 185 186 class SOAPBodyElement(SOAPNode): 187 188 "A SOAP body element." 189 190 def _fault(self): 191 return (self.xpath("./env:Fault") or [None])[0] 192 193 def _method(self): 194 return (self.xpath("./*[@env:encodingStyle = '%s']" % SOAP_ENCODING_NAMESPACE) or [None])[0] 195 196 # Node construction methods. 197 198 def createFault(self): 199 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Fault") 200 201 fault = property(_fault) 202 method = property(_method) 203 204 class SOAPMethodElement(SOAPNode): 205 206 "A SOAP method element." 207 208 def _methodName(self): 209 return self.localName 210 211 def _resultParameter(self): 212 return (self.xpath(".//rpc:result") or [None])[0] 213 214 def _resultParameterValue(self): 215 if self.resultParameter: 216 name = self.resultParameter.textContent.strip() 217 result = self.xpath(".//%s" % name, namespaces={self.prefix : self.namespaceURI}) 218 if result: 219 return result[0].textContent.strip() 220 else: 221 return None 222 else: 223 return None 224 225 def _parameters(self): 226 return self.xpath("*") 227 228 def _parameterValues(self): 229 values = [] 230 for parameter in self.parameters: 231 values.append(self._get_value(parameter)) 232 return values 233 234 def _setParameterValues(self, parameters): 235 for node in self.parameters: 236 self.removeChild(node) 237 238 # Add the parameter values. 239 240 for parameter in parameters: 241 self._add_value(self, parameter) 242 243 # Internal methods. 244 245 def _add_value(self, value, parameter): 246 247 "Add to the 'value' element the given 'parameter'." 248 249 container = self.ownerDocument.createElementNS(parameter.name.ns, parameter.name.name) 250 value.appendChild(container) 251 if isinstance(parameter.value, (list, dict)): 252 if isinstance(parameter.value, dict): 253 items = parameter.value.items() 254 else: 255 items = parameter.value 256 for item in items: 257 self._add_value(container, item) 258 else: 259 text = self.ownerDocument.createTextNode(unicode(parameter.value)) 260 container.appendChild(text) 261 262 def _get_value(self, parameter): 263 264 "Return the parameter name and value from within the given 'parameter'." 265 266 elements = parameter.xpath("*") 267 if elements: 268 items = [] 269 for element in elements: 270 items.append(self._get_value(element)) 271 return ParameterValue(ParameterName(parameter.namespaceURI, parameter.name), items) 272 else: 273 return ParameterValue(ParameterName(parameter.namespaceURI, parameter.name), parameter.textContent.strip()) 274 275 methodName = property(_methodName) 276 resultParameter = property(_resultParameter) 277 resultParameterValue = property(_resultParameterValue) 278 parameters = property(_parameters) 279 parameterValues = property(_parameterValues, _setParameterValues) 280 281 class SOAPFaultElement(SOAPNode): 282 283 "A SOAP fault element." 284 285 def _code(self): 286 code = self.xpath("./env:Code") 287 if code: 288 return code[0].value 289 else: 290 return None 291 292 def _subcode(self): 293 subcode = self.xpath("./env:Code/env:Subcode") 294 if subcode: 295 return subcode[0].value 296 else: 297 return None 298 299 def _reason(self): 300 return (self.xpath("./env:Reason") or [None])[0] 301 302 def _detail(self): 303 return (self.xpath("./env:Detail") or [None])[0] 304 305 def createCode(self): 306 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Code") 307 308 code = property(_code) 309 subcode = property(_subcode) 310 reason = property(_reason) 311 detail = property(_detail) 312 313 class SOAPSubcodeElement(SOAPNode): 314 315 "A SOAP subcode element." 316 317 def _value(self): 318 value = self.xpath("./env:Value") 319 if value: 320 return value[0].textContent.strip() 321 else: 322 return None 323 324 def _setValue(self, value): 325 nodes = self.xpath("./env:Value") 326 v = self.createValue() 327 if nodes: 328 self.replaceChild(v, nodes[0]) 329 else: 330 self.appendChild(v) 331 v.value = value 332 333 def createValue(self, value=None): 334 code_value = self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Value") 335 if value is not None: 336 code_value.value = code 337 return code_value 338 339 value = property(_value, _setValue) 340 341 class SOAPCodeElement(SOAPSubcodeElement): 342 343 "A SOAP code element." 344 345 def _subcode(self): 346 return (self.xpath("./env:Subcode") or [None])[0] 347 348 def createSubcode(self): 349 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Subcode") 350 351 subcode = property(_subcode) 352 353 class SOAPValueElement(SOAPNode): 354 355 "A SOAP value element." 356 357 def _value(self): 358 return self.textContent 359 360 def _setValue(self, value): 361 for node in self.childNodes: 362 self.removeChild(node) 363 text = self.ownerDocument.createTextNode(value) 364 self.appendChild(text) 365 366 value = property(_value, _setValue) 367 368 class SOAPTextElement(SOAPValueElement): 369 370 "A SOAP text element." 371 372 def _lang(self): 373 return self.getAttributeNS(libxml2dom.XML_NAMESPACE, "lang") 374 375 def _setLang(self, value): 376 self.setAttributeNS(libxml2dom.XML_NAMESPACE, "xml:lang", value) 377 378 lang = property(_lang, _setLang) 379 380 # Utility functions. 381 382 createDocument = libxml2dom.createDocument 383 createDocumentType = libxml2dom.createDocumentType 384 385 def createSOAPMessage(namespaceURI, localName): 386 return default_impl.createSOAPMessage(namespaceURI, localName) 387 388 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 389 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 390 391 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 392 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 393 394 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 395 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 396 397 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 398 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 399 400 # Single instance of the implementation. 401 402 default_impl = SOAPImplementation() 403 404 # vim: tabstop=4 expandtab shiftwidth=4