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, 2008 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 # Node classes. 38 39 class XMLRPCNode(libxml2dom.Node): 40 41 "Convenience modifications to nodes specific to libxml2dom.xmlrpc." 42 43 def add_or_replace_element(self, new_element): 44 elements = self.xpath(new_element.localName) 45 if elements: 46 self.replaceChild(new_element, elements[0]) 47 else: 48 self.appendChild(new_element) 49 50 class XMLRPCElement(XMLRPCNode): 51 52 "An XML-RPC element." 53 54 pass 55 56 class XMLRPCDocument(libxml2dom._Document, XMLRPCNode): 57 58 "An XML-RPC document fragment." 59 60 def _method(self): 61 return (self.xpath("methodCall|methodResponse") or [None])[0] 62 63 def _fault(self): 64 if self.method is not None: 65 return self.method.fault 66 else: 67 return None 68 69 method = property(_method) 70 fault = property(_fault) 71 72 # Node construction methods. 73 74 def createMethodCall(self, insert=0): 75 e = self.ownerDocument.createElement("methodCall") 76 if insert: 77 self.add_or_replace_element(e) 78 return e 79 80 def createMethodResponse(self, insert=0): 81 e = self.ownerDocument.createElement("methodResponse") 82 if insert: 83 self.add_or_replace_element(e) 84 return e 85 86 class XMLRPCMethodElement(XMLRPCNode): 87 88 "An XML-RPC method element." 89 90 def _fault(self): 91 return (self.xpath("./fault") or [None])[0] 92 93 def _params(self): 94 return (self.xpath("./params") or [None])[0] 95 96 def _methodNameElement(self): 97 return (self.xpath("./methodName") or [None])[0] 98 99 def _methodName(self): 100 name = self.methodNameElement 101 if name is not None: 102 return name.value 103 else: 104 return None 105 106 def _setMethodName(self, name): 107 if self.methodNameElement is None: 108 methodName = self.createMethodName() 109 self.appendChild(methodName) 110 self.methodNameElement.value = name 111 112 def _parameterValues(self): 113 return [value.container.contents for value in self.xpath("./params/param/value")] 114 115 def _parameters(self): 116 return self.xpath("./params/param") 117 118 # Node construction methods. 119 120 def createMethodName(self, insert=0): 121 e = self.ownerDocument.createElement("methodName") 122 if insert: 123 self.add_or_replace_element(e) 124 return e 125 126 def createParameters(self, insert=0): 127 e = self.ownerDocument.createElement("params") 128 if insert: 129 self.add_or_replace_element(e) 130 return e 131 132 def createFault(self, insert=0): 133 e = self.ownerDocument.createElement("fault") 134 if insert: 135 self.add_or_replace_element(e) 136 return e 137 138 fault = property(_fault) 139 params = property(_params) 140 methodNameElement = property(_methodNameElement) 141 methodName = property(_methodName, _setMethodName) 142 parameterValues = property(_parameterValues) 143 parameters = property(_parameters) 144 145 class XMLRPCParametersElement(XMLRPCNode): 146 147 "An XML-RPC parameters/params element." 148 149 def _parameters(self): 150 return self.xpath("./param") 151 152 # Node construction methods. 153 154 def createParameter(self, insert=0): 155 e = self.ownerDocument.createElement("param") 156 if insert: 157 self.appendChild(e) 158 return e 159 160 parameters = property(_parameters) 161 162 class XMLRPCParameterElement(XMLRPCNode): 163 164 "An XML-RPC parameter/param element." 165 166 def _valueElement(self): 167 return (self.xpath("./value") or [None])[0] 168 169 # Node construction methods. 170 171 def createValue(self, typename=None, insert=0): 172 value = self.ownerDocument.createElement("value") 173 if typename is not None: 174 contents = self.ownerDocument.createElement(typename) 175 value.appendChild(contents) 176 if insert: 177 self.add_or_replace_element(value) 178 return value 179 180 valueElement = property(_valueElement) 181 182 class XMLRPCArrayElement(XMLRPCNode): 183 184 "An XML-RPC array element." 185 186 def _dataElement(self): 187 return (self.xpath("./data") or [None])[0] 188 189 def _contents(self): 190 return self 191 192 # Sequence emulation. 193 194 def __len__(self): 195 if self.data: 196 return len(self.data) 197 else: 198 return 0 199 200 def __getitem__(self, i): 201 if self.data: 202 return self.data[i] 203 else: 204 raise IndexError, i 205 206 def __eq__(self, other): 207 for i, j in map(None, self, other): 208 if i != j: 209 return False 210 return True 211 212 # Node construction methods. 213 214 def createData(self, insert=0): 215 e = self.ownerDocument.createElement("data") 216 if insert: 217 self.add_or_replace_element(e) 218 return e 219 220 dataElement = property(_dataElement) 221 contents = property(_contents) 222 223 class XMLRPCStructElement(XMLRPCNode): 224 225 "An XML-RPC structure element." 226 227 def _members(self): 228 return self.xpath("./member") 229 230 def _contents(self): 231 return self 232 233 # Sequence emulation. 234 235 def __len__(self): 236 return len(self.members) 237 238 def __getitem__(self, i): 239 return self.members[i] 240 241 def __eq__(self, other): 242 for i, j in map(None, self, other): 243 if i != j: 244 return False 245 return True 246 247 # Node construction methods. 248 249 def createMember(self, insert=0): 250 e = self.ownerDocument.createElement("member") 251 if insert: 252 self.add_or_replace_element(e) 253 return e 254 255 members = property(_members) 256 contents = property(_contents) 257 258 class XMLRPCDataElement(XMLRPCNode): 259 260 "An XML-RPC array data element." 261 262 def _values(self): 263 return self.xpath("./value") 264 265 # Sequence emulation. 266 267 def __len__(self): 268 return len(self.values) 269 270 def __getitem__(self, i): 271 return self.values[i].container.contents 272 273 # Node construction methods. 274 275 def createValue(self, insert=0): 276 e = self.ownerDocument.createElement("value") 277 if insert: 278 self.add_or_replace_element(e) 279 return e 280 281 values = property(_values) 282 283 class XMLRPCMemberElement(XMLRPCNode): 284 285 "An XML-RPC structure member element." 286 287 def _valueElement(self): 288 return (self.xpath("./value") or [None])[0] 289 290 def _nameElement(self): 291 return (self.xpath("./name") or [None])[0] 292 293 def _memberName(self): 294 if self.nameElement is not None: 295 return self.nameElement.value 296 else: 297 return None 298 299 def _setMemberName(self, name): 300 if self.nameElement is None: 301 nameElement = self.createName() 302 self.appendChild(nameElement) 303 self.nameElement.value = name 304 305 def _contents(self): 306 return self 307 308 # Item (name, value) emulation. 309 310 def __len__(self): 311 return 2 312 313 def __getitem__(self, i): 314 return (self.memberName, self.value.container.contents)[i] 315 316 def __eq__(self, other): 317 return self[0] == other[0] and self[1] == other[1] 318 319 # Node construction methods. 320 321 def createName(self, insert=0): 322 e = self.ownerDocument.createElement("name") 323 if insert: 324 self.add_or_replace_element(e) 325 return e 326 327 def createValue(self, insert=0): 328 e = self.ownerDocument.createElement("value") 329 if insert: 330 self.add_or_replace_element(e) 331 return e 332 333 nameElement = property(_nameElement) 334 memberName = property(_memberName, _setMemberName) 335 valueElement = property(_valueElement) 336 contents = property(_contents) 337 338 class XMLRPCStringElement(XMLRPCNode): 339 340 "An XML-RPC string element." 341 342 typename = "string" 343 344 def _value(self): 345 return self.textContent.strip() 346 347 def _setValue(self, value): 348 for node in self.childNodes: 349 self.removeChild(node) 350 text = self.ownerDocument.createTextNode(value) 351 self.appendChild(text) 352 353 def _contents(self): 354 return convert(self.typename, self.value) 355 356 def __eq__(self, other): 357 if hasattr(other, "contents"): 358 return self.contents == other.contents 359 else: 360 return self.contents == other 361 362 value = property(_value, _setValue) 363 contents = property(_contents) 364 365 class XMLRPCNameElement(XMLRPCStringElement): 366 367 "An XML-RPC name element." 368 369 pass 370 371 class XMLRPCValueElement(XMLRPCStringElement): 372 373 "An XML-RPC value element." 374 375 def _type(self): 376 elements = self.xpath("*") 377 if elements: 378 return elements[0].localName 379 else: 380 return "string" 381 382 def _setType(self, typename): 383 new_contents = self.ownerDocument.createElement(typename) 384 self.add_or_replace_element(new_contents) 385 386 def _container(self): 387 return (self.xpath("*") or [self])[0] 388 389 type = property(_type, _setType) 390 container = property(_container) 391 392 class XMLRPCMethodNameElement(XMLRPCStringElement): 393 394 "An XML-RPC method element." 395 396 pass 397 398 class XMLRPCIntegerElement(XMLRPCStringElement): 399 400 "An XML-RPC integer element." 401 402 typename = "int" 403 404 class XMLRPCBooleanElement(XMLRPCStringElement): 405 406 "An XML-RPC boolean element." 407 408 typename = "boolean" 409 410 class XMLRPCDoubleElement(XMLRPCStringElement): 411 412 "An XML-RPC double floating point number element." 413 414 typename = "double" 415 416 class XMLRPCDateTimeElement(XMLRPCStringElement): 417 418 "An XML-RPC date/time element." 419 420 typename = "datetime" 421 422 class XMLRPCBase64Element(XMLRPCStringElement): 423 424 "An XML-RPC integer element." 425 426 typename = "base64" 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 # Conversion functions. 450 451 def convert(typename, value): 452 return default_converters[typename](value) 453 454 def boolean(s): 455 if s.lower() == "true": 456 return True 457 elif s.lower() == "false": 458 return False 459 else: 460 raise ValueError, "String value %s not convertable to boolean." % repr(s) 461 462 def iso8601(s): 463 year, month, day, hour, minute, second = map(int, (s[:4], s[4:6], s[6:8], s[9:11], s[12:14], s[15:17])) 464 return datetime.datetime(year, month, day, hour, minute, second) 465 466 default_converters = { 467 "string" : unicode, 468 "int" : int, 469 "i4" : int, 470 "double" : float, 471 "boolean" : boolean, 472 "dateTime.iso8601" : iso8601, 473 "base64" : str 474 } 475 476 # Implementation-related functionality. 477 478 class XMLRPCImplementation(libxml2dom.Implementation): 479 480 "Contains an XML-RPC-specific implementation." 481 482 # Mapping of element names to wrappers. 483 484 _class_for_name = { 485 "methodCall" : XMLRPCMethodElement, 486 "methodResponse" : XMLRPCMethodElement, 487 "methodName" : XMLRPCMethodNameElement, 488 "params" : XMLRPCParametersElement, 489 "param" : XMLRPCParameterElement, 490 "fault" : XMLRPCFaultElement, 491 "string" : XMLRPCStringElement, 492 "int" : XMLRPCIntegerElement, 493 "i4" : XMLRPCIntegerElement, 494 "boolean" : XMLRPCBooleanElement, 495 "double" : XMLRPCDoubleElement, 496 "dateTime.iso8601" : XMLRPCDateTimeElement, 497 "base64" : XMLRPCBase64Element, 498 "struct" : XMLRPCStructElement, 499 "member" : XMLRPCMemberElement, 500 "value" : XMLRPCValueElement, 501 "name" : XMLRPCNameElement, 502 "array" : XMLRPCArrayElement, 503 "data" : XMLRPCDataElement 504 } 505 506 # Wrapping of documents. 507 508 def adoptDocument(self, node): 509 return XMLRPCDocument(node, self) 510 511 # Factory functions. 512 513 def get_node(self, _node, context_node): 514 515 """ 516 Get a libxml2dom node for the given low-level '_node' and libxml2dom 517 'context_node'. 518 """ 519 520 if Node_nodeType(_node) == context_node.ELEMENT_NODE: 521 522 # Make special objects for certain elements. 523 # Otherwise, make generic XML-RPC elements. 524 525 cls = self._class_for_name.get(Node_localName(_node)) or XMLRPCElement 526 return cls(_node, self, context_node.ownerDocument) 527 528 else: 529 return libxml2dom.Implementation.get_node(self, _node, context_node) 530 531 # Convenience functions. 532 533 def createXMLRPCMessage(self, namespaceURI, localName): 534 535 "Create a new XML-RPC message document (fragment)." 536 537 return XMLRPCDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement 538 539 def createMethodCall(self, name=None): 540 541 """ 542 Create and return a message fragment for a method call having the given 543 'name'. 544 """ 545 546 message = self.createXMLRPCMessage(None, "methodCall") 547 if name is not None: 548 message.methodName = name 549 return message 550 551 def createMethodResponse(self): 552 553 "Create and return a message fragment for a method response." 554 555 return self.createXMLRPCMessage(None, "methodResponse") 556 557 # Utility functions. 558 559 createDocument = libxml2dom.createDocument 560 createDocumentType = libxml2dom.createDocumentType 561 562 def createXMLRPCMessage(namespaceURI, localName): 563 return default_impl.createXMLRPCMessage(None, localName) 564 565 def createMethodCall(name=None): 566 return default_impl.createMethodCall(name) 567 568 def createMethodResponse(): 569 return default_impl.createMethodResponse() 570 571 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 572 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 573 574 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 575 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 576 577 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 578 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 579 580 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 581 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 582 583 # Single instance of the implementation. 584 585 default_impl = XMLRPCImplementation() 586 587 # vim: tabstop=4 expandtab shiftwidth=4