Script to convert OO to markdown, tex, and PDF using pandoc

Looks good :-)

If the translation directions multiply, it can be good to use JSON / JS Objects as a hub for nested texts (outlines), something like this:

Rough two stage example below:

  1. OmniOutliner to [Node {text:String, nest:[Node], ...}]
  2. [Node {text:String, nest:[Node], ...}] to Markdown, (with same options as iThoughts exporter)
var strClip = (() => {
    'use strict';

    // OmniOutliner to TEXT-NEST, then TEXT-NEST to MARKDOWN

    // example of format translation through a textNest hasSubTopics

    // Rough draft ver 0.05
    // 0.05 -- Moved extra linefeed from after hash header to before
    // 0.04 -- Simplified ooRowsJSO
    // 0.03 -- Slightly faster version of cellTextMD – fewer AE events

    // Copyright(c) 2017 Rob Trew
    //
    // Permission is hereby granted, free of charge, to any person obtaining a
    // copy of this software and associated documentation files(the "Software"),
    // to deal in the Software without restriction, including without limitation the rights
    // to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
    // copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in all
    // copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    // SOFTWARE.

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

    // any :: (a -> Bool) -> [a] -> Bool
    const any = (f, xs) => xs.some(f);

    // concat :: [[a]] -> [a] | [String] -> String
    const concat = xs =>
        xs.length > 0 ? (() => {
            const unit = typeof xs[0] === 'string' ? '' : [];
            return unit.concat.apply(unit, xs);
        })() : [];

    // curry :: Function -> Function
    const curry = (f, ...args) => {
        const go = xs => xs.length >= f.length ? (f.apply(null, xs)) :
            function () {
                return go(xs.concat(Array.from(arguments)));
            };
        return go([].slice.call(args, 1));
    };

    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = f => (a, b) => f.apply(null, [b, a]);

    // isInfixOf :: Eq a => [a] -> [a] -> Bool
    const isInfixOf = (needle, haystack) =>
        haystack.includes(needle);

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

    // min :: Ord a => a -> a -> a
    const min = (a, b) => b < a ? b : a;

    // replicate :: Int -> a -> [a]
    const replicate = (n, a) => {
        let v = [a],
            o = [];
        if (n < 1) return o;
        while (n > 1) {
            if (n & 1) o = o.concat(v);
            n >>= 1;
            v = v.concat(v);
        }
        return o.concat(v);
    };

    // replicateS :: Int -> String -> String
    const replicateS = (n, s) => concat(replicate(n, s));

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

    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = (f, xs, ys) =>
        Array.from({
            length: min(xs.length, ys.length)
        }, (_, i) => f(xs[i], ys[i]));

    // JSO TEXT-NEST TO MARKDOWN ----------------------------------------------

    // jsoMarkdown :: Dictionary -> [Node] -> String
    const jsoMarkdown = (dctOptions, xs) => {
        const
            indent = dctOptions.indent || '\t',
            headerLevels = dctOptions.headerLevels || 3,
            listMarker = dctOptions.listMarker || '-',
            notesIndented = dctOptions.notesIndented || true;
        const
            jsoNodeMD = curry((intLevel, strIndent, node) => {
                const
                    blnHash = intLevel <= headerLevels,
                    index = node.number,
                    strNum = ((blnHash || (index === undefined)) ? (
                        ''
                    ) : index + '.'),
                    strPrefix = (blnHash ? (
                        replicateS(intLevel, '#')
                    ) : strNum || listMarker) + ' ',
                    noteIndent = notesIndented ? strIndent + indent : strIndent,
                    nextIndent = blnHash ? '' : indent + strIndent,
                    note = node.note,
                    strNotes = (note !== '') ? (
                        note.split('\n')
                        .map(x => noteIndent + x)
                        .join('\n') + '\n\n'
                    ) : '',
                    nest = node.nest;
                return (blnHash ? '\n' : '') +
                    strIndent + strPrefix + node.text + '\n' +
                    strNotes + (nest.length > 0 ? (
                        nest.map(jsoNodeMD(intLevel + 1, nextIndent))
                        .join('') + '\n'
                    ) : '');
            });

        return xs.reduce((a, x) => a + jsoNodeMD(1, '', x) + '\n', '');
    };

    // OMNI-OUTLINER TO JSO / JSON --------------------------------------------

    // Either with or without (see blnMD) MarkDown emphases for Bold/Italic
    // ooRowsJSO :: Bool -> OO.Document -> [Node {text: String, nest: [Node]]}
    const ooDocJSO = (blnMD, doc) => [{
        text: '[' + doc.name() + '](file:://' + Path(doc.file())
            .toString() + ')',
        nest: ooRowsJSO(blnMD, doc.children)
    }];

    // ooRowsJSO :: Bool -> OO.Rows -> Node {text: String, nest: [Node]}
    const ooRowsJSO = (blnMD, rows) =>
        rows()
        .map((r, i) => ({
            text: blnMD ? cellTextMD(r.topicCell) : r.topic(),
            number: r.style.attributes
                .byName('heading-type(com.omnigroup.OmniOutliner)')
                .value() !== 'None' ? i + 1 : undefined,
            note: blnMD ? cellTextMD(r.noteCell) : r.note(),
            nest: r.hasSubTopics ? ooRowsJSO(blnMD, r.children) : []
        }));

    // contains :: String -> String -> Bool
    const contains = curry(flip(isInfixOf));

    // cellTextMD :: OO.Cell -> String
    const cellTextMD = cell => {
        const as = cell.richText.attributeRuns;
        return zipWith((txt, fnt) => {
                const
                    bld = any(contains(fnt), ['Bold', 'Black']) ? (
                        '**'
                    ) : '',
                    ital = isInfixOf('Italic', fnt) ? '*' : '';
                return bld + ital + txt + ital + bld;
            }, as.text(), as.font())
            .join('');
    };

    // TEST -------------------------------------------------------------------
    const ds = Application('OmniOutliner')
        .documents,
        d = ds.length > 0 ? ds.at(0) : undefined;

    // Edit optional values for indent, headerLevels, notesIndented, listMarker
    return d ? jsoMarkdown({
        indent: replicateS(4, ' '), // or '\t'
        headerLevels: 3,
        notesIndented: true,
        listMarker: '-' // or '*' or '+'
    }, ooDocJSO(true, d)[0].nest) : '';
})();

var a = Application.currentApplication(),
    sa = (a.includeStandardAdditions = true, a);

sa.setTheClipboardTo(strClip);

strClip
4 Likes