Text in OmniOutliner

Aside from its remarkable ability to organize data, a core strength of OmniOutliner is found in its robust support for displaying textual data. The characters, words, sentences, and paragraphs of outline documents can be formatted with a broad range of styling attributes, including: typeface, color, kerning, weight, line spacing, baseline positioning, writing direction, and horizontal alignement, to name some of the basic options.

This section examines how to manipulate text in an OmniOutliner document using Omni Automation.

Text Strings and Text Objects

By default, all text in an outline document is styled. Every row’s text has a full range of style attributes applied to it, such as: typeface, weight, color, alignment, etc. Even the text in a blank outline has style attributes applied to it using the default style attribute values of the application.

When using Omni Automation to manipulate and edit the textual content of a document, scripts can work with the text as either:

In terms of Omni Automation, text without style data is a text string or “string” for short. Text with style data is a text object.

Text Strings

Text strings exist in scripts as a collection of characters, stored and manipulated within the script itself. For example, the value of the topic property of the Item class is the collection of characters represented by the styled text in a row.

textual-content-01

For example, if we use the topic property of an item to get the text of the first row of the outline shown above, the result is returned as a “string” of characters:

Note that the result of the script (line 2 above) contains no information about the typeface used to style to row, or that the words “textual contents” are displayed as bold red characters. The resulting “string” is simply the sequence of characters displayed from left to right in the row.

In those cases where all the text in a row is styled using the default style, manipulating the text contents of a row can be done easily using the many standard built-in JavaScript functions for manipulating text, like the replace() function that replaces a found substring with another in the main string.

However, if any of the characters within the row’s text have additional style attributes applied to them, that styling will be lost when the value of the topic property is set to a new value.

Executing the above script will result in edited text for row, but the styling of the row will revert to the default style if no level style is set:

textual-content-02

Here is the result if the row had a non-default level style previously applied. Note that the custom styling of the words “textual content” have been replaced with the styling of the first character in the row:

textual-content-03

SUMMARY: Editing the text content of outline rows by manipulating and reassigning text strings is perfect for those documents that have defined level styles and no additionally formatted characters within the topic string. For those documents, using the editing techniques described below will be useful. For detailed information describing all of JavaScript’s built-in string editing methods, visit the excellent w3cschools website.

The following are examples of using some common string methods to edit rows in an outline document.

Prepending/Appending Strings

One of the basic text edits is to add text to the beginning (prepend) or the ending (append) of a string. This is easily accomplished using the standard JavaScript string concatenation method: prependingString + ReceivingString + appendingString

The first example prepends the tag “DRAFT: ” to every row:

rootItem.apply(function(item){ item.topic = "DRAFT: " + item.topic })
omnioutliner://localhost/omnijs-run?script=rootItem%2Eapply%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20%22DRAFT%3A%20%22%20%2B%20item%2Etopic%0A%7D%29

To selected rows:

items = document.editors[0].selectedNodes.map(function(node){ return node.object }) items.forEach(function(item){ item.topic = "DRAFT: " + item.topic })
omnioutliner://localhost/omnijs-run?script=items%20%3D%20document%2Eeditors%5B0%5D%2EselectedNodes%2Emap%28function%28node%29%7B%0A%09return%20node%2Eobject%0A%7D%29%0Aitems%2EforEach%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20%22DRAFT%3A%20%22%20%2B%20item%2Etopic%0A%7D%29

And the second example appends a date string to every row:

dateTag = '[' + new Date().toDateString() + ']' rootItem.apply(function(item){ item.topic = item.topic + ' ' + dateTag })
omnioutliner://localhost/omnijs-run?script=rootItem%2Eapply%28function%28item%29%7B%0A%09dateTag%20%3D%20%27%5B%27%20%2B%20new%20Date%28%29%2EtoDateString%28%29%20%2B%20%27%5D%27%0A%09item%2Etopic%20%3D%20item%2Etopic%20%2B%20%27%20%27%20%2B%20dateTag%0A%7D%29

And to selected rows:

items = document.editors[0].selectedNodes.map(function(node){ return node.object }) dateTag = '[' + new Date().toDateString() + ']' items.forEach(function(item){ item.topic = item.topic + ' ' + dateTag })
omnioutliner://localhost/omnijs-run?script=items%20%3D%20document%2Eeditors%5B0%5D%2EselectedNodes%2Emap%28function%28node%29%7B%0A%09return%20node%2Eobject%0A%7D%29%0AdateTag%20%3D%20%27%5B%27%20%2B%20new%20Date%28%29%2EtoDateString%28%29%20%2B%20%27%5D%27%0Aitems%2EforEach%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20item%2Etopic%20%2B%20%27%20%27%20%2B%20dateTag%0A%7D%29

Trim Leading|Ending Tags from Topic Strings

The following example demonstrates how to trim characters from the start of a topic string. In this script, the leading tag is identified by its beginning and ending characters, which are opening and closing brackets: [ ]

The script uses the indexOf() method to confirm the existence of the leading tag, and then uses the substr() method to return the rest of the topic after past the tag.

Also note the use of the trim() method (end of line 5) to remove the space between the tag and the topic string.

trim-leading-tag-01
var openTag = "[", endTag = "]" rootItem.apply(function(item){ str = item.topic if(str.indexOf(openTag) === 0 && str.indexOf(endTag) != -1){ item.topic = str.substr(str.indexOf(endTag) + endTag.length).trim() } })
trim-leading-tag-02

And here is a variation of the previous script that uses the lastIndexOf() method to remove tags from the end of every row:

ending-tags-01
var openTag = "[", endTag = "]" rootItem.apply(function(item){ str = item.topic openTagIndex = str.lastIndexOf(openTag) endTagIndex = str.lastIndexOf(endTag) if(openTagIndex != -1 && endTagIndex === str.length - 1){ item.topic = str.substr(0,openTagIndex).trim() } })
omnioutliner://localhost/omnijs-run?script=var%20openTag%20%3D%20%22%5B%22%2C%20endTag%20%3D%20%22%5D%22%0ArootItem%2Eapply%28function%28item%29%7B%0A%09str%20%3D%20item%2Etopic%0A%09openTagIndex%20%3D%20str%2ElastIndexOf%28openTag%29%0A%09endTagIndex%20%3D%20str%2ElastIndexOf%28endTag%29%0A%09if%28openTagIndex%20%21%3D%20-1%20%26%26%20endTagIndex%20%3D%3D%3D%20str%2Elength%20-%201%29%7B%0A%09%09item%2Etopic%20%3D%20str%2Esubstr%280%2CopenTagIndex%29%2Etrim%28%29%0A%09%7D%0A%7D%29
ending-tags-02

Replacing Text within Strings

A common task when working with text is to locate and change occurrences of specific substring (a segment within a string) into something else. JavaScript’s built-in replace() method is an excellent tool for performing this task.

The replace() method searches a string for a specified value, or a regular expression, and returns a new string where the specified values are replaced. Here is the syntax for the method:

Let’s use the replace() method to replace the word Seattle with Portland in the text of the first row of this outline:

replace-01
str = rootItem.children[0].topic str = str.replace('Seattle','Portland') rootItem.children[0].topic = str
replace-02

In the illustration above, note that the replace() method only changed the first occurence of “Seattle,” not all occurrences. To change every instance of a specific word or substring, include the global parameter (g) with the method call:

str = rootItem.children[0].topic str = str.replace(/Seattle/g,'Portland') rootItem.children[0].topic = str
replace-03

Note that by default, the global parameter is case sensitive, in that the current case of replaced characters will be honored. To perform a global, case-insensitive replacement, use this syntax for the method:

A complete overview of the replace() method and its parameters, can be found at the excellent w3cschools website.

And here’s an example of how to use the replace() method for the entire outline:

rootItem.apply(function(item){ item.topic = item.topic.replace(/Seattle/g,'Portland') })

And only in the selected rows:

items = document.editors[0].selectedNodes.map(function(node){ return node.object }) items.forEach(function(item){ item.topic = item.topic.replace(/Seattle/g,'Portland') })

Changing Case

Another common text-editing task is to change the case of the text in topic strings. This can accomplished using standard JavaScript methods combined with some custom routines from the past.

Upper Case

Here’s how to change the text in a topic string to upper case using the toUpperCase() method.

case-all-lower
rootItem.apply(function(item){ item.topic = item.topic.toUpperCase() })
omnioutliner://localhost/omnijs-run?script=rootItem%2Eapply%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20item%2Etopic%2EtoUpperCase%28%29%0A%7D%29
case-all-upper
items = document.editors[0].selectedNodes.map(function(node){ return node.object }) items.forEach(function(item){ item.topic = item.topic.toUpperCase() })
omnioutliner://localhost/omnijs-run?script=items%20%3D%20document%2Eeditors%5B0%5D%2EselectedNodes%2Emap%28function%28node%29%7Breturn%20node%2Eobject%7D%29%0Aitems%2EforEach%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20item%2Etopic%2EtoUpperCase%28%29%0A%7D%29
case-selected-upper

Lower Case

Here’s how to change the text in a topic string to lower case using the toLowerCase() method.

rootItem.apply(function(item){ item.topic = item.topic.toLowerCase() })
omnioutliner://localhost/omnijs-run?script=rootItem%2Eapply%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20item%2Etopic%2EtoLowerCase%28%29%0A%7D%29
items = document.editors[0].selectedNodes.map(function(node){ return node.object }) items.forEach(function(item){ item.topic = item.topic.toLowerCase() })
omnioutliner://localhost/omnijs-run?script=items%20%3D%20document%2Eeditors%5B0%5D%2EselectedNodes%2Emap%28function%28node%29%7Breturn%20node%2Eobject%7D%29%0Aitems%2EforEach%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20item%2Etopic%2EtoLowerCase%28%29%0A%7D%29

Sentence Case

Applying a sentence case means capitalizing the first character of a sentence. This is accomplish using the charAt() and substr() methods to get the first character and the remaining characters of a sentence.

rootItem.apply(function(item){ item.topic = item.topic.charAt(0).toUpperCase() + item.topic.substr(1) })
omnioutliner://localhost/omnijs-run?script=rootItem%2Eapply%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20item%2Etopic%2EcharAt%280%29%2EtoUpperCase%28%29%20%2B%20item%2Etopic%2Esubstr%281%29%0A%7D%29
items = document.editors[0].selectedNodes.map(function(node){ return node.object }) items.forEach(function(item){ item.topic = item.topic.charAt(0).toUpperCase() + item.topic.substr(1) })
omnioutliner://localhost/omnijs-run?script=items%20%3D%20document%2Eeditors%5B0%5D%2EselectedNodes%2Emap%28function%28node%29%7B%0A%09return%20node%2Eobject%0A%7D%29%0Aitems%2EforEach%28function%28item%29%7B%0A%09item%2Etopic%20%3D%20item%2Etopic%2EcharAt%280%29%2EtoUpperCase%28%29%20%2B%20item%2Etopic%2Esubstr%281%29%0A%7D%29

Title Case

According to grammar-monster.com titles should be written in “title case.” This means only using capital letters for the principal words. Articles, conjunctions, and prepositions do not get capital letters unless they start the title. For example: The Last of the Mohicans

Applying title case to strings using a script can be very challenging. But fortunately for users of Omni Automation, John Resig translated to JavaScript a Perl script written by John Gruber, that provides a comprehensive application of title case.

With gratitude and thanks to these gentlemen for their creativity and generosity, we’re providing an enhanced version of Mr. Resig’s JavaScript function for use with Omni Automation. In addition to providing the functionality of the orignial code, this version enables the processing of those possessive terms using a curly single-quote character: ( ’ )

Here is a link to the Set to Title Case script saved as an OmniOutliner solitary action:

macOS_deviceTo install on macOS, download and unpack the ZIP archive, then choose “Plug-Ins…” from the OmniOutliner automation menu, and place the file in the PlugIns folder opened on the desktop. The script will be available from the OmniOutliner Automation menu.

macOS_deviceTo install on iOS, tap the link, choosing the “Open” option in forthcoming dialog. Then tap “More…” and choose the “Copy to OmniOutliner” option. The installed action will appear in the Plug-Ins view on your device, and will be available from the OmniOutliner automation menu.

function toTitleCase(str) { return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); }
/* * Title Caps * * Ported to JavaScript By John Resig - http://ejohn.org/ - 21 May 2008 * Original by John Gruber - http://daringfireball.net/ - 10 May 2008 * License: http://www.opensource.org/licenses/mit-license.php */ (function(){ var small = "(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v[.]?|via|vs[.]?)"; var punct = "([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]*)"; this.titleCaps = function(title){ var parts = [], split = /[:.;?!] |(?: |^)["Ò]/g, index = 0; while (true) { var m = split.exec(title); parts.push( title.substring(index, m ? m.index : title.length) .replace(/\b([A-Za-z][a-z.'Õ]*)\b/g, function(all){ return /[A-Za-z]\.[A-Za-z]/.test(all) ? all : upper(all); }) .replace(RegExp("\\b" + small + "\\b", "ig"), lower) .replace(RegExp("^" + punct + small + "\\b", "ig"), function(all, punct, word){ return punct + upper(word); }) .replace(RegExp("\\b" + small + punct + "$", "ig"), upper)); index = split.lastIndex; if ( m ) parts.push( m[0] ); else break; } return parts.join("").replace(/ V(s?)\. /ig, " v$1. ") .replace(/(['Õ])S\b/ig, "$1s") .replace(/\b(AT&T|Q&A)\b/ig, function(all){ return all.toUpperCase(); }); }; function lower(word){ return word.toLowerCase(); } function upper(word){ return word.substr(0,1).toUpperCase() + word.substr(1); } })(); function setCharAt(str,index,chr) { if(index > str.length-1) return str; return str.substr(0,index) + chr + str.substr(index+1); }; items = document.editors[0].selectedNodes.map(function(node){ return node.object }) items.forEach(function(item){ str = item.topic if (str.lastIndexOf('’') === -1){ item.topic = titleCaps(str) } else { indices = []; for (var i=0; i < str.length; i++){ if (str[i] === "’") indices.push(i); } str = str.replace(/’/g, "'") str = titleCaps(str) indices.forEach(function(index){ str = setCharAt(str,index,'’') }) item.topic = str } })
/*{ "type": "action", "targets": ["omnioutliner"], "author": "Otto Automator", "identifier": "com.omni-automation.oo.title-case", "version": "1.0", "description": "Applies title case to the text of the selected rows.", "label": "Set to Title Case", "shortLabel": "Set to Title Case" }*/ (() => { var action = new PlugIn.Action(function(selection, sender) { // action code // selection options: columns, document, editor, items, nodes, styles selection.items.forEach(function(item){ str = item.topic if (str.lastIndexOf('’') === -1){ item.topic = titleCaps(str) } else { indices = []; for (var i=0; i < str.length; i++){ if (str[i] === "’") indices.push(i); } str = str.replace(/’/g, "'") str = titleCaps(str) indices.forEach(function(index){ str = setCharAt(str,index,'’') }) item.topic = str } }) }); action.validate = function(selection, sender) { // validation code // selection options: columns, document, editor, items, nodes, styles if(selection.nodes.length > 0){return true} else {return false} }; return action; }(); function setCharAt(str,index,chr){ if(index > str.length-1) return str; return str.substr(0,index) + chr + str.substr(index+1); }; function titleCaps(title){ // Ported to JavaScript By John Resig - http://ejohn.org/ - 21 May 2008 // Original by John Gruber - http://daringfireball.net/ - 10 May 2008 // License: http://www.opensource.org/licenses/mit-license.php var small = "(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v[.]?|via|vs[.]?)"; var punct = "([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]*)"; var parts = [], split = /[:.;?!] |(?: |^)["Ò]/g, index = 0; while (true) { var m = split.exec(title); parts.push( title.substring(index, m ? m.index : title.length) .replace(/\b([A-Za-z][a-z.'Õ]*)\b/g, function(all){ return /[A-Za-z]\.[A-Za-z]/.test(all) ? all : upper(all); }) .replace(RegExp("\\b" + small + "\\b", "ig"), lower) .replace(RegExp("^" + punct + small + "\\b", "ig"), function(all, punct, word){ return punct + upper(word); }) .replace(RegExp("\\b" + small + punct + "$", "ig"), upper)); index = split.lastIndex; if ( m ) parts.push( m[0] ); else break; } return parts.join("").replace(/ V(s?)\. /ig, " v$1. ") .replace(/(['Õ])S\b/ig, "$1s") .replace(/\b(AT&T|Q&A)\b/ig, function(all){ return all.toUpperCase(); }); }; function lower(word){ return word.toLowerCase(); }; function upper(word){ return word.substr(0,1).toUpperCase() + word.substr(1); };
omnioutliner://localhost/omnijs-run?script=%28function%28%29%7B%0A%09var%20small%20%3D%20%22%28a%7Can%7Cand%7Cas%7Cat%7Cbut%7Cby%7Cen%7Cfor%7Cif%7Cin%7Cof%7Con%7Cor%7Cthe%7Cto%7Cv%5B%2E%5D%3F%7Cvia%7Cvs%5B%2E%5D%3F%29%22%3B%0A%09var%20punct%20%3D%20%22%28%5B%21%5C%22%23%24%25%26%27%28%29*%2B%2C%2E%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5C%5C%5C%5C%5C%5D%5E_%60%7B%7C%7D%7E-%5D*%29%22%3B%0A%20%20%0A%09this%2EtitleCaps%20%3D%20function%28title%29%7B%0A%09%09var%20parts%20%3D%20%5B%5D%2C%20split%20%3D%20%2F%5B%3A%2E%3B%3F%21%5D%20%7C%28%3F%3A%20%7C%5E%29%5B%22%C3%92%5D%2Fg%2C%20index%20%3D%200%3B%0A%09%09%0A%09%09while%20%28true%29%20%7B%0A%09%09%09var%20m%20%3D%20split%2Eexec%28title%29%3B%0A%0A%09%09%09parts%2Epush%28%20title%2Esubstring%28index%2C%20m%20%3F%20m%2Eindex%20%3A%20title%2Elength%29%0A%09%09%09%09%2Ereplace%28%2F%5Cb%28%5BA-Za-z%5D%5Ba-z%2E%27%C3%95%5D*%29%5Cb%2Fg%2C%20function%28all%29%7B%0A%09%09%09%09%09return%20%2F%5BA-Za-z%5D%5C%2E%5BA-Za-z%5D%2F%2Etest%28all%29%20%3F%20all%20%3A%20upper%28all%29%3B%0A%09%09%09%09%7D%29%0A%09%09%09%09%2Ereplace%28RegExp%28%22%5C%5Cb%22%20%2B%20small%20%2B%20%22%5C%5Cb%22%2C%20%22ig%22%29%2C%20lower%29%0A%09%09%09%09%2Ereplace%28RegExp%28%22%5E%22%20%2B%20punct%20%2B%20small%20%2B%20%22%5C%5Cb%22%2C%20%22ig%22%29%2C%20function%28all%2C%20punct%2C%20word%29%7B%0A%09%09%09%09%09return%20punct%20%2B%20upper%28word%29%3B%0A%09%09%09%09%7D%29%0A%09%09%09%09%2Ereplace%28RegExp%28%22%5C%5Cb%22%20%2B%20small%20%2B%20punct%20%2B%20%22%24%22%2C%20%22ig%22%29%2C%20upper%29%29%3B%0A%09%09%09%0A%09%09%09index%20%3D%20split%2ElastIndex%3B%0A%09%09%09%0A%09%09%09if%20%28%20m%20%29%20parts%2Epush%28%20m%5B0%5D%20%29%3B%0A%09%09%09else%20break%3B%0A%09%09%7D%0A%09%09%0A%09%09return%20parts%2Ejoin%28%22%22%29%2Ereplace%28%2F%20V%28s%3F%29%5C%2E%20%2Fig%2C%20%22%20v%241%2E%20%22%29%0A%09%09%09%2Ereplace%28%2F%28%5B%27%C3%95%5D%29S%5Cb%2Fig%2C%20%22%241s%22%29%0A%09%09%09%2Ereplace%28%2F%5Cb%28AT%26T%7CQ%26A%29%5Cb%2Fig%2C%20function%28all%29%7B%0A%09%09%09%09return%20all%2EtoUpperCase%28%29%3B%0A%09%09%09%7D%29%3B%0A%09%7D%3B%0A%20%20%20%20%0A%09function%20lower%28word%29%7B%0A%09%09return%20word%2EtoLowerCase%28%29%3B%0A%09%7D%0A%20%20%20%20%0A%09function%20upper%28word%29%7B%0A%09%09return%20word%2Esubstr%280%2C1%29%2EtoUpperCase%28%29%20%2B%20word%2Esubstr%281%29%3B%0A%09%7D%0A%7D%29%28%29%3B%0A%0Afunction%20setCharAt%28str%2Cindex%2Cchr%29%20%7B%0A%09if%28index%20%3E%20str%2Elength-1%29%20return%20str%3B%0A%09return%20str%2Esubstr%280%2Cindex%29%20%2B%20chr%20%2B%20str%2Esubstr%28index%2B1%29%3B%0A%7D%3B%0A%0Aitems%20%3D%20document%2Eeditors%5B0%5D%2EselectedNodes%2Emap%28function%28node%29%7B%0A%09return%20node%2Eobject%0A%7D%29%0Aitems%2EforEach%28function%28item%29%7B%0A%09str%20%3D%20item%2Etopic%0A%09if%20%28str%2ElastIndexOf%28%27%E2%80%99%27%29%20%3D%3D%3D%20-1%29%7B%0A%09%09item%2Etopic%20%3D%20titleCaps%28str%29%0A%09%7D%20else%20%7B%0A%09%09indices%20%3D%20%5B%5D%3B%0A%09%09for%20%28var%20i%3D0%3B%20i%20%3C%20str%2Elength%3B%20i%2B%2B%29%7B%0A%09%09%09if%20%28str%5Bi%5D%20%3D%3D%3D%20%22%E2%80%99%22%29%20indices%2Epush%28i%29%3B%0A%09%09%7D%0A%09%09str%20%3D%20str%2Ereplace%28%2F%E2%80%99%2Fg%2C%20%22%27%22%29%0A%09%09str%20%3D%20titleCaps%28str%29%0A%09%09indices%2EforEach%28function%28index%29%7B%0A%09%09%09str%20%3D%20setCharAt%28str%2Cindex%2C%27%E2%80%99%27%29%20%0A%09%09%7D%29%0A%09%09item%2Etopic%20%3D%20str%0A%09%7D%0A%7D%29
Title Case before Title Case after
UNDER CONSTRUCTION

This webpage is in the process of being developed. Any content may change and may not be accurate or complete at this time.

DISCLAIMER