paulb@304 | 1 | #!/usr/bin/env python |
paulb@304 | 2 | |
paulb@304 | 3 | """ |
paulb@304 | 4 | XML-RPC support using libxml2dom. |
paulb@304 | 5 | |
paulb@304 | 6 | See: http://www.xmlrpc.com/spec |
paulb@304 | 7 | |
paul@342 | 8 | Copyright (C) 2007, 2008 Paul Boddie <paul@boddie.org.uk> |
paulb@304 | 9 | |
paulb@304 | 10 | This program is free software; you can redistribute it and/or modify it under |
paulb@304 | 11 | the terms of the GNU Lesser General Public License as published by the Free |
paulb@304 | 12 | Software Foundation; either version 3 of the License, or (at your option) any |
paulb@304 | 13 | later version. |
paulb@304 | 14 | |
paulb@304 | 15 | This program is distributed in the hope that it will be useful, but WITHOUT |
paulb@304 | 16 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
paulb@304 | 17 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
paulb@304 | 18 | details. |
paulb@304 | 19 | |
paulb@304 | 20 | You should have received a copy of the GNU Lesser General Public License along |
paulb@304 | 21 | with this program. If not, see <http://www.gnu.org/licenses/>. |
paulb@304 | 22 | |
paulb@304 | 23 | -------- |
paulb@304 | 24 | |
paulb@304 | 25 | The sending and receiving of XML-RPC messages can be done using traditional HTTP |
paulb@304 | 26 | libraries. |
paulb@304 | 27 | |
paul@344 | 28 | See tests/test_xmlrpc_parse.py for more details. |
paul@344 | 29 | |
paul@344 | 30 | Useful properties: |
paul@344 | 31 | |
paul@344 | 32 | * container - returns the actual value element containing transmitted data |
paul@344 | 33 | * contents - returns the converted data from within a container or a |
paul@344 | 34 | container object |
paulb@304 | 35 | """ |
paulb@304 | 36 | |
paulb@304 | 37 | import libxml2dom |
paulb@304 | 38 | from libxml2dom.macrolib import * |
paulb@304 | 39 | from libxml2dom.macrolib import \ |
paulb@304 | 40 | createDocument as Node_createDocument |
paulb@315 | 41 | import datetime |
paul@344 | 42 | import xml.dom |
paul@345 | 43 | from libxml2dom.values import ContentValue, SequenceValue |
paulb@304 | 44 | |
paulb@304 | 45 | # Node classes. |
paulb@304 | 46 | |
paulb@304 | 47 | class XMLRPCNode(libxml2dom.Node): |
paulb@304 | 48 | |
paulb@304 | 49 | "Convenience modifications to nodes specific to libxml2dom.xmlrpc." |
paulb@304 | 50 | |
paul@342 | 51 | def add_or_replace_element(self, new_element): |
paul@344 | 52 | |
paul@344 | 53 | """ |
paul@344 | 54 | Add or replace the given 'new_element', using its localName to find any |
paul@344 | 55 | element to be replaced. |
paul@344 | 56 | """ |
paul@344 | 57 | |
paul@343 | 58 | elements = self.xpath(new_element.localName) |
paul@342 | 59 | if elements: |
paul@342 | 60 | self.replaceChild(new_element, elements[0]) |
paul@342 | 61 | else: |
paul@342 | 62 | self.appendChild(new_element) |
paulb@304 | 63 | |
paul@344 | 64 | def serialise_value(self, parent, value): |
paul@344 | 65 | |
paul@344 | 66 | "Serialise, under the 'parent', the given 'value' object." |
paul@344 | 67 | |
paul@344 | 68 | if isinstance(value, (str, int, float, bool)): |
paul@348 | 69 | valueElement = parent.makeValue(typenames[value.__class__.__name__]) |
paul@344 | 70 | valueElement.container.value = str(value) |
paul@344 | 71 | |
paul@344 | 72 | elif isinstance(value, datetime.datetime): |
paul@348 | 73 | valueElement = parent.makeValue("dateTime.iso8601") |
paul@344 | 74 | valueElement.container.value = value.strftime("%Y%m%dT%H:%M:%S") |
paul@344 | 75 | |
paul@344 | 76 | elif isinstance(value, (tuple, list)): |
paul@348 | 77 | array = parent.makeValue("array").container |
paul@348 | 78 | dataElement = array.makeData() |
paul@344 | 79 | for v in value: |
paul@344 | 80 | self.serialise_value(dataElement, v) |
paul@344 | 81 | |
paul@344 | 82 | elif isinstance(value, dict): |
paul@348 | 83 | struct = parent.makeValue("struct").container |
paul@344 | 84 | for k, v in value.items(): |
paul@348 | 85 | member = struct.makeMember() |
paul@344 | 86 | member.memberName = str(k) |
paul@344 | 87 | self.serialise_value(member, v) |
paul@344 | 88 | |
paul@344 | 89 | else: |
paul@344 | 90 | raise ValueError, "Value %r cannot be serialised." % value |
paul@344 | 91 | |
paulb@307 | 92 | class XMLRPCElement(XMLRPCNode): |
paulb@307 | 93 | |
paulb@307 | 94 | "An XML-RPC element." |
paulb@307 | 95 | |
paulb@307 | 96 | pass |
paulb@307 | 97 | |
paulb@304 | 98 | class XMLRPCDocument(libxml2dom._Document, XMLRPCNode): |
paulb@304 | 99 | |
paulb@304 | 100 | "An XML-RPC document fragment." |
paulb@304 | 101 | |
paulb@304 | 102 | def _method(self): |
paulb@309 | 103 | return (self.xpath("methodCall|methodResponse") or [None])[0] |
paulb@307 | 104 | |
paulb@307 | 105 | def _fault(self): |
paulb@307 | 106 | if self.method is not None: |
paulb@307 | 107 | return self.method.fault |
paulb@307 | 108 | else: |
paulb@307 | 109 | return None |
paulb@304 | 110 | |
paulb@309 | 111 | method = property(_method) |
paulb@309 | 112 | fault = property(_fault) |
paulb@309 | 113 | |
paulb@307 | 114 | # Node construction methods. |
paulb@307 | 115 | |
paul@348 | 116 | def createMethodCall(self): |
paul@348 | 117 | return self.ownerDocument.createElement("methodCall") |
paul@348 | 118 | |
paul@348 | 119 | def makeMethodCall(self): |
paul@348 | 120 | e = self.createMethodCall() |
paul@348 | 121 | self.add_or_replace_element(e) |
paul@342 | 122 | return e |
paulb@307 | 123 | |
paul@348 | 124 | def createMethodResponse(self): |
paul@348 | 125 | return self.ownerDocument.createElement("methodResponse") |
paul@348 | 126 | |
paul@348 | 127 | def makeMethodResponse(self): |
paul@348 | 128 | e = self.createMethodResponse() |
paul@348 | 129 | self.add_or_replace_element(e) |
paul@342 | 130 | return e |
paulb@307 | 131 | |
paulb@304 | 132 | class XMLRPCMethodElement(XMLRPCNode): |
paulb@304 | 133 | |
paulb@304 | 134 | "An XML-RPC method element." |
paulb@304 | 135 | |
paulb@304 | 136 | def _fault(self): |
paulb@307 | 137 | return (self.xpath("./fault") or [None])[0] |
paulb@304 | 138 | |
paul@343 | 139 | def _params(self): |
paul@343 | 140 | return (self.xpath("./params") or [None])[0] |
paul@343 | 141 | |
paulb@309 | 142 | def _methodNameElement(self): |
paulb@307 | 143 | return (self.xpath("./methodName") or [None])[0] |
paulb@307 | 144 | |
paulb@309 | 145 | def _methodName(self): |
paulb@309 | 146 | name = self.methodNameElement |
paulb@307 | 147 | if name is not None: |
paulb@307 | 148 | return name.value |
paulb@307 | 149 | else: |
paulb@307 | 150 | return None |
paulb@307 | 151 | |
paulb@309 | 152 | def _setMethodName(self, name): |
paulb@309 | 153 | if self.methodNameElement is None: |
paulb@307 | 154 | methodName = self.createMethodName() |
paulb@307 | 155 | self.appendChild(methodName) |
paulb@309 | 156 | self.methodNameElement.value = name |
paulb@304 | 157 | |
paulb@314 | 158 | def _parameterValues(self): |
paul@344 | 159 | if self.params: |
paul@344 | 160 | return self.params.values() |
paul@344 | 161 | else: |
paul@344 | 162 | return None |
paul@344 | 163 | |
paul@344 | 164 | def _setParameterValues(self, values): |
paul@348 | 165 | params = self.makeParameters() |
paul@344 | 166 | for value in values: |
paul@348 | 167 | param = params.makeParameter() |
paul@344 | 168 | self.serialise_value(param, value) |
paulb@307 | 169 | |
paul@343 | 170 | def _parameters(self): |
paul@344 | 171 | |
paul@344 | 172 | "Return a list of the individual param elements." |
paul@344 | 173 | |
paul@343 | 174 | return self.xpath("./params/param") |
paul@343 | 175 | |
paulb@307 | 176 | # Node construction methods. |
paulb@307 | 177 | |
paul@348 | 178 | def createMethodName(self): |
paul@348 | 179 | return self.ownerDocument.createElement("methodName") |
paul@348 | 180 | |
paul@348 | 181 | def makeMethodName(self): |
paul@348 | 182 | e = self.createMethodName() |
paul@348 | 183 | self.add_or_replace_element(e) |
paul@342 | 184 | return e |
paulb@307 | 185 | |
paul@348 | 186 | def createParameters(self): |
paul@348 | 187 | return self.ownerDocument.createElement("params") |
paul@348 | 188 | |
paul@348 | 189 | def makeParameters(self): |
paul@348 | 190 | e = self.createParameters() |
paul@348 | 191 | self.add_or_replace_element(e) |
paul@342 | 192 | return e |
paulb@307 | 193 | |
paul@348 | 194 | def createFault(self): |
paul@348 | 195 | return self.ownerDocument.createElement("fault") |
paul@348 | 196 | |
paul@348 | 197 | def makeFault(self): |
paul@348 | 198 | e = self.createFault() |
paul@348 | 199 | self.add_or_replace_element(e) |
paul@342 | 200 | return e |
paulb@304 | 201 | |
paulb@304 | 202 | fault = property(_fault) |
paul@343 | 203 | params = property(_params) |
paulb@309 | 204 | methodNameElement = property(_methodNameElement) |
paulb@309 | 205 | methodName = property(_methodName, _setMethodName) |
paul@344 | 206 | parameterValues = property(_parameterValues, _setParameterValues) |
paul@343 | 207 | parameters = property(_parameters) |
paulb@312 | 208 | |
paul@345 | 209 | class XMLRPCParametersElement(SequenceValue, XMLRPCNode): |
paul@342 | 210 | |
paul@344 | 211 | """ |
paul@344 | 212 | An XML-RPC parameters/params element. |
paul@344 | 213 | |
paul@344 | 214 | This element behaves like a list in that values can be appended to it and |
paul@344 | 215 | these will be added as new parameters. |
paul@344 | 216 | """ |
paul@342 | 217 | |
paul@343 | 218 | def _parameters(self): |
paul@344 | 219 | |
paul@344 | 220 | "Return a list of the individual param elements." |
paul@344 | 221 | |
paul@343 | 222 | return self.xpath("./param") |
paul@343 | 223 | |
paul@344 | 224 | # Sequence emulation. |
paul@344 | 225 | |
paul@344 | 226 | def append(self, value): |
paul@348 | 227 | param = self.makeParameter() |
paul@344 | 228 | self.serialise_value(param, value) |
paul@344 | 229 | |
paul@344 | 230 | def values(self): |
paul@344 | 231 | return [value.contents for value in self.xpath("./param/value")] |
paul@344 | 232 | |
paul@344 | 233 | def __repr__(self): |
paul@344 | 234 | return "<XMLRPCParametersElement: %r>" % self.parameters |
paul@344 | 235 | |
paul@342 | 236 | # Node construction methods. |
paul@342 | 237 | |
paul@348 | 238 | def createParameter(self): |
paul@348 | 239 | return self.ownerDocument.createElement("param") |
paul@348 | 240 | |
paul@348 | 241 | def makeParameter(self): |
paul@348 | 242 | e = self.createParameter() |
paul@348 | 243 | self.appendChild(e) |
paul@342 | 244 | return e |
paul@342 | 245 | |
paul@343 | 246 | parameters = property(_parameters) |
paul@343 | 247 | |
paul@345 | 248 | class XMLRPCParameterElement(ContentValue, XMLRPCNode): |
paul@344 | 249 | |
paul@344 | 250 | """ |
paul@344 | 251 | An XML-RPC parameter/param element. |
paul@342 | 252 | |
paul@344 | 253 | This element behaves like a list in that values can be appended to it and |
paul@344 | 254 | these will be added either as new elements in an existing array or struct, |
paul@344 | 255 | or as new elements in a new array. |
paul@344 | 256 | """ |
paul@342 | 257 | |
paul@343 | 258 | def _valueElement(self): |
paul@343 | 259 | return (self.xpath("./value") or [None])[0] |
paul@343 | 260 | |
paul@344 | 261 | def _contents(self): |
paul@344 | 262 | if self.valueElement is not None: |
paul@344 | 263 | return self.valueElement.contents |
paul@344 | 264 | else: |
paul@344 | 265 | return self |
paul@344 | 266 | |
paul@344 | 267 | # Sequence emulation. |
paul@344 | 268 | |
paul@344 | 269 | def __len__(self): |
paul@344 | 270 | return len(self.values()) |
paul@344 | 271 | |
paul@344 | 272 | def __getitem__(self, i): |
paul@344 | 273 | return self.values()[i] |
paul@344 | 274 | |
paul@344 | 275 | def append(self, value): |
paul@344 | 276 | if self.valueElement is None: |
paul@348 | 277 | self.makeValue("array") |
paul@344 | 278 | self.contents.append(value) |
paul@344 | 279 | |
paul@344 | 280 | def values(self): |
paul@344 | 281 | if self.contents is not self: |
paul@344 | 282 | return self.contents.values() |
paul@344 | 283 | else: |
paul@344 | 284 | return [] |
paul@344 | 285 | |
paul@344 | 286 | def __repr__(self): |
paul@344 | 287 | if self.contents is self: |
paul@344 | 288 | return "<XMLRPCParameterElement>" |
paul@344 | 289 | else: |
paul@344 | 290 | return "<XMLRPCParameterElement: %r>" % self.contents |
paul@344 | 291 | |
paul@342 | 292 | # Node construction methods. |
paul@342 | 293 | |
paul@348 | 294 | def createValue(self, typename=None): |
paul@342 | 295 | value = self.ownerDocument.createElement("value") |
paul@342 | 296 | if typename is not None: |
paul@342 | 297 | contents = self.ownerDocument.createElement(typename) |
paul@342 | 298 | value.appendChild(contents) |
paul@348 | 299 | return value |
paul@348 | 300 | |
paul@348 | 301 | def makeValue(self, typename=None): |
paul@348 | 302 | value = self.createValue(typename) |
paul@348 | 303 | self.add_or_replace_element(value) |
paul@342 | 304 | return value |
paul@342 | 305 | |
paul@343 | 306 | valueElement = property(_valueElement) |
paul@344 | 307 | contents = property(_contents) |
paul@343 | 308 | |
paul@345 | 309 | class XMLRPCArrayElement(SequenceValue, XMLRPCNode): |
paulb@312 | 310 | |
paul@344 | 311 | """ |
paul@344 | 312 | An XML-RPC array element. |
paul@344 | 313 | |
paul@344 | 314 | This element behaves like a list in that values can be appended to it and |
paul@344 | 315 | these will be added as new elements in the array. |
paul@344 | 316 | """ |
paulb@312 | 317 | |
paul@343 | 318 | def _dataElement(self): |
paulb@312 | 319 | return (self.xpath("./data") or [None])[0] |
paulb@312 | 320 | |
paulb@315 | 321 | def _contents(self): |
paulb@315 | 322 | return self |
paulb@315 | 323 | |
paulb@315 | 324 | # Sequence emulation. |
paulb@315 | 325 | |
paul@344 | 326 | def append(self, value): |
paul@344 | 327 | if self.dataElement is None: |
paul@348 | 328 | self.makeData() |
paul@344 | 329 | self.serialise_value(self.dataElement, value) |
paulb@315 | 330 | |
paul@344 | 331 | def values(self): |
paul@344 | 332 | return [v.contents for v in self.xpath("./data/value")] |
paulb@315 | 333 | |
paul@344 | 334 | def __repr__(self): |
paul@344 | 335 | return "<XMLRPCArrayElement: %r>" % self.values() |
paulb@315 | 336 | |
paulb@312 | 337 | # Node construction methods. |
paulb@312 | 338 | |
paul@348 | 339 | def createData(self): |
paul@348 | 340 | return self.ownerDocument.createElement("data") |
paul@348 | 341 | |
paul@348 | 342 | def makeData(self): |
paul@348 | 343 | e = self.createData() |
paul@348 | 344 | self.add_or_replace_element(e) |
paul@342 | 345 | return e |
paulb@312 | 346 | |
paul@343 | 347 | dataElement = property(_dataElement) |
paulb@315 | 348 | contents = property(_contents) |
paulb@309 | 349 | |
paul@345 | 350 | class XMLRPCStructElement(SequenceValue, XMLRPCNode): |
paulb@309 | 351 | |
paul@344 | 352 | """ |
paul@344 | 353 | An XML-RPC structure element. |
paul@344 | 354 | |
paul@344 | 355 | This element behaves like a list in that values can be appended to it and |
paul@344 | 356 | these will be added as new (name, value) members in the structure. |
paul@344 | 357 | """ |
paulb@309 | 358 | |
paulb@309 | 359 | def _members(self): |
paulb@309 | 360 | return self.xpath("./member") |
paulb@309 | 361 | |
paulb@315 | 362 | def _contents(self): |
paulb@315 | 363 | return self |
paulb@315 | 364 | |
paulb@315 | 365 | # Sequence emulation. |
paulb@315 | 366 | |
paulb@315 | 367 | def __len__(self): |
paulb@315 | 368 | return len(self.members) |
paulb@315 | 369 | |
paulb@315 | 370 | def __getitem__(self, i): |
paulb@315 | 371 | return self.members[i] |
paulb@315 | 372 | |
paul@344 | 373 | def append(self, item): |
paul@344 | 374 | name, value = item |
paul@348 | 375 | member = self.makeMember() |
paul@344 | 376 | member.memberName = name |
paul@344 | 377 | self.serialise_value(member, value) |
paul@344 | 378 | |
paul@344 | 379 | def __repr__(self): |
paul@344 | 380 | return "<XMLRPCStructElement: %r>" % self.items() |
paul@344 | 381 | |
paul@344 | 382 | # Dictionary emulation. |
paul@344 | 383 | |
paul@344 | 384 | def keys(self): |
paul@344 | 385 | return [member.memberName for member in self.members] |
paul@344 | 386 | |
paul@344 | 387 | def values(self): |
paul@344 | 388 | return [member.memberValue for member in self.members] |
paul@344 | 389 | |
paul@344 | 390 | def items(self): |
paul@344 | 391 | return [(member.memberName, member.memberValue) for member in self.members] |
paulb@315 | 392 | |
paulb@309 | 393 | # Node construction methods. |
paulb@309 | 394 | |
paul@348 | 395 | def createMember(self): |
paul@348 | 396 | return self.ownerDocument.createElement("member") |
paul@348 | 397 | |
paul@348 | 398 | def makeMember(self): |
paul@348 | 399 | e = self.createMember() |
paul@348 | 400 | self.appendChild(e) |
paul@342 | 401 | return e |
paulb@309 | 402 | |
paulb@309 | 403 | members = property(_members) |
paulb@315 | 404 | contents = property(_contents) |
paulb@309 | 405 | |
paul@345 | 406 | class XMLRPCDataElement(SequenceValue, XMLRPCNode): |
paul@344 | 407 | |
paul@344 | 408 | """ |
paul@344 | 409 | An XML-RPC array data element. |
paulb@312 | 410 | |
paul@344 | 411 | This element behaves like a list in that values can be appended to it and |
paul@344 | 412 | these will be added as new elements in the array. |
paul@344 | 413 | """ |
paulb@312 | 414 | |
paul@344 | 415 | def _contents(self): |
paul@344 | 416 | return self |
paulb@312 | 417 | |
paulb@315 | 418 | # Sequence emulation. |
paulb@315 | 419 | |
paul@344 | 420 | def append(self, value): |
paul@344 | 421 | self.serialise_value(self, value) |
paulb@315 | 422 | |
paul@344 | 423 | def values(self): |
paul@344 | 424 | return [v.contents for v in self.xpath("./value")] |
paulb@312 | 425 | |
paulb@312 | 426 | # Node construction methods. |
paulb@312 | 427 | |
paul@348 | 428 | def createValue(self, typename=None): |
paul@344 | 429 | value = self.ownerDocument.createElement("value") |
paul@344 | 430 | if typename is not None: |
paul@344 | 431 | contents = self.ownerDocument.createElement(typename) |
paul@344 | 432 | value.appendChild(contents) |
paul@348 | 433 | return value |
paul@348 | 434 | |
paul@348 | 435 | def makeValue(self, typename=None): |
paul@348 | 436 | value = self.createValue(typename) |
paul@348 | 437 | self.appendChild(value) |
paul@344 | 438 | return value |
paulb@312 | 439 | |
paul@344 | 440 | contents = property(_contents) |
paulb@315 | 441 | |
paulb@309 | 442 | class XMLRPCMemberElement(XMLRPCNode): |
paulb@309 | 443 | |
paul@344 | 444 | """ |
paul@344 | 445 | An XML-RPC structure member element. |
paul@344 | 446 | |
paul@344 | 447 | This element behaves like a tuple of the form (name, value). |
paul@344 | 448 | """ |
paulb@309 | 449 | |
paul@343 | 450 | def _valueElement(self): |
paulb@312 | 451 | return (self.xpath("./value") or [None])[0] |
paulb@312 | 452 | |
paulb@309 | 453 | def _nameElement(self): |
paulb@309 | 454 | return (self.xpath("./name") or [None])[0] |
paulb@309 | 455 | |
paulb@309 | 456 | def _memberName(self): |
paulb@309 | 457 | if self.nameElement is not None: |
paulb@309 | 458 | return self.nameElement.value |
paulb@309 | 459 | else: |
paulb@309 | 460 | return None |
paulb@309 | 461 | |
paulb@309 | 462 | def _setMemberName(self, name): |
paulb@309 | 463 | if self.nameElement is None: |
paulb@309 | 464 | nameElement = self.createName() |
paulb@309 | 465 | self.appendChild(nameElement) |
paulb@309 | 466 | self.nameElement.value = name |
paulb@309 | 467 | |
paul@344 | 468 | def _memberValue(self): |
paul@344 | 469 | if self.valueElement is None: |
paul@344 | 470 | return None |
paul@344 | 471 | else: |
paul@344 | 472 | return self.valueElement.contents |
paul@344 | 473 | |
paulb@315 | 474 | def _contents(self): |
paulb@315 | 475 | return self |
paulb@315 | 476 | |
paulb@315 | 477 | # Item (name, value) emulation. |
paulb@315 | 478 | |
paulb@315 | 479 | def __len__(self): |
paulb@315 | 480 | return 2 |
paulb@315 | 481 | |
paulb@315 | 482 | def __getitem__(self, i): |
paul@344 | 483 | return (self.memberName, self.valueElement.contents)[i] |
paul@344 | 484 | |
paul@344 | 485 | # Equality testing. |
paulb@315 | 486 | |
paulb@315 | 487 | def __eq__(self, other): |
paulb@315 | 488 | return self[0] == other[0] and self[1] == other[1] |
paulb@315 | 489 | |
paul@344 | 490 | def __ne__(self, other): |
paul@344 | 491 | return not self.__eq__(other) |
paul@344 | 492 | |
paulb@309 | 493 | # Node construction methods. |
paulb@309 | 494 | |
paul@348 | 495 | def createName(self): |
paul@348 | 496 | return self.ownerDocument.createElement("name") |
paul@348 | 497 | |
paul@348 | 498 | def makeName(self): |
paul@348 | 499 | e = self.createName() |
paul@348 | 500 | self.add_or_replace_element(e) |
paul@342 | 501 | return e |
paulb@309 | 502 | |
paul@348 | 503 | def createValue(self, typename=None): |
paul@344 | 504 | value = self.ownerDocument.createElement("value") |
paul@344 | 505 | if typename is not None: |
paul@344 | 506 | contents = self.ownerDocument.createElement(typename) |
paul@344 | 507 | value.appendChild(contents) |
paul@348 | 508 | return value |
paul@348 | 509 | |
paul@348 | 510 | def makeValue(self, typename=None): |
paul@348 | 511 | value = self.createValue(typename) |
paul@348 | 512 | self.add_or_replace_element(value) |
paul@344 | 513 | return value |
paulb@309 | 514 | |
paulb@309 | 515 | nameElement = property(_nameElement) |
paulb@309 | 516 | memberName = property(_memberName, _setMemberName) |
paul@344 | 517 | memberValue = property(_memberValue) |
paul@343 | 518 | valueElement = property(_valueElement) |
paulb@315 | 519 | contents = property(_contents) |
paulb@304 | 520 | |
paul@345 | 521 | class XMLRPCStringElement(ContentValue, XMLRPCNode): |
paulb@304 | 522 | |
paulb@304 | 523 | "An XML-RPC string element." |
paulb@304 | 524 | |
paulb@315 | 525 | typename = "string" |
paulb@315 | 526 | |
paulb@304 | 527 | def _value(self): |
paulb@304 | 528 | return self.textContent.strip() |
paulb@304 | 529 | |
paulb@304 | 530 | def _setValue(self, value): |
paulb@304 | 531 | for node in self.childNodes: |
paulb@304 | 532 | self.removeChild(node) |
paulb@304 | 533 | text = self.ownerDocument.createTextNode(value) |
paulb@304 | 534 | self.appendChild(text) |
paulb@304 | 535 | |
paulb@315 | 536 | def _contents(self): |
paulb@315 | 537 | return convert(self.typename, self.value) |
paulb@315 | 538 | |
paul@344 | 539 | def __repr__(self): |
paul@344 | 540 | return "<%s: %r>" % (self.__class__.__name__, self.contents) |
paulb@315 | 541 | |
paulb@304 | 542 | value = property(_value, _setValue) |
paulb@315 | 543 | contents = property(_contents) |
paulb@304 | 544 | |
paulb@309 | 545 | class XMLRPCNameElement(XMLRPCStringElement): |
paulb@309 | 546 | |
paulb@309 | 547 | "An XML-RPC name element." |
paulb@309 | 548 | |
paulb@309 | 549 | pass |
paulb@309 | 550 | |
paul@344 | 551 | class XMLRPCMethodNameElement(XMLRPCNameElement): |
paul@344 | 552 | |
paul@344 | 553 | "An XML-RPC method element." |
paul@344 | 554 | |
paul@344 | 555 | pass |
paul@344 | 556 | |
paulb@307 | 557 | class XMLRPCValueElement(XMLRPCStringElement): |
paulb@307 | 558 | |
paulb@307 | 559 | "An XML-RPC value element." |
paulb@307 | 560 | |
paul@344 | 561 | def _value(self): |
paul@344 | 562 | if self.container is self: |
paul@344 | 563 | return XMLRPCStringElement._value(self) |
paul@344 | 564 | else: |
paul@348 | 565 | return self.container.value |
paul@344 | 566 | |
paul@344 | 567 | def _setValue(self, value): |
paul@344 | 568 | if self.container is self: |
paul@348 | 569 | XMLRPCStringElement._setValue(self, value) |
paul@344 | 570 | else: |
paul@348 | 571 | self.container.value = value |
paul@344 | 572 | |
paul@344 | 573 | def _contents(self): |
paul@344 | 574 | if self.container is self: |
paul@344 | 575 | return XMLRPCStringElement._contents(self) |
paul@344 | 576 | else: |
paul@344 | 577 | return self.container.contents |
paul@344 | 578 | |
paulb@307 | 579 | def _type(self): |
paul@344 | 580 | if self.container is self: |
paul@344 | 581 | return "string" |
paulb@307 | 582 | else: |
paul@344 | 583 | return self.container.localName |
paulb@307 | 584 | |
paul@342 | 585 | def _setType(self, typename): |
paul@342 | 586 | new_contents = self.ownerDocument.createElement(typename) |
paul@342 | 587 | self.add_or_replace_element(new_contents) |
paul@342 | 588 | |
paulb@307 | 589 | def _container(self): |
paulb@307 | 590 | return (self.xpath("*") or [self])[0] |
paulb@307 | 591 | |
paul@344 | 592 | value = property(_value, _setValue) |
paul@342 | 593 | type = property(_type, _setType) |
paulb@307 | 594 | container = property(_container) |
paul@344 | 595 | contents = property(_contents) |
paulb@304 | 596 | |
paulb@304 | 597 | class XMLRPCIntegerElement(XMLRPCStringElement): |
paulb@304 | 598 | |
paulb@304 | 599 | "An XML-RPC integer element." |
paulb@304 | 600 | |
paulb@315 | 601 | typename = "int" |
paulb@304 | 602 | |
paulb@304 | 603 | class XMLRPCBooleanElement(XMLRPCStringElement): |
paulb@304 | 604 | |
paulb@304 | 605 | "An XML-RPC boolean element." |
paulb@304 | 606 | |
paulb@315 | 607 | typename = "boolean" |
paulb@304 | 608 | |
paulb@304 | 609 | class XMLRPCDoubleElement(XMLRPCStringElement): |
paulb@304 | 610 | |
paulb@304 | 611 | "An XML-RPC double floating point number element." |
paulb@304 | 612 | |
paulb@315 | 613 | typename = "double" |
paulb@304 | 614 | |
paulb@304 | 615 | class XMLRPCDateTimeElement(XMLRPCStringElement): |
paulb@304 | 616 | |
paulb@304 | 617 | "An XML-RPC date/time element." |
paulb@304 | 618 | |
paulb@315 | 619 | typename = "datetime" |
paulb@304 | 620 | |
paulb@304 | 621 | class XMLRPCBase64Element(XMLRPCStringElement): |
paulb@304 | 622 | |
paulb@304 | 623 | "An XML-RPC integer element." |
paulb@304 | 624 | |
paulb@315 | 625 | typename = "base64" |
paulb@304 | 626 | |
paulb@304 | 627 | class XMLRPCFaultElement(XMLRPCNode): |
paulb@304 | 628 | |
paulb@304 | 629 | "An XML-RPC fault element." |
paulb@304 | 630 | |
paulb@304 | 631 | def _code(self): |
paulb@307 | 632 | code = self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int") |
paulb@307 | 633 | if code: |
paulb@307 | 634 | return code[0].value |
paulb@307 | 635 | else: |
paulb@307 | 636 | return None |
paulb@304 | 637 | |
paulb@304 | 638 | def _reason(self): |
paulb@307 | 639 | reason = self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string") |
paulb@307 | 640 | if reason: |
paulb@307 | 641 | return reason[0].value |
paulb@307 | 642 | else: |
paulb@307 | 643 | return None |
paulb@304 | 644 | |
paulb@304 | 645 | code = property(_code) |
paulb@304 | 646 | reason = property(_reason) |
paulb@304 | 647 | |
paulb@315 | 648 | # Conversion functions. |
paulb@315 | 649 | |
paulb@315 | 650 | def convert(typename, value): |
paulb@315 | 651 | return default_converters[typename](value) |
paulb@315 | 652 | |
paulb@315 | 653 | def boolean(s): |
paulb@315 | 654 | if s.lower() == "true": |
paulb@315 | 655 | return True |
paulb@315 | 656 | elif s.lower() == "false": |
paulb@315 | 657 | return False |
paulb@315 | 658 | else: |
paul@344 | 659 | raise ValueError, "String value %r not convertable to boolean." % s |
paulb@315 | 660 | |
paulb@315 | 661 | def iso8601(s): |
paulb@315 | 662 | 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])) |
paulb@315 | 663 | return datetime.datetime(year, month, day, hour, minute, second) |
paulb@315 | 664 | |
paulb@315 | 665 | default_converters = { |
paulb@315 | 666 | "string" : unicode, |
paulb@315 | 667 | "int" : int, |
paulb@315 | 668 | "i4" : int, |
paulb@315 | 669 | "double" : float, |
paulb@315 | 670 | "boolean" : boolean, |
paulb@315 | 671 | "dateTime.iso8601" : iso8601, |
paulb@315 | 672 | "base64" : str |
paulb@315 | 673 | } |
paulb@315 | 674 | |
paul@344 | 675 | typenames = { |
paul@344 | 676 | "str" : "string", |
paul@344 | 677 | "int" : "int", |
paul@344 | 678 | "bool" : "boolean", |
paul@344 | 679 | "float" : "double" |
paul@344 | 680 | } |
paul@344 | 681 | |
paul@342 | 682 | # Implementation-related functionality. |
paul@342 | 683 | |
paul@342 | 684 | class XMLRPCImplementation(libxml2dom.Implementation): |
paul@342 | 685 | |
paul@342 | 686 | "Contains an XML-RPC-specific implementation." |
paul@342 | 687 | |
paul@342 | 688 | # Mapping of element names to wrappers. |
paul@342 | 689 | |
paul@342 | 690 | _class_for_name = { |
paul@342 | 691 | "methodCall" : XMLRPCMethodElement, |
paul@342 | 692 | "methodResponse" : XMLRPCMethodElement, |
paul@342 | 693 | "methodName" : XMLRPCMethodNameElement, |
paul@342 | 694 | "params" : XMLRPCParametersElement, |
paul@342 | 695 | "param" : XMLRPCParameterElement, |
paul@342 | 696 | "fault" : XMLRPCFaultElement, |
paul@342 | 697 | "string" : XMLRPCStringElement, |
paul@342 | 698 | "int" : XMLRPCIntegerElement, |
paul@342 | 699 | "i4" : XMLRPCIntegerElement, |
paul@342 | 700 | "boolean" : XMLRPCBooleanElement, |
paul@342 | 701 | "double" : XMLRPCDoubleElement, |
paul@342 | 702 | "dateTime.iso8601" : XMLRPCDateTimeElement, |
paul@342 | 703 | "base64" : XMLRPCBase64Element, |
paul@342 | 704 | "struct" : XMLRPCStructElement, |
paul@342 | 705 | "member" : XMLRPCMemberElement, |
paul@342 | 706 | "value" : XMLRPCValueElement, |
paul@342 | 707 | "name" : XMLRPCNameElement, |
paul@342 | 708 | "array" : XMLRPCArrayElement, |
paul@342 | 709 | "data" : XMLRPCDataElement |
paul@342 | 710 | } |
paul@342 | 711 | |
paul@342 | 712 | # Wrapping of documents. |
paul@342 | 713 | |
paul@342 | 714 | def adoptDocument(self, node): |
paul@342 | 715 | return XMLRPCDocument(node, self) |
paul@342 | 716 | |
paul@342 | 717 | # Factory functions. |
paul@342 | 718 | |
paul@342 | 719 | def get_node(self, _node, context_node): |
paul@342 | 720 | |
paul@342 | 721 | """ |
paul@342 | 722 | Get a libxml2dom node for the given low-level '_node' and libxml2dom |
paul@342 | 723 | 'context_node'. |
paul@342 | 724 | """ |
paul@342 | 725 | |
paul@342 | 726 | if Node_nodeType(_node) == context_node.ELEMENT_NODE: |
paul@342 | 727 | |
paul@342 | 728 | # Make special objects for certain elements. |
paul@342 | 729 | # Otherwise, make generic XML-RPC elements. |
paul@342 | 730 | |
paul@342 | 731 | cls = self._class_for_name.get(Node_localName(_node)) or XMLRPCElement |
paul@342 | 732 | return cls(_node, self, context_node.ownerDocument) |
paul@342 | 733 | |
paul@342 | 734 | else: |
paul@342 | 735 | return libxml2dom.Implementation.get_node(self, _node, context_node) |
paul@342 | 736 | |
paul@342 | 737 | # Convenience functions. |
paul@342 | 738 | |
paul@342 | 739 | def createXMLRPCMessage(self, namespaceURI, localName): |
paul@342 | 740 | |
paul@342 | 741 | "Create a new XML-RPC message document (fragment)." |
paul@342 | 742 | |
paul@342 | 743 | return XMLRPCDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement |
paul@342 | 744 | |
paul@342 | 745 | def createMethodCall(self, name=None): |
paul@342 | 746 | |
paul@342 | 747 | """ |
paul@342 | 748 | Create and return a message fragment for a method call having the given |
paul@342 | 749 | 'name'. |
paul@342 | 750 | """ |
paul@342 | 751 | |
paul@342 | 752 | message = self.createXMLRPCMessage(None, "methodCall") |
paul@342 | 753 | if name is not None: |
paul@342 | 754 | message.methodName = name |
paul@342 | 755 | return message |
paul@342 | 756 | |
paul@342 | 757 | def createMethodResponse(self): |
paul@342 | 758 | |
paul@342 | 759 | "Create and return a message fragment for a method response." |
paul@342 | 760 | |
paul@342 | 761 | return self.createXMLRPCMessage(None, "methodResponse") |
paul@342 | 762 | |
paulb@304 | 763 | # Utility functions. |
paulb@304 | 764 | |
paulb@304 | 765 | createDocument = libxml2dom.createDocument |
paulb@304 | 766 | createDocumentType = libxml2dom.createDocumentType |
paulb@304 | 767 | |
paulb@304 | 768 | def createXMLRPCMessage(namespaceURI, localName): |
paulb@307 | 769 | return default_impl.createXMLRPCMessage(None, localName) |
paulb@307 | 770 | |
paul@342 | 771 | def createMethodCall(name=None): |
paul@342 | 772 | return default_impl.createMethodCall(name) |
paulb@307 | 773 | |
paulb@307 | 774 | def createMethodResponse(): |
paulb@307 | 775 | return default_impl.createMethodResponse() |
paulb@304 | 776 | |
paulb@304 | 777 | def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@304 | 778 | return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@304 | 779 | |
paulb@304 | 780 | def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@304 | 781 | return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@304 | 782 | |
paulb@304 | 783 | def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@304 | 784 | return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@304 | 785 | |
paulb@304 | 786 | def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@304 | 787 | return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@304 | 788 | |
paulb@304 | 789 | # Single instance of the implementation. |
paulb@304 | 790 | |
paulb@304 | 791 | default_impl = XMLRPCImplementation() |
paulb@304 | 792 | |
paulb@304 | 793 | # vim: tabstop=4 expandtab shiftwidth=4 |