Previous Page
Next Page

A.2. A Day in the Life

Although I know that FrameMaker is scriptable, I have no idea how to script it. I haven't the slightest notion how to talk to FrameMaker, using AppleScript, about the illustrations in my manuscript. So the first thing I need to do is to try to find this out.

A.2.1. Caught in the Web of Words

My starting place, as with any new AppleScript programming task, is the dictionary of the application I'm going to be talking to, where I examine the terminology I can use in speaking to this application with AppleScript. To see FrameMaker's dictionary, I start up Apple's Script Editor, open the Library window, add FrameMaker to the library, and double-click its icon in the Library window. The dictionary opens, as shown in Figure A-1.

This is a massive document and, to the untrained eye (or even to the trained eye), largely incomprehensible. What are we looking for here? Basically, I'd like to know whether FrameMaker gives me a way to talk about illustrations. To find out, I open each of the headings on the left, and under each heading I study its classes. What I'm trying to find out is what things FrameMaker knows about, so that I can guess which of those things is likely to be most useful for the problem I'm facing. In particular, I'd like to find a class that stands a chance of being what FrameMaker thinks my illustrations are.

The fact is, however, that I don't see anything that looks promising. The trouble is that I don't really understand what an illustration is, in FrameMaker's terms. I know that when working with FrameMaker through its graphical user interface, to add an illustration to a FrameMaker document, using the template my publisher has set up, I begin by inserting a table, and then, to make the reference to an illustration file, I choose the Import File menu item. Sure enough, there is a table class in the FrameMaker dictionary, but it is not at all clear to me what kind of entity I generate by choosing Import File.

At this point an idea strikes me. Perhaps I should start with an existing illustration and see if I can find a way to ask FrameMaker, "Hey, what's this?" In fact, very near the start of FrameMaker's dictionaryand you can see this in Figure A-1there's a listing called selection. This suggests that perhaps if I select an illustration manually in FrameMaker and then use AppleScript to ask FrameMaker for its selection, I will learn what sort of thing an illustration is.

Figure A-1. FrameMaker dictionary


The word selection is listed as a property of the application class. This means I should be able to talk directly about the selection when I'm targeting FrameMaker. So, in FrameMaker, I manually select an illustration; then, in Script Editor, I make a new script window and enter this code:

tell application "FrameMaker 7.0"
    get selection
end tell

I run that code, and the result comes back in the lower part of the script window, in the Result pane (I've reformatted the result here to emphasize its structure):

inset 1
    of anchored frame 22
        of document "gromit:Users:matt2:extra:astdg:ch02"
            of application "FrameMaker 7.0"

Wow! All of a sudden I'm getting someplace. I now have a chain of ofs showing the classes needed to refer to an illustration. So it turns out there's a class called inset, and that an inset belongs to another class called anchored frame. I certainly would never have thought of any of that on my own; even with the help of the dictionary I wouldn't have realized that these were the classes I needed. Now that I know, of course, I see that these classes are indeed listed in the FrameMaker dictionary.

Looking at the dictionary listing for the newly discovered inset class, I see that it has a property inset file, which is described as follows:

inset file (alias) : The file where the graphic originated

This could be just what I'm afterthe link between an illustration in FrameMaker and the illustration file on disk. To find out, I'll ask for the inset file property of the same illustration I just selected a moment ago. So I make a new script window and enter some new code and run it. To form this script, I simply take the reply I just received from AppleScript a moment ago and turn it inside-out, with nested tell blocks instead of the word of:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell anchored frame 22
            get inset file of inset 1
        end tell
    end tell
end tell

And here's the answer that comes back:

"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps"

Perfect! This is sensational. I've started with a way of referring to an illustration inside FrameMaker, and I've ended up with the pathname of the corresponding illustration file on diskthe very file I'm going to want to rename. Clearly I'm on the right track. I set this script aside and move on to the next part of the problem.

A.2.2. One for All and All for One

I now have some idea of how I'm going to refer to an illustration in FrameMaker and how I'm going to mediate between that reference and the pathname of the file on disk that the illustration comes from. So, having solved this piece of the puzzle for one illustration, I move on to the problem of generalizing. I'm going to want to do this for every illustration in the document. How, using AppleScript, am I going to talk about every illustration?

In the previous code, I specified a particular illustration by talking about "anchored frame 22." That's an element specifier, referring to a particular member of the anchored frame class by number. So perhaps the way I'm going to solve the problem is by cycling numerically through the anchored frame elements of my documentthat is, by talking about "anchored frame 1," then "anchored frame 2," and so on. I'm a little surprised, to be sure, by how the numbers are working here. I don't have 22 illustrations in this document, so why am I talking about "anchored frame 22"? However, I press on regardless; we'll cross that bridge when we come to it.

Let's see if I can list the inset files for all the illustrations in the document. To do so, I'll start by gathering up a list of all the anchored frame elements; if FrameMaker will let me, I should be able to do it using the word every. Let's try it:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        get every anchored frame
    end tell
end tell

Here's the response:

{anchored frame 1 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0",
 anchored frame 2 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0",
 anchored frame 3 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0",
...
}

And so on. I've left out most of it, but you get the idea. This seems to be working nicely so far. What I've gotten back is a list, and each item of this list is a reference to one of the anchored frames in the document. So I should be able to run through this list and ask each anchored frame for the inset file property of its "inset 1" element, just as I did with anchored frame 22 earlier. I'll start by making a variable allFrames to store the list in, and then I'll see if I can run through it:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        set allFrames to get every anchored frame
        repeat with oneFrame in allFrames
        end repeat
    end tell
end tell

That code runs, which is good. First I've made a variable allFrames to hold the list; then I've made another variable oneFrame to represent each item of that list as I run through it. But the code doesn't do anything, because I haven't said yet what I want to do with oneFrame; there is no code inside the repeat block.

What I'll do now is create yet another variable, allPaths, to hold my file paths. I'll start this variable as an empty list; every time I get a file path, I'll append it to the list. So here's my code:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        set allPaths to {}
        set allFrames to get every anchored frame
        repeat with oneFrame in allFrames
            set end of allPaths to inset file of inset 1 of oneFrame
        end repeat
    end tell
end tell

I run this code, andit doesn't work! I get an error message:

FrameMaker 7.0 got an error: Can't get inset file of inset 1 of anchored frame 1
    of document "gromit:Users:matt2:extra:astdg:ch02".

I don't really know what this error message means. That sort of thing happens a lot when you're working with AppleScript; stuff goes wrong, but you don't get a very helpful error message explaining why. However, I do see that we didn't get far in the list; right at the start, with anchored frame 1, we had a problem. Now, we know that this is going to work for anchored frame 22, so maybe the problem is related to the mystery of the numbering of the anchored frames. Maybe I've got two kinds of anchored frame: those that represent illustrations and those that don't, which apparently is the same thing as saying those that have an inset file and those that don't.

Because I believe this code should work when I get up to anchored frame 22, I'd like to ignore the problem with anchored frame 1 and any other anchored frames that may not be relevant here. There's an easy way to do this: I'll wrap the code in a try block. I expect I'll still get an error, but now the code won't stop; it will shrug off the error and keep going. In this way I hope to cycle far enough through the list of anchored frames that I get to the ones where I don't get an error. Here's the code now:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        set allPaths to {}
        set allFrames to get every anchored frame
        repeat with oneFrame in allFrames
            try
                set end of allPaths to inset file of inset 1 of oneFrame
            end try
        end repeat
    end tell
end tell

I run that code, and there's no error. However, I'm not seeing any result! Oh, wait, I understand what I did wrong. I constructed the list, as the variable allPaths, but I forgot to ask for that list as the final result of the script. The result that you see in the Result pane of the Script Editor after you run a script is the value of the last command that was executed. So the way to display as your result the value of a variable you're interested in is to say the name of that variable as the last executable line of your code. Let's try again, like this:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        set allPaths to {}
        set allFrames to get every anchored frame
        repeat with oneFrame in allFrames
            try
                set end of allPaths to inset file of inset 1 of oneFrame
            end try
        end repeat
    end tell
end tell
allPaths

And here's the result:

{"gromit:Users:matt2:extra:astdg:figs:fileMaker1.eps",
"gromit:Users:matt2:extra:astdg:figs:fileMaker2.eps",
"gromit:Users:matt2:extra:astdg:figs:cocoa.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptEditorDict.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptDebugger.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptDebuggerDict.eps",
"gromit:Users:matt2:extra:astdg:figs:radio.eps",
"gromit:Users:matt2:extra:astdg:figs:automator.eps",
"gromit:Users:matt2:extra:astdg:figs:automator2.eps",
"", "", "", "", "", "", ""}

Well, that's pretty good. I have no idea what those last seven items are, the ones that just show up as empty strings (symbolized by empty pairs of quotation marks). But in my final code I guess I could just ignore the empty strings, so that's not really a problem. And we've got ten pathnames, which is exactly right because the chapter has ten illustrations.

But there's a problem. A really big problem. The pathnames are in the wrong order.

Remember, our entire purpose is to rename these files in accordance with the order in which they appear in the document. But this is not the order in which they appear in the document. I don't know what order it is, but I do know that the first illustration in the document is scriptEditor.eps. This is a disaster. Our efforts so far have probably not been a total waste, but there's no denying that we're completely stuck. The "every anchored frame" strategy is a failure.

A.2.3. Seek and Ye Shall Find

At this point I'm exhausted and frustrated, so I do something else for a while and brainstorm subconsciously about the problem to see if I can come up with a new angle. Instead of gathering up all anchored frame references as FrameMaker understands them, we want to run forward through the document itself, looking for anchored frames in order, just as a user would. Hmm... as a user would....

This gives me an idea. How would I, as a user, run through the illustrations in a FrameMaker document? I'd use the Find dialog. Perhaps FrameMaker lets me do the same thing with AppleScript. Yes, by golly; looking in the dictionary, I discover there's a find command. Here's the dictionary entry:

find v : Find text, objects, or properties in a Frame document.
    find text/text having paragraph tag/text having character tag/marker/
        marker having type/marker containing text/variable/variable having name/
        anchored frame/table/table having tag/footnote/xref/xref having format/
        unresolved xref/autohyphen : The type of object or property to be found.
        [with value string] : The value of the text, tag, type, format, or name
            being found.
        [with properties list of list] : The properties of the text to be found.
             Most text properties will work here. Finding both value and properties
             is unlikely to work, and some combinations of properties don't work
             well together.
        in reference : The document in which to find.
        [using use case/whole word/use wildcards/backwards/no wrap/find next] :
             The options to be applied to the find.
         reference : to the object that was found.

The words "anchored frame" in the first paragraph leap right off the screen at me. I can find an anchored frame! I create a new script window in Script Editor, and try it.

tell application "FrameMaker 7.0"
    find anchored frame
end tell

No, when I run that it generates an error. What's gone wrong? Oh, I see: I've left out the in parameter. I have to tell FrameMaker what document to look in.

tell application "FrameMaker 7.0"
    find anchored frame in document "gromit:Users:matt2:extra:astdg:ch02"
end tell

It works! The first illustration is selected, and the result is a reference to it, just as the dictionary promises:

anchored frame 22 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0"

So now it begins to look like I can use find repeatedly to get a succession of references to the anchored frames in the document in the order in which they actually appear. To test this idea, I'll just make an artificial loop without worrying for now about how many times I would have to loop in real life:

tell application "FrameMaker 7.0"
    set allPaths to {}
    repeat 5 times
        set oneFrame to find anchored frame 
in document "gromit:Users:matt2:extra:astdg:ch02" set end of allPaths to inset file of inset 1 of oneFrame end repeat end tell allPaths

Here's the result:

{"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps"}

Oops. We're not moving forward through the document; we're just finding the same illustration repeatedly. In FrameMaker itself, hitting the Find button over and over keeps finding the next match, which is what we want; in AppleScript, though, it appears that giving the find command over and over keeps finding the same match. But wait; the find command has a using parameter where I can specify find next. Let's try that:

tell application "FrameMaker 7.0"
    set allPaths to {}
    repeat 5 times
        set oneFrame to find anchored frame 
in document "gromit:Users:matt2:extra:astdg:ch02" using find next set end of allPaths to inset file of inset 1 of oneFrame end repeat end tell allPaths

Darn it; this generates the same result. I guess "find next" simply means to find forwards as opposed to backwards. The trouble is I'm not finding forwards. It appears that once I've selected something, finding again just finds that same thing again. All right, then, maybe if I can just somehow move the selection point forward a little after finding an illustration, I'll be able to find the next illustration instead of the current one. So now I have to figure out how to move the selection point forward. I start by selecting some text, and then comes a long round of experimentation , of which I'll spare you the details, at the end of which I come up with this:

tell application "FrameMaker 7.0"
    select insertion point after selection
end tell

This works just fine when the selection is some text. Unfortunately, as I soon discover, when the selection is an illustration, I get an error message. This is so frustrating! Just when I thought I had the problem solved, I'm completely blocked again, simply because I don't know how to move the selection off an illustration.

A.2.4. Turning the Tables

At this point I remember that every illustration is embedded in a table. According to FrameMaker's dictionary, the find command has an option to find a table. Perhaps this will work better if I start by dealing with tables instead of anchored frames. So I try this:

tell application "FrameMaker 7.0"
    find table in document "gromit:Users:matt2:extra:astdg:ch02"
    select insertion point after selection
end tell

Gee, there's no error. Could it be that this is actually working? To find out, I'll try to cycle through several tables, collecting references to them to see if I'm finding different ones:

tell application "FrameMaker 7.0"
    set allTables to {}
    repeat 5 times
        set oneTable to find table 
in document "gromit:Users:matt2:extra:astdg:ch02" set end of allTables to oneTable select insertion point after selection end repeat end tell allTables

Here's the result:

{table 33 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0",
table 34 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0",
table 32 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0",
table 35 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0",
table 26 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0"}

That's great. The numbers are once again mystifying; I have no idea why FrameMaker thinks there are at least 35 tables in this document, and of course it is numbering them in a different order than they appear in the document, just as it did with anchored frames. But the important thing is that those are five different tables. That means I really can cycle through the tables of the document this way.

Now I need to prove to myself that having found a table, I can get to the anchored framethe illustrationinside it. This could be tricky, but surely there's a way. Examining the table class listing in the dictionary, I see that a table has cell elements. Well, for a table representing an illustration, that should be simple enough; there's only one cell. Let's see:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell table 33
            get cell 1
        end tell
    end tell
end tell

Yes, that runs without error. Now, what's inside a cell? Looking in the cell class listing in the dictionary, I see that it has various possible elements, including paragraph, word, and text. Let's try paragraph:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell table 33
            tell cell 1
                get paragraph 1
            end tell
        end tell
    end tell
end tell

Yes, that too runs without error. But can I get from this paragraph to the anchored frame, the actual illustration? There's only one way to find outtry it:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell table 33
            tell cell 1
                tell paragraph 1
                    get anchored frame 1
                end tell
            end tell
        end tell
    end tell
end tell

Here's the result:

anchored frame 22 of document "gromit:Users:matt2:extra:astdg:ch02"
    of application "FrameMaker 7.0"

Son of a gun, it worked. Starting with a reference to a table, I've found a way to refer to the anchored frame inside it. But in that casedare I say it?the problem is essentially solved. I know that in principle I can cycle through all the tables in a document, in the order in which they appear. I know that in principle I can get from a reference to a table to a reference to an anchored frame. I know that, given an anchored frame, I can obtain the pathname for the file on disk that is the source of the illustration. So I should be able to put it all together and get the pathnames for the illustration files, in the order in which the illustrations appear in the document.

Let's try it. I'll start things off at the top of the document by selecting the first paragraph. Then I'll cycle through the tables. As I come to each table, I'll get the anchored frame, and from there I'll get the pathname to its source file and append it to a list. I'll continue my policy of cycling some arbitrary number of times, because I don't want to worry yet about the real question of how many times to do it; I just want to prove to myself that I can do it.

The first thing is to learn how to select the first paragraph. This turns out to be somewhat tricky. I try this, but it doesn't work:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        select paragraph 1
    end tell
end tell

By once again using my trick of selecting the first paragraph manually and then asking FrameMaker for its selection, so as to learn how it thinks of a paragraph, I finally come up with this:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        select paragraph 1 of text flow 1
    end tell
end tell

Now I'm ready to put it all together:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        set allPaths to {}
        select paragraph 1 of text flow 1
        repeat 5 times
            set oneTable to find table in it
            set end of allPaths to inset file of inset 1 
of anchored frame 1 of paragraph 1 of cell 1 of oneTable select insertion point after selection end repeat end tell end tell allPaths

A change you'll notice here is the use of the word it. Because the current target is the document, the word it refers to this document. This is needed because the find command requires a reference to a document, but all the other commands are already being addressed to that document.

Here's the result:

{"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptEditorDict.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptDebugger.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptDebuggerDict.eps",
"gromit:Users:matt2:extra:astdg:figs:fileMaker1.eps"}

That's the right answer: those are the pathnames to the first five illustrations in the document, in the order in which they appear. For the first time since starting to work on the problem, I now believe I'm going to be able to solve it.

A.2.5. Refiner's Fire

Now let's make a few refinements. First, it occurs to me that I'm doing something rather stupid here; I'm finding every table. That's going be troublesome, because some tables are illustrations but some are just ordinary tables. I want to find illustration tables only. I know that in my FrameMaker template these are tables whose tag (or style name) is "Figure." The find command, according to FrameMaker's dictionary, lets me find a table having tag. So that's the way I should find my tables. After a short struggle to understand the syntax of this command, I come up with the following new version of my script:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        set allPaths to {}
        select paragraph 1 of text flow 1
        repeat 5 times
            set oneTable to find table having tag with value "Figure" in it
            set end of allPaths to inset file of inset 1 
of anchored frame 1 of paragraph 1 of cell 1 of oneTable select insertion point after selection end repeat end tell end tell allPaths

The result is just the same, so I haven't wrecked the successes I've already had (known in the programming business as a "regression"), and I believe I've eliminated some possible false positives from the find.

Next, let's worry about how to know how many times to loop. By changing "5 times" to "20 times," which is more times than the number of illustrations in the document, and then running the script again, I discover that when I get to the end of the document the search wraps around and starts from the top once more. I try to fix this by adding the option using no wrap to the find, but it doesn't help. Therefore I'd like to know beforehand exactly how many times to loop.

Now, I know from the dictionary that a table has a table tag property. AppleScript's boolean test specifier allows me to specify particular objects of a class in terms of the value of one of that class's properties; not every scriptable application implements this construct when you'd like it to, but the only way to find out whether FrameMaker does in this case is to try it, so I do. After some stumbling about, I realize that a document has a text flow element that I have to refer to before I can refer to a table, and I come up with this:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell text flow 1
            get tables whose table tag is "Figure"
        end tell
    end tell
end tell

That works. But I don't really want this entire list; I just want to know how many items it contains. The size of a list can be obtained with the count command:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell text flow 1
            count (get tables whose table tag is "Figure")
        end tell
    end tell
end tell

The result is 10. That's correct. So I should be able to use this approach before starting my loop in order to know just how many times to loop. Here's my new version of the script:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell text flow 1
            set howMany to count (get tables whose table tag is "Figure")
        end tell
        set allPaths to {}
        select paragraph 1 of text flow 1
        repeat howMany times
            set oneTable to find table having tag with value "Figure" in it
            set end of allPaths to inset file of inset 1 
of anchored frame 1 of paragraph 1 of cell 1 of oneTable select insertion point after selection end repeat end tell end tell allPaths

And here's the result; it's absolutely perfect, the correct names of the correct illustrations in the correct order:

{"gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptEditorDict.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptDebugger.eps",
"gromit:Users:matt2:extra:astdg:figs:scriptDebuggerDict.eps",
"gromit:Users:matt2:extra:astdg:figs:fileMaker1.eps",
"gromit:Users:matt2:extra:astdg:figs:fileMaker2.eps",
"gromit:Users:matt2:extra:astdg:figs:radio.eps",
"gromit:Users:matt2:extra:astdg:figs:cocoa.eps",
"gromit:Users:matt2:extra:astdg:figs:automator.eps",
"gromit:Users:matt2:extra:astdg:figs:automator2.eps"}

A.2.6. Naming of Parts

Let's now turn our attention to the business of deriving the new name of each illustration. This will involve the chapter number. How can we learn this number?

It happens that in the FrameMaker template I'm using, every chapter document has exactly one paragraph whose tag (paragraph style) is "ChapterLabel," and that the text of this paragraph is the chapter number. So if FrameMaker gives me a way to refer to this paragraph, I should be home free. The dictionary tells me that the paragraph class has a paragraph tag property. Using the same sort of boolean test specifier construct I used a moment ago to find only those tables with a particular table tag, I try to find just those paragraphs that have this particular paragraph tag, expecting there to be just one:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell text flow 1
            get paragraphs whose paragraph tag is "ChapterLabel"
        end tell
    end tell
end tell

This doesn't give me an error, but the result is not quite what I expected:

{""}

I guess the problem is that the paragraph itself is empty; the chapter number is generated automatically through FrameMaker's autonumbering feature, and doesn't really count as its text. The dictionary lists a couple of paragraph properties that look promising here:

autoNum string (string) : The automatic numbering format string
paragraph number (string) : The formatted string representation of the
    paragraph number

The second one looks like what I'm after, so I try it:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell text flow 1
            get paragraph number of paragraphs 
whose paragraph tag is "ChapterLabel" end tell end tell end tell

The result is this:

{"Chapter 2"}

That's the right answer! It's a list because I asked for all such paragraphs, but it's a list of just one item because there is only one such paragraph, and the string "Chapter 2" provides the chapter number for this chapter. Now, of course, I need to extract just the "2" from this string, but that's easy, because AppleScript understands the concept of a word:

tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell text flow 1
            set chapNum to (get paragraph number of paragraphs 
whose paragraph tag is "ChapterLabel") end tell end tell end tell set chapNum to word -1 of item 1 of chapNum chapNum

The element item 1 exTRacts the single string "Chapter 2" from the list, and the element word -1 extracts the last word of that. The result is "2", which is perfect.

I want this number formatted to have two digits. Now, this is a piece of functionality I'm going to need more than once, so I write a little handler (a subroutine). This handler's job is to accept a string and pad it with zero at the front until it is two characters long. Having written the handler, I add some code to call the handler and test it, twice, because I want to make sure it works for a string that is either one or two characters long.

on pad(s)
    repeat while length of s < 2
        set s to ("0" & s)
    end repeat
    return s
end pad
log pad("2")
log pad("22")

The pad handler makes use of concatenation (via the ampersand operator) to assemble the desired string. The last two lines do two things:

  • The pad command calls the pad handler that appears earlier in the code.

  • The log command puts the result of the pad command into the Event Log History window. The Event Log History window must be opened manually before running the script.

Logging like this is a good approach when you want to test more than one thing in a single running of a script. The result looks good:

    (*02*)
    (*22*)

Those parentheses and asterisks are comment delimiters; I don't quite understand why the log window uses them, but it doesn't matter.

Another problem is that the new name of each illustration is going to be based partly on its old name. I need to break up the illustration file's pathname into its components, and I need to break up the last component, the actual name of the file, into the name itself and the file-type suffix, because the only part of the pathname I want to change is the name itself.

The typical AppleScript way to break up a string into fields based on some delimiter is to set the special variable text item delimiters to that delimiter and then ask for the string's text items. So I'll do that twice, once with colon as the delimiter and again with period as the delimiter. Once again, I'll make this a handler, just because it makes the script so much neater. I'll have the handler return both results, the list of pathname components together with the list of filename components, combined as a single list. That way, when I call this handler, I will have all the pieces and can reassemble them the way I want:

on bust(s)
    set text item delimiters to ":"
    set pathParts to text items of s
    set text item delimiters to "."
    set nameParts to text items of last item of pathParts
    return {pathParts, nameParts}
end bust
bust("disk:folder:folder:file.suffix")

The result shows that the right thing is happening:

{{"disk", "folder", "folder", "file.suffix"}, {"file", "suffix"}}

Now I'm ready to practice renaming an illustration file. I'll write a handler that takes two numbers and the current pathname of the illustration file and generates the new pathname for that file:

on pad(s)
    repeat while length of s < 2
        set s to ("0" & s)
    end repeat
    return s
end pad
on bust(s)
    set text item delimiters to ":"
    set pathParts to text items of s
    set text item delimiters to "."
    set nameParts to text items of last item of pathParts
    return {pathParts, nameParts}
end bust
on rename(n1, n2, oldPath)
    set bothLists to bust(oldPath)
    set extension to last item of item 2 of bothLists
    set pathPart to items 1 thru -2 of item 1 of bothLists
    set newFileName to "as_" & pad(n1) & pad(n2)
    set newFileName to newFileName & "." & extension
    set text item delimiters to ":"
    return (pathPart as string) & ":" & newFileName
end rename
rename("2", "3", "disk:folder:folder:oldName.eps")

The expression as string when applied to a list, as in the very last line of the rename handler, assembles the items of the list, together with the text item delimiters between each pair of items, into a single string. And here's the result:

"disk:folder:folder:as_0203.eps"

Got it right the first time! It's hard to believe, but I am now ready for a practice run using an actual FrameMaker document.

A.2.7. Practice Makes Perfect

If I've learned one thing about programming over the years, it's to practice before doing anything drastic. I'm going to run through the document and pretend to change the illustration names. Instead of really changing them, I'll log a note telling myself what I would have changed the name to if this had been the real thing. So, putting it all together, here's my practice script:

on pad(s)
    repeat while length of s < 2
        set s to ("0" & s)
    end repeat
    return s
end pad
on bust(s)
    set text item delimiters to ":"
    set pathParts to text items of s
    set text item delimiters to "."
    set nameParts to text items of last item of pathParts
    return {pathParts, nameParts}
end bust
on rename(n1, n2, oldPath)
    set bothLists to bust(oldPath)
    set extension to last item of item 2 of bothLists
    set pathPart to items 1 thru -2 of item 1 of bothLists
    set newFileName to "as_" & pad(n1) & pad(n2)
    set newFileName to newFileName & "." & extension
    set text item delimiters to ":"
    return (pathPart as string) & ":" & newFileName
end rename
tell application "FrameMaker 7.0"
    tell document "gromit:Users:matt2:extra:astdg:ch02"
        tell text flow 1
            set howMany to count (get tables whose table tag is "Figure")
            set chapNum to (get paragraph number of paragraphs 
whose paragraph tag is "ChapterLabel") end tell set chapNum to word -1 of item 1 of chapNum set allPaths to {} select paragraph 1 of text flow 1 set counter to 1 repeat howMany times set oneTable to find table having tag with value "Figure" in it set thisFile to inset file of inset 1
of anchored frame 1 of paragraph 1 of cell 1 of oneTable set newName to
my rename(chapNum, (counter as string), thisFile) log "I found " & thisFile log "I'm thinking of changing it to " & newName select insertion point after selection set counter to counter + 1 end repeat end tell end tell

Observe that I have put in the variable counter, which starts at 1 and is incremented in every loop; this is how I know how many times I've done the find, and therefore tells me the number of the current illustration. I must admit that it took me a couple of tries to get this script to run. When I first tried to run it, I got an error at the point where I call the rename handler. This was because I had forgotten to put the magic word my before it; this tells AppleScript that even though I'm talking to FrameMaker I want to call a handler in my own script. And then, after I made that change and ran the script again, I got another error: it appeared that the rename handler was called but was now choking. The reason was that the variable counter was a number, and the rename handler was passing this on to the pad handler, which was expecting a string. The phrase counter as string converts the number to a string for purposes of passing it to the rename handler.

Here are the first couple of relevant entries of the resulting log:

(*I found gromit:Users:matt2:extra:astdg:figs:scriptEditor.eps*)
(*I'm thinking of changing it to gromit:Users:matt2:extra:astdg:figs:as_0201.eps*)
(*I found gromit:Users:matt2:extra:astdg:figs:scriptEditorDict.eps*)
(*I'm thinking of changing it to gromit:Users:matt2:extra:astdg:figs:as_0202.eps*)
...

And so forth. That looks as good as I could wish. I'm rapidly becoming very confident that my script, when unleashed for real upon my FrameMaker document, will correctly calculate the new names for the illustration files.

A.2.8. Finder's Keepers

I must not change only the file reference of each illustration within FrameMaker; I must change also the name of the actual illustration file. This involves speaking to the Finder. As a test, I create a file and run a little code to make sure I know how to tell the Finder how to change the name of a file.

tell application "Finder"
    set name of file "feathers:Users:mattneub:Desktop:testing.txt" to "itWorked"
end tell

This works, and now I'm ready to run my original script for real. Before doing so, I make sure I have backup copies of everything involved, in case something goes wrong. Even with backups, it's a scary business making such possibly disastrous changes, both in a FrameMaker document and on disk, so I start by running the script against a document that has just one illustrationin fact, I run it against this very document, Appendix A.

To make the script work for real, I change it in two places. First, at the start I add another little handler to extract the final component from a pathname, so that I can obtain the new name that the Finder is to give to the illustration file:

on justName(s)
    set text item delimiters to ":"
    return last text item of s
end justName

Second, I replace the two "log" lines from the previous version with this:

set newShortName to my justName(newName)
tell application "Finder" 
to set name of file thisFile to newShortName set inset file of inset 1
of anchored frame 1 of paragraph 1 of cell 1 of oneTable
to newName

The first two lines are simply a rewrite of the Finder file-renaming code just tested a moment ago, with the values coming from variables in the script instead of being hard-coded as literal strings. The second line actually changes the name of the illustration file on disk. (Observe that I can talk to the Finder even inside code where I'm already talking to FrameMaker.) The last line is the only one that makes an actual change in the FrameMaker documentthe crucial change, the one I came here to make, altering the illustration's file reference to match the new pathname of the illustration file. I run the script against Appendix A, and it works; the illustration file's name is changed, and the illustration's file reference in the FrameMaker document is changed to match.

A.2.9. I've Got a Little List

Recall that one of my purposes is to generate the figure list requested by the illustration department, as shown in Table A-1. I already know the chapter number, the illustration number, and the illustration file's name. The only missing piece of information is the illustration's caption. The FrameMaker dictionary shows that a table has a title property that looks like what I want. A quick test against a specific table shows that it is:

tell application "FrameMaker 7.0"
    set theTitle to (get title of table 36 of document 
"gromit:Users:matt2:extra:astdg:appa") end tell

This works, but because of the way the template is constructed, it includes an unwanted return character at the start of the result. To eliminate this, I use an AppleScript expression that extracts all but the first character of a string:

tell application "FrameMaker 7.0"
    set theTitle to text from character 2 to -1 of 
(get title of table 36 of document
"gromit:Users:matt2:extra:astdg:appa") end tell

That works; the result is this:

"FrameMaker dictionary"

That is indeed the caption of the first illustration of this chapter. AppleScript can write to a file, so all I need now is a handler that appends to a file, nicely formatted, a line containing the information for the illustration currently being processed. Here, then, is the final version of the script, including this handler and a call to it:

on pad(s)
    repeat while length of s < 2
        set s to ("0" & s)
    end repeat
    return s
end pad
on bust(s)
    set text item delimiters to ":"
    set pathParts to text items of s
    set text item delimiters to "."
    set nameParts to text items of last item of pathParts
    return {pathParts, nameParts}
end bust
on rename(n1, n2, oldPath)
    set bothLists to bust(oldPath)
    set extension to last item of item 2 of bothLists
    set pathPart to items 1 thru -2 of item 1 of bothLists
    set newFileName to "as_" & pad(n1) & pad(n2)
    set newFileName to newFileName & "." & extension
    set text item delimiters to ":"
    return (pathPart as string) & ":" & newFileName
end rename
on justName(s)
    set text item delimiters to ":"
    return last text item of s
end justName
on writeInfo(n1, n2, theName, theTitle)
    set s to return & n1 & "-" & n2 & tab & theName & tab & theTitle & return
    set f to open for access 
file "feathers:Users:mattneub:figs" with write permission write s to f starting at (get eof of f) close access f end writeInfo tell application "FrameMaker 7.0" tell document "gromit:Users:matt2:extra:astdg:appa" tell text flow 1 set howMany to count (get tables whose table tag is "Figure") set chapNum to (get paragraph number of paragraphs
whose paragraph tag is "ChapterLabel") end tell set chapNum to word -1 of item 1 of chapNum set allPaths to {} select paragraph 1 of text flow 1 set counter to 1 repeat howMany times set oneTable to find table having tag with value "Figure" in it set thisFile to inset file of inset 1
of anchored frame 1 of paragraph 1 of cell 1 of oneTable set newName to my rename(chapNum, (counter as string), thisFile) set newShortName to my justName(newName) tell application "Finder" to set name of file thisFile to newShortName set inset file of inset 1
of anchored frame 1 of paragraph 1 of cell 1 of oneTable
to newName set theTitle to text from character 2 to -1 of (get title of oneTable) my writeInfo(chapNum, (counter as string), newShortName, theTitle) select insertion point after selection set counter to counter + 1 end repeat end tell end tell

There is just one thing I don't like about that script, namely this line:

    tell document "gromit:Users:matt2:extra:astdg:appa"

That line hard-codes the pathname of the document file. This works, but it means that I have to change the script manually for each file I process. That's not so terrible, as only a few chapters of this book have any illustrations at all, but it would be nice not to have to do it at all, if only because it seems a possible source of error. Nevertheless, I think we can save this matter for some future round of refinements, and for now at least, consider the problem solved.


Previous Page
Next Page