Script: Export to OPML or BML (HTML outline)

An export script for TaskPaper 3 which uses the built-in ItemSerializer object.

To use the script, adjust the option at the end (‘opml’ or ‘bml’) and install and run as in:

http://guide.taskpaper.com/using_scripts.html

(function (dctOptions) {
    'use strict';

    // TASKPAPER CONTEXT

    function serialized(editor, options) {
        return ItemSerializer.serializeItems(
            editor.outline.items,
            editor,
            ItemSerializer[
                options.format.toUpperCase() + 'MimeType'
            ]
        );
    }

    // JAVASCRIPT FOR AUTOMATION CONTEXT
    // writeFile :: FilePath -> String -> IO ()
    function writeFile(strPath, strText) {
        $.NSString.alloc.initWithUTF8String(strText)
            .writeToFileAtomicallyEncodingError(
                $(strPath)
                .stringByStandardizingPath, false,
                $.NSUTF8StringEncoding, null
            );
    }

    function exportFromTP3(docTP3, strPath, strFormat) {
        var strSerial = docTP3.evaluate({
            script: serialized.toString(),
            withOptions: {
                format: strFormat
            }
        });

        return (
            writeFile(strPath, strSerial),
            strSerial
        );
    }

    var tp3 = Application('com.hogbaysoftware.TaskPaper3'),
        ds = tp3.documents;

    if (ds.length) {
        var d = ds[0],
            a = Application('System Events'),
            sa = (a.includeStandardAdditions = true, a),

            pathFile = d.file(),
            lstParts = pathFile ? pathFile.toString()
            .split('/') : undefined,

            strFolder = lstParts ? (
                lstParts.slice(0, -1)
                .join('/') + '/'
            ) : undefined,

            strFormat = dctOptions.format.toLowerCase(),
            lstSupported = ['opml', 'bml', 'text'],
            strKnownFormat = (
                lstSupported
                .indexOf(strFormat) !== -1 ? strFormat : undefined
            );

        if (strKnownFormat) {
            sa.activate();
            var pathWrite = sa.chooseFileName({
                withPrompt: 'Save as ' + strKnownFormat.toUpperCase(),
                defaultName: strFolder ? (
                    lstParts.slice(-1)[0].split('.')[0] + '.' +
                    strKnownFormat
                ) : 'Untitled.' + strKnownFormat,
                defaultLocation: strFolder ? (
                    Path(strFolder)
                ) : sa.pathTo('desktop')
            });

            return pathWrite ? (
                exportFromTP3(d, pathWrite.toString(), strKnownFormat)
            ) : undefined;
        } else {
            return "Unknown format: '" + dctOptions.format + "'" +
                "\nExpected: ( '" + lstSupported.join("' | '") + "' )";
        };

    } else return undefined;

})({
    format: 'opml'
});

// 'bml' or 'BML'  (HTML outline e.g. FoldingText for Atom)
// 'opml' or 'OPML'

Does this script still work with TaskPaper 3.5? I have used it in the past and it worked extremely well. But I just tried it and the resultant file is not able to be opened.

It looks as if the serializer built into the Preview build may only be outputting the BML outline format at the moment - for OPML you may need to use an earlier build, or the next release …

(In the meanwhile, if you do need to generate OPML from the current build, it is possible that an amended version of the script, with this alternative version of the serialized() function at the top - could be worth experimenting with:

    function serialized(editor, options) {
        return ItemSerializer.serializeItems(
            editor.outline.items,
            "text/opml+xml"
        );
    }

Thanks for the update. I exported about a dozen files with the alternate version of serialized() and they all worked fine except for one. It exported correctly, but would not stop running. All I had to do though was quit Script Editor.

Either way everything went well. Thanks for the help.

1 Like

I hate to keep asking about the script, but I tried to run it and it stated that on Line 38: Application can’t be found. I am using the 3.5 update so I am guessing the name got changed?

Does anyone know what it should be changed to?

Change line

var tp3 = Application('com.hogbaysoftware.TaskPaper3'),

to:

var tp3 = Application('TaskPaper'),

Here’s the full script:

(function (dctOptions) {
    'use strict';

    // TASKPAPER CONTEXT

    function serialized(editor, options) {
        return ItemSerializer.serializeItems(
            editor.outline.items,
            editor,
            ItemSerializer[
                options.format.toUpperCase() + 'MimeType'
            ]
        );
    }

    // JAVASCRIPT FOR AUTOMATION CONTEXT
    // writeFile :: FilePath -> String -> IO ()
    function writeFile(strPath, strText) {
        $.NSString.alloc.initWithUTF8String(strText)
            .writeToFileAtomicallyEncodingError(
                $(strPath)
                .stringByStandardizingPath, true,
                $.NSUTF8StringEncoding, null
            );
    }

    function exportFromTP3(docTP3, strPath, strFormat) {
        var strSerial = docTP3.evaluate({
            script: serialized.toString(),
            withOptions: {
                format: strFormat
            }
        });

        return (
            writeFile(strPath, strSerial),
            strSerial
        );
    }

    var tp3 = Application('TaskPaper'),
        ds = tp3.documents;

    if (ds.length) {
        var d = ds[0],
            a = Application('System Events'),
            sa = (a.includeStandardAdditions = true, a),

            pathFile = d.file(),
            lstParts = pathFile ? pathFile.toString()
            .split('/') : undefined,

            strFolder = lstParts ? (
                lstParts.slice(0, -1)
                .join('/') + '/'
            ) : undefined,

            strFormat = dctOptions.format.toLowerCase(),
            lstSupported = ['opml', 'bml', 'text'],
            strKnownFormat = (
                lstSupported
                .indexOf(strFormat) !== -1 ? strFormat : undefined
            );

        if (strKnownFormat) {
            sa.activate();
            var pathWrite = sa.chooseFileName({
                withPrompt: 'Save as ' + strKnownFormat.toUpperCase(),
                defaultName: strFolder ? (
                    lstParts.slice(-1)[0].split('.')[0] + '.' +
                    strKnownFormat
                ) : 'Untitled.' + strKnownFormat,
                defaultLocation: strFolder ? (
                    Path(strFolder)
                ) : sa.pathTo('desktop')
            });

            return pathWrite ? (
                exportFromTP3(d, pathWrite.toString(), strKnownFormat)
            ) : undefined;
        } else {
            return "Unknown format: '" + dctOptions.format + "'" +
                "\nExpected: ( '" + lstSupported.join("' | '") + "' )";
        };

    } else return undefined;

})({
    format: 'opml'
});

// 'bml' or 'BML'  (HTML outline e.g. FoldingText for Atom)
// 'opml' or 'OPML'
1 Like

Thanks for the extremely quick reply! This worked flawlessly.

As you can see, I use this all the time and it has been indispensable as I move files to DevonThink. I appreciate your work on this.

Thanks again

FWIW the app id has now changed to com.hogbaysoftware.TaskPaper3.direct

but clearly more prudent to keep to the more generic Application('TaskPaper'), except in code which specifically wants to check which version it is running in.

1 Like

The link is 404

Thanks:

https://www.taskpaper.com/guide/customizing-taskpaper/creating-scripts.html

Beautiful script! Thank you for developing it!

A problem I have is that all the characters exceeding the base ASCII ones are converted to HTML Entities. Not all text editors can understand them in the correct way.

Is there a chance to modify it, so that it can export to Unicode encoding?

Paolo

It’s OPML output that you are after ?

(and is it by any chance the Zavala editor which is choking on the entities ?)

I would like to reuse the outlines created in TaskPaper as starting points in other word processors, in particular in Scrivener. But also in mind mapping programs, like MindNode or iThought. So yes, I guess OMPL is the ideal file format.

Frankly, I don’t know. I copied the script from this thread, pasted it into the Mac’s Script Editor (with the JavaScript file format), and saved it with no further changes.

Paolo

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();
})();

Complexpoint, please find attached a very simple snippet containing several diacritical marks.

snippet.zip (952 Bytes)

It only contains a single item:

image

I then run the “Eport to OPML” script from TaskPaper (the script in this thread), and get the attached .opml file, with the diacritical marks replaced by HTML Entities.

snippet.opml.zip (756 Bytes)

Something similar, but with a different set of replacement characters, happens if I change the .taskpaper extension to the original file into .txt, and open the file in Mac Word:

It doesn’t happen if I open the file with BBEdit or Apple Pages.

At this point, importing the .opml file into Scrivener, either by dragging it onto the Draft folder, or by using the Import > File command, imports a file that has already the characters replaced.

Paolo

Thanks.

Could you tell me whether this variant script seems to fix it ?

tp3_opml-002.scpt.zip (1.7 KB)

(Same code as behind the disclosure triangle in the post immediately before your latest):

Thank you for the new script! However, it looks like it still can’t export correctly, despite using different replacement characters.

Paolo

snippet-with-new-script.opml.zip (853 Bytes)

I wonder if I am missing something.

With the variant script, and your source snippet, I get this:

snippetB.opml.zip (727 Bytes)

Which seems to open OK in the OPML reading apps which I have tried.

Is there an app in which that OPML file seems to be opening incorrectly ?

(I have tested here in Bike, Scrivener, and iThoughts)


and your snippet-with-new-script.opml.zip seems to open correctly here too.

Screenshot 2023-01-08 at 01.33.37

Complexpoint – sorry! I was checking it with BBEdit and TextEdit, that can read the raw code, but not interpret OPML.

Opening it with Scrivener or MindNode works perfectly. Thank you!

Paolo

1 Like