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 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 _rawParameterValues(self): 229 values = [] 230 for parameter in self.parameters: 231 values.append(self._get_value(parameter)) 232 return values 233 234 def _setRawParameterValues(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 def _parameterValues(self): 244 return libxml2dom.rpc.convert(self.rawParameterValues, getattr(self.ownerDocument, "converters", None)) 245 246 # Internal methods. 247 248 def _add_value(self, value, parameter): 249 250 "Add to the 'value' element the given 'parameter'." 251 252 parameter_name, parameter_value = parameter 253 container = self.ownerDocument.createElementNS(*parameter_name) 254 value.appendChild(container) 255 if isinstance(parameter_value, (list, dict)): 256 if isinstance(parameter_value, dict): 257 items = parameter_value.items() 258 else: 259 items = parameter_value 260 for item in items: 261 self._add_value(container, item) 262 else: 263 text = self.ownerDocument.createTextNode(unicode(parameter_value)) 264 container.appendChild(text) 265 266 def _get_value(self, parameter): 267 268 "Return the parameter name and value from within the given 'parameter'." 269 270 elements = parameter.xpath("*") 271 if elements: 272 items = [] 273 for element in elements: 274 items.append(self._get_value(element)) 275 return ParameterName(parameter.namespaceURI, parameter.name), items 276 else: 277 return ParameterName(parameter.namespaceURI, parameter.name), parameter.textContent.strip() 278 279 methodName = property(_methodName) 280 resultParameter = property(_resultParameter) 281 resultParameterValue = property(_resultParameterValue) 282 parameters = property(_parameters) 283 rawParameterValues = property(_rawParameterValues, _setRawParameterValues) 284 parameterValues = property(_parameterValues) 285 286 class SOAPFaultElement(SOAPNode): 287 288 "A SOAP fault element." 289 290 def _code(self): 291 code = self.xpath("./env:Code") 292 if code: 293 return code[0].value 294 else: 295 return None 296 297 def _subcode(self): 298 subcode = self.xpath("./env:Code/env:Subcode") 299 if subcode: 300 return subcode[0].value 301 else: 302 return None 303 304 def _reason(self): 305 return (self.xpath("./env:Reason") or [None])[0] 306 307 def _detail(self): 308 return (self.xpath("./env:Detail") or [None])[0] 309 310 def createCode(self): 311 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Code") 312 313 code = property(_code) 314 subcode = property(_subcode) 315 reason = property(_reason) 316 detail = property(_detail) 317 318 class SOAPSubcodeElement(SOAPNode): 319 320 "A SOAP subcode element." 321 322 def _value(self): 323 value = self.xpath("./env:Value") 324 if value: 325 return value[0].textContent.strip() 326 else: 327 return None 328 329 def _setValue(self, value): 330 nodes = self.xpath("./env:Value") 331 v = self.createValue() 332 if nodes: 333 self.replaceChild(v, nodes[0]) 334 else: 335 self.appendChild(v) 336 v.value = value 337 338 def createValue(self, value=None): 339 code_value = self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Value") 340 if value is not None: 341 code_value.value = code 342 return code_value 343 344 value = property(_value, _setValue) 345 346 class SOAPCodeElement(SOAPSubcodeElement): 347 348 "A SOAP code element." 349 350 def _subcode(self): 351 return (self.xpath("./env:Subcode") or [None])[0] 352 353 def createSubcode(self): 354 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Subcode") 355 356 subcode = property(_subcode) 357 358 class SOAPValueElement(SOAPNode): 359 360 "A SOAP value element." 361 362 def _value(self): 363 return self.textContent 364 365 def _setValue(self, value): 366 for node in self.childNodes: 367 self.removeChild(node) 368 text = self.ownerDocument.createTextNode(value) 369 self.appendChild(text) 370 371 value = property(_value, _setValue) 372 373 class SOAPTextElement(SOAPValueElement): 374 375 "A SOAP text element." 376 377 def _lang(self): 378 return self.getAttributeNS(libxml2dom.XML_NAMESPACE, "lang") 379 380 def _setLang(self, value): 381 self.setAttributeNS(libxml2dom.XML_NAMESPACE, "xml:lang", value) 382 383 lang = property(_lang, _setLang) 384 385 # Utility functions. 386 387 createDocument = libxml2dom.createDocument 388 createDocumentType = libxml2dom.createDocumentType 389 390 def createSOAPMessage(namespaceURI, localName): 391 return default_impl.createSOAPMessage(namespaceURI, localName) 392 393 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 394 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 395 396 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 397 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 398 399 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 400 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 401 402 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 403 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 404 405 # Single instance of the implementation. 406 407 default_impl = SOAPImplementation() 408 409 # vim: tabstop=4 expandtab shiftwidth=4