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.

Regular Expressions in FrameMaker

FrameMaker 12 adds regular expressions support in the Find/Change panel. To use it, select the Regular Expressions radio button as shown in the screenshot.

FindChangeFM12

I had the privilege of presenting an Adobe webinar on regular expressions on November 20, 2014. The webinar was well-attended and nearly all attendees stayed until the end. If you attended, I urge you to take what you learned and go further so you confidently use regular expressions in FrameMaker.

If you have any questions from the webinar, or if you didn’t attend and have questions about regular expressions in FrameMaker, please post them in the Comments section below. It would be great to have some “Find/Change” challenges that we could attempt to solve with regular expressions. I look forward to hearing from you!

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:

Running a FrameScript Script from DITA-FMx

DITA-FMx is a great plugin for working with DITA in FrameMaker. One of its best features is the ability to create a complete FrameMaker book from a ditamap. In some situations you may want to run a script on the book before creating the PDF. Scott Prentice, the developer of DITA-FMx, has a blog post explaining how you can call an ExtendScript script from DITA-FMx. This article will show you how to call a FrameScript script from DITA-FMx.

To set this up in DITA-FMx, you will need to edit the BookBuildOverrides section of the book-build INI file that you are using with DITA-FMx. Here are the three keys that need to be edited:

RunScript is a 0 or 1 value. Setting it to a 1 tells DITA-FMx that you want run one or more scripts or FDK clients. ScriptName for FrameScript is fsl. The ScriptArgs value is the name of the installed FrameScript “event script” that you want to run.

Before we go further, let me give a little background on FrameScript scripts. FrameScript has two kinds of scripts: Standard scripts and Event scripts. A standard script can consist of functions, subroutines, and events, but it always has an entry point that is not inside of a function, subroutine, etc. Typically, you “run” a Standard script, it loads into memory, runs to completion, then is flushed from memory.

Event scripts are not run directly; they are “installed” first and then “wait” for some kind of event to happen; for example, a menu command, a FrameMaker event, an outside call, etc. All of the code in an event script must be inside of a function, subroutine, or event. The entry point for an event script is some kind of an event inside of the script. One point that is pertinent to this post is that an installed Event script has a name, and this name is the value you use for the ScriptArgs key.

Instead of installing your event script manually, it is best to install it automatically with an “Initial Script”, which runs automatically whenever you start FrameMaker. That way, your event script will installed automatically when you start FrameMaker. Here is an example Initial Script:

This command will install the script “Script1.fsl” that is in the same folder as the Initial Script (InitialScript.fsl). The important parameter on the Install Script command is Name; the name supplied must match the name you give to the ScriptArgs key in DITA-FMx’s book-build INI file. Here we are using “myEventScript”.

To run this script automatically whenever FrameMaker starts, choose FrameScript > Options and click the Browse button next to the Initial Script Name field. Navigate to the InitialScript.fsl file and select it, and then click Save to close the Options dialog box.

FrameScript Options dialog box

Before you quit and restart FrameMaker, you will need to have Script1.fsl in the same folder as the InitialScript.fsl file. Here is a shell you can use for this script:

The NoteClientCall event is a built-in event that “waits” for the outside call; in this case, from DITA-FMx. We test to make sure that there is an active book, which should be the book that DITA-FMx just created from the ditamap. If there is an active book, we call the ProcessBook function, which is where we process our book with FrameScript code. We could have our book code right in the ProcessBook function, or we could use this function to call other scripts or functions.

Please let me know if you have any questions or comments about calling FrameScript scripts with DITA-FMx.

Riding in Memory of Bruce Foster

Bruce Foster, creator of many fine FrameMaker plugins, passed away suddenly on Saturday, June 11, 2011. Although Bruce had been diagnosed with multiple myeloma in September 2010, his death still came earlier than was expected. Bruce’s plugins for FrameMaker were BookInfo, ImpGraph, MifSave, MifToFM, RtfSave, and his most famous, Archive.

My son, Jason, and I will be riding the 2013 Ride for Roswell to benefit cancer research. This year we will ride the 104 mile route. I will be riding in memory of Bruce and Jason will be riding in memory of his grandfather, Dr. Richard J. Quatro. We would appreciate your sponsorship.

If you donate $40 or more to either of us, I will give you a copy of the ExtendScript version of PageLabeler or ImportFormatsSpecial. Both of these have been written in ExtendScript for use in FrameMaker 10 and higher. To see the documentation for these scripts, click the links above. For a $75 donation, you will receive both scripts, including the source code.

If you are using FrameMaker 9 or below, I will send you the plugin versions instead. Be sure to email me to tell me what you would like to receive after donating.

Rick has met his fundraising goal. Please click here to donate to Jason’s ride.

Thank you very much for your support!

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.

Bypass: Surgery

With the bike trip out of the question, we turned our attention to getting my heart fixed. I went for an angiogram on Monday, which was to give the precise location of the blockage. If they find the blockage, and it is a candidate for a stent, they basically leave you on the table and put in the stent. You spend the night in the hospital and go home the next morning. I spoke to a friend that had a stent put in 12 years ago, and he is still doing fine. I was pretty optimistic that this is probably what would happen.

The angiogram showed two bad blockages in areas that couldn’t be opened with a stent. I would definitely need bypass surgery. But the chief heart surgeon at Strong Memorial Hospital had an opening for Wednesday afternoon. This would have to work because he was scheduled for vacation the following week. One of my nurses, a twenty-five year veteran, told me that Dr. Knight is the one you want to do this surgery. I was very blessed that he was available when he was.

After all of the annoying pre-op testing Tuesday, I was admitted Wednesday for surgery scheduled for around 12:45 pm. I was pretty calm because I knew that a lot of people were praying for me and because of my relationship with Jesus Christ. However, I got a little spooked just before the operation and fainted in pre-op while getting an IV line inserted. Then my poor wife got a call from the surgeon just after I went into the operating room. “Your husband’s heart stopped on the operating table, but we got it going. We are going to proceed with the surgery.”

The operation was done in about two and a half hours. Amazingly, Dr. Knight did the procedure “off pump,” which means that my heart was beating as they did the surgery. Normally they hook you up to a machine that circulates your blood while they work on the heart. I was told that he is one of the few doctors that sometimes uses the off pump technique. One of the reasons to do it this way is to speed recovery time because the body does not take on as much fluid.

After spending the night in the coronary intensive care, they sent me to a step-down unit for recovery. Actually, they make you walk. It is amazing how soon they get you out of bed and moving around. They put me in a chair the night of the operation and walked me around the unit 3 separate times.

I had a remarkable experience concerning my roommate in the step-down unit. I could hear some of his visitors talking about the “old neighborhood,” referring to streets in my childhood neighborhood. Gary is 10 years older than me, but he was born and raised in the same neighborhood and went to the same schools, etc. It was amazing to discover common families, restaurants, and shops that we both knew as children, in spite of our age difference. I prayed with him that night and thanked the Lord for putting us together.

I was shocked Friday morning when the surgeon told me I could go home if I wanted to. My wife was horrified and said it was too soon. I agreed with her, but I still felt relief that he was that positive about my recovery so far. That night, though, I got a mixture of homesickness and worry about getting an infection in the hospital. I went home on Saturday afternoon, leaving my new friend Gary behind.