JXA question about getting the text of a plist node

I have som JXA code that gives me an error:

var error = null;
var _text = $.NSPropertyListSerialization
    .dataWithPropertyList(
        macro.actions,
        $.XMLFormat_v1_0,
        null, error);

It tells me “$.NSPropertyListSerialization.dataWithPropertyList’ is not a function”. But it’s listed in the docs.

What am I doing wrong?

To translate the function names in the docs to their JSC ObjC interface equivalents, search for the 'JSExport' conventions (and the examples) in:

https://developer.apple.com/library/mac/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-10.html#//apple_ref/doc/uid/TP40014508-CH109-SW1

You can use the debugger to check that you have got the live function name, but another quick trick is to try the function name out in Script Editor, and see if the Results panel shows undefined or something like function prototype or [function anonymous]

Here’s my whole script. I’ve tried 15 different things:

(function Run() {
    'use strict';

    var _targetMacroUUID = "0BDD9DFE-8CC2-4578-A6F1-E972DCFA1595";

    function getGroupsAndMacrosArray() {
        var _kme = Application("Keyboard Maestro Engine");
        var _macros = _kme.getmacros({
            asstring: true
        });
        var _data = $.NSString.alloc.initWithUTF8String(_macros);
        return ObjC.deepUnwrap(
            $.NSPropertyListSerialization
            .propertyListWithDataOptionsFormatError(
                $(_data)
                .dataUsingEncoding($.NSUTF8StringEncoding),
                0, 0, null
            )
        );
    }

    function filterMacro(macro, index, array) {
        var _data = $.NSArray.arrayWithObject($.NSDictionary.dictionaryWithDictionary(macro.actions));
        var _textObjc =
            $.NSPropertyListSerialization
            .dataWithPropertyListFormatOptionsError(
                $(_data).dataUsingEncoding($.NSUTF8StringEncoding),
                0, 0, null);
        var _text = ObjC.unwrap(_textObjc);
        return _textObjc.includes(_targetMacroUUID);
    }

    function processGroup(group, index, array) {
        return group.macros
            .filter(filterMacro)
            .map(function(dict) {
                return dict.name;
            });
    }
    var _groups = getGroupsAndMacrosArray();

    return _groups
        .map(processGroup)
        .join("\n");
})()

I can do it in Swift, but the deserialization function that works in Swift, “dataFromPropertyList”, isn’t available (apparently) in JXA.

I’m really trying to learn this JXA stuff, but I gotta tell you, the documentation sucks. No two ways about it. And I’m really sick of it. I can read manuals, I can read technical blogs, white papers. But I can’t read what I can’t find.

Dan, I’m not sure I understand what you are trying to accomplish, but from little I do understand it looks like to me that the issues you are having are not with JXA itself, but with JavaScriptObjC, which really means Objective-C.

If you don’t mind explaining your objective in more detail, I’ll be glad to try to help.
But keep this in mind: In the land of the blind, the one-eyed man is king. :wink:

@DanThomas, have you looked at these posts:

Reads the main Keyboard Maestro Macros.plist directly ===>

On the plist side, these seem to be working well for me:

// writePlist :: Object -> String -> IO ()
function writePlist(jsObject, strPath) {
    $(jsObject)
        .writeToFileAtomically(
            $(strPath)
            .stringByStandardizingPath, true
        );
}

// readPlist :: String -> Object
function readPlist(strPath) {
    var strFullPath = $(strPath)
        .stringByStandardizingPath;

    return ObjC.deepUnwrap(
        $.NSDictionary.dictionaryWithContentsOfFile(
            strFullPath
        )
    ) || ObjC.deepUnwrap(
        $.NSArray.arrayWithContentsOfFile(strFullPath)
    );
}

On the set of Foundation and other ObjC classes available to the Automation interface, a quick preliminary check for availability, having made any relevant calls to

ObjC.import(strLibraryName)

Is just to evaluate one of the constants for the library that interests you. So, in Script Editor, or Atom with the Script package installed, the fact that evaluating:

$.NSPropertyListFormat

returns undefined is an immediate and really quite conclusive indication that this is perhaps “not the class we are looking for” to solve a particular problem.

I have no idea, but perhaps left out, in this case, because there are simpler routes from Automation JavaScript into and out of property lists ?

( or perhaps worth reporting, and making an argument that it would be useful to have in the Sierra iteration of macOS, if you feel it might be )

I don’t understand how to use this? What do I use for “strLibraryName”? How do I “Evaluate one of the constants”? Does this give me a list of some kind I can look through? That would be awesome.

Thanks.

All hail the king! Wherever he is. :slight_smile:

I can read the plist just fine, and parse the nodes just fine.

What I’m trying to do is, when I get to each macro node, get a string that represents the array of Actions in each macro. Given that string, I just want to see if a particular UUID is in the string anywhere.

This is so I can find what macros call a sub-macro.

Wasn’t that obvious from my code - the code that doesn’t work? Guess not, huh? :slight_smile:

I just found this:

http://opensource.apple.com/release/os-x-10112/

It looks really interesting. But I don’t know which file(s) might contain what I’d like to know…

There are some odds and ends here that might be relevant:

If you wanted to use AppKit functions you would start the script with

ObjC.import('AppKit')

ObjC.import('Foundation') is a default inclusion, so you don't have to write it.

Evaluate a constant:

  • Paste the $. - prefixed constant name into Script Editor
  • Hit Run to evaluate the expression
  • Inspect the output in the Results panel.

Here you can see one constant that turns out to be exposed by default in the Automation context,
and one that is not (undefined), indicating that its class is not available in the context

So I have to already know what I’m looking for, in order to find it?

Can’t I just get an API listing of what’s available, what parameters it takes, etc? You know, standard developer documentation-type stuff?

Look at this from my point of view, for a minute. How am I supposed to learn anything, without a dictionary? You have NO IDEA how FRUSTRATING this is. I’m just about ready to chuck it all and say it’s just not worth the frustration.

What we have at the moment for ObjC from JavaScript is just the standard Apple Class documents, with the JSExport rules for converting function names.

When you are reading the documentation for an Apple class, the first thing you need to check is whether it requires an import beyond the default 'Foundation' (top right of page)

and the second thing you need to check is whether it is actually available (given any required imports) to the Automation interface.

(As I suggested, typically by evaluating a bare constant or function name or class name, with the $. prefix)

I think making use of the ObjC documentation does take a bit of getting used to. Use of it from Swift is more fully documented. Perhaps it would make sense for you to get to know the framework classes through Swift first. I think their use from JS will be a lot more transparent then.

I did find using ObjC classes from JS a bit opaque at first - but I'm finding it works well now. Documentation could certainly be fuller :slight_smile:

PS I think this may be quite good material to look at:

Stepping back, you are looking (by UID) for macros which contain a particular action ?

Or looking for macros by their UID ?

@ComplexPoint, @JMichaelTX - Thanks, guys. I finally figured out what was happening. I wasn’t calling the function incorrectly after all. It turned out I wasn’t passing valid data to it. My unfamiliarity with JS and interpreting error messages, along with some bad assumptions on my part, were the problem.

And now I know that this wasn’t going to work after all. But that’s OK, because I can do it a different way.

Thanks for your help, and your patience with my impatience and frustration. It means a lot that you guys didn’t give up on me. Thanks.

1 Like