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