Inserting Cross-References
There are two types of cross-references in FrameMaker: paragraph
cross-references and spot cross-references. (There is a third type, element
cross-references, available only in FrameMaker+SGML. This tutorial will not
discuss element cross-references) Both paragraph and spot cross-references are
similar, in that you have to insert an Xref object that points to a Cross-Ref
Marker. The Xref object has an XRefSrcText property that must exactly match the
marker text of the Cross-Ref Marker. Let’s start with spot cross-references,
since they are a little simpler to work with.
Spot Cross-References
As an example, here is a table that contains the names of maintenance
procedures that occur in a document. We want to use FrameScript to make
cross-references from the entries in the table to the actual procedures in the
document. A sample procedure is shown below the table.


In our example, all of the procedures listed in the table are in the same
document as the table itself. Each procedure title uses a Heading2 paragraph
format.
Adding Cross-Reference Markers
One principle we like to adhere to when solving problems with FrameScript, is
to break down the problem into small tasks. Our first task is to put
cross-reference markers in each procedure heading paragraph. Since we know that
procedures start with Heading2 paragraphs, we will only put markers in those
paragraphs. The marker text will be set to the text of the paragraph. It is
important that each cross-reference marker in the document contains unique text.
We will assume in our example that each Heading2 paragraph contains text that is
unique from all other Heading2 paragraphs in the document.
// Test for an active document.
If ActiveDoc = 0
MsgBox 'No active document. ';
LeaveSub;
Else
Set vCurrentDoc = ActiveDoc;
EndIf
Loop ForEach(Pgf) In(vCurrentDoc) LoopVar(vPgf)
If vPgf.Name = 'Heading2'
New Marker NewVar(vMarker) MarkerName('Cross-Ref')
TextLoc(vPgf);
// Set the marker text to the paragraph text.
Set vMarker.MarkerText = vPgf.Text;
EndIf
EndLoop
Each Heading2 paragraph will have a cross-reference marker at the beginning
of it with the text of the paragraph as the marker text. Here is one of the
Heading2 paragraphs with the marker window showing the marker text.

Because we are creating markers with FrameScript instead of the FrameMaker
interface, let’s take a look at the marker properties. You can run the following
code with the marker anchor selected to see the marker’s properties. Notice that
the MarkerText property is the text that shows in the Marker dialog.
Get TextList InRange(TextSelection) MarkerAnchor
NewVar(vTextList);
If vTextList.Count > 0
Get Member Number(1) From(vTextList) NewVar(vMarker);
Set vMarker = vMarker.TextData;
Display vMarker.Properties;
Else
MsgBox 'There is no marker selected. ';
EndIf

Adding the Cross-References
Now we can go to the table and have a script add cross-references to the
markers (“spots”) we inserted earlier. We will assume that the cursor is in the
table when the following code is run.
// Test for an active document.
If ActiveDoc = 0
MsgBox 'There is no active document. ';
LeaveSub;
Else
Set vCurrentDoc = ActiveDoc;
EndIf
// Set a variable for the current table.
Set vTbl = vCurrentDoc.SelectedTbl;
// Make sure a the cursor is in a table.
If vTbl.ObjectName not= 'Tbl'
MsgBox 'There is no selected table. ';
LeaveSub; // Exit the script.
EndIf
// Find the first body row in the table.
Set vRow = vTbl.FirstRowInTbl;
Loop While(vRow.RowType = RowHeading)
Set vRow = vRow.NextRowInTbl;
EndLoop
// Go to the first cell in the first body row.
Set vCell = vRow.FirstCellInRow;
Now that we are in the first cell, we can begin a loop down through the
cells. For each cell, we need to “select” the text so that the new
cross-reference can replace it. The XRefSrcText property of the XRef
(cross-reference) will be set to the selected text since this will match the
marker text of the corresponding cross-reference marker that was inserted
earlier. To select the text, we will make a text range, delete the text, and
then insert the XRef object in its place.
// Begin the loop.
Loop While(vCell)
// Select the text by making a TextRange.
New TextRange NewVar(vTextRange) Object(vCell.FirstPgf)
Offset(0) Offset(ObjEndOffset-1);
// Set a variable for the TextRange text.
Set vXRefSrcText = vTextRange.Text;
// Delete the text.
Delete Text TextRange(vTextRange);
// Insert the cross-reference.
New XRef Format('Heading & Page') TextLoc(vCell.FirstPgf)
NewVar(vXRef);
Set vXRef.XRefSrcText = vXRefSrcText;
// Go to the next cell and repeat the loop.
Set vCell = vCell.CellBelowInCol;
EndLoop
// Update the cross-references.
Update DocObject(vCurrentDoc) XRefs Everything;
The table now shows the original text replaced with the cross-references.

If you double-click on one of the cross-references, it will open the
Cross-Reference dialog box as shown below.
The key thing to remember is that the XRef.XRefSrcText property must exactly
match the XRef.MarkerText property of the corresponding marker; otherwise, you
will have an unresolved cross-reference. To see the cross-reference properties,
highlight one of the cross-references and run the following code. Notice the
XRefSrcText property.
Get TextList InRange(TextSelection) XRefBegin
NewVar(vTextList);
If vTextList.Count > 0
Get Member Number(1) From(vTextList) NewVar(XRefBegin);
Set XRefBegin = XRefBegin.TextData;
Display XRefBegin.Properties;
Else
MsgBox 'There is no cross-reference selected. ';
EndIf
Cross-References to Other Documents
One other important property to note is the XRefFile property. For the above
cross-reference it is a NULL string because it is an internal cross-reference.
If we were cross-referencing another file, then the XRefFile property would
contain the absolute path to the other file.
Paragraph Cross-References
Before discussing the insertion of paragraph cross-references with
FrameScript, let’s review how they differ from spot cross-references in the
FrameMaker interface. When you insert a spot cross-reference with FrameMaker,
you have to insert a cross-reference marker before you insert the
cross-reference. You have to make a “spot” that will appear in the
Cross-Reference dialog box.
In contrast, paragraph cross-references don’t require the insertion of a
cross-reference marker ahead of time. Instead, you point to the desired
paragraph in the Cross-Reference dialog box, click Insert, and FrameMaker
inserts the cross-reference and the cross-reference marker in the appropriate
places.
Once inserted, spot and paragraph cross-references don’t differ in their
basic underlying properties. All cross-references are accessed as XRef objects
by FrameScript. There is a difference, however, in the way they are presented in
the FrameMaker Cross-Reference dialog box. When you double-click on a spot
cross-reference, the dialog shows Cross-Ref markers under the Source Type and
marker text in the right-hand list. Double-clicking a paragraph cross-reference
shows the Paragraph Tag of the source paragraph under Source Type and the
paragraph text each of the selected Paragraph Tag paragraphs in the right-hand
list. Below is the Cross-Reference dialog box showing a paragraph
cross-reference.

The difference between the cross-reference types comes from a difference in
the syntax of the cross-reference markers. To see this, insert a paragraph
cross-reference, Control-Alt-Click on the cross-reference to go to the source,
and open the marker window. You will see the syntax of the marker that
FrameMaker inserts with a paragraph cross-reference. Below is a sample marker
window followed by the XRef properties.

Like the spot cross-reference markers we inserted earlier, you can see the
text of the source paragraph in the marker window and the XRefSrcText property.
In addition, the paragraph text is preceded by a five-digit number (followed by
a colon and space), and the name of the paragraph format of the source paragraph
(followed by a colon and space). It is these two additional components in the
XRefSrcText property of the XRef that causes FrameMaker to “see” this as a
paragraph cross-reference.
Keeping Cross-Ref Markers Unique
There is another reason for the different syntax between paragraph and spot
cross-references. It is essential that Cross-Ref markers within a document are
unique. While you can have more than one cross-reference pointing to a single
Cross-Ref marker, you can’t have a single cross-reference trying to point to
more than one marker with the same marker text. When you insert spot
cross-references, it is up to you to make sure that each Cross-Ref marker is
unique. When you insert paragraph cross-references, FrameMaker makes sure the
markers it inserts are unique by using the special syntax, particularly the
five-digit serial number on the front.
You may be thinking that the paragraph tag portion of the XRefSrcText helps
FrameMaker “know” what paragraph tag to highlight in the Cross Reference dialog
box when you double-click on the cross-reference. Actually, this isn’t the case.
In the example above, I inserted a cross-reference to a Heading2 paragraph. If I
change it to a Heading1 paragraph, the marker text doesn’t change, but
double-clicking the cross-reference correctly shows that the source paragraph is
now a Heading1.
This is illustrates another important point about cross-references: the
Cross-Ref marker text should never be changed after the markers are inserted.
You could have several cross-references pointing to the same marker; if you
change the marker text, the cross-references would become unresolved. Likewise,
changing the text of the source paragraph will not change the marker text.
Before writing some code to insert paragraph cross-references, here are the
important points to remember about both paragraph and spot cross-references.
- A resolved cross-reference will have an XRefSrcText string value that
exactly matches the MarkerText string value of a Cross-Ref marker.
- Both types of cross-references are functionally the same; they are only
presented differently in the FrameMaker Cross-Reference dialog box.
- Cross-Ref marker text should always be unique within a document for both
types of cross-references.
- The text of a Cross-Ref marker should never be changed, even if the
paragraph tag or text of its paragraph changes.
Adding Paragraph Cross-References
We will use the same example we used with spot cross-references, except this
time we will insert the cross-references and its corresponding marker at the
same time. In our previous example, we inserted all of the markers first and
then the cross-references. We assumed that for every entry in the table, there
would be an exactly matching Heading2 paragraph in the document. This might not
always be a safe assumption and could result in unresolved cross-references.
We will start with our loop through the table. The entire code listing is
below; changes from the previous example are shown in red.
// Test for an active document.
If ActiveDoc = 0
MsgBox 'There is no active document. ';
LeaveSub;
Else
Set vCurrentDoc = ActiveDoc;
EndIf
// Set a variable for the current table.
Set vTbl = vCurrentDoc.SelectedTbl;
// Make sure a the cursor is in a table.
If vTbl.ObjectName not= 'Tbl'
MsgBox 'There is no selected table. ';
LeaveSub; // Exit the script.
EndIf
// Make a property list for the color red.
Get Object Type(Color) Name('Red') DocObject(vCurrentDoc)
NewVar(vColor);
New PropertyList NewVar(vProps) Color(vColor);
// Find the first body row in the table.
Set vRow = vTbl.FirstRowInTbl;
Loop While(vRow.RowType = RowHeading)
Set vRow = vRow.NextRowInTbl;
EndLoop
// Go to the first cell in the first body row.
Set vCell = vRow.FirstCellInRow;
// Begin the loop.
Loop While(vCell)
// Select the text by making a TextRange.
New TextRange NewVar(vTextRange) Object(vCell.FirstPgf)
Offset(0) Offset(ObjEndOffset-1);
// Set a variable for the TextRange text.
Set vXRefSrcText = vTextRange.Text;
// Run a subroutine to find the corresponding
heading.
Set vHeadingFound = 0;
Run FindSourceHeading Returns vMarkerText(vXRefSrcText);
If vHeadingFound = 1
// Delete the text.
Delete Text TextRange(vTextRange);
// Insert the cross-reference.
New XRef Format('Heading & Page') TextLoc(vCell.FirstPgf)
NewVar(vXRef);
Set vXRef.XRefSrcText = vXRefSrcText;
Else
// If the corresponding heading can’t be found, color the
// text red so it stands out.
Apply TextProperties TextRange(vTextRange)
Properties(vProps);
EndIf
// Go to the next cell and repeat the loop.
Set vCell = vCell.CellBelowInCol;
EndLoop
// Update the cross-references.
Update DocObject(vCurrentDoc) XRefs Everything;
For each entry in the table, we are going to run a subroutine called
FindSourceHeading that attempts to locate the corresponding heading in the
document. If it doesn’t find a corresponding head with the same text, it will
apply the color red to the text in the table cell so you can easily know that
there is a problem. Here is the code listing for the subroutine.
Sub FindSourceHeading
//
Loop ForEach(Pgf) In(vCurrentDoc) LoopVar(vPgf)
If vPgf.Name = 'Heading2'
// See if the paragraph text is the same as the table
// cell text.
If vPgf.Text = vXRefSrcText
// Make marker text with paragraph cross-ref syntax.
// Get the unique Id of the paragraph and convert it to
// a string.
New String NewVar(vMarkerText) Value(vPgf.Unique);
// Drop the first character so we end up with 5 digits.
Get String FromString(vMarkerText) NewVar(vMarkerText)
StartPos(vMarkerText.Size - 4);
// Add the paragraph tag and text.
Set vMarkerText = vMarkerText + ': ' + vPgf.Name + ': ' +
vPgf.Text;
// Add the cross-reference marker.
New Marker NewVar(vMarker) MarkerName('Cross-Ref')
TextLoc(vPgf);
// Set the marker text to the paragraph text.
Set vMarker.MarkerText = vMarkerText;
// Set the vHeadingFound variable to 1.
Set vHeadingFound = 1;
// Leave the subroutine.
LeaveSub;
EndIf
EndIf
EndLoop
//
EndSub
Notice that this code similar to the earlier script that inserted the spot
cross-reference markers. It is a simple loop through the document’s paragraphs
looking for Heading2 paragraphs. It has an addition test to see if the
paragraph’s text matches the text of the table cell.
Making the Marker Text
Once a match is made between the table cell text and a heading the script
builds the string that will become the MarkerText of the marker and the
XRefSrcText of the cross-reference. We need a unique five-digit number at the
beginning of the string. Almost every object in FrameMaker has a Unique
property, which is an integer serial number that is unique for that kind of
object. If you look at the earlier screen shots of cross-references, you can see
that they have a Unique property also. Type the following code in the Script
Window, put your text cursor in a paragraph, and click the Run button.
Set vPgf = TextSelection.Begin.Object;
Display vPgf.Unique;
The Unique property is usually six digits long, but never less than five
digits. The following code from the subroutine converts the number to a string
and drops the first character. This code will still work if the Unique property
is five digits long.
New String NewVar(vMarkerText) Value(vPgf.Unique);
// Drop the first character so we end up with 5 digits.
Get String FromString(vMarkerText) NewVar(vMarkerText)
StartPos(vMarkerText.Size - 4);
Note that dropping the first digit introduces the unlikely possibility of
making the number the same as another paragraph’s number. Even if that were to
occur, any difference in the paragraph text would still make the marker text
unique. The next line adds the paragraph’s tag and text to the number with
colons and spaces in the correct locations.
// Add the paragraph tag and text.
Set vMarkerText = vMarkerText + ': ' + vPgf.Name + ': ' +
vPgf.Text;
The subroutine finishes by inserting a cross-reference marker and setting the
vHeadingFound variable to 1. This variable notifies the code after the
subroutine call that the correct heading was found and the Cross-Ref marker was
inserted. Notice the LeaveSub command; once the correct heading is found, there
is no reason to continue looping through the rest of the paragraphs.
The block of code after the subroutine call checks to see if the heading was
found. If it was, the text is deleted and the cross-reference is inserted in its
place.
Set vHeadingFound = 0;
Run FindSourceHeading Returns vMarkerText(vXRefSrcText);
If vHeadingFound = 1
// Delete the text.
Delete Text TextRange(vTextRange);
// Insert the cross-reference.
New XRef Format('Heading & Page') TextLoc(vCell.FirstPgf)
NewVar(vXRef);
Set vXRef.XRefSrcText = vXRefSrcText;
If a matching heading was not found, the value of vHeadingFound remains 0,
and the Else block is executed. The purpose of this is to color the table cell
text red so you can readily see that a corresponding heading wasn’t found.
Else
// If the corresponding heading can’t be found, color the
// text red so it stands out.
Apply TextProperties TextRange(vTextRange)
Properties(vProps);
EndIf
Below is a screen shot of our sample procedure after the script was run. One
of the heads was changed so you can see what it looks like when a head is not
found.

Double-clicking on one of the cross-references shows that it is recognized as
a paragraph cross-reference by FrameMaker.
|