DekGenius.com
[ Team LiB ] Previous Section Next Section

19.5 Inadequacies of the Dictionary

One purpose of the dictionary is to show the human user how to speak AppleScript to a scriptable application in order to drive that application. But a dictionary, by its very nature, is not completely adequate to this task. A dictionary is merely a list of words. Knowing a lot of words is not the same as knowing a language. Languages refer to the real world, they develop under certain conventions of communication, and they have idioms. You might know every word of the English language, including the words "how," "you," and "do"; but nothing about these words, qua words, would tell you what "How do you do?" means, nor would anything about these words lead you to think of generating such a phrase at the appropriate moment. An AppleScript dictionary is like that. It tells you the building blocks of the phrases you can say, but it does not tell you what to say—how, as Austin famously put it, to do things with words. Yet this is exactly what you want to know.

This section lists the main types of problem you're likely to encounter. Forewarned, as they say, is forearmed. It is hoped that study of this section will make you a better reader of dictionaries and a wiser AppleScript programmer.

19.5.1 Defective Object Model

Since an application's object model (see Section 19.3.5.3, earlier in this chapter) is a hierarchy, essentially equivalent to the chain of ofs allowing you specify any of the application's objects, it's clear that it requires a starting point. If we are to specify any object, there must be some ultimate, top-level object in terms of which we will specify it. In an Apple event, that top-level object is null( ). But in AppleScript there is no way to express this null( ) explicitly; it is simply supplied for you as the end of the chain, whenever you specify an object. (You can see this happening in Example 4-1.)

By convention, in a well-written dictionary for a well-behaved application, the application class can be used to describe the top-level null( ) object. For example, the Finder's application class has a home property, and sure enough, you can refer to the home property as a property of the top-level object, with no further qualification:

tell application "Finder"
        get home -- folder "mattneub" of folder "Users" of startup disk ¬
                 of application "Finder"
end tell

In reality, though, you never know quite what an application will do if you simply refer to a property or element with no further qualification. You can't find out from the dictionary; you just have to try it. A good example is what happens when you refer to a top-level element in the Finder:

tell application "Finder"
        get folder 1 -- folder "Moe" of desktop of application "Finder"
end tell

Since desktop is a property of the Finder's application object, it's a property of the top level; thus, a folder on the desktop is not an element of the top level. But the Finder permits you to speak as if it were; you can refer to a folder element without further qualification, and the Finder supplies the interpretation that this means a folder on the desktop. We say that the Finder supplies an implicit subcontainer (the desktop) when you speak of certain elements without qualification. That's convenient, but you have no way to know from the dictionary that it will do this.

Some applications are particularly badly behaved in this regard. A good example is Eudora. There is a mailbox class in Eudora, but how can you speak of any particular mailbox? The only place mailbox is listed is as an element of mail folder. But in fact not every mailbox in Eudora is in a mail folder, so that makes no sense. It turns out, however, that you can speak of a mailbox as an element of the top-level object:

tell application "Eudora" to count messages of mailbox "In"

Nothing about Eudora's dictionary informs you that this is legal. This is a good example of how an application can have an object model in its head, as it were, without showing it to you in its dictionary. In Eudora's dictionary, mailbox is an orphan class.

A dictionary may also simply omit pieces of the puzzle, such as not listing all of a class's elements. Much of the time in Chapter 3 was spent discovering, by experimentation, that in FrameMaker an anchored frame can be an element of a paragraph or of a document. This was a relief, and made our ultimate solution possible, but the dictionary said no such thing; only experimentation revealed it. In the Finder, too, a Finder window can have items, as if it were a folder, even though the dictionary doesn't list it as having any elements at all.

19.5.2 Defective Element Specifiers

There are many ways to refer to an element (see Section 10.7), but you can't be sure from the dictionary which ones are implemented for any particular element. The dictionary can list some element specifier forms as ways of accessing a particular element, but the list might not be correct. The programmer's difficulty here is closely related to the defective object model—how to work your way down the chain of ofs and tells to refer to some particular object or set of objects.

For example, as we saw earlier, the Finder lists only the 'indx' and 'name' specifier forms as ways of referring to a folder object's file elements. But this code both compiles and runs on my machine:

tell application "Finder" to get files 1 thru 2 of folder 1

That code doesn't use the 'indx' or 'name' specifier form; it uses the 'rang' (range) specifier form. So the Finder fails to list in its dictionary a specifier form that is valid. It also does list in its dictionary a specifier form that is not valid: it claims you can specify a folder by ID, but since you can't get a folder's ID, that's not true. So you often have to ignore the element specifier form information in a dictionary and just use good old trial and error to determine what specifiers are really possible.

Sometimes an application's implementation of element specifier forms (or lack thereof) is nothing short of astounding. Certain specifiers may work on the very same object in one context but not in another. In Eudora, you can say this:

tell application "Eudora" to get name of mailbox 1 -- "In"

but you can't say this:

tell application "Eudora" to get mailbox 1 -- error

and you can't say this:

tell application "Eudora" to get every mailbox -- error

or this, which amounts to the same thing:

tell application "Eudora" to count mailboxes -- error

As far as I know, there's no way to learn how many Eudora mailboxes there are; the only way to cycle through all mailboxes in Eudora is simply to keep cycling by index number, incrementing the index, until you get an error.

Boolean test specifiers are, of course, the most chancy. When a boolean test specifier works, it's a superbly elegant feature. We used this one in Section 1.4:

tell application "System Events" to get process 1 where it is frontmost

If System Events didn't implement that boolean test, we would have had to get the list and then cycle through it ourselves:

tell application "System Events"
        repeat with p in (get every process)
                if frontmost of p then exit repeat
        end repeat
end tell
contents of p

That gets the same result, but it takes much longer and involves lots of Apple events.

19.5.3 Make

The verb make, used to create objects in AppleScript, poses many peculiar difficulties, so it's worth some individual attention. (See also Section 10.7.7.)

The first question is what to make. You might think, for example, that to make a new email message in Mailsmith—I mean a new outgoing message, one that you intend to populate with an address, a body, and so forth, so as to send it later—you'd ask to make a new message, but that creates a new incoming message (which makes no sense whatever). The way to make a new outgoing message is to ask for a new message window. Nothing in the dictionary would lead you to this solution.

Similarly, the way you create a new window in AppleWorks isn't to ask for a new window, which just gets you an incomprehensible error message, but to ask for a new document. The way you create a new window in the Finder isn't to ask for a new window but to ask for a new Finder window.

The verb make also often requires that you say where to create the new object. The dictionary lists this parameter:

at location reference -- the location at which to insert the element

Every application seems to have a different idea of what constitutes an appropriate location. In Eudora, for example, if you're trying to make an outgoing message, it turns out that the place to create it is at the end of the "Out" mailbox:

tell application "Eudora"
        make new message at end of mailbox "Out"
end tell

With Cocoa applications, the at parameter must typically refer to a collection (sometimes imaginary) of the same things you're trying to make one of. An example appears in Section 2.6 (where the Cocoa application is AppleScript Studio). Here's another example; in the outliner NoteTaker, to make a new page, you have to say something like this:

tell application "NoteTaker"
        tell book 1
                tell section 1
                        make new page at end of pages
                end tell
        end tell
end tell

If you target the wrong object, or if you don't say at end of pages, you get an error, or nothing happens, or NoteTaker crashes.

Not only the at parameter, but also the meaning of at, varies from application to application. This code inserts a new word after word 2:

tell application "BBEdit"
        tell window 1
                make new word at word 2 with data "howdy"
        end tell
end tell

This code inserts a new word replacing word 2:

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

This code inserts a new folder inside folder 1:

tell application "Finder"
        tell desktop
                make new folder at folder 1 ¬
                        with properties {name:"Cool new folder"}
        end tell
end tell

Then there's the with properties parameter. Sometimes you have to use this. For example, the way to add a recipient address to a Mailsmith message window is as follows:

tell application "Mailsmith"
        tell message window 1
                make new to_recipient at end ¬
                        with properties {address:"matt@tidbits.com"}
        end tell
end tell

If you leave out the with properties parameter, you'll get a runtime error. Notice also the peculiar at end; you have to say it this way—you can't omit the at parameter, and you can't say something more sensible-sounding such as at end of to_recipients. None of this comes from the dictionary.

One more thing to know is that with the make command you can omit the word new; what follows the word make, where the direct object would go, is taken to be the new parameter. The dictionary fails to express this fact, which is hardcoded into the inner workings of AppleScript itself.

19.5.4 Idioms for Common Tasks

The commonest tasks are often the hardest to express using the terms the dictionary gives you. The object model is often not at all like the mental picture of the application you've built up from using it in the ordinary way. The verbs you think you need aren't there, or the verbs that are there don't do what you expect.

Take the problem of deleting a message in Eudora. You're used to simply selecting a message and deleting it (with the Delete key). This sounds like the delete event, so you try it:

tell application "Eudora"
        delete message 1 of mailbox "Out" -- error
end tell

The error message says, "Message 1 of mailbox `Out' doesn't understand the delete message." So how on earth are you supposed to delete it? The solution is to move the message to the end of the trash:

tell application "Eudora"
        move message 1 of mailbox "Out" to end of mailbox "Trash"
end tell

Who would ever have thought of saying something like that? And the dictionary doesn't tell you to say it, so how are you supposed to find out?

Another good example is how you insert text into a BBEdit window. There is no insert verb, and make turns out to be unreliable. The best way turns out to be to position the selection where you want to insert the text and then say set the selection. You have to be careful, because set is not being used here quite the way you might suppose. For example, what do you think this code does?

tell application "BBEdit"
        tell window 1
                set selection to word 1
        end tell
end tell

If you expect that code to select the first word of the window, you're wrong; it changes whatever text is currently selected to the same text as the first word of the window. The way you "set the selection" in the sense of positioning the current selection point is with the select command.

To produce Fetch's shortcut window (a Fetch shortcut is like a bookmark in other Internet applications), you choose Fetch Shortcuts from the Window menu. How do you do it with AppleScript? There's a shortcut window class, so naturally you try various incantations based on make new shortcut window, but none of them work. Eventually you discover shortcut window listed as an element of the application class, and experimentation shows that you can say this:

tell application "Fetch 4.0.3" to open shortcut window 1

That makes no sense whatever. The application has only one Shortcut Window, after all. There is no shortcut window 1 (there are no shortcut windows at all, which is why you're trying to produce one), and you're never allowed to speak of shortcut window 2. This shouldn't be an element, but a property, and you should be using make, not open; but such is not Fetch's idiom. The dictionary didn't tell you what to do; you had to guess.

19.5.5 Events and Classes

A dictionary lists events (verbs) and classes (nouns), but it doesn't tell you what verbs apply to what nouns. The verb make creates a new object, but what objects am I allowed to create? The verb delete deletes things, but what objects am I allowed to delete? The dictionary doesn't say.

The problem is particularly acute when the dictionary entry for a verb doesn't provide any meaningful information. For example, here's how delete is listed in most dictionaries:

delete reference -- the element to delete

That could mean anything, so of course it means nothing. The only way to find out what it does mean is by trying it. If AppleScript or an application doesn't want to apply a particular verb to a particular object, it will usually return an error message that "such-and-such an object doesn't understand the so-and-so message." In other words: sorry, guess again.

19.5.6 Inconsistent Return Types

Dictionaries give no information about what sort of value will be returned when you use a verb that isn't defined in the dictionary (like get). As usual, experimentation is your best bet.

For example, when you ask the Finder for every folder (of any container), you get a list; but when you ask the Finder for every disk, you get a list unless there is only one disk, in which case you get a reference to a single disk object. Quite apart from the inconsistency, this is troublesome because it means a script like this can break:

tell application "Finder"
        repeat with d in (get every disk)
                -- do something here
        end repeat
end tell

The script will break in a subtle way: if you have more than one disk, then on every iteration of the repeat block, d represents a disk, but if you have just one disk, then on every iteration of the repeat block, d represents an item at the top level of that disk, and your script will behave very differently. What's more, if it just so happens that you have more than one disk, you have no way to find this out! You can test your script until you're blue in the face, believe that it works fine, and distribute it to others, only to learn later that it mysteriously breaks on someone else's computer.

Also, determining what sort of return value you've got is not always easy. Asking for its class doesn't necessary tell you what you want to know. The Finder, for example, simply lies to you about the class of the desktop, claiming that it's desktop when in fact it's desktop-object. But there is no desktop class! The problem is that the Finder's dictionary foolishly uses the same four-letter code for the desktop-object class and the application class's desktop property; when AppleScript tries to decompile that four-letter code, the term desktop comes first in the dictionary and hides the term desktop-object.

19.5.7 Coercions

Dictionaries don't list the coercions that can be performed by an application in response to get...as. (See Section 14.2.) Only trial and error can give you this information.

A rare exception is the Finder, which defines a class alias list and almost tells you (but not quite) that the purpose of this class is to let you coerce to it:

tell application "Finder"
        get every disk as alias list 
        -- {alias "xxx:", alias "main:", alias "second:", alias "extra:"}
end tell

A related problem is that dictionaries typically don't tell you about the implicit coercions that an application is willing to perform on a parameter of verb. We saw an example of this in Section 13.8, where it turned out that, even though GraphicConverter's dictionary says it expects an alias as the in parameter of the save command, a string would do:

tell application "GraphicConverter"
        set s to "xxx:Users:mattneub:Desktop:joconde"
        save window 1 in s as PICT
end tell

Ironically, the 'aeut' resource contains an alias or string class, expressly so that a dictionary has a way to convey to the user that an alias or a string is acceptable as a parameter. But GraphicConverter's dictionary fails to take advantage of this.

19.5.8 Enumerations

The way enumerations are presented in the human-readable version of a dictionary, there's no place for comments. There's no real reason for this, because in the dictionary itself, enumerators can and sometimes do have comments.

So, for example, in the 'aeut' resource, the enumerators of the 'savo' enumeration have comments:

yes -- Save objects now
no -- Do not save objects
ask -- Ask the user whether to save

But in the human-readable version of the dictionary, you aren't shown the enumeration in columnar form like this; instead, you see the English-like enumerators listed in their verb context. So, for example, GraphicConverter's dictionary entry for the verb close might be shown like this:

close reference -- the object to close
                 [saving yes/no/ask]
                -- specifies whether to save currently open documents

In effect, the comments are thrown away. This isn't a big deal for yes/no/ask, since you can guess what they do; but there are lots of enumerations where comments would be more than welcome.

19.5.9 Borderline Syntax

Some syntactical constructions in AppleScript are of borderline legitimacy, and you can't be sure of what they'll do until you try them. A good example is the construction described in Section 10.8. This works:

tell application "Finder"
        name of every disk -- {"xxx", "main", "second", "extra"}
end tell

So does this:

tell application "BBEdit"
        contents of every word of window 1 -- {"this", "is", "a", "test"}
end tell

But this doesn't:

tell application "BBEdit"
        length of every word of window 1 -- 4
end tell

Instead of a list of lengths, we were given the length of the list (that is, the number of words in the window). This sort of inconsistency adds to the uncertainty of the programmer's task.

19.5.10 Bad Grammar

When developers decide on the English-like terminology for a dictionary, do they think about the experience of the users who will actually employ this terminology in typical AppleScript expressions? I sometimes wonder, when I find myself saying something like this:

if application file n is has scripting terminology then

The trouble is that has scripting terminology is the name of a property. Why would anyone make a property name a verb? If the name of this property were an adjective, such as scriptable, this expression would seem natural. The real trouble with this sort of mistake is that sooner or later the user will be misled by English into trying to write an expression that won't compile, such as this:

if application file n has scripting terminology then

See Section 5.3.

19.5.11 Lying Dictionary

If a dictionary wants to lie right to your face, it can. AppleScript has no way of checking to see whether the application's behavior matches the description in the dictionary.

The Finder's dictionary says that when you're using the make command, the new and at parameters are compulsory, not optional. That just isn't true; this works:

tell application "Finder" to make Finder window

In NoteTaker, the make command is said by the dictionary to return a reference to what has just been created. It should (since this is how most applications work), but it doesn't.

The StandardAdditions dictionary says that the POSIX file class's POSIX path property is a file; it's Unicode text. It say that list disks returns a list of aliases; it returns a list of strings. It says that do shell script returns plain text; it returns Unicode text.

StuffIt Expander's dictionary contains just one entry—the verb expand. The dictionary says that this verb returns an integer representing the number of files that were successfully expanded. It doesn't. It doesn't return anything at all. Just one term in the dictionary, and the folks who wrote the dictionary couldn't be bothered to tell the truth. I discovered this while trying to write the script for Section 2.5. Naturally the script took much longer to write than it should have, because of the lying dictionary.

This is the sort of thing you must expect to encounter all the time. The road to AppleScript is strewn with the bodies of programmers who believed what the dictionaries told them. Be skeptical; you'll live longer.

19.5.12 Bad Comments

Because a dictionary, by its very nature, is inadequate in so many ways, its main chance to be informative is through its comments. Comments are just strings, so they are the developer's opportunity to say anything at all to the user. Unfortunately, many developers don't take advantage of this; they seem to feel that a comment should be terse and, if possible, opaque. AppleScript would be much easier to use if developers would take fuller advantage of the educational potential of comments in dictionaries.

My favorite example of this is Excel's dictionary, which in certain areas has no comments at all. (One area that has no comments is the Chart class; see Section 1.6 for some code written despite a complete lack of assistance from Excel's dictionary.) Excel has one of the weirdest dictionaries under the sun, along with one of the weirdest object models. The Microsoft folks have actually done a rather clever thing here: instead of working out an AppleScript scriptability implementation from scratch, they've simply taken the existing internal scripting implementation (Visual Basic for Applications) and exposed its entire object model, lock, stock, and barrel, to AppleScript. This is ingenious because it means that if you can drive Excel with Visual Basic you can drive it in just the same way with AppleScript, but it also means that if you don't know how to drive Excel with Visual Basic it's really hard to figure out how to drive it with AppleScript, since there are no comments to help you.

Here's an example of a good comment—the to parameter from the Finder's make command:

[to reference] -- when creating an alias file, the original item to create an alias to
        or when creating a file viewer window, the target of the window

That tells me exactly what this parameter is for; it's used on a limited set of occasions, and the comment says just what they are.

Here's an excerpt from Mailsmith's listing for the text_object class:

Class text_object: abstract class describing a text object and its basic properties
Properties:
        offset integer [r/o] -- offset of a text object from the beginning of the document
                 (first char has offset 1)

That is really superb. The dictionary itself has no way to let you know a class is abstract, so the Mailsmith folks come right out and tell you in a comment. And the description of offset tells you how the characters are numbered—it all but gives you an example of how to use this property. Would that all comments were like these.

    [ Team LiB ] Previous Section Next Section