|
|
|
 |
Using a macro to replace text where ever it appears in a document including
Headers, Footers, Textboxes, etc.
|
Article contributed by Doug Robbins
When the Find or Replace utility on the Edit menu is used, it will find or
replace text no matter where it appears in the document. If you record
that action however, it will only act on the text in the body of the document
and it will have no effect on text that is in the headers or footers of the
document, for example, or in a textbox, footnotes, or any other area that is
outside the main body of the document.
This is well worth emailing mswish@microsoft.com
about.
To use a macro to find or replace text no matter where it is located in the
document, it is necessary to loop through each of the StoryRanges in the
document.
There are 11 different types of stories that can be part of a document,
corresponding to the following WdStoryType constants:
wdCommentsStory, wdEndnotesStory, wdEvenPagesFooterStory,
wdEvenPagesHeaderStory, wdFirstPageFooterStory, wdFirstPageHeaderStory,
wdFootnotesStory, wdMainTextStory, wdPrimaryFooterStory, wdPrimaryHeaderStory,
and wdTextFrameStory.
The following code will loop through the first story for each story type in
the document, replacing all instances of the text that is to be replaced in the first
story of each type:
Sub FindAndReplaceFirstStoryOfEachType()
Dim myStoryRange As Range
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "findme"
.Replacement.Text = ""
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
End Sub
(Note for those already familiar with VBA: whereas if you use
Selection.Find, you have to specify all of the Find and Replace parameters,
such as .Forward = True, because the settings are otherwise taken from the Find
and Replace dialog's current settings, which are sticky, this is not necessary if using
[Range].Find where the parameters use their default values if you don't
specify their values in your code).
As mentioned previously, the above code will only act upon the first
story for each story type in the document. (The first Header, the first Text
Box, and so on). If your document contains sections with un-linked headers and
footers in them, or, for example, contains more than one Text Box, the code
will not act upon the second and subsequent occurrences of each type of story.
So to make sure that the code does act on each occurrence of the text, no
matter where it appears, you have to make use of the NextStoryRange method as
in the following code:
Sub FindAndReplaceAllStoriesHopefully()
Dim myStoryRange As Range
For Each myStoryRange In
ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "findme"
.Replacement.Text = ""
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Do While Not (myStoryRange.NextStoryRange
Is Nothing)
Set
myStoryRange = myStoryRange.NextStoryRange
With myStoryRange.Find
.Text =
"findme"
.Replacement.Text = ""
.Wrap =
wdFindContinue
.Execute
Replace:=wdReplaceAll
End With
Loop
Next myStoryRange
Problems with cycling through StoryRanges, and some workarounds
Some Headers can get missed out
Unfortunately, even this method doesn't work reliably if you have any blank
Headers or Footers in your document. If, for example, you have a document in
which the first section has a blank primary Header in the first section (such
as might be the case for a report cover sheet), then none of the primary
Headers in the subsequent sections will be checked! Another thing that is well
worth emailing mswish@microsoft.com
about.
One workaround for this is to design all your templates such that none of
the Headers are blank insert a space in any blank Headers. If doing so
alters the layout of that section, apply a different style in that Header, so
that it doesn't. There is really no other satisfactory workaround, for reasons
that will be discussed in a separate article shortly.
Speed of the macro
Another problem with the above code is that it can be very slow if the document
is large, or if it contains a large number of story ranges. This is especially
a problem in Word 97. In Word 97, using Selection.Find is much faster than
using [Range].Find. So you can speed up the above code up very significantly,
in Word 97, by using the following instead:
Sub FasterFindAndReplaceAllStoriesHopefully()
Dim myStoryRange As Range
'First search the main document using the Selection
With Selection.Find
.Text = "findme"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute Replace:=wdReplaceAll
End With
'Now search all other stories using Ranges
For Each myStoryRange In
ActiveDocument.StoryRanges
If myStoryRange.StoryType <>
wdMainTextStory Then
With myStoryRange.Find
.Text =
"findme"
.Replacement.Text = ""
.Wrap =
wdFindContinue
.Execute
Replace:=wdReplaceAll
End With
Do While Not (myStoryRange.NextStoryRange
Is Nothing)
Set
myStoryRange = myStoryRange.NextStoryRange
With
myStoryRange.Find
.Text = "findme"
.Replacement.Text = ""
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End
With
Loop
End If
Next myStoryRange
And the fastest, but flakiest, workaround ...
If you want your code to be as fast as doing a replace via the user
interface, and/or if you want it to be completely immune to the headers get missed out if the first
header is blank bug, the only solution is to call
Word's Find and Replace dialog directly, by executing the menu button (using
Word's Dialogs collection doesn't help); and using the SendKeys command to
execute it. You can set the dialog's settings the way you want them by using
the a With Selection.Find ... End With construct, with no .Execute command,
before you execute the menu button. You would need to include DoEvents in your
code to allow the command to complete it's search.
But using SendKeys is notoriously flaky, and especially so in this case,
because the Find and Replace dialog is modeless; so this is usually not
a good method. It is mentioned here for the sake of completeness.
|