I am using ASObjC Runner for its XML parsing capabilities (among other things). I’m getting a strange result when trying to use the ‘extract from XML’ command: ASObjC Runner got an error: Connection is invalid.
The command I was using was:
tell "ASObjC Runner" to set oneValue_asList to extract from XML someXML matching nodeXPath expression {oneKeyName} without including output element
where someXML was a valid block of XML, nodeXPath was an XPath to the desired node, and oneKeyName was the item whose value I wanted to read. I do this for many different key names and many different XPaths. It is hard to debug because it seems to get this error at different points every time I run the whole script. I haven’t discovered a particular key name or XPath that causes the error. One that fails once might succeed the next time, without any change to the underlying XML or key name and XPath choices.
I don’t see anything online that would explain this. I am running this in Yosemite, 10.10.1, and have the most recent version of AsObjC Runner. Any ideas on what might be causing this? The XML being processed is fairly large. I’ve even tried quitting AsObjC Runner periodically during the script to see if it is leaking memory. That seems to have no effect.
Correction: it seems like quitting AsObjC Runner often does somewhat mitigate this problem.
We tried modifying the script so that it tells AsObjC Runner to quit before every time we call the ‘extract from XML’ command. We were able to get through a run of the entire script, but only on the second try. The first time it got fairly far through, but eventually got the error. So, it seems like it isn’t a complete fix for this error.
Is there some memory limit for AsObjC Runner? Would it help to split my large XML data into separate pieces? Any other ideas or information I can collect to help with figuring out a solution? I’m hoping Shane Stanley sees this post and has some thoughts.
On one hand, I’m a bit surprised. Unless the XPath expression potentially returns a huge number of results, there should be no problem with limits. I presume you’re running v1.9.15. And I know several people are still using it under 10.10, specifically for the XML stuff.
But it really is time to start moving away from ASObjC Runner – better to do it now while it’s still working, than waiting for it to break (completely).
Is there some reason you’re not using a “native” ASObjC solution? This should get you started:
use framework "Foundation"
on extractFrom:thePath matchingXPath:theQuery
set aURL to current application's |NSURL|'s fileURLWithPath:thePath -- make NSURL
set theXMLDoc to current application's NSXMLDocument's alloc()'s initWithContentsOfURL:aURL options:0 |error|:(missing value) -- make XMLDoc
set attStrings to {} -- where attributes will be stored
set theXMLOutput to current application's NSXMLElement's alloc()'s initWithName:"output" -- found nodes are added to this
set {theResults, theError} to (theXMLDoc's nodesForXPath:theQuery |error|:(reference)) -- query
if theResults is not missing value then
repeat with aNode in (theResults as list)
aNode's detach() -- need to detach first
if aNode's |kind|() as integer = current application's NSXMLAttributeKind then -- see if it's an attribute or node
set end of attStrings to (aNode's stringValue()) as text
else
(theXMLOutput's addChild:aNode) -- add node
end if
end repeat
return {(theXMLOutput's XMLStringWithOptions:(current application's NSXMLNodePrettyPrint)) as text, attStrings} -- change options to suit
else
return missing value
end if
end extractFrom:matchingXPath:
Thanks, Shane!
I took your advice and we re-worked all of our uses of AsObjC Runner to use the framework instead, since we can control which machine this runs on and can stick to Yosemite. We actually tweaked your example to create an NSXMLDocument from content instead of a file, since we often have snippets of XML already loaded into AppleScript variables. We also added some features to strip off the node’s own tag (to get just the content or value of a node, as well as all the other parts we needed. It’s been an interesting dive into the Cocoa documentation for an AppleScript/FileMaker programmer.
One interesting thing we ran into is that, in an AppleScript script that has use framework “Foundation” in it, certain Standard Additions commands are unusable. That prevented us from using do shell script to log to console certain interstitial variables while debugging. I suppose we’ll have to move a lot of code that uses those functions into other libraries, and stick to very vanilla AppleScript when we ‘use frameworks.’
Just add a “use scripting additions” statement, and you should then be able to use the scripting addition commands.
But for logging to console, you can also use NSLog, like this:
current application's NSLog("%@", someVariable)
There are some gotchas, but basically %@ gets replaced by the variable’s value. If there are numbers, you have to use %d or %f. So:
use framework "Foundation"
set a to "Some string"
set b to {1, 2, 3}
set c to 5
set d to 3.5
current application's NSLog("Logging %@ and %@ and %d and %f", a, b, c, d)
will log this:
25/01/2015 2:08:43.657 pm ASObjC Explorer 4[8008]: Logging Some string and (
1,
2,
3
) and 5 and 3.500000
Great advice, as usual - thanks!
I’ll probably wrap whatever I want to log in a handler I use often called coerceToString. It’s a little hacky, using AppleScript’s error message returned when trying to coerce a variable into a number to extract an exact string representation of the variable’s value. That means that this is an English-specific function:
on coerceToString(incomingObject)
-- version 1.8, Daniel A. Shockley, http://www.danshockley.com
-- 1.8 - instead of trying to store the error message used, generate it
-- 1.7 - added "Can't make " with a curly single-quote.
-- 1.6 - can add additional errMsg parts (just add to lists to handle other languages.
-- Currently handles English in both 10.3 and 10.4 (10.3 uses " into a number."
-- while 10.4 uses " into type number.")
-- 1.5 - added Unicode Text
-- 1.0 - based on ideas/advice from MacScripter.net, etc
set errMsgLeadList to {"Can't make ", "Can't make "}
set errMsgTrailList to {" into a number.", " into type number."}
if class of incomingObject is string then
set {text:incomingObject} to (incomingObject as string)
return incomingObject
else if class of incomingObject is integer then
set {text:incomingObject} to (incomingObject as string)
return incomingObject as string
else if class of incomingObject is real then
set {text:incomingObject} to (incomingObject as string)
return incomingObject as string
else if class of incomingObject is Unicode text then
set {text:incomingObject} to (incomingObject as string)
return incomingObject as string
else
-- LIST, RECORD, styled text, or unknown
try
try
"XXXX" as number
-- GENERATE the error message for a known string so we can get
-- the 'lead' and 'trail' part of the error message
on error errMsg number errNum
set {oldDelims, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {"\"XXXX\""}}
set {errMsgLead, errMsgTrail} to text items of errMsg
set AppleScript's text item delimiters to oldDelims
end try
set testMultiply to 1 * incomingObject -- now, generate error message for OUR item
-- what items is THIS used for?
-- how does script ever get past the above step??
set listText to (first character of incomingObject)
on error errMsg
--tell me to log errMsg
set objectString to errMsg
if objectString contains errMsgLead then
set {od, AppleScript's text item delimiters} to {AppleScript's text item delimiters, errMsgLead}
set objectString to text item 2 of objectString
set AppleScript's text item delimiters to od
end if
if objectString contains errMsgTrail then
set {od, AppleScript's text item delimiters} to {AppleScript's text item delimiters, errMsgTrail}
set AppleScript's text item delimiters to errMsgTrail
set objectString to text item 1 of objectString
set AppleScript's text item delimiters to od
end if
set {text:objectString} to (objectString as string)
end try
return objectString
end if
end coerceToString