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