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 84 # Otherwise, make generic XML-RPC elements. 85 86 return XMLRPCElement(_node, self, context_node.ownerDocument) 87 88 else: 89 return libxml2dom.Implementation.get_node(self, _node, context_node) 90 91 # Convenience functions. 92 93 def createXMLRPCMessage(self, namespaceURI, localName): 94 95 "Create a new XML-RPC message document (fragment)." 96 97 return XMLRPCDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement 98 99 def createMethodCall(self): 100 return self.createXMLRPCMessage(None, "methodCall") 101 102 def createMethodResponse(self): 103 return self.createXMLRPCMessage(None, "methodResponse") 104 105 # Internal utility functions. 106 107 def boolean(s): 108 if s.lower() == "true": 109 return True 110 elif s.lower() == "false": 111 return False 112 else: 113 raise ValueError, "String value %s not convertable to boolean." % repr(s) 114 115 def iso8601(s): 116 return s 117 118 # Node classes. 119 120 class XMLRPCNode(libxml2dom.Node): 121 122 "Convenience modifications to nodes specific to libxml2dom.xmlrpc." 123 124 pass 125 126 class XMLRPCElement(XMLRPCNode): 127 128 "An XML-RPC element." 129 130 pass 131 132 class XMLRPCDocument(libxml2dom._Document, XMLRPCNode): 133 134 "An XML-RPC document fragment." 135 136 def _method(self): 137 return (self.xpath("./methodCall|./methodResponse") or [None])[0] 138 139 method = property(_method) 140 141 # Convenience methods and properties. 142 143 def _methodName(self): 144 if self.method is not None: 145 return self.method.name 146 else: 147 return None 148 149 def _parameterItems(self): 150 if self.method is not None: 151 return self.method.parameterItems 152 else: 153 return None 154 155 def _parameterValues(self): 156 if self.method is not None: 157 return self.method.parameterValues 158 else: 159 return None 160 161 def _parameterMap(self): 162 if self.method is not None: 163 return self.method.parameterMap 164 else: 165 return None 166 167 def _fault(self): 168 if self.method is not None: 169 return self.method.fault 170 else: 171 return None 172 173 # Node construction methods. 174 175 def createMethodCall(self): 176 return self.ownerDocument.createElement("methodCall") 177 178 def createMethodResponse(self): 179 return self.ownerDocument.createElement("methodResponse") 180 181 methodName = property(_methodName) 182 parameterItems = property(_parameterItems) 183 parameterValues = property(_parameterValues) 184 parameterMap = property(_parameterMap) 185 fault = property(_fault) 186 187 class XMLRPCMethodElement(XMLRPCNode): 188 189 "An XML-RPC method element." 190 191 def _fault(self): 192 return (self.xpath("./fault") or [None])[0] 193 194 def _methodName(self): 195 return (self.xpath("./methodName") or [None])[0] 196 197 def _name(self): 198 name = self.methodName 199 if name is not None: 200 return name.value 201 else: 202 return None 203 204 def _setName(self, name): 205 if self.methodName is None: 206 methodName = self.createMethodName() 207 self.appendChild(methodName) 208 self.methodName.value = name 209 210 def _parameters(self): 211 return self.xpath("./params/param") 212 213 def _parameterItems(self): 214 values = self.xpath("./params/param/value") 215 if values: 216 items = [] 217 for value in values: 218 items.append(self._item_contents(value)) 219 return items 220 else: 221 return [] 222 223 def _parameterValues(self): 224 return self._value_contents(self.parameterItems) 225 226 def _setParameterValues(self, parameters): 227 param_list = self.parameters 228 params = (self.xpath("./params") or [None])[0] 229 if params: 230 if param_list: 231 for param in param_list: 232 params.removeChild(param) 233 else: 234 params = self.createParameters() 235 self.appendChild(params) 236 237 for parameter in parameters: 238 param = self.ownerDocument.createElement("param") 239 params.appendChild(param) 240 value = self.ownerDocument.createElement("value") 241 param.appendChild(value) 242 243 # NOTE: Only handles simple types. 244 245 container = self._make_container(parameter) 246 value.appendChild(container) 247 text = self.ownerDocument.createTextNode(unicode(parameter)) 248 container.appendChild(text) 249 250 def _parameterMap(self): 251 return self._map_contents(self.parameterItems) 252 253 # Internal data. 254 255 converters = { 256 "string" : unicode, 257 "int" : int, 258 "i4" : int, 259 "double" : float, 260 "boolean" : boolean, # see the module globals 261 "dateTime.iso8601" : iso8601, # see the module globals 262 "base64" : str 263 } 264 265 # Internal methods. 266 267 def _make_container(self, parameter): 268 if isinstance(parameter, (str, unicode)): 269 return self.ownerDocument.createElement("string") 270 elif isinstance(parameter, (int, long)): 271 return self.ownerDocument.createElement("int") 272 elif isinstance(parameter, float): 273 return self.ownerDocument.createElement("double") 274 elif isinstance(parameter, bool): 275 return self.ownerDocument.createElement("boolean") 276 elif isinstance(parameter, datetime.datetime): 277 return self.ownerDocument.createElement("dateTime.iso8601") 278 279 def _from_string(self, typename, value): 280 return self.converters.get(typename, str)(value) 281 282 def _item_contents(self, value): 283 if value.type == "struct": 284 items = [] 285 for member in value.container.members: 286 if member.value.type == "struct": 287 items.append((member.name, self._item_contents(member.value))) 288 else: 289 items.append((member.name, self._from_string(member.value.type, member.value.container.value))) 290 return None, items 291 else: 292 return None, self._from_string(value.type, value.container.value) 293 294 def _value_contents(self, items): 295 values = [] 296 for name, value in items: 297 if isinstance(value, list): 298 values.append(self._value_contents(value)) 299 elif name is None: 300 values.append(value) 301 else: 302 values.append((name, value)) 303 return values 304 305 def _map_contents(self, items): 306 d = {} 307 for n, (name, value) in enumerate(items): 308 key_name = name or str(n) 309 if isinstance(value, list): 310 d[key_name] = self._map_contents(value) 311 else: 312 d[key_name] = value 313 return d 314 315 # Node construction methods. 316 317 def createMethodName(self): 318 return self.ownerDocument.createElement("methodName") 319 320 def createParameters(self): 321 return self.ownerDocument.createElement("params") 322 323 def createFault(self): 324 return self.ownerDocument.createElement("fault") 325 326 fault = property(_fault) 327 name = property(_name, _setName) 328 methodName = property(_methodName) 329 parameters = property(_parameters) 330 parameterItems = property(_parameterItems) 331 parameterValues = property(_parameterValues, _setParameterValues) 332 parameterMap = property(_parameterMap) 333 334 class XMLRPCStringElement(XMLRPCNode): 335 336 "An XML-RPC string element." 337 338 def _value(self): 339 return self.textContent.strip() 340 341 def _setValue(self, value): 342 for node in self.childNodes: 343 self.removeChild(node) 344 text = self.ownerDocument.createTextNode(value) 345 self.appendChild(text) 346 347 value = property(_value, _setValue) 348 349 class XMLRPCValueElement(XMLRPCStringElement): 350 351 "An XML-RPC value element." 352 353 def _type(self): 354 elements = self.xpath("*") 355 if elements: 356 return elements[0].localName 357 else: 358 return "string" 359 360 def _container(self): 361 return (self.xpath("*") or [self])[0] 362 363 type = property(_type) 364 container = property(_container) 365 366 class XMLRPCMethodNameElement(XMLRPCStringElement): 367 368 "An XML-RPC method element." 369 370 pass 371 372 class XMLRPCIntegerElement(XMLRPCStringElement): 373 374 "An XML-RPC integer element." 375 376 pass 377 378 class XMLRPCBooleanElement(XMLRPCStringElement): 379 380 "An XML-RPC boolean element." 381 382 pass 383 384 class XMLRPCDoubleElement(XMLRPCStringElement): 385 386 "An XML-RPC double floating point number element." 387 388 pass 389 390 class XMLRPCDateTimeElement(XMLRPCStringElement): 391 392 "An XML-RPC date/time element." 393 394 pass 395 396 class XMLRPCBase64Element(XMLRPCStringElement): 397 398 "An XML-RPC integer element." 399 400 pass 401 402 class XMLRPCStructElement(XMLRPCNode): 403 404 "An XML-RPC structure element." 405 406 def _members(self): 407 return self.xpath("./member") 408 409 members = property(_members) 410 411 class XMLRPCMemberElement(XMLRPCNode): 412 413 "An XML-RPC structure member element." 414 415 def _name(self): 416 value = self.xpath("./name") 417 if value: 418 return value[0].textContent.strip() 419 else: 420 return None 421 422 def _value(self): 423 return (self.xpath("./value") or [None])[0] 424 425 name = property(_name) 426 value = property(_value) 427 428 class XMLRPCFaultElement(XMLRPCNode): 429 430 "An XML-RPC fault element." 431 432 def _code(self): 433 code = self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int") 434 if code: 435 return code[0].value 436 else: 437 return None 438 439 def _reason(self): 440 reason = self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string") 441 if reason: 442 return reason[0].value 443 else: 444 return None 445 446 code = property(_code) 447 reason = property(_reason) 448 449 # Utility functions. 450 451 createDocument = libxml2dom.createDocument 452 createDocumentType = libxml2dom.createDocumentType 453 454 def createXMLRPCMessage(namespaceURI, localName): 455 return default_impl.createXMLRPCMessage(None, localName) 456 457 def createMethodCall(): 458 return default_impl.createMethodCall() 459 460 def createMethodResponse(): 461 return default_impl.createMethodResponse() 462 463 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 464 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 465 466 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 467 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 468 469 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 470 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 471 472 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 473 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 474 475 # Single instance of the implementation. 476 477 default_impl = XMLRPCImplementation() 478 479 # vim: tabstop=4 expandtab shiftwidth=4