DekGenius.com
[ Team LiB ] Previous Section Next Section

10.7 Element Specifiers

To refer to a particular element, you must say which one you mean. To do this, you use an element specifier (or just specifier for short). A specifier has two components: the name of a class and some way of picking out the right one(s). AppleScript has eight built-in forms of specifier,[1] and these are the only ones you are allowed to use. The next eight sections describe those eight specifier forms.

[1] Well, okay, there are actually nine element specifiers. I don't tell you about middle (returns the middle element), because it is rarely used. Plus, a reference to a property is actually a form of specifier, so that makes ten.

The variety of specifier forms makes a specifier quite an interesting and complicated part of an Apple event. If you look back at the raw Apple event shown as Example 4-1, you will see a repeated pattern involving four items called form, want, seld, and from. That pattern denotes a specifier.

In real life it will rarely be open to you to use just whichever specifier form you please. Given any particular application, object, and class of element, only certain specifier forms will work, and experimentation is the best guide as to which ones they are. An application's dictionary is supposed to help you here, but it might not be accurate (Section 19.5.2).

10.7.1 Name

An element may have a name, which is some kind of string. To specify an element by name, say the class followed by the name:

tell application "Finder" to get disk "main"

You may insert the keyword named between the class and the name, but I never do.

Typically, there is also a name property, so that you can learn, based on some other element specifier, how to specify a particular element by name:

tell application "Finder" to get name of disk 2 -- "main"

10.7.2 Index

Elements may be ordered, and numbered in accordance with this ordering. The number is an index. The first element has index 1, the next element has index 2, and so forth. The last element can be referred to by index -1, the next-to-last by index -2, and so forth. (If you want to know just how many elements of this class there are, you have to find out in some other way, such as count.)

To specify an element by index, say the class followed by the index number:

tell application "Finder" to get disk 2

You may insert the keyword index between the class and the number, but I never do. Instead of a cardinal numeric value, you're allowed to say a wide variety of English-like ordinal numeric literals followed by the class name. So, for instance, you can say such things as 1st disk, third disk, last disk, front disk, and back disk.

There is sometimes also an index property, so that you can learn, based on some other element specifier, how to specify a particular element by index; but this is not implemented anywhere near as often as one would like, and is sometimes buggy:

tell application "Finder" to get index of disk 2 -- 3, for heaven's sake

10.7.3 ID

Elements may have a unique ID, which is often a number but needn't be. An ID has the advantage of not changing. For example, in the Finder a folder's name can be changed, and its index may change if its name changes or the number of folders in its containing folder changes, but its ID would be constant, if it had one.

To specify an element by ID, say the class followed by the keyword id followed by the ID value. This value will have been obtained at some earlier point, typically by asking for an element's id property:

tell application "Mailsmith"
        set theMailboxID to id of mailbox 3 -- 162, if you must know
        -- . . . 
        get mailbox id theMailboxID
end tell

10.7.4 Some

A random element may be specified by saying some followed by the class:

tell application "Finder"
        name of some disk -- "extra"
        name of some disk -- "main"
        name of some disk -- "main"
end tell

10.7.5 Every

It may be possible to get a list of every element of a class. To ask for such a list, say the keyword every followed by the class; alternatively, you may be able to say just the plural of the class:

tell application "Finder" to get every disk
tell application "Finder" to get disks

If asking for just one element would result in a reference, the result in this case is a list of references.

10.7.6 Range

Elements may be ordered, and you may be able to obtain a list of contiguous elements (a range) by giving the first and last index number you're interested in. It is generally not important in what order you give these index numbers. To specify elements by range, say the class in a plural form (or every and the class) followed by an index number, the keyword thru (or through), and another index number. You can say beginning or end instead of an index number:

get words 1 thru 4 of "now is the winter of our discontent"
get words beginning thru 4 of "now is the winter of our discontent"

Alternatively you may be able to get a list of contiguous elements of a class where the range is marked off by two element specifiers for some other class. In this case you say the class in a plural form (or every and the class) followed by the keyword from, an element specifier for the starting point, the keyword to, and an element specifier for the ending point. Again, you can say beginning or end instead of an element specifier:

get words from character 12 to character 17 of "now is the winter"
get words from character 12 to character -1 of "now is the winter"
get words from character 12 to end of "now is the winter"

There is a tendency to confuse or conflate these two forms, and to try to say something like this:

get words 1 to 3 of "now is the winter of our discontent" -- error

You can't do that. "To" is not "thru"! Keep these two constructions straight. Practice them before going to bed.

10.7.7 Relative

Elements may be ordered, and it may be possible to refer to an element as the successor or predecessor of another element. To ask for an element in this way, say the name of the class, the keyword before or after, and an element specifier:

tell application "Tex-Edit Plus"
        tell window 1
                get word after word 1
        end tell
end tell

A synonym for before is in front of. Synonyms for after are behind and in back of.

In real life the main place this is used is in specifying something like an insertion point. For example, in BBEdit all text has insertion point elements lying between the characters. Thus you can say this:

tell application "BBEdit"
        tell text of window 1
                get insertion point before word 4
        end tell
end tell

The main use of an insertion point in BBEdit is that you can set its contents property to alter the text. So:

tell application "BBEdit"
        tell text of window 1
                set pt to insertion point before word 4
                set contents of pt to "great "
        end tell
end tell

Before running that script, the text in BBEdit's window said, "This is a test." After running it, it said, "This is a great test." If the insertion point reference doesn't need capturing, that whole script can be expressed as a single command:

tell application "BBEdit" to set contents of ¬
        insertion point before word 4 of text 1 of window 1 to "great "

There is also a class called insertion location defined by some applications. You'll see it in an application's dictionary referred to as a "location reference," and you usually can't get one directly; instead, you use it primarily in conjunction with the duplicate, make, and move commands, after the keyword at or to.

A location reference is specified using before or after and an element specifier (which yields wonderfully bizarre locutions such as at after); or using beginning of or end of and a reference to an object, or just beginning or end alone; or using just an element specifier.

Applications can be extraordinarily touchy[2] about how they respond to commands of this form, with results differing from application to application. Here are some examples, just to show the syntax in action; in each case I first show you some code and then describe its effect:

[2] Non-lawyers may read "buggy".

tell application "TextEdit"
        tell text of document 1
                make new word at after word 2 with data "not "
        end tell
end tell

That changes "this is a test" to "this is not a test".

tell application "TextEdit"
        tell text of document 1
                duplicate word 1 to end
        end tell
end tell

That changes "fair is foul and foul is " to "fair is foul and foul is fair".

tell application "TextEdit"
        tell text of document 1
                duplicate word 1 to beginning of word 3
        end tell
end tell

That changes "wonder of s, miracle of miracles" to "wonder of wonders, miracle of miracles".

tell application "TextEdit"
        tell text of document 1
                duplicate word 1 to word 7
        end tell
end tell

That changes "fair is foul and foul is foul" to "fair is foul and foul is fair".

tell application "Script Debugger"
        move window 2 to beginning
end tell

That brings the second window frontmost, and shows that this sort of locution is good for manipulating more than just text.

10.7.8 Boolean Test

It may be possible to get a list of those elements that satisfy a boolean test (the dictionary, if it lists this possibility, will say simply "satisfying a test"). What you test may be a property of the target, or it may be the target itself. The test may involve any boolean operator (see Section 15.3 and Section 15.4).

A boolean test also involves an index-based specifier: index, range, or every (or some). This is because the boolean test yields a list, and you can ask for the whole list or for particular elements of it.

To specify elements by boolean test, say an index-based specifier and the keyword where, followed by a property of that class or the word it, followed by a boolean operator and any value that can function as that operator's second operand. In this context the target is each element as it is tested. Thus, the word it means the element to be tested. You can use it optionally to make your test read more like English. You can use whose instead of where, but this is mere syntactic sugar.

tell application "Finder" to get files where name begins with "s"
tell application "Finder" to get every file where name begins with "s"
tell application "Finder" to get files where name of it begins with "s"
tell application "Finder" to get files where its name begins with "s"
tell application "Finder" to get files whose name begins with "s"

Those are all equivalent. The index-based specifier is the every specifier, with file as its class. Then comes where or whose. Now every file will be tested, and name means the name property of the file being tested; the words of it or its are redundant but harmless. The boolean operator is begins with, and its second operand is the string "s".

When a boolean test specifier tests the value of a boolean property, you can say it is (or it is not) and the name of the property. This can make your expression more English-like, assuming the property name is an adjective:

tell application "System Events"
        get process 1 whose frontmost is true
        get process 1 where it is frontmost
end tell

The two formulations shown are equivalent, but most people prefer the latter, as being more English-like. Now let's have an example where you'd need it instead of a property name after where or whose:

tell application "TextEdit"
        tell text of document 1
                get every word where it contains "t"
                get words whose it contains "t"
        end tell
end tell

The two formulations shown are equivalent, but most people prefer the former, as being more English-like. AppleScript has no equivalent for where it, such as "which" or "that"; it doesn't let you be as English-like as all that.

That example works because when you ask TextEdit for words, you get a list of strings; each string then functions as the first operand of contains. But this is by no means how every application works. For example, in BBEdit, when you ask for words you get a list of references. To obtain the text of each word you ask for its contents, so you don't end up using it at all:

tell application "BBEdit"
        tell text 1 of window 1
                get every word whose contents contains "t"
        end tell
end tell

Finally, here's an example involving an index specifier other than every:

tell application "BBEdit"
        tell text 1 of window 1
                get word 1 whose contents contains "t"
        end tell
end tell

The only downside to that sort of formulation is what happens when no elements satisfy the test. When you use every, if no elements satisfy the text, you get an empty list. But when you specify the index, if that index doesn't exist, you get an error. (This is because you get an error if you ask for a nonexistent item of any list.) This is not much of a downside, since you can easily catch such an error and handle it (Chapter 12); it's just something to watch out for.

If the target application is willing, you may even be able to combine multiple boolean tests (Section 15.2). This requires that you supply another first operand for the second test, even if this is the same as the first operand of the first test:

tell application "Tex-Edit Plus"
        tell window 1
                words where it begins with "t" and it ends with "t" -- {"test"}
        end tell
end tell

The boolean test, where it works, is a very powerful specifier; with a single Apple event you're getting the target application to do a lot of thinking and testing for you. The only problem is that you never know whether it will work; only experimentation will show this.

AppleScript itself, most disappointingly, fails to implement boolean test specifiers for its own lists. The two halves of this example are deliberately parallel, yet the second half fails:

tell application "Finder"
        get every disk whose name begins with "M" -- "main"
end tell
set pepBoys to {"Mannie", "Moe", "Jack"}
tell pepBoys
        get every item whose text begins with "M" -- error
end tell

As a workaround, use the list-filtering handler developed earlier in this book (Section 9.5.3).

    [ Team LiB ] Previous Section Next Section