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):

#target framemaker

var doc = app.ActiveDoc;

// Store the current ShowAll setting and show all conditions.
var showAll = doc.ShowAll;
doc.ShowAll = 1;

// Get the CondFmt object and delete it (and any text and rows) from the document.
var condFmt = doc.GetNamedCondFmt ("Comment");
if (condFmt.ObjectValid () === 1) {
    condFmt.Delete ();
}

// Restore the document's ShowAll setting.
doc.ShowAll = showAll;

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:

#target framemaker

var doc = app.ActiveDoc;

var textRange = doc.TextSelection;
var prop = doc.GetTextPropVal (textRange.beg, Constants.FP_InCond);
alert (prop);

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.

#target framemaker

var doc = app.ActiveDoc;

var textRange = doc.TextSelection;
var prop = doc.GetTextPropVal (textRange.beg, Constants.FP_InCond);
// Use the ExtendScript reflection interface to see the properties.
alert (prop.reflect.properties);

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:

#target framemaker

var doc = app.ActiveDoc;

var textRange = doc.TextSelection;
var prop = doc.GetTextPropVal (textRange.beg, Constants.FP_InCond);
// Use the ExtendScript reflection interface to see the properties.
alert (prop.propVal.reflect.properties);

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:

#target framemaker

var doc = app.ActiveDoc;

var textRange = doc.TextSelection;
var prop = doc.GetTextPropVal (textRange.beg, Constants.FP_InCond);
alert (prop.propVal.osval);

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.

#target framemaker

var doc = app.ActiveDoc;

var textRange = doc.TextSelection;
var prop = doc.GetTextPropVal (textRange.beg, Constants.FP_InCond);

var condFmt;
for (var i = 0; i < prop.propVal.osval.length; i += 1) {
    condFmt = prop.propVal.osval[i];
    alert (condFmt.Name);
}

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.

function setupNotifications () {

  // Set up a notification for after a book is updated.
  Notification (Constants.FA_Note_PostGenerate, true);
}

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.

setupNotifications ();

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:

function Notify (note, object, sparam, iparam) {

  // Test for the event type.
  switch (note) {
    case Constants.FA_Note_PostGenerate :
    // Do something here (respond to the event).
    break;
  }
}

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.

#target framemaker

setupNotifications ();

function setupNotifications () {
    
    // Set the notification for after a book is updated.
    Notification(Constants.FA_Note_PostFunction , true);

}

function Notify (note, object, sparam, iparam) {
    
    // Handle the generate book event.
    switch (note) {
        case Constants.FA_Note_PostFunction :
        // Display the command number.
        alert (iparam);
        break;
    }
}

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.

function Notify (note, object, sparam, iparam) {

  // Test for the event type.
  switch (note) {
    case Constants.FA_Note_PostFunction :
      if (iparam === 790) { // Formats were imported.
        // Do something here.
      }
    break;
  }
}

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:

Set vDoc = ActiveDoc;
Set vPgf = vDoc.MainFlowInDoc.FirstPgfInFlow;

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,

Set oDoc = ActiveDoc;
Set oPgf = oDoc.MainFlowInDoc.FirstPgfInFlow;

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.

Function ApplyPgfFmt oPgf sName oDoc
//
Local oPgfFmt(0);

Get Object Type(PgfFmt) Name(sName) DocObject(oDoc) NewVar(oPgfFmt);
If oPgfFmt
  Set oPgf.Properties = oPgfFmt.Properties;
Else
  Set oPgf.Name = sName;
EndIf
//
EndFunc //-------------------------------------------

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:

var doc = ActiveDoc;
var pgf = doc.MainFlowInDoc.FirstTextFrameInFlow.FirstPgf;

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:

function applyPgfFmt(pgf,name,doc) {

  var pgfFmt = 0, props = 0;

  pgfFmt = doc.GetNamedPgfFmt(name);
  if (pgfFmt.ObjectValid()) {
    props = pgfFmt.GetProps();
    pgf.SetProps(props);
  }
  else {
    pgf.Name = name;
  }
}

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:

[BookBuildOverrides]
...
RunScript=1
ScriptName=fsl
ScriptArgs=myEventScript

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:

// InitialScript.fsl Version 0.1b August 26, 2013

Local sPath('');

// Get the path to this script.
Set sPath = eSys.ExtractPathName{ThisScript};

// Install the event script that will receive the DITA-FMx call.
Install Script File(sPath+'Script1.fsl') Name('myEventScript');

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:

// Script1.fsl Version 0.1b August 26, 2013

Event NoteClientCall
//	
If ActiveBook
  Set iResult = ProcessBook{ActiveBook};
EndIf
//
EndEvent

Function ProcessBook oBook
//
//... Do something with the book here ...
//
EndFunc

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.

Bypass: Part 1

This post is for those that may be interested in the events of the last couple of weeks. Thank you very much for all of the prayers, love, and words of encouragement. I thank God for such good friends and an amazing family.

A little background: I am 53 years old and have been fairly active over my lifetime. I was an avid cyclist in my late teens and early twenties. Over the years, I have struggled a bit with my weight, fluctuating from my wedding day weight of 155 (in 1984) to 198 when my 8th son was born (in 2002). Shortly after I hit my high, I lost 40 pounds and eventually settled in the low 160s (I am 5′ 6″).

In early 2010, I bought a nice bicycle and started riding again. I really enjoy aerobic exercise and there are some great roads here in Orleans County (New York). My main riding partner is my 14-year-old son Jason. That first year, I rode 2,300 miles on the bike and finished with a 108 mile ride the length of NYS Route 19. I hadn’t been in such good shape in over 25 years. The next year, I rode 1,900 miles on the bike.

I have struggled to maintain fitness during the winters. You stop riding, but the cycling appetite doesn’t stop right away, so it’s easy to keep eating. This past winter my weight crept up to a high of 188. But we had big plans for 2012: I was to help lead a bunch of young men on a 380 mile ride across NYS along the Erie Canal. We had a great spring here, so by June 14, two days before the trip, I had 794 miles on the bike and had cut back to 176 lbs.

A week or so before the trip, things got a bit interesting. In spite of being in good cycling shape, I started having some lousy rides where I just didn’t feel right. To put things in perspective, here is an entry from my cycling log, one month previous:

5/17/2012 39 miles. Rode with Jason and Jonathan to Batavia library and back. Had a 16.1 mph pace to beat the darkness. Ate up the hills on NY 98. Felt terrific. Rated 9/10.

I was still having rides like this in early June:

6/6/2012 6:30 PM 23 miles. Rode solo to church via Barre, Clarendon, and Byron. Beautiful ride on a beautiful evening. Saw a nice rainbow.

Look at the contrast just 3 days later:

6/9/2012 8:26 PM 16 miles. Rode with Timothy to Albion then to Morriseys. Met Bill’s sister Joan. Suffered through the whole ride chasing Timothy. If all rides were like this, I wouldn’t be a cyclist.

6/11/2012 8:53 PM 16 miles. Rode with Jason west on 31 to Phipps Road to Eagle Harbor to 31 to Allen Road. Then rode around Albion and back home via Butts Road and 31. Felt lousy with tightness in chest for much of the way.

By now, I am sure you can see the warning signs for some real problems. I told my wife Sherry how I was feeling on the bike and she scheduled an appointment with my primary care physician for Wednesday. I had a normal EKG so she scheduled me for a stress-test on Friday, the day before the trip. I really wanted to go on the trip, so I wanted a least one more ride to see if the others were a fluke. Here is my log entry for my last ride that Thursday evening:

6/14/2012 8:00 PM 21 miles. Rode from Bike Zone with Joe Lodice and Jason. Felt lousy with chest discomfort and fatigue. Probably the worse ride of the year. I am going for my stress test tomorrow :-(.

Sure enough, the stress test on Friday showed an 80% chance of blockage in at least one of my coronary arteries. So I was officially grounded. But thankfully, my two sons and the rest of the group successfully finished the ride on Saturday, June 23rd. (If you are interested in learning about the trip, take a look at bike300.wordpress.com.)

Lord willing, I am going to add one or two more posts regarding what happened next. But I want to finish this post with a bit of advice. The cardiologist told me that had I been sedentary, this problem may not have surfaced for 10 or more years, and that I may have just dropped dead from a heart attack. It was the aerobic exercise that manifested the problem early. If you are mainly sedentary or don’t often stress your heart during exercise, I would strongly suggest that you have a stress test. It was really not bad at all as far as the process and it is apparently a good diagnostic tool.

Let me say it one more time. Perhaps your weight is under control, your cholesterol is good (like mine is), and you “work out” or exercise regularly. But if your heart-rate is never elevated for sustained periods of time, a heart problem may not show up. Get a stress test.