# HG changeset patch # User paulb # Date 1191281197 0 # Node ID 35c2b7e260c9223f673c10a52190d6058d26dcbc # Parent fe6ce4bd69901a01acbd4fe00457d394e6773422 [project @ 2007-10-01 23:26:37 by paulb] Made the XML-RPC and SOAP APIs more consistent. Changed parameter access to focus only on parameter values, separating type conversions out into a separate module. Implemented hierarchical editing of parameters, most likely accessed through a higher level API. diff -r fe6ce4bd6990 -r 35c2b7e260c9 libxml2dom/conversions.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libxml2dom/conversions.py Mon Oct 01 23:26:37 2007 +0000 @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +""" +Conversion functions and data used by XML-RPC and SOAP. + +Copyright (C) 2007 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 3 of the License, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see . +""" + +converters = { + "string" : unicode, + "int" : int, + "i4" : int, + "double" : float, + "boolean" : boolean, # see the module globals + "dateTime.iso8601" : iso8601, # see the module globals + "base64" : str + } + +def from_string(typename, value): + return converters.get(typename, str)(value) + +# Utility functions. + +def boolean(s): + if s.lower() == "true": + return True + elif s.lower() == "false": + return False + else: + raise ValueError, "String value %s not convertable to boolean." % repr(s) + +def iso8601(s): + # NOTE: To be written. + return s + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r fe6ce4bd6990 -r 35c2b7e260c9 libxml2dom/soap.py --- a/libxml2dom/soap.py Sun Sep 30 23:23:48 2007 +0000 +++ b/libxml2dom/soap.py Mon Oct 01 23:26:37 2007 +0000 @@ -91,6 +91,13 @@ elif Node_localName(_node) == "Text": return SOAPTextElement(_node, self, context_node.ownerDocument) + # Detect the method element. + + if Node_parentNode(_node) and Node_localName(Node_parentNode(_node)) == "Body" and \ + Node_namespaceURI(Node_parentNode(_node)) == SOAP_ENVELOPE_NAMESPACE: + + return SOAPMethodElement(_node, self, context_node.ownerDocument) + # Otherwise, make generic SOAP elements. return SOAPElement(_node, self, context_node.ownerDocument) @@ -136,26 +143,14 @@ # Convenience methods and properties. - def _methodName(self): - return self.envelope.body.methodName - - def _parameterItems(self): - return self.envelope.body.parameterItems - - def _parameterValues(self): - return self.envelope.body.parameterValues - - def _parameterMap(self): - return self.envelope.body.parameterMap - def _fault(self): return self.envelope.body.fault - methodName = property(_methodName) - parameterItems = property(_parameterItems) - parameterValues = property(_parameterValues) - parameterMap = property(_parameterMap) + def _method(self): + return self.envelope.body.method + fault = property(_fault) + method = property(_method) class SOAPElement(SOAPNode): @@ -197,19 +192,28 @@ def _method(self): return (self.xpath("./*[@env:encodingStyle = '%s']" % SOAP_ENCODING_NAMESPACE) or [None])[0] + # Node construction methods. + + def createFault(self): + return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Fault") + + fault = property(_fault) + method = property(_method) + +class SOAPMethodElement(SOAPNode): + + "A SOAP method element." + def _methodName(self): - if self.method is not None: - return self.method.localName - else: - return None + return self.localName def _resultParameter(self): - return (self.method.xpath(".//rpc:result") or [None])[0] + return (self.xpath(".//rpc:result") or [None])[0] def _resultParameterValue(self): if self.resultParameter: name = self.resultParameter.textContent.strip() - result = self.method.xpath(".//%s" % name, namespaces={self.method.prefix : self.method.namespaceURI}) + result = self.xpath(".//%s" % name, namespaces={self.prefix : self.namespaceURI}) if result: return result[0].textContent.strip() else: @@ -218,70 +222,55 @@ return None def _parameters(self): - if self.method is not None: - return self.method.xpath("*") - else: - return [] + return self.xpath("*") - def _parameterItems(self): + def _parameterValues(self): values = [] for parameter in self.parameters: - values.append(self._item_contents(parameter)) + values.append(self._get_value(parameter)) return values - def _parameterValues(self): - return self._value_contents(self.parameterItems) + def _setParameterValues(self, parameters): + for node in self.parameters: + self.removeChild(node) - def _parameterMap(self): - return self._map_contents(self.parameterItems) + # Add the parameter values. + + for parameter in parameters: + self._add_value(self, parameter) # Internal methods. - def _item_contents(self, parameter): + def _add_value(self, value, parameter): + (ns, name), parameter = parameter + container = self.ownerDocument.createElementNS(ns, name) + value.appendChild(container) + if isinstance(parameter, (list, dict)): + if isinstance(parameter, dict): + items = parameter.items() + else: + items = parameter + for item in items: + self._add_value(container, item) + else: + text = self.ownerDocument.createTextNode(unicode(parameter)) + container.appendChild(text) + + def _get_value(self, parameter): elements = parameter.xpath("*") if elements: items = [] for element in elements: - items.append(self._item_contents(element)) - return (parameter.namespaceURI, parameter.localName), items + items.append(self._get_value(element)) + return (parameter.namespaceURI, parameter.name), items else: - return (parameter.namespaceURI, parameter.localName), parameter.textContent.strip() - - def _value_contents(self, items): - values = [] - for (typename, name), value in items: - if isinstance(value, list): - values.append(self._value_contents(value)) - elif name is None: - values.append(value) - else: - values.append((name, value)) - return values + return (parameter.namespaceURI, parameter.name), parameter.textContent.strip() - def _map_contents(self, items): - d = {} - for n, ((typename, name), value) in enumerate(items): - key_name = name or str(n) - if isinstance(value, list): - d[(typename, key_name)] = self._map_contents(value) - else: - d[(typename, key_name)] = value - return d - - # Node construction methods. - - def createFault(self): - return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Fault") - - fault = property(_fault) - method = property(_method) methodName = property(_methodName) resultParameter = property(_resultParameter) resultParameterValue = property(_resultParameterValue) parameters = property(_parameters) - parameterItems = property(_parameterItems) - parameterValues = property(_parameterValues) - parameterMap = property(_parameterMap) + parameterValues = property(_parameterValues, _setParameterValues) class SOAPFaultElement(SOAPNode): diff -r fe6ce4bd6990 -r 35c2b7e260c9 libxml2dom/xmlrpc.py --- a/libxml2dom/xmlrpc.py Sun Sep 30 23:23:48 2007 +0000 +++ b/libxml2dom/xmlrpc.py Mon Oct 01 23:26:37 2007 +0000 @@ -80,6 +80,8 @@ return XMLRPCMemberElement(_node, self, context_node.ownerDocument) elif Node_localName(_node) == "value": return XMLRPCValueElement(_node, self, context_node.ownerDocument) + elif Node_localName(_node) == "name": + return XMLRPCNameElement(_node, self, context_node.ownerDocument) # Otherwise, make generic XML-RPC elements. @@ -102,19 +104,6 @@ def createMethodResponse(self): return self.createXMLRPCMessage(None, "methodResponse") -# Internal utility functions. - -def boolean(s): - if s.lower() == "true": - return True - elif s.lower() == "false": - return False - else: - raise ValueError, "String value %s not convertable to boolean." % repr(s) - -def iso8601(s): - return s - # Node classes. class XMLRPCNode(libxml2dom.Node): @@ -134,35 +123,7 @@ "An XML-RPC document fragment." def _method(self): - return (self.xpath("./methodCall|./methodResponse") or [None])[0] - - method = property(_method) - - # Convenience methods and properties. - - def _methodName(self): - if self.method is not None: - return self.method.name - else: - return None - - def _parameterItems(self): - if self.method is not None: - return self.method.parameterItems - else: - return None - - def _parameterValues(self): - if self.method is not None: - return self.method.parameterValues - else: - return None - - def _parameterMap(self): - if self.method is not None: - return self.method.parameterMap - else: - return None + return (self.xpath("methodCall|methodResponse") or [None])[0] def _fault(self): if self.method is not None: @@ -170,6 +131,9 @@ else: return None + method = property(_method) + fault = property(_fault) + # Node construction methods. def createMethodCall(self): @@ -178,12 +142,6 @@ def createMethodResponse(self): return self.ownerDocument.createElement("methodResponse") - methodName = property(_methodName) - parameterItems = property(_parameterItems) - parameterValues = property(_parameterValues) - parameterMap = property(_parameterMap) - fault = property(_fault) - class XMLRPCMethodElement(XMLRPCNode): "An XML-RPC method element." @@ -191,41 +149,41 @@ def _fault(self): return (self.xpath("./fault") or [None])[0] - def _methodName(self): + def _methodNameElement(self): return (self.xpath("./methodName") or [None])[0] - def _name(self): - name = self.methodName + def _methodName(self): + name = self.methodNameElement if name is not None: return name.value else: return None - def _setName(self, name): - if self.methodName is None: + def _setMethodName(self, name): + if self.methodNameElement is None: methodName = self.createMethodName() self.appendChild(methodName) - self.methodName.value = name + self.methodNameElement.value = name def _parameters(self): return self.xpath("./params/param") - def _parameterItems(self): + def _parameterValues(self): values = self.xpath("./params/param/value") if values: items = [] for value in values: - items.append(self._item_contents(value)) + items.append(self._get_value(value)) return items else: return [] - def _parameterValues(self): - return self._value_contents(self.parameterItems) - def _setParameterValues(self, parameters): param_list = self.parameters params = (self.xpath("./params") or [None])[0] + + # Remove any previous parameters. + if params: if param_list: for param in param_list: @@ -234,83 +192,46 @@ params = self.createParameters() self.appendChild(params) + # Add parameter values. + for parameter in parameters: param = self.ownerDocument.createElement("param") params.appendChild(param) value = self.ownerDocument.createElement("value") param.appendChild(value) - - # NOTE: Only handles simple types. - - container = self._make_container(parameter) - value.appendChild(container) - text = self.ownerDocument.createTextNode(unicode(parameter)) - container.appendChild(text) - - def _parameterMap(self): - return self._map_contents(self.parameterItems) - - # Internal data. - - converters = { - "string" : unicode, - "int" : int, - "i4" : int, - "double" : float, - "boolean" : boolean, # see the module globals - "dateTime.iso8601" : iso8601, # see the module globals - "base64" : str - } + self._add_value(value, parameter) # Internal methods. - def _make_container(self, parameter): - if isinstance(parameter, (str, unicode)): - return self.ownerDocument.createElement("string") - elif isinstance(parameter, (int, long)): - return self.ownerDocument.createElement("int") - elif isinstance(parameter, float): - return self.ownerDocument.createElement("double") - elif isinstance(parameter, bool): - return self.ownerDocument.createElement("boolean") - elif isinstance(parameter, datetime.datetime): - return self.ownerDocument.createElement("dateTime.iso8601") + def _add_value(self, value, parameter): + typename, parameter = parameter + if typename == "struct": + if isinstance(parameter, dict): + items = parameter.items() + else: + items = parameter + struct = self.ownerDocument.createElement("struct") + for item_name, item_value in items: + member = struct.createMember() + struct.appendChild(member) + member.memberName = item_name + memberValue = member.createValue() + member.appendChild(memberValue) + self._add_value(memberValue, item_value) + value.appendChild(struct) + else: + container = self.ownerDocument.createElement(typename) + value.appendChild(container) + container.value = unicode(parameter) - def _from_string(self, typename, value): - return self.converters.get(typename, str)(value) - - def _item_contents(self, value): + def _get_value(self, value): if value.type == "struct": items = [] for member in value.container.members: - if member.value.type == "struct": - items.append((member.name, self._item_contents(member.value))) - else: - items.append((member.name, self._from_string(member.value.type, member.value.container.value))) - return None, items + items.append((member.memberName, self._get_value(member.value))) + return (value.type, items) else: - return None, self._from_string(value.type, value.container.value) - - def _value_contents(self, items): - values = [] - for name, value in items: - if isinstance(value, list): - values.append(self._value_contents(value)) - elif name is None: - values.append(value) - else: - values.append((name, value)) - return values - - def _map_contents(self, items): - d = {} - for n, (name, value) in enumerate(items): - key_name = name or str(n) - if isinstance(value, list): - d[key_name] = self._map_contents(value) - else: - d[key_name] = value - return d + return (value.type, value.container.value) # Node construction methods. @@ -324,12 +245,58 @@ return self.ownerDocument.createElement("fault") fault = property(_fault) - name = property(_name, _setName) - methodName = property(_methodName) + methodNameElement = property(_methodNameElement) + methodName = property(_methodName, _setMethodName) parameters = property(_parameters) - parameterItems = property(_parameterItems) parameterValues = property(_parameterValues, _setParameterValues) - parameterMap = property(_parameterMap) + +class XMLRPCStructElement(XMLRPCNode): + + "An XML-RPC structure element." + + def _members(self): + return self.xpath("./member") + + # Node construction methods. + + def createMember(self): + return self.ownerDocument.createElement("member") + + members = property(_members) + +class XMLRPCMemberElement(XMLRPCNode): + + "An XML-RPC structure member element." + + def _nameElement(self): + return (self.xpath("./name") or [None])[0] + + def _memberName(self): + if self.nameElement is not None: + return self.nameElement.value + else: + return None + + def _setMemberName(self, name): + if self.nameElement is None: + nameElement = self.createName() + self.appendChild(nameElement) + self.nameElement.value = name + + def _value(self): + return (self.xpath("./value") or [None])[0] + + # Node construction methods. + + def createName(self): + return self.ownerDocument.createElement("name") + + def createValue(self): + return self.ownerDocument.createElement("value") + + nameElement = property(_nameElement) + memberName = property(_memberName, _setMemberName) + value = property(_value) class XMLRPCStringElement(XMLRPCNode): @@ -346,6 +313,12 @@ value = property(_value, _setValue) +class XMLRPCNameElement(XMLRPCStringElement): + + "An XML-RPC name element." + + pass + class XMLRPCValueElement(XMLRPCStringElement): "An XML-RPC value element." @@ -399,32 +372,6 @@ pass -class XMLRPCStructElement(XMLRPCNode): - - "An XML-RPC structure element." - - def _members(self): - return self.xpath("./member") - - members = property(_members) - -class XMLRPCMemberElement(XMLRPCNode): - - "An XML-RPC structure member element." - - def _name(self): - value = self.xpath("./name") - if value: - return value[0].textContent.strip() - else: - return None - - def _value(self): - return (self.xpath("./value") or [None])[0] - - name = property(_name) - value = property(_value) - class XMLRPCFaultElement(XMLRPCNode): "An XML-RPC fault element."