About the same speed as the AppleScript version - both are much faster now, and use the .notes() method which I had at first failed to spot in the dictionary, as a single apple-event route to the full collection of children for a given note.
Only one (very minor) request emerges from this:
- Perhaps a more easily read value type when nextSibling has no real referent ? Something like
missing value
in AppleScript, ornull
/ orundefined
in JS.
Note that if you want to run the JavaScript for Automation code below in Script Editor, you will need to change the language selection tab at top left from AppleScript to JavaScript.
Now takes less than a second here for several traversals of a document with c 14o notes.
(() => {
'use strict';
// TESTING THE JAVASCRIPT FOR AUTOMATION API
const main = () => {
const ds = Application('Tinderbox 8').documents;
return bindLR(
0 < ds.length ? (
Right(ds.at(0))
) : Left('No documents open in Tinderbox'),
d => {
const tree = pureTreeTBX(d);
return 'Nodes: ' + foldTree(nodeCount, tree) +
'\nLeaf nodes: ' + foldTree(treeWidth, tree) +
'\nLayers of nesting: ' + foldTree(treeDepth, tree) +
'\nTitles:' + unlines(foldTree(preorder, tree));
}
);
};
// TINDERBOX OSA API ----------------------------------
// noteID :: Note -> String
const noteID = note =>
note.attributes.byName('ID').value();
// pureTreeTBX :: Note -> Tree Note
const pureTreeTBX = note => {
const go = x =>
Node(x, x.notes().map(go));
return go(note);
};
// GENERIC TREE TRAVERSALS FOR USE WITH FOLDTREE ------
// nodeCount :: Tree a -> Int
const nodeCount = (_, xs) =>
// One more than the total number of descendants.
// (With foldTree, returns the total number of nodes in tree,
// including the document/root node)
1 + sum(xs);
// treeDepth :: Tree a -> Int
const treeDepth = (_, xs) =>
// One more than that of the deepest child.
// (With foldTree, returns the total number of levels in the tree)
0 < xs.length ? 1 + maximum(xs) : 0;
const treeWidth = (_, xs) =>
// Sum of widths of any children, or a minimum of 1.
// (With foldTree, returns the total count of
// childless leaf notes in the tree)
0 < xs.length ? sum(xs) : 1;
// preorder :: a -> [[a]] -> [a]
const preorder = (x, xs) =>
// Name of this node followed by the rest.
// (With foldTree, returns an ordered list of note names)
cons(x.name(), concat(xs))
// GENERIC FUNCTIONS ----------------------------
// https://github.com/RobTrew/prelude-jxa
// Just :: a -> Maybe a
const Just = x => ({
type: 'Maybe',
Nothing: false,
Just: x
});
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Node :: a -> [Tree a] -> Tree a
const Node = (v, xs) => ({
type: 'Node',
root: v, // any type of value (consistent across tree)
nest: xs || []
});
// Nothing :: Maybe a
const Nothing = () => ({
type: 'Maybe',
Nothing: true,
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = (a, b) => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
const bindLR = (m, mf) =>
undefined !== m.Left ? (
m
) : mf(m.Right);
// concat :: [[a]] -> [a]
// concat :: [String] -> String
const concat = xs =>
0 < xs.length ? (() => {
const unit = 'string' !== typeof xs[0] ? (
[]
) : '';
return unit.concat.apply(unit, xs);
})() : [];
// cons :: a -> [a] -> [a]
const cons = (x, xs) =>
Array.isArray(xs) ? (
[x].concat(xs)
) : 'GeneratorFunction' !== xs.constructor.constructor.name ? (
x + xs
) : ( // Existing generator wrapped with one additional element
function*() {
yield x;
let nxt = xs.next()
while (!nxt.done) {
yield nxt.value;
nxt = xs.next();
}
}
)();
// foldTree :: (a -> [b] -> b) -> Tree a -> b
const foldTree = (f, tree) => {
const go = node => f(node.root, node.nest.map(go));
return go(tree);
};
// foldl1 :: (a -> a -> a) -> [a] -> a
const foldl1 = (f, xs) =>
1 < xs.length ? xs.slice(1)
.reduce(f, xs[0]) : xs[0];
// maximum :: Ord a => [a] -> a
const maximum = xs =>
0 < xs.length ? (
foldl1((a, x) => x > a ? x : a, xs)
) : undefined;
// sum :: [Num] -> Num
const sum = xs => xs.reduce((a, x) => a + x, 0);
// unfoldl(x => 0 !== x ? Just([x - 1, x]) : Nothing(), 10);
// --> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// unfoldl :: (b -> Maybe (b, a)) -> b -> [a]
const unfoldl = (f, v) => {
let
xr = [v, v],
xs = [];
while (true) {
const mb = f(xr[0]);
if (mb.Nothing) {
return xs
} else {
xr = mb.Just;
xs = [xr[1]].concat(xs);
}
}
};
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// MAIN ---
return main();
})();