Script: Export to OPML or BML (HTML outline)

Got it.

Could you give me:

  • a snippet of TaskPaper (as a file attachment here, perhaps zipped) which provokes the problem
  • a quick description of an import procedure (for Scrivener, for example, or iThought), which will reproduce the issue

?


In the meanwhile, it may be worth experimenting with this variant, which just post-processes the TP3 ItemSerializer OPML with .replace(/&/gu, "&")

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

    // RobTrew @2023
    // Possible entity fix

    // Ver 0.01

    // ---------- TASKPAPER EVALUATION CONTEXT -----------

    const tp3Context = editor =>
        ItemSerializer.serializeItems(
            editor.outline.items,
            editor,
            ItemSerializer.OPMLMimeType
        );

    // ------------- JXA EVALUATION CONTEXT --------------

    // main :: IO ()
    const main = () => {
        const
            doc = Application("TaskPaper")
            .documents.at(0);

        return either(
            alert("Save TaskPaper as OPML")
        )(
            fpWritten => fpWritten
        )(
            doc.exists() ? (() => {
                const maybeFile = doc.file();

                return bindLR(
                    null !== maybeFile ? (
                        Right(`${maybeFile}`)
                    ) : Left("First save TaskPaper document.")
                )(fpTaskPaper => {
                    const
                        fpFolder = takeDirectory(fpTaskPaper),
                        fileStem = takeBaseName(fpTaskPaper),
                        fpOut = Object.assign(
                            Application.currentApplication(),
                            {includeStandardAdditions: true}
                        )
                        .chooseFileName({
                            withPrompt: "Save as OPML",
                            defaultName: `${fileStem}.opml`,
                            defaultLocation: fpFolder
                        });

                    return writeFileLR(`${fpOut}`)(
                        doc.evaluate({
                            script: `${tp3Context}`
                        })
                        .replace(/&/gu, "&")
                    );
                });
            })() : Left("No document open in TaskPaper.")
        );
    };

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


    // takeBaseName :: FilePath -> String
    const takeBaseName = fp =>
    // The filename without any extension.
        ("" !== fp) ? (
            ("/" !== fp[fp.length - 1]) ? (() => {
                const fn = fp.split("/").slice(-1)[0];

                return fn.includes(".") ? (
                    fn.split(".").slice(0, -1)
                    .join(".")
                ) : fn;
            })() : ""
        ) : "";


    // takeDirectory :: FilePath -> FilePath
    const takeDirectory = fp =>
    // The directory component of a filepath.
        "" !== fp ? (
            (xs => xs.length > 0 ? xs.join("/") : ".")(
                fp.split("/").slice(0, -1)
            )
        ) : ".";


    // writeFileLR :: FilePath ->
    // String -> Either String IO FilePath
    const writeFileLR = fp =>
    // Either a message or the filepath
    // to which the string has been written.
        s => {
            const
                uw = ObjC.unwrap,
                e = $(),
                efp = $(fp).stringByStandardizingPath;

            return $.NSString.alloc.initWithUTF8String(s)
            .writeToFileAtomicallyEncodingError(
                efp, false,
                $.NSUTF8StringEncoding, e
            ) ? (
                    Right(uw(efp))
                ) : Left(uw(e.localizedDescription));
        };

    // MAIN ---
    return main();
})();