Some AppleScript Techniques

Michael Terry formido at mac.com
Wed Apr 7 21:50:00 PDT 2004


Hi,


I've been scripting Outliner for a long time now, and for all this time 
I thought a few things were just beyond easy reach. It turns out that I 
just didn't know what I was doing. This is easy to understand, though, 
given that AppleScript is so confusing. Also, Outliner's scripting 
interface has a small, but crucial, bug that made it a lot harder to 
figure out how much power lay beneath the surface.

I discovered how to correctly script Outliner after analyzing OmniWeb's 
scripting interface and reading Apple's Technote 2106, which describes 
the way a scripting interface should behave. After studying these two, 
I realized that it is actually easy to carve out any two-dimensional 
array from an Outliner document that you need with one line. Also, I  
realized that you can easily get all the values of a column, or a 
subset of the values of a column, and further, that you can specify the 
column by name rather than having to hard code its index into the 
script.

Suppose you have a column named "Item". Here's how to get all the 
column's values without a repeat loop:

tell application "OmniOutliner" to tell front document
	get value of first cell of every row where its column's title is "Item"
end tell

This example demonstrates how to avoid the bug I was talking about. 
Whenever I've tried this sort of construct before, I used the following 
syntax, which should work:

tell application "OmniOutliner" to tell front document
	get value of first cell of every row whose column's title is "Item"
end tell

This is how I write all of my filter clauses (a type of which is the 
'every row whose' syntax). If it weren't for the fact that 'where' is a 
synonym for 'whose' in these constructs, I probably would have never 
figured out how to compare against a column's title. Imagine writing:

tell application "OmniOutliner" to tell front document
	get value of first cell of every row whose its title is "Item"
end tell

That also works, but it's so ugly that it never crosses one's mind. 
Anyway, before finding the solution, I had no idea this sort of bug 
could exist. See, a filter clause turns the predicate into a sort of 
tell. When you say:

every row whose topic is "Hello"

you're saying:

tell row
	if topic is "hello"
end

They'd seemed comparable to me in a vague way before, but now I see 
that that must be precisely the way to look at it. Most of the time in 
tell blocks, one can refer to the properties of the tell's target with 
no problem:

tell object
	property
end

or refer to an object's element's properties with no problem, either:

tell object
	element's property
end

Sometimes, though, you have to explicitly qualify the expression with 
the keyword 'its', which is always implied inside a tell block anyway, 
so shouldn't be necessary; 'its' is just a reference to the target of 
the tell. But, sometimes you have to do this:

tell object
	its element's property
end

or the code raises an error. Now, I hope you can see that this is the 
same problem as with the filter clause above, and that it's because 
filter clauses create these sort of implied tells.

I think this problem is like the implied 'get' bug that strikes some 
expressions and applications. Expressions involving object properties 
and elements in a tell block supply a 'get' command if the scripter 
doesn't write one in the code himself. Most of the time this just 
works. Occasionally, though, a developer doesn't do something quite 
right, and an expression one expects to work inexplicably fails. If the 
scripter isn't in the know, he'll probably just give up or approach the 
problem from a different angle. Here's an example:

tell application "OmniOutliner" to tell front document
	repeat with i in title of every column
		log i
	end repeat
end tell
--> error

Now, supply a strategically-placed 'get':

tell application "OmniOutliner" to tell front document
	repeat with i in (get title of every column)
		log i
	end repeat
end tell
--> ""
--> ""
--> "Item"
--> "Price"
--> "Vons"

To be excruciatingly careful, that might not be a bug, or it may be 
disputable. Perhaps the example code is too ambiguous for AS to handle. 
That's just an illustration. I know that in certain contexts this sort 
of thing is a bug in the sense that an application developer can take 
steps to prevent its happening to scripters; I read a post by one of 
the AppleScript language's original developers that said so.

Anyway, that's neither here nor there--this:

tell application "OmniOutliner" to tell front document
	get value of first cell of every row where its column's title is "Item"
end tell

shouldn't require an 'its'. This code:

tell application "OmniOutliner" to tell front document
	get every cell of first row whose column's title is "Item"
end tell

is analogous to this code:

tell application "OmniOutliner" to tell front document
	get every row whose note's text is ""
end tell

in that it queries an object property that always holds an application 
object (first case 'column', second case 'text'), and then in turn 
queries that object for one of its properties. The first one throws an 
error, though.

The foregoing might seem like no big deal, just one little 'its', but 
unless one just happens to know the trick, he is unlikely to guess it. 
I figured it out because I remembered watching the event log in Script 
Editor and how it always converts normal looking filter clauses (normal 
in that almost everyone writes them like this):

every whatever whose foo is bar

into lines like this:

every whatever where its foo = bar

Back to getting slices out of Outliner: Here's one way to get a whole 
block of values:

tell application "OmniOutliner" to tell front document
	get value of cells 2 thru 3 of rows 10 thru 15
end tell

Here's another:

tell application "OmniOutliner" to tell front document
	get value of cell after cell 1 of (every row whose topic is not "")
end tell

Here's a problem that comes up a lot. You want to get a cell value 
based on a row's topic and column title. So, if the row's topic 
matches, you want to get the value of column such-and-such's cell:

tell application "OmniOutliner" to tell front document
	get value of first cell of (every row whose topic is "avocado") where 
its column's title is "Vons"
end tell

It turns out, then, that you can query Outliner for virtually any 
values you want once you understand the concepts. I finally understand 
why one scripter on another list said that application scripting in 
applescript is "founded on a query-style interface a-la SQL, XPath, 
OPath, etc". I listed some common queries that I hope can get 
interested folks started. If you have a complicated set of values you 
want to query Outliner for, and you're having trouble working out how 
to get Outliner to give them to you, maybe I can help.

Also, to clarify, you can seÆ8Öëe values by substituting 'set' for 
'get', too.


Mike




More information about the OmniOutliner-Users mailing list