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 | |
paulb@304 | 8 | Copyright (C) 2007 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 | |
paulb@304 | 28 | See tests/xmlrpc_test.py for more details. |
paulb@304 | 29 | """ |
paulb@304 | 30 | |
paulb@304 | 31 | import libxml2dom |
paulb@304 | 32 | from libxml2dom.macrolib import * |
paulb@304 | 33 | from libxml2dom.macrolib import \ |
paulb@304 | 34 | createDocument as Node_createDocument |
paulb@304 | 35 | |
paulb@304 | 36 | class XMLRPCImplementation(libxml2dom.Implementation): |
paulb@304 | 37 | |
paulb@304 | 38 | "Contains an XML-RPC-specific implementation." |
paulb@304 | 39 | |
paulb@304 | 40 | # Wrapping of documents. |
paulb@304 | 41 | |
paulb@304 | 42 | def adoptDocument(self, node): |
paulb@304 | 43 | return XMLRPCDocument(node, self) |
paulb@304 | 44 | |
paulb@304 | 45 | # Factory functions. |
paulb@304 | 46 | |
paulb@304 | 47 | def get_node(self, _node, context_node): |
paulb@304 | 48 | |
paulb@304 | 49 | """ |
paulb@304 | 50 | Get a libxml2dom node for the given low-level '_node' and libxml2dom |
paulb@304 | 51 | 'context_node'. |
paulb@304 | 52 | """ |
paulb@304 | 53 | |
paulb@304 | 54 | if Node_nodeType(_node) == context_node.ELEMENT_NODE: |
paulb@304 | 55 | |
paulb@304 | 56 | # Make special elements. |
paulb@304 | 57 | |
paulb@304 | 58 | if Node_localName(_node) in ("methodCall", "methodResponse"): |
paulb@304 | 59 | return XMLRPCMethodElement(_node, self, context_node.ownerDocument) |
paulb@304 | 60 | elif Node_localName(_node) == "methodName": |
paulb@304 | 61 | return XMLRPCMethodNameElement(_node, self, context_node.ownerDocument) |
paulb@304 | 62 | elif Node_localName(_node) == "fault": |
paulb@304 | 63 | return XMLRPCFaultElement(_node, self, context_node.ownerDocument) |
paulb@304 | 64 | elif Node_localName(_node) == "string": |
paulb@304 | 65 | return XMLRPCStringElement(_node, self, context_node.ownerDocument) |
paulb@304 | 66 | elif Node_localName(_node) in ("int", "i4"): |
paulb@304 | 67 | return XMLRPCIntegerElement(_node, self, context_node.ownerDocument) |
paulb@304 | 68 | elif Node_localName(_node) == "boolean": |
paulb@304 | 69 | return XMLRPCBooleanElement(_node, self, context_node.ownerDocument) |
paulb@304 | 70 | elif Node_localName(_node) == "double": |
paulb@304 | 71 | return XMLRPCDoubleElement(_node, self, context_node.ownerDocument) |
paulb@304 | 72 | elif Node_localName(_node) == "dateTime.iso8601": |
paulb@304 | 73 | return XMLRPCDateTimeElement(_node, self, context_node.ownerDocument) |
paulb@304 | 74 | elif Node_localName(_node) == "base64": |
paulb@304 | 75 | return XMLRPCBase64Element(_node, self, context_node.ownerDocument) |
paulb@304 | 76 | elif Node_localName(_node) == "struct": |
paulb@304 | 77 | return XMLRPCStructElement(_node, self, context_node.ownerDocument) |
paulb@304 | 78 | elif Node_localName(_node) == "member": |
paulb@304 | 79 | return XMLRPCMemberElement(_node, self, context_node.ownerDocument) |
paulb@304 | 80 | |
paulb@304 | 81 | # Otherwise, make generic XML-RPC elements. |
paulb@304 | 82 | |
paulb@304 | 83 | return XMLRPCElement(_node, self, context_node.ownerDocument) |
paulb@304 | 84 | |
paulb@304 | 85 | else: |
paulb@304 | 86 | return libxml2dom.Implementation.get_node(self, _node, context_node) |
paulb@304 | 87 | |
paulb@304 | 88 | # Convenience functions. |
paulb@304 | 89 | |
paulb@304 | 90 | def createXMLRPCMessage(self, namespaceURI, localName): |
paulb@304 | 91 | |
paulb@304 | 92 | "Create a new XML-RPC message document (fragment)." |
paulb@304 | 93 | |
paulb@304 | 94 | return XMLRPCDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement |
paulb@304 | 95 | |
paulb@304 | 96 | # Node classes. |
paulb@304 | 97 | |
paulb@304 | 98 | class XMLRPCNode(libxml2dom.Node): |
paulb@304 | 99 | |
paulb@304 | 100 | "Convenience modifications to nodes specific to libxml2dom.xmlrpc." |
paulb@304 | 101 | |
paulb@304 | 102 | pass |
paulb@304 | 103 | |
paulb@304 | 104 | class XMLRPCDocument(libxml2dom._Document, XMLRPCNode): |
paulb@304 | 105 | |
paulb@304 | 106 | "An XML-RPC document fragment." |
paulb@304 | 107 | |
paulb@304 | 108 | def _method(self): |
paulb@304 | 109 | return self.xpath("./methodCall|./methodResponse")[0] |
paulb@304 | 110 | |
paulb@304 | 111 | method = property(_method) |
paulb@304 | 112 | |
paulb@304 | 113 | class XMLRPCElement(XMLRPCNode): |
paulb@304 | 114 | |
paulb@304 | 115 | "An XML-RPC element." |
paulb@304 | 116 | |
paulb@304 | 117 | pass |
paulb@304 | 118 | |
paulb@304 | 119 | class XMLRPCMethodElement(XMLRPCNode): |
paulb@304 | 120 | |
paulb@304 | 121 | "An XML-RPC method element." |
paulb@304 | 122 | |
paulb@304 | 123 | def _fault(self): |
paulb@304 | 124 | return self.xpath("./fault")[0] |
paulb@304 | 125 | |
paulb@304 | 126 | def _methodName(self): |
paulb@304 | 127 | return self.xpath("./methodName")[0] |
paulb@304 | 128 | |
paulb@304 | 129 | def _parameters(self): |
paulb@304 | 130 | return self.xpath("./params/param/value//*[not(./*)]") |
paulb@304 | 131 | |
paulb@304 | 132 | def _parameterValues(self): |
paulb@304 | 133 | values = {} |
paulb@304 | 134 | for parameter in self.parameters: |
paulb@304 | 135 | values[(parameter.namespaceURI, parameter.localName)] = parameter.textContent.strip() |
paulb@304 | 136 | return values |
paulb@304 | 137 | |
paulb@304 | 138 | def createFault(self): |
paulb@304 | 139 | return self.ownerDocument.createElement("fault") |
paulb@304 | 140 | |
paulb@304 | 141 | fault = property(_fault) |
paulb@304 | 142 | methodName = property(_methodName) |
paulb@304 | 143 | parameters = property(_parameters) |
paulb@304 | 144 | parameterValues = property(_parameterValues) |
paulb@304 | 145 | |
paulb@304 | 146 | class XMLRPCStringElement(XMLRPCNode): |
paulb@304 | 147 | |
paulb@304 | 148 | "An XML-RPC string element." |
paulb@304 | 149 | |
paulb@304 | 150 | def _value(self): |
paulb@304 | 151 | return self.textContent.strip() |
paulb@304 | 152 | |
paulb@304 | 153 | def _setValue(self, value): |
paulb@304 | 154 | for node in self.childNodes: |
paulb@304 | 155 | self.removeChild(node) |
paulb@304 | 156 | text = self.ownerDocument.createTextNode(value) |
paulb@304 | 157 | self.appendChild(text) |
paulb@304 | 158 | |
paulb@304 | 159 | value = property(_value, _setValue) |
paulb@304 | 160 | |
paulb@304 | 161 | class XMLRPCMethodNameElement(XMLRPCStringElement): |
paulb@304 | 162 | |
paulb@304 | 163 | "An XML-RPC method element." |
paulb@304 | 164 | |
paulb@304 | 165 | pass |
paulb@304 | 166 | |
paulb@304 | 167 | class XMLRPCIntegerElement(XMLRPCStringElement): |
paulb@304 | 168 | |
paulb@304 | 169 | "An XML-RPC integer element." |
paulb@304 | 170 | |
paulb@304 | 171 | pass |
paulb@304 | 172 | |
paulb@304 | 173 | class XMLRPCBooleanElement(XMLRPCStringElement): |
paulb@304 | 174 | |
paulb@304 | 175 | "An XML-RPC boolean element." |
paulb@304 | 176 | |
paulb@304 | 177 | pass |
paulb@304 | 178 | |
paulb@304 | 179 | class XMLRPCDoubleElement(XMLRPCStringElement): |
paulb@304 | 180 | |
paulb@304 | 181 | "An XML-RPC double floating point number element." |
paulb@304 | 182 | |
paulb@304 | 183 | pass |
paulb@304 | 184 | |
paulb@304 | 185 | class XMLRPCDateTimeElement(XMLRPCStringElement): |
paulb@304 | 186 | |
paulb@304 | 187 | "An XML-RPC date/time element." |
paulb@304 | 188 | |
paulb@304 | 189 | pass |
paulb@304 | 190 | |
paulb@304 | 191 | class XMLRPCBase64Element(XMLRPCStringElement): |
paulb@304 | 192 | |
paulb@304 | 193 | "An XML-RPC integer element." |
paulb@304 | 194 | |
paulb@304 | 195 | pass |
paulb@304 | 196 | |
paulb@304 | 197 | class XMLRPCStructElement(XMLRPCNode): |
paulb@304 | 198 | |
paulb@304 | 199 | "An XML-RPC structure element." |
paulb@304 | 200 | |
paulb@304 | 201 | def _members(self): |
paulb@304 | 202 | return self.xpath("./member") |
paulb@304 | 203 | |
paulb@304 | 204 | members = property(_members) |
paulb@304 | 205 | |
paulb@304 | 206 | class XMLRPCMemberElement(XMLRPCNode): |
paulb@304 | 207 | |
paulb@304 | 208 | "An XML-RPC structure member element." |
paulb@304 | 209 | |
paulb@304 | 210 | def _name(self): |
paulb@304 | 211 | return self.xpath("./name")[0].textContent.strip() |
paulb@304 | 212 | |
paulb@304 | 213 | def _value(self): |
paulb@304 | 214 | return self.xpath("./value")[0] |
paulb@304 | 215 | |
paulb@304 | 216 | name = property(_name) |
paulb@304 | 217 | value = property(_value) |
paulb@304 | 218 | |
paulb@304 | 219 | class XMLRPCFaultElement(XMLRPCNode): |
paulb@304 | 220 | |
paulb@304 | 221 | "An XML-RPC fault element." |
paulb@304 | 222 | |
paulb@304 | 223 | def _code(self): |
paulb@304 | 224 | return self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int")[0] |
paulb@304 | 225 | |
paulb@304 | 226 | def _reason(self): |
paulb@304 | 227 | return self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string")[0] |
paulb@304 | 228 | |
paulb@304 | 229 | code = property(_code) |
paulb@304 | 230 | reason = property(_reason) |
paulb@304 | 231 | |
paulb@304 | 232 | # Utility functions. |
paulb@304 | 233 | |
paulb@304 | 234 | createDocument = libxml2dom.createDocument |
paulb@304 | 235 | createDocumentType = libxml2dom.createDocumentType |
paulb@304 | 236 | |
paulb@304 | 237 | def createXMLRPCMessage(namespaceURI, localName): |
paulb@304 | 238 | return default_impl.createXMLRPCMessage(namespaceURI, localName) |
paulb@304 | 239 | |
paulb@304 | 240 | def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@304 | 241 | return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@304 | 242 | |
paulb@304 | 243 | def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@304 | 244 | return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@304 | 245 | |
paulb@304 | 246 | def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@304 | 247 | return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@304 | 248 | |
paulb@304 | 249 | def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@304 | 250 | return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@304 | 251 | |
paulb@304 | 252 | # Single instance of the implementation. |
paulb@304 | 253 | |
paulb@304 | 254 | default_impl = XMLRPCImplementation() |
paulb@304 | 255 | |
paulb@304 | 256 | # vim: tabstop=4 expandtab shiftwidth=4 |