Search code examples
macoscocoaapplescriptcocoa-scripting

Cocoa Scripting: Use special string types like raw data


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.


Solution

  • 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