Thanks, Rob. I actually managed to combine the three scripts I wanted into one (1, 2, 3).
Click to unfold
// 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]));
function run() {
// My pandoc template file; you'll need one of these to include necessary LaTeX front- and back-matter e.g. "\begin{document}." (You'll also need to look up where pandoc stores these and put yours there.)
var pandocCmd = '-drefs2 -dabntex';
// Setup
var app = Application.currentApplication();
app.includeStandardAdditions = true;
var OmniOutliner = Application('OmniOutliner');
// Get the current document
var doc = OmniOutliner.documents[0];
// Get the name (stripped of spaces) of the Omni Outliner document. The script may fail if your filename includes certain characters, e.g. parentheses.
var fileName = doc.name().replace(/\s/g, '');
// Create a directory on the desktop to hold our new files
var desktopString = app.pathTo("desktop").toString()
app.doShellScript(`mkdir -p ${desktopString}/LaTeX/`);
// The text of the paper
var paperText = "";
var theRow="";
var newline = '\n';
// Loop through rows and append their text to paperText
doc.rows().forEach(function(theRow) {
var pre = ""
var post = ""
var theSection="";
if (Object.keys(theRow.style.namedStyles).length > 0) {
switch(theRow.style.namedStyles[0].name()) {
case "Heading 1":
pre = newline + "# "
post = " " + newline
break;
case "Heading 2":
pre = "## "
post = " " + newline
break;
case "Heading 3":
pre = "### "
post = " " + newline
break;
case "Heading 4":
pre = "#### "
post = " " + newline
break;
case "Heading 5":
pre = "##### "
post = " " + newline
break;
case "Comentário":
pre = "<!-- "
post = "--> " + newline
break;
case "YAML":
pre = ""
post = ""
break;
case "Ordered List":
pre = "1. "
isList = true
break;
case "Unordered List":
pre = "- "
isList = true
break;
case "Task":
pre = "1. [ ] "
isList = true
break;
case "Paragraph":
pre = newline
post = " " + newline
break;
case "YAMLitem":
pre = ""
post = ": | " + newline
break;
case "YAMLtexto":
pre = " "
post = " "
break;
case "HeadingNo":
pre = "# "
post = " {-} "
break;
case "Small":
pre = newline + "<small>"
post = "</small> " + newline
break;
case "Blockquote":
pre = "> "
// post = newline
break;
}
}
theSection = pre + cellTextMD(theRow) + post + newline + cellNoteMD(theRow);
paperText += theSection;
paperText += "\n\n";
});
// Convert the text of the paper to UTF8 encoding so pandoc can read it
paperText = $.NSString.alloc.initWithUTF8String(paperText);
// Write paperText to a new markdown file
var file = `${desktopString}/LaTeX/${fileName}.md`
paperText.writeToFileAtomicallyEncodingError(file, true, $.NSUTF8StringEncoding, null);
// Use pandoc to convert that markdown file to a tex file
shellCommand = `/usr/local/bin/pandoc ${desktopString}/LaTeX/${fileName}.md -f markdown ${pandocCmd} -o ${desktopString}/LaTeX/${fileName}.tex `;
app.doShellScript(shellCommand);
// Compile our new tex file to PDF using xelatex
shellCommand = `/Library/TeX/texbin/xelatex --output-directory=${desktopString}/LaTeX/ ${desktopString}/LaTeX/${fileName}.tex && open ${desktopString}/LaTeX/${fileName}.pdf`;
app.doShellScript(shellCommand);
return true;
}
// From apple's documentation for Javascript for Automation
function writeTextToFile(text, file, overwriteExistingContent) {
try {
// Convert the file to a string
var fileString = file.toString()
// Open the file for writing
var openedFile = app.openForAccess(Path(fileString), { writePermission: true })
// Clear the file if content should be overwritten
if (overwriteExistingContent) {
app.setEof(openedFile, { to: 0 })
}
// Write the new content to the file
app.write(text, { to: openedFile, startingAt: app.getEof(openedFile) })
// Close the file
app.closeAccess(openedFile)
// Return a boolean indicating that writing was successful
return true
}
catch(error) {
try {
// Close the file
app.closeAccess(file)
}
catch(error) {
// Report the error is closing failed
console.log(`Couldn't close file: ${error}`)
}
// Return a boolean indicating that writing was successful
return false
}
}
// Code below written by draft8, based on code written by SGIII, in turn adapted from AppleScript code written by Rob Trew
// contains :: String -> String -> Bool
const contains = curry(flip(isInfixOf));
// cellTextMD :: OO.Cell -> String
const cellTextMD = row => {
const as = row.topic.attributeRuns;
return zipWith3((txt, fnt, style) => {
const
bld = any(contains(fnt), ['Bold', 'Black']) ? (
'**'
) : '',
ital = any(contains(fnt), ['Oblique', 'Italic']) ? (
'*'
) : '',
url = style.attributes.byName('link')
.value();
return bld + ital + (
isNull(url) ? txt : ('[' + txt + '](' + url + ')')
) + ital + bld;
}, as.text(), as.font(), as.style())
.join('');
};
const cellNoteMD = row => {
const as = row.note.attributeRuns;
return zipWith3((txt, fnt, style) => {
const
bld = any(contains(fnt), ['Bold', 'Black']) ? (
'**'
) : '',
ital = any(contains(fnt), ['Oblique', 'Italic']) ? (
'*'
) : '',
url = style.attributes.byName('link')
.value();
return '\n' + bld + ital + (
isNull(url) ? txt : ('[' + txt + '](' + url + ')')
) + ital + bld + '\n';
}, as.text(), as.font(), as.style())
.join('');
};
// zipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]
const zipWith3 = (f, xs, ys, zs) =>
Array.from({
length: Math.min(xs.length, ys.length, zs.length)
}, (_, i) => f(xs[i], ys[i], zs[i]));
// isNull :: [a] | String -> Bool
const isNull = xs =>
Array.isArray(xs) || typeof xs === 'string' ? (
xs.length < 1
) : undefined;
The only thing I could not find a way to do was to perform the regex search and replace in the final text before converting it. With this I mean that I couldn’t do it using JS, but I could add another shell script to get it done in the script.
I am much more familiar with applescript, so I also made a version in this language (using this snippet) which I will probably end up using.
Click to unfold
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
property includeNotes : true
property convertWithPandoc : false
property openAfter : true
set PandocPath to "export PATH=/Library/TeX/texbin:$PATH && /usr/local/bin/pandoc"
set PandocDefaults to "-drefs2 -dabntex -dpdf"
set theText to {}
tell application "OmniOutliner"
set theFile to get file of front document
set thePath to POSIX path of theFile
tell front document
set theRows to rows
-- set theRow to item 1 of theRows
repeat with theRow in theRows
set {theTxt, theNoteTxt, theStyles, thePre, thePos} to {"", "", "", "", ""} -- clean variables
set theStyles to name of named styles of style of theRow -- get Named Styles
set {thePre, thePos} to my translateStyle(theStyles) -- get what comes before the row text and what comes after
set {lstText, lstFont} to {its text, its font} of topic's attribute runs of theRow
set theTxt to my rtftomd(lstText, lstFont) -- check for italics and bold in row text
set {lstNoteText, lstNoteFont} to {its text, its font} of note's attribute runs of theRow
set theNoteTxt to my rtftomd(lstNoteText, lstNoteFont) -- check for italics and bold in note text
set theTxt to thePre & theTxt & thePos & linefeed
if includeNotes then
if theNoteTxt is not "" then set theTxt to theTxt & linefeed & theNoteTxt & " " & linefeed
end if
set theText to theText & theTxt
end repeat
--return theText as text
-- set theRow to item 1 of theRows
--return theText as text
set the clipboard to (theText as text)
end tell
end tell
if convertWithPandoc then
set theMDPath to thePath & ".md"
set thePDFPath to thePath & ".pdf"
do shell script "touch " & quoted form of theMDPath & " && LANG=pt_BR.UTF-8 pbpaste > " & quoted form of theMDPath
set theSH to PandocPath & space & "-s" & space & quoted form of theMDPath & space & PandocDefaults & space & "-o" & space & quoted form of thePDFPath
if openAfter then set theSH to theSH & "&& open " & quoted form of thePDFPath
do shell script theSH
end if
on translateStyle(theStyles)
set pre to linefeed
set pos to " "
if theStyles contains "Heading 1" then set {pre, pos} to {linefeed & linefeed & "# ", " "}
if theStyles contains "Heading 2" then set {pre, pos} to {linefeed & linefeed & "# ", " "}
if theStyles contains "Heading 3" then set {pre, pos} to {linefeed & linefeed & "## ", " "}
if theStyles contains "Heading 4" then set {pre, pos} to {linefeed & linefeed & "### ", " "}
if theStyles contains "Heading 5" then set {pre, pos} to {linefeed & linefeed & "#### ", " "}
if theStyles contains "Heading 6" then set {pre, pos} to {linefeed & linefeed & "##### ", " "}
if theStyles contains "Heading 7" then set {pre, pos} to {linefeed & linefeed & "####### ", " "}
if theStyles contains "Comentário" then set {pre, pos} to {linefeed & "<!--", "-->" & linefeed}
if theStyles contains "YAML" then set {pre, pos} to {"", ""}
if theStyles contains "Ordered List" then set {pre, pos} to {"1. ", ""}
if theStyles contains "Unordered List" then set {pre, pos} to {"- ", ""}
if theStyles contains "Paragraph" then set {pre, pos} to {linefeed & "", " "}
if theStyles contains "YAMLitem" then set {pre, pos} to {"", ": |"}
if theStyles contains "YAMLtexto" then set {pre, pos} to {linefeed & " ", " "}
if theStyles contains "HeadingNo" then set {pre, pos} to {linefeed & "# ", " {-} "}
if theStyles contains "Small" then set {pre, pos} to {linefeed & "<small>", "</small>"}
if theStyles contains "Blockquote" then set {pre, pos} to {linefeed & "> ", " "}
if theStyles contains "Code" then set {pre, pos} to {linefeed, ""}
return {pre, pos}
end translateStyle
on rtftomd(lstText, lstFont)
set outTxt to ""
repeat with i from 1 to lstText's length
set {aChunk, aFont} to {lstText's item i, lstFont's item i}
if aFont contains "bold" or aFont contains "black" then
set outTxt to outTxt & "**" & aChunk & "** "
else if lstFont's item i contains "italic" then
set outTxt to outTxt & "*" & aChunk & "*"
else if lstFont's item i contains "link" then
set outTxt to outTxt & "*" & aChunk & "*"
else
set outTxt to outTxt & aChunk
end if
end repeat
return outTxt
end rtftomd
on fixHomePath(thePath)
if thePath contains "~/" then
set thePath to replaceText(thePath, "~/", "$HOME/")
else
set HomePath to (POSIX path of (path to home folder))
set thePath to replaceText(thePath, HomePath, "$HOME/")
end if
return thePath
end fixHomePath
on replaceText(theString, old, new)
set {TID, text item delimiters} to {text item delimiters, old}
set theStringItems to text items of theString
set text item delimiters to new
set theString to theStringItems as text
set text item delimiters to TID
return theString
end replaceText