1 #!/usr/bin/env python 2 3 """ 4 XML-RPC support using libxml2dom. 5 6 See: http://www.xmlrpc.com/spec 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 XML-RPC messages can be done using traditional HTTP 26 libraries. 27 28 See tests/xmlrpc_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 import datetime 36 37 class XMLRPCImplementation(libxml2dom.Implementation): 38 39 "Contains an XML-RPC-specific implementation." 40 41 # Wrapping of documents. 42 43 def adoptDocument(self, node): 44 return XMLRPCDocument(node, self) 45 46 # Factory functions. 47 48 def get_node(self, _node, context_node): 49 50 """ 51 Get a libxml2dom node for the given low-level '_node' and libxml2dom 52 'context_node'. 53 """ 54 55 if Node_nodeType(_node) == context_node.ELEMENT_NODE: 56 57 # Make special elements. 58 59 if Node_localName(_node) in ("methodCall", "methodResponse"): 60 return XMLRPCMethodElement(_node, self, context_node.ownerDocument) 61 elif Node_localName(_node) == "methodName": 62 return XMLRPCMethodNameElement(_node, self, context_node.ownerDocument) 63 elif Node_localName(_node) == "fault": 64 return XMLRPCFaultElement(_node, self, context_node.ownerDocument) 65 elif Node_localName(_node) == "string": 66 return XMLRPCStringElement(_node, self, context_node.ownerDocument) 67 elif Node_localName(_node) in ("int", "i4"): 68 return XMLRPCIntegerElement(_node, self, context_node.ownerDocument) 69 elif Node_localName(_node) == "boolean": 70 return XMLRPCBooleanElement(_node, self, context_node.ownerDocument) 71 elif Node_localName(_node) == "double": 72 return XMLRPCDoubleElement(_node, self, context_node.ownerDocument) 73 elif Node_localName(_node) == "dateTime.iso8601": 74 return XMLRPCDateTimeElement(_node, self, context_node.ownerDocument) 75 elif Node_localName(_node) == "base64": 76 return XMLRPCBase64Element(_node, self, context_node.ownerDocument) 77 elif Node_localName(_node) == "struct": 78 return XMLRPCStructElement(_node, self, context_node.ownerDocument) 79 elif Node_localName(_node) == "member": 80 return XMLRPCMemberElement(_node, self, context_node.ownerDocument) 81 elif Node_localName(_node) == "value": 82 return XMLRPCValueElement(_node, self, context_node.ownerDocument) 83 elif Node_localName(_node) == "name": 84 return XMLRPCNameElement(_node, self, context_node.ownerDocument) 85 86 # Otherwise, make generic XML-RPC elements. 87 88 return XMLRPCElement(_node, self, context_node.ownerDocument) 89 90 else: 91 return libxml2dom.Implementation.get_node(self, _node, context_node) 92 93 # Convenience functions. 94 95 def createXMLRPCMessage(self, namespaceURI, localName): 96 97 "Create a new XML-RPC message document (fragment)." 98 99 return XMLRPCDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement 100 101 def createMethodCall(self): 102 return self.createXMLRPCMessage(None, "methodCall") 103 104 def createMethodResponse(self): 105 return self.createXMLRPCMessage(None, "methodResponse") 106 107 # Node classes. 108 109 class XMLRPCNode(libxml2dom.Node): 110 111 "Convenience modifications to nodes specific to libxml2dom.xmlrpc." 112 113 pass 114 115 class XMLRPCElement(XMLRPCNode): 116 117 "An XML-RPC element." 118 119 pass 120 121 class XMLRPCDocument(libxml2dom._Document, XMLRPCNode): 122 123 "An XML-RPC document fragment." 124 125 def _method(self): 126 return (self.xpath("methodCall|methodResponse") or [None])[0] 127 128 def _fault(self): 129 if self.method is not None: 130 return self.method.fault 131 else: 132 return None 133 134 method = property(_method) 135 fault = property(_fault) 136 137 # Node construction methods. 138 139 def createMethodCall(self): 140 return self.ownerDocument.createElement("methodCall") 141 142 def createMethodResponse(self): 143 return self.ownerDocument.createElement("methodResponse") 144 145 class XMLRPCMethodElement(XMLRPCNode): 146 147 "An XML-RPC method element." 148 149 def _fault(self): 150 return (self.xpath("./fault") or [None])[0] 151 152 def _methodNameElement(self): 153 return (self.xpath("./methodName") or [None])[0] 154 155 def _methodName(self): 156 name = self.methodNameElement 157 if name is not None: 158 return name.value 159 else: 160 return None 161 162 def _setMethodName(self, name): 163 if self.methodNameElement is None: 164 methodName = self.createMethodName() 165 self.appendChild(methodName) 166 self.methodNameElement.value = name 167 168 def _parameters(self): 169 return self.xpath("./params/param") 170 171 def _parameterValues(self): 172 values = self.xpath("./params/param/value") 173 if values: 174 items = [] 175 for value in values: 176 items.append(self._get_value(value)) 177 return items 178 else: 179 return [] 180 181 def _setParameterValues(self, parameters): 182 param_list = self.parameters 183 params = (self.xpath("./params") or [None])[0] 184 185 # Remove any previous parameters. 186 187 if params: 188 if param_list: 189 for param in param_list: 190 params.removeChild(param) 191 else: 192 params = self.createParameters() 193 self.appendChild(params) 194 195 # Add parameter values. 196 197 for parameter in parameters: 198 param = self.ownerDocument.createElement("param") 199 params.appendChild(param) 200 value = self.ownerDocument.createElement("value") 201 param.appendChild(value) 202 self._add_value(value, parameter) 203 204 # Internal methods. 205 206 def _add_value(self, value, parameter): 207 typename, parameter = parameter 208 if typename == "struct": 209 if isinstance(parameter, dict): 210 items = parameter.items() 211 else: 212 items = parameter 213 struct = self.ownerDocument.createElement("struct") 214 for item_name, item_value in items: 215 member = struct.createMember() 216 struct.appendChild(member) 217 member.memberName = item_name 218 memberValue = member.createValue() 219 member.appendChild(memberValue) 220 self._add_value(memberValue, item_value) 221 value.appendChild(struct) 222 else: 223 container = self.ownerDocument.createElement(typename) 224 value.appendChild(container) 225 container.value = unicode(parameter) 226 227 def _get_value(self, value): 228 if value.type == "struct": 229 items = [] 230 for member in value.container.members: 231 items.append((member.memberName, self._get_value(member.value))) 232 return (value.type, items) 233 else: 234 return (value.type, value.container.value) 235 236 # Node construction methods. 237 238 def createMethodName(self): 239 return self.ownerDocument.createElement("methodName") 240 241 def createParameters(self): 242 return self.ownerDocument.createElement("params") 243 244 def createFault(self): 245 return self.ownerDocument.createElement("fault") 246 247 fault = property(_fault) 248 methodNameElement = property(_methodNameElement) 249 methodName = property(_methodName, _setMethodName) 250 parameters = property(_parameters) 251 parameterValues = property(_parameterValues, _setParameterValues) 252 253 class XMLRPCStructElement(XMLRPCNode): 254 255 "An XML-RPC structure element." 256 257 def _members(self): 258 return self.xpath("./member") 259 260 # Node construction methods. 261 262 def createMember(self): 263 return self.ownerDocument.createElement("member") 264 265 members = property(_members) 266 267 class XMLRPCMemberElement(XMLRPCNode): 268 269 "An XML-RPC structure member element." 270 271 def _nameElement(self): 272 return (self.xpath("./name") or [None])[0] 273 274 def _memberName(self): 275 if self.nameElement is not None: 276 return self.nameElement.value 277 else: 278 return None 279 280 def _setMemberName(self, name): 281 if self.nameElement is None: 282 nameElement = self.createName() 283 self.appendChild(nameElement) 284 self.nameElement.value = name 285 286 def _value(self): 287 return (self.xpath("./value") or [None])[0] 288 289 # Node construction methods. 290 291 def createName(self): 292 return self.ownerDocument.createElement("name") 293 294 def createValue(self): 295 return self.ownerDocument.createElement("value") 296 297 nameElement = property(_nameElement) 298 memberName = property(_memberName, _setMemberName) 299 value = property(_value) 300 301 class XMLRPCStringElement(XMLRPCNode): 302 303 "An XML-RPC string element." 304 305 def _value(self): 306 return self.textContent.strip() 307 308 def _setValue(self, value): 309 for node in self.childNodes: 310 self.removeChild(node) 311 text = self.ownerDocument.createTextNode(value) 312 self.appendChild(text) 313 314 value = property(_value, _setValue) 315 316 class XMLRPCNameElement(XMLRPCStringElement): 317 318 "An XML-RPC name element." 319 320 pass 321 322 class XMLRPCValueElement(XMLRPCStringElement): 323 324 "An XML-RPC value element." 325 326 def _type(self): 327 elements = self.xpath("*") 328 if elements: 329 return elements[0].localName 330 else: 331 return "string" 332 333 def _container(self): 334 return (self.xpath("*") or [self])[0] 335 336 type = property(_type) 337 container = property(_container) 338 339 class XMLRPCMethodNameElement(XMLRPCStringElement): 340 341 "An XML-RPC method element." 342 343 pass 344 345 class XMLRPCIntegerElement(XMLRPCStringElement): 346 347 "An XML-RPC integer element." 348 349 pass 350 351 class XMLRPCBooleanElement(XMLRPCStringElement): 352 353 "An XML-RPC boolean element." 354 355 pass 356 357 class XMLRPCDoubleElement(XMLRPCStringElement): 358 359 "An XML-RPC double floating point number element." 360 361 pass 362 363 class XMLRPCDateTimeElement(XMLRPCStringElement): 364 365 "An XML-RPC date/time element." 366 367 pass 368 369 class XMLRPCBase64Element(XMLRPCStringElement): 370 371 "An XML-RPC integer element." 372 373 pass 374 375 class XMLRPCFaultElement(XMLRPCNode): 376 377 "An XML-RPC fault element." 378 379 def _code(self): 380 code = self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int") 381 if code: 382 return code[0].value 383 else: 384 return None 385 386 def _reason(self): 387 reason = self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string") 388 if reason: 389 return reason[0].value 390 else: 391 return None 392 393 code = property(_code) 394 reason = property(_reason) 395 396 # Utility functions. 397 398 createDocument = libxml2dom.createDocument 399 createDocumentType = libxml2dom.createDocumentType 400 401 def createXMLRPCMessage(namespaceURI, localName): 402 return default_impl.createXMLRPCMessage(None, localName) 403 404 def createMethodCall(): 405 return default_impl.createMethodCall() 406 407 def createMethodResponse(): 408 return default_impl.createMethodResponse() 409 410 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 411 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 412 413 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 414 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 415 416 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 417 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 418 419 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 420 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 421 422 # Single instance of the implementation. 423 424 default_impl = XMLRPCImplementation() 425 426 # vim: tabstop=4 expandtab shiftwidth=4