Script: Import OPML file to a nested OG6 diagram (JXA)

A script which imports OPML into OmniGraffle, using a specified template and shape size to create a nested diagram.

//Ver 0.02
//Import an OPML file to a nested OG6 diagram
// (JavaScript for Automation)
// Run from Script editor with language selected at top left set to JavaScript
// 1. Choose an OPML from the file dialog which appears
// 2. Confirm

// Draws a nested diagram using a specified:
// - Template
// - Width and height for node shapes

// Select alternative template name and/or shape size in last line of script
(function (strTemplate, lstSize) {
    'use strict';

    // for type strings see: Apple's 'System-Declared Uniform Type Identifiers'
    // if strType is omitted, files of all types will be selectable
    // String -> String -> String
    function pathChoice(strPrompt, strType) {
        var a = Application.currentApplication();

        return (a.includeStandardAdditions = true, a)
            .chooseFile({
                withPrompt: strPrompt,
                ofType: strType
            }).toString();
    }

    // String -> String
    function textFileContents(strPath) {
        return $.NSString.stringWithContentsOfFile(strPath);
    }


    // String -> [dct]
    function jsoFromOPML(strOPML) {
        // [NSXMLElement] -> [jso]
        function jsoNest(lst) {
            return lst.map(function (oElem) {
                var lstNest = ObjC.unwrap(oElem.children),
                    dctNode = lstNest ? {
                        text: '',
                        nest: jsoNest(lstNest)
                    } : {
                        text: ''
                    };

                ObjC.unwrap(oElem.attributes)
                    .forEach(function (oAttrib) {
                        var strKey = ObjC.unwrap(oAttrib.name),

                            strVal = ObjC.unwrap(oAttrib.stringValue),
                            iKey = ['text', '_note'].indexOf(strKey);

                        if (iKey !== 0) {
                            if (iKey === 1) dctNode.note = strVal;
                            else {
                                if (!dctNode.tags) dctNode.tags = {};
                                dctNode.tags[strKey] = strVal;
                            }
                        } else dctNode.text = strVal;
                    });

                return dctNode;
            });
        }

        return jsoNest(
            ObjC.unwrap(
                $.NSXMLDocument.alloc.initWithXMLStringOptionsError(
                    strOPML, 0, null
                ).rootElement.childAtIndex(1).children
            )
        );
    }


    // [{}] -> String -> (Integer, Integer) -> OmniGraffle6Doc
    function jsoToOG6(lstJSO, strTemplate, lstSize) {
        function drawTree(graphics, shp, idShape, lstChiln) {

            lstChiln.forEach(function (oNode) {
                var shpChild = og.Shape({
                        text: oNode.text,
                        size: lstSize,
                        notes: oNode.note
                    }),
                    lstNest = oNode.nest,
                    dctTags = oNode.tags;

                // add shape to canvas graphics
                graphics.push(shpChild);

                var idChild = shpChild.id();

                // record link for later creation through AppleScript kludge
                // (see connect bug below)
                lstLinks.push([idShape, idChild]);

                // BUG: It seems that the connect command may be broken in the JS interface
                //      (trips a type error)
                // og.connect(shp(), {
                //  to: child()
                // });

                if (lstNest && lstNest.length) {
                    drawTree(graphics, shpChild, idChild, lstNest);
                }

                if (dctTags) addData(shpChild, dctTags);

            });
        };

        function addData(shp, dctUser) {
            var userData = shp.userDataItems;

            Object.keys(dctUser).forEach(function (k) {
                userData[k].value = dctUser[k];
            });
        }

        if (lstJSO.length > 1) {

            var og = Application("OmniGraffle"),
                a = Application.currentApplication(),
                sa = (a.includeStandardAdditions = true, a),
                blnTemplate = og.availableTemplates()
                .map(function (x) {
                    return x.split("/")[1];
                })
                .indexOf(strTemplate) !== -1;


            // Template found ?
            if (!blnTemplate) {
                sa.activate();
                sa.displayDialog('Template not found:\n\n\t"' + strTemplate + '\"', {
                    withTitle: "Import OPML into OmniGraffle"
                });
                return;
            }

            var ds = og.documents,
                d = ((function () {
                    return ds.push(
                        og.Document({
                            template: strTemplate
                        })
                    )
                })() && ds[0]),
                cnv = d ? d.canvases.at(0) : undefined,
                oLayout = cnv ? cnv.layoutInfo() : undefined;


            if (oLayout) {
                var graphics = cnv.graphics,
                    layer = d.layers.at(0),
                    lstLinks = [];

                // suspended during drawing
                oLayout.automaticLayout = false;
                if (graphics.length) og.delete(graphics);

                lstJSO.forEach(function (oNode) {
                    var shp = og.Shape({
                            text: oNode.text,
                            size: lstSize,
                            notes: oNode.note
                        }),
                        lstChiln = oNode.nest,
                        dctTags = oNode.tags;

                    graphics.push(shp);

                    if (lstChiln) {
                        drawTree(graphics, shp, shp.id(), lstChiln);
                    }

                    if (dctTags) addData(shp, dctTags);
                });

                // FALL BACK TO APPLESCRIPT KLUDGE EMBEDDED IN JS TO CREATE PARENT-CHILD LINKS BETWEEN SHAPES
                // (The JavaScript interface to doing this appears to be broken at the moment)
                sa.doShellScript(
                    "osascript <<AS_END 2>/dev/null\ntell application \"OmniGraffle\" to tell front canvas of front document\n" +
                    "set recP to {line type: straight}\n" +
                    lstLinks.map(function (pair) {
                        return 'connect shape id ' + pair[0] + ' to shape id ' + pair[1] //+
                            //' with properties recP';
                    }).join('\n') + "\nend tell\nAS_END\n"
                );

                // restored
                oLayout.automaticLayout = true;
                cnv.layout(graphics);
                og.activate();

            }
        }
    }


    // MAIN

    jsoToOG6(
        jsoFromOPML(
            textFileContents(
                pathChoice('OPML file:', 'public.xml')
            )
        ),
        strTemplate, lstSize
    );

})('Hierarchical', [80, 80]);
1 Like

Ver 0.02 above transfers any OPML attributes to OmniGraffle shape metadata

_note (or OO note) -> Shape.notes
other -> Shape.userData

e.g.

1 Like

Is this working on OG7?

Editing rights seem to have expired for the original above, but this slightly adjusted version (Ver 3) appears to be working with OG7:

//Ver 0.03
//Import an OPML file to a nested OG6/7 diagram
// (JavaScript for Automation)
// Run from Script editor with language selected at top left set to JavaScript
// 1. Choose an OPML from the file dialog which appears
// 2. Confirm

// Draws a nested diagram using a specified:
// - Template
// - Width and height for node shapes

// Select alternative template name and/or shape size in last line of script
(function (strTemplate, lstSize) {
    'use strict';

    // for type strings see: Apple's 'System-Declared Uniform Type Identifiers'
    // if strType is omitted, files of all types will be selectable
    // String -> String -> String
    function pathChoice(strPrompt, strType) {
        var a = Application.currentApplication();

        return (a.includeStandardAdditions = true, a)
            .chooseFile({
                withPrompt: strPrompt,
                ofType: strType
            })
            .toString();
    }

    // String -> [dct]
    function jsoFromOPML(strOPML) {

        // [NSXMLElement] -> [jso]
        function jsoNest(lst) {
            return lst.map(function (oElem) {
                var lstNest = ObjC.unwrap(oElem.children),
                    dctNode = lstNest ? {
                        text: '',
                        nest: jsoNest(lstNest)
                    } : {
                        text: ''
                    };

                ObjC.unwrap(oElem.attributes)
                    .forEach(function (oAttrib) {
                        var strKey = ObjC.unwrap(oAttrib.name),

                            strVal = ObjC.unwrap(oAttrib.stringValue),
                            iKey = ['text', '_note'].indexOf(strKey);

                        if (iKey !== 0) {
                            if (iKey === 1) dctNode.note = strVal;
                            else {
                                if (!dctNode.tags) dctNode.tags = {};
                                dctNode.tags[strKey] = strVal;
                            }
                        } else dctNode.text = strVal;
                    });

                return dctNode;
            });
        }

        return jsoNest(
            ObjC.unwrap(
                $.NSXMLDocument.alloc.initWithXMLStringOptionsError(
                    strOPML, 0, null
                )
                .rootElement.childAtIndex(1)
                .children
            )
        );
    }
    // [{}] -> String -> (Integer, Integer) -> OmniGraffle6Doc
    function jsoToOG6(lstJSO, strTemplate, lstSize) {
        function drawTree(graphics, shp, idShape, lstChiln) {

            lstChiln.forEach(function (oNode) {
                var shpChild = og.Shape({
                        text: oNode.text,
                        size: lstSize,
                        notes: oNode.note
                    }),
                    lstNest = oNode.nest,
                    dctTags = oNode.tags;

                // add shape to canvas graphics
                graphics.push(shpChild);

                var idChild = shpChild.id();

                // record link for later creation through AppleScript kludge
                // (see connect bug below)
                lstLinks.push([idShape, idChild]);

                // BUG: It seems that the connect command may be broken in the JS interface
                //      (trips a type error)
                // og.connect(shp(), {
                //  to: child()
                // });

                if (lstNest && lstNest.length) {
                    drawTree(graphics, shpChild, idChild, lstNest);
                }

                if (dctTags) addData(shpChild, dctTags);

            });
        };

        function addData(shp, dctUser) {
            var userData = shp.userDataItems;

            Object.keys(dctUser)
                .forEach(function (k) {
                    userData[k].value = dctUser[k];
                });
        }

        if (lstJSO.length > 0) {

            var og = Application("OmniGraffle"),
                a = Application.currentApplication(),
                sa = (a.includeStandardAdditions = true, a),
                blnTemplate = og.availableTemplates()
                .map(function (x) {
                    return x.split("/")[1];
                })
                .indexOf(strTemplate) !== -1;
            // Template found ?
            if (!blnTemplate) {
                sa.activate();
                sa.displayDialog('Template not found:\n\n\t"' + strTemplate + '\"', {
                    withTitle: "Import OPML into OmniGraffle"
                });
                return;
            }

            var ds = og.documents,
                d = ((function () {
                    return ds.push(
                        og.Document({
                            template: strTemplate
                        })
                    )
                })() && ds[0]),
                cnv = d ? d.canvases.at(0) : undefined,
                oLayout = cnv ? cnv.layoutInfo() : undefined;

            if (oLayout) {
                var graphics = cnv.graphics,
                    layer = d.layers.at(0),
                    lstLinks = [];

                // suspended during drawing
                oLayout.automaticLayout = false;
                if (graphics.length) og.delete(graphics);

                lstJSO.forEach(function (oNode) {
                    var shp = og.Shape({
                            text: oNode.text,
                            size: lstSize,
                            notes: oNode.note
                        }),
                        lstChiln = oNode.nest,
                        dctTags = oNode.tags;

                    graphics.push(shp);

                    if (lstChiln) {
                        drawTree(graphics, shp, shp.id(), lstChiln);
                    }

                    if (dctTags) addData(shp, dctTags);
                });

                // FALL BACK TO APPLESCRIPT KLUDGE EMBEDDED IN JS TO CREATE PARENT-CHILD LINKS BETWEEN SHAPES
                // (The JavaScript interface to doing this appears to be broken at the moment)
                sa.doShellScript(
                    "osascript <<AS_END 2>/dev/null\ntell application \"OmniGraffle\" to tell front canvas of front document\n" +
                    "set recP to {line type: straight}\n" +
                    lstLinks.map(function (pair) {
                        return 'connect shape id ' + pair[0] + ' to shape id ' + pair[1] //+
                        //' with properties recP';
                    })
                    .join('\n') + "\nend tell\nAS_END\n"
                );

                // restored
                oLayout.automaticLayout = true;
                cnv.layout();
                og.activate();

            }
        }
    }

    // GENERIC FUNCTIONS ---------------------------------------------------------------------------

    // log :: a -> IO ()
    const log = (...args) =>
        console.log(
            args
            .map(show)
            .join(' -> ')
        );

    // readFile :: FilePath -> maybe String
    function readFile(strPath) {
        var error = $(),
            str = ObjC.unwrap(
                $.NSString.stringWithContentsOfFileEncodingError($(strPath)
                    .stringByStandardizingPath, $.NSUTF8StringEncoding, error)
            ),
            blnValid = typeof error.code !== 'string';
        return {
            nothing: !blnValid,
            just: blnValid ? str : undefined,
            error: blnValid ? '' : error.code
        };
    };

    // show :: a -> String
    const show = x => JSON.stringify(x); //, null, 2);

    // MAIN ---------------------------------------------------------------------------

    const dctFile = readFile(pathChoice('OPML file:', 'public.xml'));

    return dctFile.nothing ? dctFile.error : (
        jsoToOG6(
            jsoFromOPML(dctFile.just),
            strTemplate, lstSize
        )
    );

})('Hierarchical', [80, 80]);

Thank you very much I’ll try it now

I’m getting a < Template not found: “Hierarchical” > error.

I’m using OmniGraffle 7.3.1 in Spanish localization. Already changed it to “Jerárquica” as localized in spanish, but I get the same error.

What is returned from this in JXA on your system ?

(function () {
    'use strict';

    var og = Application('OmniGraffle');

    return og.availableTemplates();
})();

Depending on what that function returns, you may need to use a longer string, including a path, analogous to things like:

"Diagrams/Hierarchical"