1

My app has some raw data content I want to be able to offer to AppleScript so that it can be at least looked at, if not even handled by saving it to a file or setting it to some other object that supports it.

Now, I don't understand which data type is used to accomplish that.

See this output from Script Editor, for instance:

tell application "Script Editor"
  the clipboard as record
    --> {Unicode text:"text",
         «class BBLM»:«data BBLM6C6C756E»,
         string:"text"}
end tell

How do I return these «data ...», which are apparently a combination of a 4-char-code and hex-string-encoded bytes of the actual data.

I've tried returning an NSData object containing the raw bytes data from my scriptable property, but that doesn't work.

Update

It appears it has to do with implementing scripting<type>Descriptor and scripting<type>WithDescriptor. I cannot find any documentation on this other than it being used in the Sketch sample code. I assume these will be invoked for the type if I happen to define such a custom type in my Sdef.

However: I will not know the types I want to send in advance, so I cannot pre-define them in the Sdef. I'm more in the situation similar to the clipboard: I have clipboard-like data I want to return, so I only know their 4-char-types at runtime. Which means I won't be asked through these handlers. There must be some other way to generically create and receive these types, the same way the clipboard implementation does it.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
  • 1
    `NSData` objects are used for custom `value-types` conforming to `NSCoding` which cannot be represented by the basic classes AppleScript supports. For example the basic AppleScript types `point` and `rectangle` are C-types wrapped in `NSData`. To define a custom `value-type` you have to provide the target class in the sdef file and implement both `scripting...` methods in a category of the target class. – vadian Apr 03 '16 at 13:56
  • After some more digging based on your comments in my other question (http://stackoverflow.com/questions/36363705/), I've finally figured out the thing that you meant and that I had not comprehended yet, i.e. that it all comes down to using NSAppleEventDescriptor, which is the type that every Cocoa type gets assigned to eventually before returning the value to the running Applescript. – Thomas Tempelmann Apr 03 '16 at 14:17
  • 1
    Exactly, Cocoa Scripting could be a great thing, but poorly documented and pretty error-prone. Here in Switzerland we would say "s'isch en saich" (similar to "pain in the ass") – vadian Apr 03 '16 at 14:21
  • So, thanks for your help, without your pointers I had not figured this out, despite it now being all so "of course, that makes total sense!" :) I hope this might help others as well in the future. For those who had worked with AS before the Cocoa layer, this might all be obvious stuff, but I was totally stumped. – Thomas Tempelmann Apr 03 '16 at 14:28

3 Answers3

2

RE: "...implementing scripting<Key>Descriptor and scripting<Key>WithDescriptor. I cannot find any documentation on this..."

The first place to start is "Key-Value Coding and Cocoa Scripting" section in the Cocoa Scripting Guide (2008). There are a whole slew of these methods that embed the type in the method name. Many are also documented in the Foundation's NSScriptKeyValueCoding Protocol Reference page, but you have to read the "Discussion" section to find them. For instance, in:

- (id)valueWithUniqueID:(id)uniqueID inPropertyWithKey:(NSString *)key

the discussion says: "The method valueIn<Key>WithUniqueID: is invoked if it exists."

So in a Widgets class, you would implement valueInWidgetsWithUniqueID:

scripting<Key>Descriptor and scripting<Key>WithDescriptor are special conversion handlers that are used when you use a element in your application's .sdef, which is why they show up in Sketch to handle the typeRGBColor data type, a list of 3 integers. I can't find these documented outside of the Sketch code either, but I can confirm that

scriptingRGBColorDescriptor

is called by methods in:

NSObject(NSScriptAppleEventConversion)
NSAppleEventDescriptor(NSScriptConversion)
Ron Reuter
  • 1,287
  • 1
  • 8
  • 14
  • 1
    My testing shows that even if you do not implement the `scriptingWithDescriptor` function, you will get the original `NSAppleEventDescriptor` through the `valueWithUniqueID:` handler and then you can extract the `data` and `descriptorType` from it. – Thomas Tempelmann Apr 04 '16 at 13:59
  • 1
    It appears implementing `scriptingDescriptor` becomes necessary once one specifies a particular type for the property in the the Sdef. If one uses "any" as the type, this is not needed. – Thomas Tempelmann Apr 22 '16 at 14:41
  • 1
    That would seem to confirm that they are called as part of the descriptor coercion process. When you specify "any" type, you are telling the engine not to coerce the result. – Ron Reuter Apr 25 '16 at 15:39
1

RE: "However: I will not know the types I want to send in advance, so I cannot pre-define them in the Sdef."

There is a way to solve that problem: you can return a special list structure known as a User-Field Record (typeUserField). This record includes alternating Key and Value descriptors, and does not require anything to be defined in the SDEF.

Here's an item I posted on the ASOC mailing list last year: http://lists.apple.com/archives/applescriptobjc-dev/2015/Jan/msg00036.html

And here's the code (using AppleScript-ObjectiveC code) to build the typeUserField record from an NSDictionary.

# ASOC implementation of - (NSAppleEventDescriptor *)scriptingRecordDescriptor for an NSDictionary
# Creates an empty record descriptor and an empty list descriptor, then
# Iterates over the dictionary and inserts descriptors for each key and each value into the list descriptor
# Finally, populates the record descriptor with the type 'usrf' and the list descriptor

on makeUserRecordDescriptor(aDict)

log aDict

set recordDescriptor to aedClass's recordDescriptor()
set listDescriptor   to aedClass's listDescriptor()

set typeUserField to 1970500198 -- 'usrf'

set itemIndex to 1 -- AS records are 1-based

repeat with aKey in aDict's allKeys()

    set aVal to aDict's valueForKey_(aKey)

    -- The values can be several different types. This code DOES NOT handle them all.

    set isStringValue  to aVal's isKindOfClass_(nssClass's |class|) = 1
    set isNumericValue to aVal's isKindOfClass_(nsnClass's |class|) = 1
    set isBooleanValue to aVal's className()'s containsString_("Boolean") = 1

    -- Insert a descriptor for the key into the list descriptor

    set anItem to aedClass's descriptorWithString_(aKey)
    listDescriptor's insertDescriptor_atIndex_(anItem, itemIndex)
    set itemIndex to itemIndex + 1

    -- Insert a descriptor (of the correct type for the value) into the list descriptor

    if isStringValue
        set anItem to aedClass's descriptorWithString_(aVal)
    else if isBooleanValue
        set anItem to aedClass's descriptorWithBoolean_(aVal's boolValue())
    else if isNumericValue
        set intValue to aVal's intValue()
        set fpValue to aVal's doubleValue()

        if intValue = fpValue
            set anItem to  aedClass's descriptorWithInt32_(aVal's intValue())
        else
            set anItem to  aedClass's descriptorWithString_(aVal's stringValue) # TODO: 'doub'
        end
    else
        set anItem to  aedClass's descriptorWithString_("Unhandled Data Type")
    end

    listDescriptor's insertDescriptor_atIndex_(anItem, itemIndex)
    set itemIndex to itemIndex + 1

end

recordDescriptor's setDescriptor_forKeyword_(listDescriptor, typeUserField)

return recordDescriptor

end

Ron Reuter
  • 1,287
  • 1
  • 8
  • 14
0

The magic lies in using NSAppleEventDescriptor. It offers a lot of initializers. It's the one that eventually holds any value that gets passed back to the calling AppleScript (or JXA or whatever uses the Scripting engine).

Apparently, any value returned to the Cocoa Scripting layer, such as strings as NSString and numeric values as NSNumber, end up being assign to a NSAppleEventDescriptor object, and converted by that step to the AppleEvent-internal format.

So, if I want to return a string of bytes, e.g. stored in an NSData object, all I have to do is this from my property method:

-(id)returnSomeBytes {
    return [NSAppleEventDescriptor descriptorWithDescriptorType:'Raw ', myNSDataObject];
}

This will end up in AppleScript as «data Raw ...».

I now also understand why the scripting engine won't automatically convert NSData for me: It needs a type code, which NSData doesn't inherit.

The reverse works just as well - any such raw data gets passed to my code as a NSAppleEventDescriptor, which I can then decode accordingly.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149