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