Much to Jim’s chagrin, I’m trying to work out how to get a list of end-node tags from DEVONthink - however, while I’m happy with old-skool JavaScript, I’m not too sure I’m talking to DEVONthink properly to extract them.
Here’s my chunk of JS that I’d hope would recurse through the root item, looking for things that look like tags that have no child nodes (and continuing to hunt through thing that do have child nodes):
function run() {
this.console.log("---RUNNING-------------");
this.console.log(getEndTags(0));
}
function getEndTags(fromItem) {
// given an itemID, recurse through items that have children and find ones that are tags
var listOfTags=[];
var itemsToCheck = Application("DEVONthink Pro Office").databases.byId(fromItem).children;
this.console.log("start: FromItem = ", mydump(fromItem) );
for(var itemToCheck in itemsToCheck) {
var i = Application("DEVONthink Pro Office").databases.byId(itemToCheck);
this.console.log("from: ",i);
if( (i.excludeFromClassification===false) &&
(i.excludeFromSearch===false) &&
(i.excludeFromSeeAlso===false) &&
(i.excludeFromTagging===false) &&
(i.numberOfDuplicates===0) &&
(i.numberOfReplicants===0) &&
(i.children.length===0) ) {
listOfTags.append(i.name);
//listOfTagGroups[i.name]=i;
}
if( (i.children.length>0) ) {
listOfTags.append( getEndTags(i) );
}
}
return( listOfTags );
}
“root” is a property of databases and the application object contains the databases. For more info just drag & drop DEVONthink Pro (Office) onto the AppleScript Editor app to display its script suite and change the language in the toolbar to JavaScript.
(incidentally console.log() may not be the best way to get output – JXA returns a value to whatever calls it, and the value can be used for IO events like dialogs, file-writes, returns to Keyboard Maestro macros etc etc).
If it’s the set of tag strings that you want, here is one approach:
(function () {
'use strict';
// DEVONTHINK ------------------------------------------------------------
// Unique set of all the tags attached to any leaf records
// (List of records -> list of strings)
// dtLeafTags :: [DT Record] -> [String]
function dtLeafTags(dtRecs) {
return sortBy(comparing(toLower), nub(concatMap(function (x) {
var nest = x.children;
return nest.length !== 0 ? dtLeafTags(nest()) : x.tags();
}, dtRecs)));
};
// currentDBLeafTags :: () -> [String]
function currentDBLeafTags() {
return dtLeafTags(
Application('DevonThink Pro')
.currentDatabase.records()
);
};
// GENERIC ---------------------------------------------------------------
// append :: [a] -> [a] -> [a]
function append(xs, ys) {
return xs.concat(ys);
};
// comparing :: (a -> b) -> (a -> a -> Ordering)
function comparing(f) {
return function (x, y) {
var a = f(x),
b = f(y);
return a < b ? -1 : a > b ? 1 : 0;
};
};
// concatMap :: (a -> [b]) -> [a] -> [b]
function concatMap(f, xs) {
return xs.length > 0 ? function () {
var unit = typeof xs[0] === 'string' ? '' : [];
return unit.concat.apply(unit, xs.map(f));
}() : [];
};
// isNull :: [a] | String -> Bool
function isNull(xs) {
return Array.isArray(xs) || typeof xs === 'string' ? xs.length < 1 : undefined;
};
// log :: a -> IO ()
function log() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return console.log(args.map(JSON.stringify)
.join(' -> '));
};
// nub :: [a] -> [a]
function nub(xs) {
return nubBy(function (a, b) {
return a === b;
}, xs);
};
// nubBy :: (a -> a -> Bool) -> [a] -> [a]
function nubBy(p, xs) {
var mbx = xs.length ? {
just: xs[0]
} : {
nothing: true
};
return mbx.nothing ? [] : [mbx.just].concat(nubBy(p, xs.slice(1)
.filter(function (y) {
return !p(mbx.just, y);
})));
};
// show :: Int -> a -> Indented String
// show :: a -> String
function show() {
for (var _len2 = arguments.length, x = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
x[_key2] = arguments[_key2];
}
return JSON.stringify.apply(null, x.length > 1 ? [x[1], null, x[0]] : x);
};
// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
function sortBy(f, xs) {
return xs.slice()
.sort(f);
};
// toLower :: Text -> Text
function toLower(s) {
return s.toLowerCase();
};
// TEST ------------------------------------------------------------------
return currentDBLeafTags();
})();
Thanks, houthakker! That’s exactly what I want! You’re amazing!
(your JavaScript skills are intense, by the way - I have to sit down in a quiet room to work out what’s going on!)
So now I’m can move ahead to my next stumbling block, which is to wrap a gui around the intersection of the tag list and matching phrases in the record text and make it easy for the user to hit immediate tags. It’s my super-cheap approach to combining ontologies, folksonomies and … shall we just say it’s a folkology? It won’t compete with hadoop clusters and machine-learning APIs but it will save me a heck of a lot of hunting around with my tag structures.