Process XML and extract to a tab-delimited file

For simple cases of fetching data like this, without much further processing, XQuery may be more than you need, and the XPath expression alone is probably enough:

//ntig/termGrp/term/text()

It just requires a slightly different calling function in the JXA:

Tabbed pairs from TXE via XPATH.kmmacros (15 KB)


Expand disclosure triangle to view JS source
(() => {
    "use strict";

    const main = () => {
        const
            uw = ObjC.unwrap,
            kmVar = kmValue(kmInstance());

        return either(
            alert("Term pairs from XML by XPath")
        )(
            compose(
                intercalate("\n"),
                map(intercalate("\t")),
                chunksOf(2)
            )
        )(
            fmapLR(
                xs => xs.map(x => uw(x.stringValue))
            )(
                xPathMatchesFromXmlLR(
                    kmVar("local_XPath")
                )(
                    kmVar("local_TXE_XML")
                )
            )
        );
    };

    // ---------------- KEYBOARD MAESTRO -----------------

    // kmValue :: KM Instance -> String -> IO String
    const kmValue = instance =>
        k => Application("Keyboard Maestro Engine")
        .getvariable(k, {instance});


    // kmInstance :: () -> IO String
    const kmInstance = () =>
        ObjC.unwrap(
            $.NSProcessInfo.processInfo.environment
            .objectForKey("KMINSTANCE")
        ) || "";

    // --------------------- XPATH ----------------------

    // xPathMatchesFromXmlLR :: String ->
    // String -> Either String [NSXMLElement]
    const xPathMatchesFromXmlLR = xpath =>
        xml => {
            const
                uw = ObjC.unwrap,
                error = $(),
                xmlDoc = $.NSXMLDocument.alloc
                .initWithXMLStringOptionsError(
                    xml, 0, error
                );

            return bindLR(
                xmlDoc.isNil()
                    ? Left(uw(error.localizedDescription))
                    : Right(xmlDoc)
            )(doc => {
                const
                    e = $(),
                    matches = (
                        doc.documentContentKind = (
                            $.NSXMLDocumentXMLKind
                        ),
                        doc.nodesForXPathError(
                            xpath, e
                        )
                    );

                return matches.isNil()
                    ? Left(uw(e.localizedDescription))
                    : Right(uw(matches));
            });
        };


    // ----------------------- JXA -----------------------

    // alert :: String => String -> IO String
    const alert = title =>
        s => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            return (
                sa.activate(),
                sa.displayDialog(s, {
                    withTitle: title,
                    buttons: ["OK"],
                    defaultButton: "OK"
                }),
                s
            );
        };

    // --------------------- GENERIC ---------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });


    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);


    // chunksOf :: Int -> [a] -> [[a]]
    const chunksOf = n => {
    // xs split into sublists of length n.
    // The last sublist will be short if n
    // does not evenly divide the length of xs .
        const go = xs => {
            const chunk = xs.slice(0, n);

            return 0 < chunk.length
                ? [chunk, ...go(xs.slice(n))]
                : [];
        };

        return go;
    };


    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
    // A function defined by the right-to-left
    // composition of all the functions in fs.
        fs.reduce(
            (f, g) => x => f(g(x)),
            x => x
        );


    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);


    // fmapLR (<$>) :: (b -> c) -> Either a b -> Either a c
    const fmapLR = f =>
        // Either f mapped into the contents of any Right
        // value in e, or e unchanged if is a Left value.
        e => "Left" in e ? (
            e
        ) : Right(f(e.Right));


    // intercalate :: String -> [String] -> String
    const intercalate = s =>
    // The concatenation of xs
    // interspersed with copies of s.
        xs => xs.join(s);


    // map :: (a -> b) -> [a] -> [b]
    const map = f =>
    // The list obtained by applying f
    // to each element of xs.
    // (The image of xs under f).
        xs => [...xs].map(f);

    return main();
})();
1 Like