Automatically Saving to MIF

It is not unusual to have “mixed” FrameMaker workflows where members of a team are using different versions of FrameMaker. Documents saved in a lower version can be opened with higher versions, but you can’t open documents saved with a higher version with a lower version of FrameMaker. There are two solutions: First, you can use the higher version to “save down” to the lower version. However, you can only save down to the next lowest version, for example, from FrameMaker 12 to FrameMaker 11. Second, you can save the document to MIF (Maker Interchange Format) in which case you can open it with any lower version of FrameMaker.

The main sticking point with saving to MIF is that you have to remember to do it. But with ExtendScript, you can automate this. You can create a script that will automatically save the document to MIF whenever you choose File > Save or press Control+S. When you are ready to pass the MIF file onto another team member that may be using a lower FrameMaker version, you know that it will always reflect the latest saved changes.

Here is how to set up the script below:

  1. Copy the code below and paste it into an empty text file.
  2. Save the text file to a convenient location with the name “SaveAsMif_Event.jsx”.
  3. Quit FrameMaker if it is running.
  4. Copy the file to C:\Users\<UserName>\AppData\Roaming\Adobe\FrameMaker\<Version>\startup, where <UserName> is your Windows login name and <Version> is the FrameMaker version that you are using. If the startup folder does not exist, create it.
  5. Start FrameMaker and test the script. Open a document, make some changes to it, and save it. You should see a corresponding MIF file in the same folder as the document.

If you have any questions, problems, or comments, please post them in the comments section. Here is a link to a video that walks through the creation of the script.

FrameMaker ExtendScript Training

Adobe is hosting a pair of ExtendScript webinars, taught by Rick Quatro (me). Here is the press release and a link to the registration page. I hope to see you there!

Write your own FrameMaker ExtendScript, Part 1

Wed Jan 14 at 10 AM

Registration link: http://adobe.ly/1DOIa9x

In this two part series, FrameMaker maven Rick Quatro of Carmen Publishing will be walking you through the process of writing your own Extendscripts, and will include files you can download during the session. Rick’s previous session gave an excellent overview of Regular Expressions in FrameMaker. Join this two-part deep dive and discover how you can automate many functions w/in FrameMaker via your own, custom scripts.

Removing Conditions From Text and Table Rows – Part 4

Before continuing our discussion of removing conditions from text, let’s take a look at table rows. Working with conditional table rows is easier that working with conditionalized text, so we can get this code out of the way. To set the stage, add a table to a document and apply two conditions, Condition1 and Condition2 to one of the rows. Select the conditionalized row and run this code:

Condition Formats applied to the row

This is similar to what we did when working with a text range in Part 1, but more direct. Since we have a function for removing conditions from text, we can use this as a starting point for a table rows function.

Let’s rework it together line-by-line: on line 1, change the name to removeConditionsFromRow and change the first argument from textRange to row.

We won’t need a prop variable so remove it from line 3:

Lines 5-10 can be shortened to this:

Lines 12-19 from the original function can be used as is, except for a minor change to the first comment:

Finally, lines 21-25 of the original function can be changed to work with a row:

Now we can test the finished function. Add the following code before the removeConditionsFromRow function and add the augmentObjects function to the end of the script. Select the table row that has Condition1 and Condition2 applied to it and run the complete script. You should see the Condition2 format removed from the selected row.

In an upcoming post, we will return to working removing conditions from text.

Removing Conditions From Text and Table Rows – Part 3

Part 1 and Part 2 of this series show the beginning of the typical script development process that I use. I like to isolate scripting tasks to the smallest units possible. For this task, we worked on a single selection of conditionalized text in the active document. When we switched to tables, we selected a single row with conditions applied. This approach allows us to get the basic functionality working without worrying about the overhead that the larger script requires. Once the basic functionality is working, we can expand it to work on an entire document or book.

Let’s start by figuring out how to specify which Condition Formats to remove from the text and table rows in a document. To keep things simple, we can use an array of Condition Format names:

Even if we add an interface to the script later (like a dialog box), we can still use an array to store the condition names. We need to figure out a way to compare the contents of the array with the conditions that are applied to a given range of text. To set this up, make sure you have a document open with two Condition Formats, Condition1 and Condition2, applied to a paragraph. Select some of the text in the paragraph. We can add our array to some of the code we previously developed:

The script will display each condition format that is applied to the selected text. We need a way to test each name and see if it is in the removeConditions array. Any conditions that are in the removeConditions array need to be removed from the condFmts list. In some implementations, a JavaScript array has an indexOf method so you can test and see if the array contains a particular member. If the member exists, indexOf returns the array index; otherwise, it returns -1. We could do this:

The indexOf method is being used in the if statement on line 12. There is one problem with this code: The current Adobe ExtendScript implementation does not have an indexOf method on arrays. We are going to call (line 4 in the following code) a function (lines 27-45) that will augment the built-in Array object and add an indexOf method to it. Object augmentation is beyond the scope of this lesson, but google it if you are interested in how it works. Here is the entire code:

OK, we have the basic functionality working, so let’s encapsulate it into a function that we easily drop into a bigger script. We want the function to take a text range, a list of conditions to remove, and a document object. Here is one way we can do this:

Some important points:

  • The new function is on lines 14-34.
  • We followed best practices and declare all variables at the top of the function (line 16).
  • Because the function uses the indexOf method, we have to include the augmentObjects function in any script that uses our new function. In addition, we have to remember to call augmentObjects at the top of our script (like we do on line 4).
  • IMPORTANT: The script gets the condition format properties at the beginning of the text range. It removes the appropriate conditions from that list and then applies those properties to the entire text range. For this reason, we want to pass in text ranges that only have distinct condition formats applied to them. If you are not sure why, post a comment below.

Before moving on, there is one issue we should address. One line 32, we apply the updated list of conditions back to the propVal object; then line 33 applies the property list to the text range. But what if this list was empty to start with, or hasn’t been changed by the loop on lines 23-29? There is no sense in going further, so let’s add a couple of tests to the function. We can add this below line 20:

If there are no conditions applied to the text, then there are none that can be removed; so we simply exit the function. If there are conditions applied, they may be conditions that aren’t in our removeConditions array. So we can test to make sure that conditions were actually removed before updating the text (this code would replace lines 31-33):

Here is the finished function:

In a future post, we will show how to loop through the text in a paragraph and isolate ranges of text that have distinct sets of conditions applied to them. These will get passed to our new removeConditionsFromText function to remove the specified conditions from paragraphs in a document. We will also develop a function that will remove conditions from table rows.

Removing Conditions From Text and Table Rows – Part 2

In the previous post, we gained a basic understanding of how to remove a CondFmt (Condition Format) from a text range. Let’s see it in action. To set this up, make sure you have a sample document with two conditions, Condition1 and Condition2, and have both conditions applied to a paragraph in the document. Select some of the text in the paragraph and run this:

To keep the example simple, we are making a couple of assumptions: First, we are removing Condition2 from the text and second, that Condition2 is the second CondFmt in the osval list. We will use code to eliminate these assumptions later.

On line 7, we assign the osval list to a variable (condFmts). We need a way to remove members from this condFmts list. If condFmts was a true JavaScript array, we could use its splice method to remove the desired member. But it is actually an array-like list of CondFmt objects that doesn’t contain a splice method. However, JavaScript allows us to “borrow” the splice method from the built-in Array object and use it on our list; this is what we are doing on line 10.

On line 11, we assign the modified list back to prop.propVal.osval and on line 14 we apply the updated property to the selected text. When you run this, you should see the Condition2 format removed from the selected text.

If we wanted to remove all formats from the text, we could use this shortcut:

Line 9 simply sets the length of the osval list to 0 (zero), which removes all of the members.

Conditionalized table rows are handled a little bit differently than text. To set this up, add a table to your document and apply Condition1 and Condition2 to one of the rows and leave the row selected. Run this code:

As you can see, the condition list is easier to get to with a table row than it is with text. Let’s splice the second member from the list and apply it back to the table row:

As with text, we can use a shortcut to remove all conditions from the row:

In the next post, we will generalize this code into functions so that you can remove conditions from all of the text and table rows in a document.

Removing Conditions From Text and Table Rows

It is easy to delete Condition Formats from a FrameMaker document in the interface. If you try to delete a Condition Format that is use, you will be prompted on what to do to the conditionalized text:

DeleteConditionTag

With ExtendScript, things aren’t so easy. If you delete a Condition Format programmatically, the conditionalized content is always deleted along with the format. There is no “option” on the Delete function to make the text unconditional. If you want to keep the text, you will have to remove the Condition Format from any text (and table rows) where it is applied.

A couple of preliminary things: First of all, you should show all of the conditions at the beginning of your code. You can store the current ShowAll setting so you can restore it later. Second, if you do want to delete the text and rows with the Condition Format, just delete the Condition Format (CondFmt):

To see how to remove Condition Formats from text, make a sample document and add two Condition Formats to it, Condition1 and Condition2. Apply both conditions to a single paragraph. Select part of the paragraph and run this code:

This will show us a PropVal object that has information about the Condition Formats (if any) that are applied to the text. Run the following code to see the properties of the PropVal object.

The propIndent value is the kind of property it is (FP_InCond), while the propVal properties has specific information about the Condition Formats that are applied. Let’s poke further down and look at the propVal properties with this code:

As you can see, the propVal property has a lot of child properties. For Condition Formats, we are interested in the osval property. Make sure your conditional text is still selected and run this code:

You should see a dialog box like this:

osval

The osval property is an array-like list of the CondFmt objects that are applied to the text. If there is no CondFmts applied, this osval list will be empty. Because this list is similar to an array, we can loop through it to look at the individual CondFmt objects.

When you run this code, you should see the name of each CondFmt displayed in a dialog box. You can probably see what we need to do here. Assuming that we want to remove Condition2 from the text, we need to figure out how to remove the Condition2 CondFmt object from the osval list. Once we do that, we can take the modified PropVal and apply it back to the text, which will remove the unwanted Condition Format. We will explore that in Part 2.

Run a Script in Response to a Command

Here is a question from the Adobe FrameMaker ExtendScript forum: “I wonder if there is there a way to make [a] script event-driven, so that it will be triggered after importing formats from another file?” This post describes how to do this with an ExtendScript script.

First, some terminology: When something happens in FrameMaker, it is called an event. You want your script to listen for one or more events to occur. In ExtendScript, you want to tell FrameMaker to notify to you when one or more events has occurred.

You can set this up in your script with a couple of functions. You first have to tell the script which events you want it to listen for.

Here, we are telling FrameMaker to notify us after a book is updated. This is sometimes referred to as registering the event. You can register as many events as you want in this function.

NOTE: There are no specific notifications when importing formats, but we will show you how to handle this shortly.

You will call this function at the beginning of your script.

Now you need a function that will listen for the notifications (or events) that you specified. FrameMaker ExtendScript has a built-in function for this:

The Notify function will receive four arguments:

  • note: This is the event that was triggered. We usually use a JavaScript switch statement to test for the event, because we may want to test for more than one event.
  • object: This is the relevant object that was in play when the event occurred. In the above example, this will be the Book object of the book that was updated.
  • sparam: This will be a relevant string value or null, depending on the event.
  • iparam: This will be a relevant integer value or null, depending on the event.

Once you receive the correct notification in the Notify function, you can call the appropriate code in your script.

Let’s go back to the forum question. FrameMaker ExtendScript has a bunch of built-in notifications, many of them in a “Pre” and “Post” format. For example, FA_Note_PreGenerate is triggered just before a book is updated and FA_Note_PostGenerate happens just after a book is updated. However, there are no specific notifications that occur before or after importing formats. To handle this, we can use the generic FA_Note_PostFunction notification, which gets triggered after nearly every FrameMaker event. When this notification calls the Notify function, the iparam value will be the unique number of the function. To find the number for a particular command, run the following code, then run the desired command in FrameMaker.

After running the script and importing formats, you should see the number 790 displayed in the alert dialog box. You probably saw a lot of other numbers if you performed other FrameMaker operations before importing formats! Now that you have the number you need, uninstall the script above or quit and restart FrameMaker.

Now we can go back and edit the Notify function that we added to our script. We will test for the Import Formats command by using the command number.

There are a couple of things to note about this particular example. If you cancel the Import Formats dialog box, the event is still triggered and the Notify event is called. If you go ahead with the Import Formats command, the Notify event is actually called twice. At this point, I am not sure how to work around this. In any case, always test Notify event carefully to ensure that your code gets called exactly when it is needed.

Naming Variables and Functions in Scripts

A friend recently asked me about how I name variables in my FrameScript scripts. I used to use a “v” prefix on all of my variables names. For example, for a paragraph I would use:

The “v” prefix would simply indicate that I was setting a variable. This is the convention I used in my 2003 FrameScript book FrameScript: A Crash Course. Later, I decided to make the prefixes reflect the data type that the variable represents. I replaced the “v” with another letter indicating the data type. For example,

The “o” prefix means “object” since both lines set variables for FrameMaker objects. I use “s” for strings, “i” for integers, “r” for real numbers, “m” for metric values, etc. These prefixes help me see at a glance what kind of data type the variable represents. This is important when I am looking at a script that I may not have worked on in awhile. Prefixes like this are also helpful when you are reusing functions. For example, here is a function that applies a named paragraph format to a paragraph.

Even if I don’t know exactly how this function works, I can see at a glance that it takes three parameters: a paragraph object (oPgf), a string indicating the paragraph format name (sName), and a document object (oDoc). The prefixes help me to quickly see the data types of each parameter.

As far as the variable name itself, I try to use the object name that the variable represents. In the ApplyPgfFmt function, I use the Get Object command to get a PgfFmt object. So, I use oPgfFmt as the variable name. Using this convention lets me quickly see that oPgfFmt represents a PgfFmt object. The sName parameter gets matched up with Name in the Get Object command and so on. I find that this method helped me memorize the FrameMaker object model because I have closely associated my variable names with the built-in FrameMaker object names.

When naming functions, the common sense approach is to use some kind of a verb form that describes what the function does. When you look at the function name and its parameters, it should be evident what the function does without looking at the actual function code.

With ExtendScript, I do things a little bit different. I don’t use the prefixes, I simply use the object name but with a lowercase first letter. Here is the example I showed earlier:

ExtendScript (JavaScript) is a case sensitive language, so pgf is different from Pgf. For function names (and longer variable names) I use the “camel case” convention. I don’t use the datatype prefixes, mainly because this is not a normal convention for JavaScript programmers. I suppose it is a bit vain, but I don’t want my ExtendScript code to look too out-of-the-ordinary. Here is the ExtendScript version of the ApplyPgfFmt function:

Beginning Scripting Webinar – Functions – January 22, 2013

Thank you very much to all who attended the webinar. Download or view the webinar slides. FrameScript users download this PDF.

You can download the ExtendScript sample scripts here.

For more information on the Unstructured FrameMaker 11 book, go to http://www.scriptorium.com/books/unstructured-framemaker-11/.

For the webinar replay in wmv format, click here.

Beginning FrameMaker Scripting Webinar – January 15, 2013

Thank you very much to all who attended the webinar. Download or view the webinar slides.

Download the webinar code in both ExtendScript and FrameScript versions. Download the sample FrameMaker document that we used during the webinar.

Download the FrameMaker 10 Object Reference chm file. The zip archive includes a screenshot that shows you how to disable the security warning.

For more information on the Unstructured FrameMaker 11 book, go to http://www.scriptorium.com/books/unstructured-framemaker-11/.

For the webinar replay in wmv format, click here.