Word Hacks [Electronic resources]

Andrew Savikas

نسخه متنی -صفحه : 162/ 68
نمايش فراداده

Hack 43 Cross-Reference Automatically

Using the Cross-reference dialog to insert references, particularly in a lengthy document, can be frustrating because it shows you only a few headings at a time. This hack shows you how to create references automatically, without a visit to the dialog.

Whoever decided that the Cross-reference dialog in Word (InsertReferenceCross-reference or InsertCross-reference, depending on your version of Word) should display only nine items at a time clearly didn't have your best interests in mind. Most lengthy documents include more than nine headings, captions, or other items to reference.

In many cases, creating a cross-reference means converting static text into a dynamic reference by selecting it, then replacing it with the corresponding item from the Cross-reference dialog (as shown in Figure 4-27). But since there's that nine-item limit, you're in for some serious scrolling if you need to make many references. In a Sisyphean spiral, the longer your document is, the more references you likely need, and the longer it will take to find each item in that teeny, tiny list.

Figure 4-27. Only nine items at a time are visible in the Cross-reference dialog

This hack shows you two ways to use VBA to automatically create cross-references to headings. In each case, the selected text is compared to the headings in a document, trying to find a match and create a cross-reference.

4.18.1 Referencing the Way Word Does

The procedure shown in this section uses Word VBA's GetCrossReferenceItems method, which returns a list of potential reference targets in a document. Because Word continually updates and indexes this list, accessing it is very fast. This code runs significantly faster than the code in the next section, but that speed comes at a price: you're limited to creating cross-references to items that Word considers potential targets, such as headings that use one of the built-in heading styles. If you've also got a different kind of heading style in your document, such as SidebarTitle, those headings don't "count" as possible reference targets.

Place this macro in the template of your choice [Hack #50] and either run it from the ToolsMacroMacros dialog or put a button for it on a menu or toolbar [Hack #1].

If your current selection includes more than one paragraph, the macro exits without taking any action.

Sub InsertAutoXRef( )
Dim sel As Selection
Dim doc As Document
Dim vHeadings As Variant
Dim v As Variant
Dim i As Integer
Set sel = Selection
Set doc = Selection.Document
' Exit if selection includes multiple paragraphs
If sel.Range.Paragraphs.Count <> 1 Then Exit Sub
' Collapse selection if there are spaces or paragraph
' marks on either end
sel.MoveStartWhile cset:=(Chr$(32) & Chr$(13)), Count:=sel.Characters.Count
sel.MoveEndWhile cset:=(Chr$(32) & Chr$(13)), Count:=-sel.Characters.Count
vHeadings = doc.GetCrossReferenceItems(wdRefTypeHeading)
i = 1
For Each v In vHeadings
If Trim (sel.Range.Text) = Trim (v) Then
sel.InsertCrossReference _
referencetype:=wdRefTypeHeading, _
referencekind:=wdContentText, _
referenceitem:=i
Exit Sub
End If
i = i + 1
Next v
MsgBox "Couldn't match: " & sel.Range.Text
End Sub

There are two important limitations to note about this code. First, if multiple headings match the selected text, the code creates a reference to the first match and ignores subsequent matches. This limitation is a problem if you have multiple headings with the same text, such as "The Code," used throughout this book.

Second, the code offers no protection against creating a self-reference. If the match found by the code is the text you've selected, the reference that's created will replace the text it's supposed to reference, resulting in a broken reference, as shown in Figure 4-28.

Figure 4-28. Self-referencing creates a broken reference

This kind of inadvertent text deletion is also possible when creating references from the Word Cross-reference dialog. When you create a self-reference, Word displays a message telling you the reference is emptybut only after the text has been deleted.

4.18.2 A Better Way to Reference

This method won't match the speed offered by the code shown above, but its flexibility makes it a better starting point for hacking your own solutions.

Rather than looking only at paragraphs that use one of Word's built-in heading styles, this code examines every paragraph in the document, looking for a match to the selected text. That means you can easily create a reference to a heading that uses a custom paragraph style, such as SidebarTitle.

Unlike the code in the previous section, this procedure also checks to be sure the match it's found isn't the selected text, avoiding the possibility of a self-reference.

This code is divided into five separate procedures: the MakeAutoXRef procedure and four supporting procedures, each of which performs an operation needed to create the reference. Place all five procedures in the template of your choice [Hack #50] and run the one named MakeAutoXRef to create a reference in place of the selected text.

The first procedure, named MakeAutoXRef, is shown first. In conjunction with the supporting procedures shown afterward, it examines each paragraph in the document. If it finds one that matches the selected text, it creates a bookmark around the match and then replaces the selected text with a reference pointing to the bookmark. If the matched paragraph has already been referenced elsewhere, the existing bookmark is used.

Sub MakeAutoXRef( )
Dim sel As Selection
Dim rng As Range
Dim para As Paragraph
Dim doc As Document
Dim sBookmarkName As String
Dim sSelectionText As String
Dim lSelectedParaIndex As Long
Set sel = Selection
Set doc = sel.Document
If sel.Range.Paragraphs.Count <> 1 Then Exit Sub
lSelectedParaIndex = GetParaIndex(sel.Range.Paragraphs.First)
sel.MoveStartWhile cset:=(Chr$(32) & Chr$(13)), Count:=sel.Characters.Count
sel.MoveEndWhile cset:=(Chr$(32) & Chr$(13)), Count:=-sel.Characters.Count
sSelectionText = sel.Text
For Each para In doc.Paragraphs
Set rng = para.Range
rng.MoveStartWhile cset:=(Chr$(32) & Chr$(13)), _
Count:=rng.Characters.Count
rng.MoveEndWhile cset:=(Chr$(32) & Chr$(13)), _
Count:=-rng.Characters.Count
If rng.Text = sSelectionText Then
If Not GetParaIndex(para) = lSelectedParaIndex Then
sBookmarkName = GetOrSetXRefBookmark(para)
If Len(sBookmarkName) = 0 Then
MsgBox "Couldn't get or set bookmark"
Exit Sub
End If
sel.InsertCrossReference _
referencekind:=wdContentText, _
referenceitem:=doc.Bookmarks(sBookmarkName), _
referencetype:=wdRefTypeBookmark, _
insertashyperlink:=True
Exit Sub
Else
MsgBox "Can't self reference!"
End If
End If
Next para
End Sub

The code shown in bold is the part of the procedure that actually creates the reference. Note that it's very similar to part of the code shown in the previous section.

4.18.2.1 The supporting procedures

The following function removes from a string characters that Word won't allow in bookmark names (except for spaces, which are replaced by underscores in a different procedure):

Function RemoveInvalidBookmarkCharsFromString(ByVal str As String) As String
Dim i As Integer
For i = 33 To 255
Select Case i
Case 33 To 47, 58 To 64, 91 To 96, 123 To 255
str = Replace(str, Chr (i), vbNullString)
End Select
Next i
RemoveInvalidBookmarkCharsFromString = str
End Function

The next function takes a string and turns it into a valid bookmark name, including prefacing it with "XREF" for easier identification and adding in a five-digit random number [Hack #68] to ensure that it's unique.

In addition, the function replaces underscores with spaces. So, for example, the heading "Foo the bar" would be converted into something like "XREF56774_Foo_the_bar"a bit easier to work with than the "_Ref45762234"-style names that Word assigns to its own cross-reference bookmarks.

Function ConvertStringRefBookmarkName(ByVal str As String) As String
str = RemoveInvalidBookmarkCharsFromString(str)
str = Replace(str, Chr$(32), "_")
str = "_" & str
str = "XREF" & CStr(Int(90000 * Rnd + 10000)) & str
ConvertStringRefBookmarkName = str
End Function

This next function just determines a paragraph's index in the document (e.g., the second paragraph in the document has an index of 2):

Function GetParagraphIndex(para As Paragraph) As Long
GetParagraphIndex = _
para.Range.Document.Range(0, para.Range.End).Paragraphs.Count
End Function

The final function creates cross-reference bookmarks in paragraphs that do not contain bookmarks and returns the bookmark name for use in the cross-reference. If the paragraph already has a cross-reference bookmark, it simply returns the existing bookmark name for use in the cross-reference.

Function GetOrSetXRefBookmark(para As Paragraph) As String
Dim i As Integer
Dim rng As Range
Dim sBookmarkName As String
If para.Range.Bookmarks.Count <> 0 Then
For i = 1 To para.Range.Bookmarks.Count
If InStr(1, para.Range.Bookmarks(i).Name, "XREF") Then
GetOrSetXRefBookmark = para.Range.Bookmarks(i).Name
Exit Function
End If
Next i
End If
Set rng = para.Range
rng.MoveEnd unit:=wdCharacter, Count:=-1
sBookmarkName = ConvertStringRefBookmarkName(rng.Text)
para.Range.Document.Bookmarks.Add _
Name:=sBookmarkName, _
Range:=rng
GetOrSetXRefBookmark = sBookmarkName
End Function

4.18.2.2 Running the hack

This hack wouldn't be much of a time-saver if you had to go through a menu to run it. This code is most helpful when assigned to a keyboard shortcut.

To assign a macro to a keyboard shortcut, select ToolsCustomize and click the Keyboard button. Save your changes in the same template in which you installed the code. In the Categories column, select Macros, and in the Commands column, select MakeAutoXRef. Choose and assign a keyboard shortcut using the dialog.