Has anyone get AppleScript SOAP call to work

I have try many times to get the build-in SOAP call to work in AppleScript without success. So I made a prototype example with do shell script and curl. So other could try if they have more luck.

Apples documentation for SOAP call could be find here.

Here is prototype code to do SOAP call.

use framework "Foundation"
use scripting additions

(**
* [Example to use SOAP call to a server to get response]
* 
* History: 
* 	I like to get AppleScript build-in call soap to work.
*	I have try many times but its not so easy like xml-rpc was.
*
* 	Here is a example to convert Celsius to Fahrenheit but it could
* 	easily be converted to FahrenheitToCelsius in xml request.
*
* 	This prototype code use do shell script and curl.
*	The handler will convert the xml response to AppleScript type.
*
* 	Test case: Value of 0 in Celsius will return 32 in Fahrenheit.
*)

-- Define the SOAP request XML
set soapRequestXML to "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
set soapRequestXML to soapRequestXML & "<soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">"
set soapRequestXML to soapRequestXML & "<soap12:Body>"
--
-- This 3 lines below will convert from Celsius to Fahrenheit.
-- set soapRequestXML to soapRequestXML & "<CelsiusToFahrenheit xmlns=\"https://www.w3schools.com/xml/\">"
-- set soapRequestXML to soapRequestXML & "<Celsius>0</Celsius>"
-- set soapRequestXML to soapRequestXML & "</CelsiusToFahrenheit>"
--
-- This 3 lines below convert from Fahrenheit to Celsius
set soapRequestXML to soapRequestXML & "<FahrenheitToCelsius xmlns=\"https://www.w3schools.com/xml/\">"
set soapRequestXML to soapRequestXML & "<Fahrenheit>32</Fahrenheit>"
set soapRequestXML to soapRequestXML & "</FahrenheitToCelsius>"
--
set soapRequestXML to soapRequestXML & "</soap12:Body>"
set soapRequestXML to soapRequestXML & "</soap12:Envelope>"

set soapServiceURL to "https://www.w3schools.com/xml/tempconvert.asmx"
set headers to "-H 'Content-Type: text/xml;charset=UTF-8'"
set curlCommand to "curl -X POST " & headers & " -d " & quoted form of soapRequestXML & " " & quoted form of soapServiceURL
set soapResponse to do shell script curlCommand

set xmlString to current application's NSString's stringWithString:soapResponse
set xmlData to xmlString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
return (its nodesForXPath:"//soap:Body" inXMLData:xmlData)

on nodesForXPath:xpathString inXMLData:xmlData
	set {theXMLDoc, theError} to current application's NSXMLDocument's alloc()'s ¬
		initWithData:xmlData options:(current application's NSXMLDocumentTidyHTML) |error|:(reference)
	if theXMLDoc is missing value then error (theError's localizedDescription() as string)
	set {theMatches, theError} to (theXMLDoc's nodesForXPath:xpathString |error|:(reference))
	if theMatches = missing value then error (theError's localizedDescription() as string)
	set noteElements to theMatches's allObjects()
	set elements to noteElements's firstObject()
	return (elements's childAtIndex:0)'s objectValue() as anything
end nodesForXPath:inXMLData:

If you are doing development to understand different Protocols.
I do recommend this tools its free: Postman.app
I have used it many times in my Node-Red development but also
for this SOAP call and XML-RPC call.

Maybe there is different version of the SOAP protocol. I try other example from
the Postman.app. And use the request in AppleScript example.

This example use empty parameters and SOAPAction is not used.

tell application "http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso"
	return call soap {method name:"ListOfLanguagesByName", method namespace uri:"http://www.oorsprong.org/websamples.countryinfo", parameters:{}}
end tell

Congratulation to myself I solve the issue with the first script.
In the parameter field in XML request in SOAP call. We need to add “m:” before
the key of parameters of SOAP Call in AppleScript.

ex.

parameters:{|Celsius|:0} --> incorrect way
parameters:{|m:Celsius|:0} --> correct way

The XML standard could use prefix in the namespace so it will not conflict with other element name. In this case it was “m” but it could be anything. If a user do not know the prefix of namespace for SOAP call. I believe it maybe are possible to extract the prefix using XMLParser or maybe XPath. Its not directly clear to me why curl didn’t complain but Apple’s implementation of SOAP call did.

Here is SOAP call in AppleScript for the temperatur converter.

tell application "https://www.w3schools.com/xml/tempconvert.asmx"
	return (call soap {method name:"CelsiusToFahrenheit", method namespace uri:"https://www.w3schools.com/xml/", parameters:{|m:Celsius|:0}, SOAPAction:"https://www.w3schools.com/xml/CelsiusToFahrenheit"})
end tell

It looks like someone else had the same problem and some answer why.

1 Like

In this example we will take the first script and use prefix ‘m’ in the namespace url.
To build a prototype example how AppleScript SOAP implementation do it.

The prototype code with curl also give a greater description for ‘error’.

To look for: xmlns:m= in the method namespace url.

use framework "Foundation"
use scripting additions

(**
* Here is example used 'm' as prefix for namespace.
*)

-- Define the SOAP request XML
set soapRequestXML to "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
set soapRequestXML to soapRequestXML & "<soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">"
set soapRequestXML to soapRequestXML & "<soap12:Body>"
--
-- This 3 lines below will convert from Celsius to Fahrenheit.
-- set soapRequestXML to soapRequestXML & "<CelsiusToFahrenheit xmlns=\"https://www.w3schools.com/xml/\">"
-- set soapRequestXML to soapRequestXML & "<Celsius>0</Celsius>"
-- set soapRequestXML to soapRequestXML & "</CelsiusToFahrenheit>"
--
-- This 3 lines below convert from Fahrenheit to Celsius
set soapRequestXML to soapRequestXML & "<m:FahrenheitToCelsius xmlns:m=\"https://www.w3schools.com/xml/\">"
set soapRequestXML to soapRequestXML & "<m:Fahrenheit>32</m:Fahrenheit>"
set soapRequestXML to soapRequestXML & "</m:FahrenheitToCelsius>"
--
set soapRequestXML to soapRequestXML & "</soap12:Body>"
set soapRequestXML to soapRequestXML & "</soap12:Envelope>"

set soapServiceURL to "https://www.w3schools.com/xml/tempconvert.asmx"
set headers to "-H 'Content-Type: text/xml;charset=UTF-8'"
set curlCommand to "curl -X POST " & headers & " -d " & quoted form of soapRequestXML & " " & quoted form of soapServiceURL
set soapResponse to do shell script curlCommand

set xmlString to current application's NSString's stringWithString:soapResponse
set xmlData to xmlString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
return (its nodesForXPath:"//soap:Body" inXMLData:xmlData)

on nodesForXPath:xpathString inXMLData:xmlData
	set {theXMLDoc, theError} to current application's NSXMLDocument's alloc()'s ¬
		initWithData:xmlData options:(current application's NSXMLDocumentTidyHTML) |error|:(reference)
	if theXMLDoc is missing value then error (theError's localizedDescription() as string)
	set {theMatches, theError} to (theXMLDoc's nodesForXPath:xpathString |error|:(reference))
	if theMatches = missing value then error (theError's localizedDescription() as string)
	set noteElements to theMatches's allObjects()
	set elements to noteElements's firstObject()
	return (elements's childAtIndex:0)'s objectValue() as anything
end nodesForXPath:inXMLData:

The prototype example that used curl and AppleScriptObjC handler to convert the response in XML to AppleScript type.

Used 2 parameters: xpath as string, xml as Objective-C data

The Objective-C data could be done my converting the xml response string to NSData.

set xmlData to xmlString's dataUsingEncoding:(current application's NSUTF8StringEncoding)

How about xpath as string if we do not know anything about SOAP response.
We could include in the handler a log to get the information we need.

log elements's |description|() as string

And in the xpath we use

nodesForXPath:"//*"...

In the log we could find <soap:Body>

nodesForXPath:"//soap:Body"...

The full code

its nodesForXPath:"//*" inXMLData:xmlData
on nodesForXPath:xpathString inXMLData:xmlData
	set {theXMLDoc, theError} to current application's NSXMLDocument's alloc()'s ¬
		initWithData:xmlData options:(current application's NSXMLDocumentTidyHTML) |error|:(reference)
	if theXMLDoc is missing value then error (theError's localizedDescription() as string)
	set {theMatches, theError} to (theXMLDoc's nodesForXPath:xpathString |error|:(reference))
	if theMatches = missing value then error (theError's localizedDescription() as string)
	set noteElements to theMatches's allObjects()
	set elements to noteElements's firstObject()
	-- logging the xml response if we use xpath string "//*"
	log elements's |description|() as string
	return (elements's childAtIndex:0)'s objectValue() as anything
end nodesForXPath:inXMLData:

Note!
The (childAtIndex:0)'s objectValue() will only return the first object from elements.
If the description of the log from elements include more we need to change its index
or return allObjects.

In XML form the Apple’s implementation of call soap. In the temperatur converter example. Apple include the prefix in method name and method namespace url but missing it from parameters key.

Apple do

<Celsius>0</Celsius>

When it should be

<m:Celsius>0</m:Celsius>

In others words
<prefix:key>value type</prefix:key>

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Body>
    <m:CelsiusToFahrenheit xmlns:m="https://www.w3schools.com/xml/">
      <Celsius>0</Celsius>
    </m:CelsiusToFahrenheit>
  </soap:Body>
</soap:Envelope>

So end of the day its a bug that Apple properly already know but do not like to fix.

So if you get response that looks like this:

It could be that you have not include the prefix before the key of the parameters.
And if you ask me I did get exactly that error that didn’t make me more wiser.
Knowing better it was easy to make a use case in Postman.app

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <CelsiusToFahrenheitResponse xmlns="https://www.w3schools.com/xml/">
            <CelsiusToFahrenheitResult>Error</CelsiusToFahrenheitResult>
        </CelsiusToFahrenheitResponse>
    </soap:Body>
</soap:Envelope>