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 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 _parameterValues(self): 173 return [value.container.contents for value in self.xpath("./params/param/value")] 174 175 # Node construction methods. 176 177 def createMethodName(self): 178 return self.ownerDocument.createElement("methodName") 179 180 def createParameters(self): 181 return self.ownerDocument.createElement("params") 182 183 def createFault(self): 184 return self.ownerDocument.createElement("fault") 185 186 fault = property(_fault) 187 methodNameElement = property(_methodNameElement) 188 methodName = property(_methodName, _setMethodName) 189 parameterValues = property(_parameterValues) 190 191 class XMLRPCArrayElement(XMLRPCNode): 192 193 "An XML-RPC array element." 194 195 def _data(self): 196 return (self.xpath("./data") or [None])[0] 197 198 def _contents(self): 199 return self 200 201 # Sequence emulation. 202 203 def __len__(self): 204 if self.data: 205 return len(self.data) 206 else: 207 return 0 208 209 def __getitem__(self, i): 210 if self.data: 211 return self.data[i] 212 else: 213 raise IndexError, i 214 215 def __eq__(self, other): 216 for i, j in map(None, self, other): 217 if i != j: 218 return False 219 return True 220 221 # Node construction methods. 222 223 def createData(self): 224 return self.ownerDocument.createElement("data") 225 226 data = property(_data) 227 contents = property(_contents) 228 229 class XMLRPCStructElement(XMLRPCNode): 230 231 "An XML-RPC structure element." 232 233 def _members(self): 234 return self.xpath("./member") 235 236 def _contents(self): 237 return self 238 239 # Sequence emulation. 240 241 def __len__(self): 242 return len(self.members) 243 244 def __getitem__(self, i): 245 return self.members[i] 246 247 def __eq__(self, other): 248 for i, j in map(None, self, other): 249 if i != j: 250 return False 251 return True 252 253 # Node construction methods. 254 255 def createMember(self): 256 return self.ownerDocument.createElement("member") 257 258 members = property(_members) 259 contents = property(_contents) 260 261 class XMLRPCDataElement(XMLRPCNode): 262 263 "An XML-RPC array data element." 264 265 def _values(self): 266 return self.xpath("./value") 267 268 # Sequence emulation. 269 270 def __len__(self): 271 return len(self.values) 272 273 def __getitem__(self, i): 274 return self.values[i].container.contents 275 276 # Node construction methods. 277 278 def createValue(self): 279 return self.ownerDocument.createElement("value") 280 281 values = property(_values) 282 283 class XMLRPCMemberElement(XMLRPCNode): 284 285 "An XML-RPC structure member element." 286 287 def _value(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): 322 return self.ownerDocument.createElement("name") 323 324 def createValue(self): 325 return self.ownerDocument.createElement("value") 326 327 value = property(_value) 328 nameElement = property(_nameElement) 329 memberName = property(_memberName, _setMemberName) 330 contents = property(_contents) 331 332 class XMLRPCStringElement(XMLRPCNode): 333 334 "An XML-RPC string element." 335 336 typename = "string" 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 def _contents(self): 348 return convert(self.typename, self.value) 349 350 def __eq__(self, other): 351 if hasattr(other, "contents"): 352 return self.contents == other.contents 353 else: 354 return self.contents == other 355 356 value = property(_value, _setValue) 357 contents = property(_contents) 358 359 class XMLRPCNameElement(XMLRPCStringElement): 360 361 "An XML-RPC name element." 362 363 pass 364 365 class XMLRPCValueElement(XMLRPCStringElement): 366 367 "An XML-RPC value element." 368 369 def _type(self): 370 elements = self.xpath("*") 371 if elements: 372 return elements[0].localName 373 else: 374 return "string" 375 376 def _container(self): 377 return (self.xpath("*") or [self])[0] 378 379 type = property(_type) 380 container = property(_container) 381 382 class XMLRPCMethodNameElement(XMLRPCStringElement): 383 384 "An XML-RPC method element." 385 386 pass 387 388 class XMLRPCIntegerElement(XMLRPCStringElement): 389 390 "An XML-RPC integer element." 391 392 typename = "int" 393 394 class XMLRPCBooleanElement(XMLRPCStringElement): 395 396 "An XML-RPC boolean element." 397 398 typename = "boolean" 399 400 class XMLRPCDoubleElement(XMLRPCStringElement): 401 402 "An XML-RPC double floating point number element." 403 404 typename = "double" 405 406 class XMLRPCDateTimeElement(XMLRPCStringElement): 407 408 "An XML-RPC date/time element." 409 410 typename = "datetime" 411 412 class XMLRPCBase64Element(XMLRPCStringElement): 413 414 "An XML-RPC integer element." 415 416 typename = "base64" 417 418 class XMLRPCFaultElement(XMLRPCNode): 419 420 "An XML-RPC fault element." 421 422 def _code(self): 423 code = self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int") 424 if code: 425 return code[0].value 426 else: 427 return None 428 429 def _reason(self): 430 reason = self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string") 431 if reason: 432 return reason[0].value 433 else: 434 return None 435 436 code = property(_code) 437 reason = property(_reason) 438 439 # Conversion functions. 440 441 def convert(typename, value): 442 return default_converters[typename](value) 443 444 def boolean(s): 445 if s.lower() == "true": 446 return True 447 elif s.lower() == "false": 448 return False 449 else: 450 raise ValueError, "String value %s not convertable to boolean." % repr(s) 451 452 def iso8601(s): 453 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])) 454 return datetime.datetime(year, month, day, hour, minute, second) 455 456 default_converters = { 457 "string" : unicode, 458 "int" : int, 459 "i4" : int, 460 "double" : float, 461 "boolean" : boolean, 462 "dateTime.iso8601" : iso8601, 463 "base64" : str 464 } 465 466 # Utility functions. 467 468 createDocument = libxml2dom.createDocument 469 createDocumentType = libxml2dom.createDocumentType 470 471 def createXMLRPCMessage(namespaceURI, localName): 472 return default_impl.createXMLRPCMessage(None, localName) 473 474 def createMethodCall(): 475 return default_impl.createMethodCall() 476 477 def createMethodResponse(): 478 return default_impl.createMethodResponse() 479 480 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 481 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 482 483 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 484 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 485 486 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 487 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 488 489 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 490 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 491 492 # Single instance of the implementation. 493 494 default_impl = XMLRPCImplementation() 495 496 # vim: tabstop=4 expandtab shiftwidth=4