Read a segment in a TMX file

How to read a segment in a TMX file? Example at: Translation Memory eXchange - Wikipedia

Input: “Hello world!”
Output: “Bonjour tout le monde!”

It’s easy to get the English segment for instance via a Prompt with list. But how to get the next or the n+3 paragraph?

Broadly, JavaScript for Automation, using the NSXMLDocument interface.


You would need to show us a longer sample of the XML to clarify what

  • "n+3", and
  • "paragraph"

actually mean, concretely.

(The linked snippet only has one pair of terms)

If a larger TMX file just has, for example, a longer chain of <tu\> units, then you may be able to extract a JSON Array version in this pattern:

[
  {
    "en": "Hello world!",
    "fr": "Bonjour tout le monde!"
  },
  {
    "en": "spring",
    "fr": "printemps"
  }
]

and then extract the parts you want with Keyboard Maestro's JSON substring notation:

manual:JSON [Keyboard Maestro Wiki]

token:JSONValue [Keyboard Maestro Wiki]


Elements extracted from TMX file.kmmacros (8.2 KB)

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

    // Rob Trew @2022
    // First guess at reading TMX files.

    const main = () => {
        const
            kme = Application("Keyboard Maestro Engine"),
            xml = kme.getvariable("tmxSample");

        return either(
            alert("Reading TMX files")
        )(x => x)(
            bindLR(
                xmlNodeFromXmlStringLR(xml)
            )(
                node => Right(transUnitsFromNode(node))
            )
        );
    };

    // ----------------------- XML -----------------------

    // transUnitsFromNode :: XMLNode -> [Dict]
    const transUnitsFromNode = xmlNode => {
        const
            unWrap = ObjC.unwrap,
            tmx = xmlNode.childAtIndex(0),
            body = tmx.childAtIndex(1),
            tuList = unWrap(body.children);

        return tuList.map(
            tu => unWrap(tu.children).reduce(
                (a, tuv) => Object.assign(a, {
                    [
                        unWrap(
                            tuv
                            .attributeForName("xml:lang")
                            .stringValue
                        )
                    ]: unWrap(
                        tuv.childAtIndex(0).stringValue
                    )
                }), {}
            )
        );
    };

    // xmlNodeFromXmlStringLR :: XML String ->
    // Either String NSXMLNode
    const xmlNodeFromXmlStringLR = s => {
        const
            error = $(),
            node = $.NSXMLDocument.alloc
            .initWithXMLStringOptionsError(
                s, 0, error
            );

        return node.isNil() ? (() => {
            const problem = ObjC.unwrap(
                error.localizedDescription
            );

            return Left(
                `Not parseable as XML:\n\n${problem}`
            );
        })() : Right(node);
    };

    // ----------------------- 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);


    // 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);


    // sj :: a -> String
    const sj = (...args) =>
        // Abbreviation of showJSON for quick testing.
        // Default indent size is two, which can be
        // overriden by any integer supplied as the
        // first argument of more than one.
        JSON.stringify.apply(
            null,
            1 < args.length && !isNaN(args[0]) ? [
                args[1], null, args[0]
            ] : [args[0], null, 2]
        );

    return sj(main());
})();