# HG changeset patch # User paulb # Date 1191194620 0 # Node ID d1afa576a419eed5769ff87ced9bd1556bbc020c # Parent 3d2d934e9910131d1ef07e4bab2df9b43e3a41d6 [project @ 2007-09-30 23:23:40 by paulb] Enhanced the SOAP and XML-RPC modules in order to provide better access to structured data and better editing capabilities. diff -r 3d2d934e9910 -r d1afa576a419 libxml2dom/soap.py --- a/libxml2dom/soap.py Sun Sep 30 00:57:06 2007 +0000 +++ b/libxml2dom/soap.py Sun Sep 30 23:23:40 2007 +0000 @@ -134,6 +134,29 @@ envelope = property(_envelope) + # 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) + fault = property(_fault) + class SOAPElement(SOAPNode): "A SOAP element." @@ -169,30 +192,84 @@ "A SOAP body element." def _fault(self): - return self.xpath("./env:Fault")[0] + return (self.xpath("./env:Fault") or [None])[0] def _method(self): - return self.xpath("./*[@env:encodingStyle = '%s']" % SOAP_ENCODING_NAMESPACE)[0] + return (self.xpath("./*[@env:encodingStyle = '%s']" % SOAP_ENCODING_NAMESPACE) or [None])[0] def _methodName(self): - return self.method.localName + if self.method is not None: + return self.method.localName + else: + return None def _resultParameter(self): - return self.method.xpath(".//rpc:result")[0] + return (self.method.xpath(".//rpc:result") or [None])[0] def _resultParameterValue(self): - name = self.resultParameter.textContent.strip() - return self.method.xpath(".//%s" % name, namespaces={self.method.prefix : self.method.namespaceURI})[0].textContent.strip() + if self.resultParameter: + name = self.resultParameter.textContent.strip() + result = self.method.xpath(".//%s" % name, namespaces={self.method.prefix : self.method.namespaceURI}) + if result: + return result[0].textContent.strip() + else: + return None + else: + return None def _parameters(self): - return self.method.xpath(".//*[not(./*)]") + if self.method is not None: + return self.method.xpath("*") + else: + return [] + + def _parameterItems(self): + values = [] + for parameter in self.parameters: + values.append(self._item_contents(parameter)) + return values def _parameterValues(self): - values = {} - for parameter in self.parameters: - values[(parameter.namespaceURI, parameter.localName)] = parameter.textContent.strip() + return self._value_contents(self.parameterItems) + + def _parameterMap(self): + return self._map_contents(self.parameterItems) + + # Internal methods. + + def _item_contents(self, parameter): + elements = parameter.xpath("*") + if elements: + items = [] + for element in elements: + items.append(self._item_contents(element)) + return (parameter.namespaceURI, parameter.localName), 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 + 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") @@ -202,25 +279,39 @@ resultParameter = property(_resultParameter) resultParameterValue = property(_resultParameterValue) parameters = property(_parameters) + parameterItems = property(_parameterItems) parameterValues = property(_parameterValues) + parameterMap = property(_parameterMap) class SOAPFaultElement(SOAPNode): "A SOAP fault element." def _code(self): - return self.xpath("./env:Code")[0] + code = self.xpath("./env:Code") + if code: + return code[0].value + else: + return None + + def _subcode(self): + subcode = self.xpath("./env:Code/env:Subcode") + if subcode: + return subcode[0].value + else: + return None def _reason(self): - return self.xpath("./env:Reason")[0] + return (self.xpath("./env:Reason") or [None])[0] def _detail(self): - return self.xpath("./env:Detail")[0] + return (self.xpath("./env:Detail") or [None])[0] def createCode(self): return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Code") code = property(_code) + subcode = property(_subcode) reason = property(_reason) detail = property(_detail) @@ -229,7 +320,11 @@ "A SOAP subcode element." def _value(self): - return self.xpath("./env:Value")[0].textContent.strip() + value = self.xpath("./env:Value") + if value: + return value[0].textContent.strip() + else: + return None def _setValue(self, value): nodes = self.xpath("./env:Value") @@ -253,7 +348,7 @@ "A SOAP code element." def _subcode(self): - return self.xpath("./env:Subcode")[0] + return (self.xpath("./env:Subcode") or [None])[0] def createSubcode(self): return self.ownerDocument.createElementNS(SOAP_ENVELOPE_NAMESPACE, "env:Subcode") diff -r 3d2d934e9910 -r d1afa576a419 libxml2dom/xmlrpc.py --- a/libxml2dom/xmlrpc.py Sun Sep 30 00:57:06 2007 +0000 +++ b/libxml2dom/xmlrpc.py Sun Sep 30 23:23:40 2007 +0000 @@ -32,6 +32,7 @@ from libxml2dom.macrolib import * from libxml2dom.macrolib import \ createDocument as Node_createDocument +import datetime class XMLRPCImplementation(libxml2dom.Implementation): @@ -77,6 +78,8 @@ return XMLRPCStructElement(_node, self, context_node.ownerDocument) elif Node_localName(_node) == "member": return XMLRPCMemberElement(_node, self, context_node.ownerDocument) + elif Node_localName(_node) == "value": + return XMLRPCValueElement(_node, self, context_node.ownerDocument) # Otherwise, make generic XML-RPC elements. @@ -93,6 +96,25 @@ return XMLRPCDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement + def createMethodCall(self): + return self.createXMLRPCMessage(None, "methodCall") + + 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): @@ -101,47 +123,213 @@ pass +class XMLRPCElement(XMLRPCNode): + + "An XML-RPC element." + + pass + class XMLRPCDocument(libxml2dom._Document, XMLRPCNode): "An XML-RPC document fragment." def _method(self): - return self.xpath("./methodCall|./methodResponse")[0] + return (self.xpath("./methodCall|./methodResponse") or [None])[0] method = property(_method) -class XMLRPCElement(XMLRPCNode): + # 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 - "An XML-RPC element." + def _parameterMap(self): + if self.method is not None: + return self.method.parameterMap + else: + return None + + def _fault(self): + if self.method is not None: + return self.method.fault + else: + return None - pass + # Node construction methods. + + def createMethodCall(self): + return self.ownerDocument.createElement("methodCall") + + 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." def _fault(self): - return self.xpath("./fault")[0] + return (self.xpath("./fault") or [None])[0] def _methodName(self): - return self.xpath("./methodName")[0] + return (self.xpath("./methodName") or [None])[0] + + def _name(self): + name = self.methodName + if name is not None: + return name.value + else: + return None + + def _setName(self, name): + if self.methodName is None: + methodName = self.createMethodName() + self.appendChild(methodName) + self.methodName.value = name def _parameters(self): - return self.xpath("./params/param/value//*[not(./*)]") + return self.xpath("./params/param") + + def _parameterItems(self): + values = self.xpath("./params/param/value") + if values: + items = [] + for value in values: + items.append(self._item_contents(value)) + return items + else: + return [] def _parameterValues(self): - values = {} - for parameter in self.parameters: - values[(parameter.namespaceURI, parameter.localName)] = parameter.textContent.strip() + return self._value_contents(self.parameterItems) + + def _setParameterValues(self, parameters): + param_list = self.parameters + params = (self.xpath("./params") or [None])[0] + if params: + if param_list: + for param in param_list: + params.removeChild(param) + else: + params = self.createParameters() + self.appendChild(params) + + 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 + } + + # 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 _from_string(self, typename, value): + return self.converters.get(typename, str)(value) + + def _item_contents(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 + 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 + + # Node construction methods. + + def createMethodName(self): + return self.ownerDocument.createElement("methodName") + + def createParameters(self): + return self.ownerDocument.createElement("params") + def createFault(self): return self.ownerDocument.createElement("fault") fault = property(_fault) + name = property(_name, _setName) methodName = property(_methodName) parameters = property(_parameters) - parameterValues = property(_parameterValues) + parameterItems = property(_parameterItems) + parameterValues = property(_parameterValues, _setParameterValues) + parameterMap = property(_parameterMap) class XMLRPCStringElement(XMLRPCNode): @@ -158,6 +346,23 @@ value = property(_value, _setValue) +class XMLRPCValueElement(XMLRPCStringElement): + + "An XML-RPC value element." + + def _type(self): + elements = self.xpath("*") + if elements: + return elements[0].localName + else: + return "string" + + def _container(self): + return (self.xpath("*") or [self])[0] + + type = property(_type) + container = property(_container) + class XMLRPCMethodNameElement(XMLRPCStringElement): "An XML-RPC method element." @@ -208,10 +413,14 @@ "An XML-RPC structure member element." def _name(self): - return self.xpath("./name")[0].textContent.strip() + value = self.xpath("./name") + if value: + return value[0].textContent.strip() + else: + return None def _value(self): - return self.xpath("./value")[0] + return (self.xpath("./value") or [None])[0] name = property(_name) value = property(_value) @@ -221,10 +430,18 @@ "An XML-RPC fault element." def _code(self): - return self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int")[0] + code = self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int") + if code: + return code[0].value + else: + return None def _reason(self): - return self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string")[0] + reason = self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string") + if reason: + return reason[0].value + else: + return None code = property(_code) reason = property(_reason) @@ -235,7 +452,13 @@ createDocumentType = libxml2dom.createDocumentType def createXMLRPCMessage(namespaceURI, localName): - return default_impl.createXMLRPCMessage(namespaceURI, localName) + return default_impl.createXMLRPCMessage(None, localName) + +def createMethodCall(): + return default_impl.createMethodCall() + +def createMethodResponse(): + return default_impl.createMethodResponse() def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl))