Convert NSData into raw data string

My scriptable app iClip can take clipboard data arguments in the form of raw strings (those that contain both the type as a 4-char-code along with the corresponding data) but it can’t take NSData types currently.

Now, I have data in an ObjC’s NSData object and like to convert it into a raw string so that I can pass that to iClip’s functions that take an “any” parameter.

It’s binary data so I can’t just convert it into a textual string.

How can I do that, provided I know the 4-char type code for it?

(See also my related question on StackOverflow)

If I understand correctly — and I’m not sure I do — AppleScript has a data type.

Indeed, I can use “data” as a type in the .sdef file:

<suite name="Demo Suite" code="Demo" description="Demo suite">
	<class name="application" code="capp" description="The application&apos;s top-level scripting object.">
		<property name="raw data" code="rawD" description="for testing raw data" type="data">
			<cocoa key="rawData"/>
		</property>
	</class>

And then run this script:

use framework "Foundation"

tell application id "org.tempel.xojo.demo.scriptable" -- AppleScriptable.app
	set theData to current application's NSData's dataWithContentsOfFile:"/etc/hosts"
	set raw data to theData
end tell

However, that leads to the error, even before my rawData method is invoked:

Couldn’t write data because C and Objective-C pointers cannot be saved in scripts.
Offending object: «data optr0000000000276300C0600000»

I guess I need some coercion or conversion function somewhere, but I can’t figure out where and how.

Here are handlers for converting from NSData to AppleScript data, and vice-versa:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set aString to current application's NSString's stringWithString:"one two"

set theData to aString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set asData to my ASDataFromData:theData
set newData to my dataFromASData:asData

on ASDataFromData:theData
	set theCode to current application's NSHFSTypeCodeFromFileType("'rdat'")
	return (current application's NSAppleEventDescriptor's descriptorWithDescriptorType:theCode |data|:theData) as data
end ASDataFromData:

on dataFromASData:asData
	return (current application's NSArray's arrayWithObject:asData)'s firstObject()'s |data|()
end dataFromASData:
1 Like

Wow, that looks good. It appears ‘rdat’ is a “special” type for NSData conversion, right?
How did you learn about that? RE?

Also, that’s an odd method with the [NSArray firstObject]. Basically, that’s no-op, yet it’s needed to get the conversion going?

So, I managed to receive these “rdat” raw string in my scriptable app now, i.e. assigning NSData to my app works now. What doesn’t work is the other way around, although I thought that should be easier: Returning NSData (or its rdat equivalent) from my app to a script that wants to get such a value. See my SO answer that is incomplete in this regard: https://stackoverflow.com/a/55870034/43615 - a little help would be great.

It’s the standard AS code for the data descriptor type.

It’s not a no-op. When you create an array, any non-bridgeable AS items in it get converted to NSAppleEventDescriptors. So it’s effectively calling the -data method on an event descriptor.

1 Like

Try this:

    return [NSAppleEventDescriptor descriptorWithDescriptorType:typeData data:data];

I already tried that (see https://stackoverflow.com/a/55870034/43615), although I used literally ‘rdat’ as OSType. Maybe I’m using the wrong type here?

I can’t find typeData declared. At least not in AEDataModel.h. Ah - AERegistry.h declares it, but as ‘tdta’!

Sadly, that still leads to error -10000.

What fixes the issue is something else:

Up until now, I declare, in the sdef, the property for the “rawdata” as: type=“rdat”. That seems to be the issue. If I change it to type=“any”, then I can return a data value, and then it does not even matter whether I use ‘tdta’ or ‘rdat’ with the descriptorWithDescriptorType method.

“any” as property type works, although SD’s dictionary viewer shows that as a type, whereas if I’d use “data” or “rdat”, SD shows that as the data type, with a more fitting description of what that type means.

I guess I need to provide a conversion function for this. Just not clear for which types, as there is no explicitly value-type for “data” or “rdat”. Will some try some things.

Could it be that you made a mistake and meant to use “tdta” (typeData)? Because, ‘rdat’ does not appear anywhere in the AE headers as far as I can tell (searched the entire AE.framework headers with BBEdit).

Answer to self: No, only ‘rdat’ works. Any other type won’t convert NSData. It’s magic!

Anyway. In regards to passing raw data to and from my app’s properties, this is good enough. It would be nicer if the property type was actually shown as being of “data” type instead of “any”, but that I can live with.

Shane, many thanks for your invaluable help!

Run this code with a real alias:

read someAlias as data

Now look at the the source of the result, which begins: «data rdat....

Choose AEPrint view and it’s even clearer.

That’s a limitation of Cocoa scripting — only a handful of classes are automatically handled.

Thomas, the “raw data” enum is defined in Carbon/OpenScripting/ASRegistry.h

cRawData = 'rdat',

1 Like