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 # Convenience methods and properties. 138 139 def _methodName(self): 140 return self.envelope.body.methodName 141 142 def _parameterItems(self): 143 return self.envelope.body.parameterItems 144 145 def _parameterValues(self): 146 return self.envelope.body.parameterValues 147 148 def _parameterMap(self): 149 return self.envelope.body.parameterMap 150 151 def _fault(self): 152 return self.envelope.body.fault 153 154 methodName = property(_methodName) 155 parameterItems = property(_parameterItems) 156 parameterValues = property(_parameterValues) 157 parameterMap = property(_parameterMap) 158 fault = property(_fault) 159 160 class SOAPElement(SOAPNode): 161 162 "A SOAP element." 163 164 pass 165 166 class SOAPEnvelopeElement(SOAPNode): 167 168 "A SOAP envelope element." 169 170 def _body(self): 171 return self.xpath("./env:Body")[0] 172 173 def _setBody(self, body): 174 self.appendChild(body) 175 176 def _delBody(self): 177 self.removeChild(self.body) 178 179 def createBody(self): 180 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Body") 181 182 body = property(_body, _setBody, _delBody) 183 184 class SOAPHeaderElement(SOAPNode): 185 186 "A SOAP header element." 187 188 pass 189 190 class SOAPBodyElement(SOAPNode): 191 192 "A SOAP body element." 193 194 def _fault(self): 195 return (self.xpath("./env:Fault") or [None])[0] 196 197 def _method(self): 198 return (self.xpath("./*[@env:encodingStyle = '%s']" % SOAP_ENCODING_NAMESPACE) or [None])[0] 199 200 def _methodName(self): 201 if self.method is not None: 202 return self.method.localName 203 else: 204 return None 205 206 def _resultParameter(self): 207 return (self.method.xpath(".//rpc:result") or [None])[0] 208 209 def _resultParameterValue(self): 210 if self.resultParameter: 211 name = self.resultParameter.textContent.strip() 212 result = self.method.xpath(".//%s" % name, namespaces={self.method.prefix : self.method.namespaceURI}) 213 if result: 214 return result[0].textContent.strip() 215 else: 216 return None 217 else: 218 return None 219 220 def _parameters(self): 221 if self.method is not None: 222 return self.method.xpath("*") 223 else: 224 return [] 225 226 def _parameterItems(self): 227 values = [] 228 for parameter in self.parameters: 229 values.append(self._item_contents(parameter)) 230 return values 231 232 def _parameterValues(self): 233 return self._value_contents(self.parameterItems) 234 235 def _parameterMap(self): 236 return self._map_contents(self.parameterItems) 237 238 # Internal methods. 239 240 def _item_contents(self, parameter): 241 elements = parameter.xpath("*") 242 if elements: 243 items = [] 244 for element in elements: 245 items.append(self._item_contents(element)) 246 return (parameter.namespaceURI, parameter.localName), items 247 else: 248 return (parameter.namespaceURI, parameter.localName), parameter.textContent.strip() 249 250 def _value_contents(self, items): 251 values = [] 252 for (typename, name), value in items: 253 if isinstance(value, list): 254 values.append(self._value_contents(value)) 255 elif name is None: 256 values.append(value) 257 else: 258 values.append((name, value)) 259 return values 260 261 def _map_contents(self, items): 262 d = {} 263 for n, ((typename, name), value) in enumerate(items): 264 key_name = name or str(n) 265 if isinstance(value, list): 266 d[(typename, key_name)] = self._map_contents(value) 267 else: 268 d[(typename, key_name)] = value 269 return d 270 271 # Node construction methods. 272 273 def createFault(self): 274 return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Fault") 275 276 fault = property(_fault) 277 method = property(_method) 278 methodName = property(_methodName) 279 resultParameter = property(_resultParameter) 280 resultParameterValue = property(_resultParameterValue) 281 parameters = property(_parameters) 282 parameterItems = property(_parameterItems) 283 parameterValues = property(_parameterValues) 284 parameterMap = property(_parameterMap) 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