Excerpt taken from Learning Word Programming by Steven Roman, published by O'Reilly and Associates. ISBN: 1-56592-524-6.
Copyright © 1999 by The Roman Press, Inc. All Rights Reserved. You may view and print this document for your own personal use only. No portion of this document may be sold or incorporated into any other document for any reason.
Searching (and replacing) is one of the most commonly performed operations. The Word object model provides a Find object and a Replacement object for this purpose. The Range object and the Selection object both have a Find property that returns a Find object, which is used to search within the given range or selection (and possibly beyond).
Searching for text (or formatting) amounts to little more than setting various properties of the Find object and then executing the object's Execute method.
Incidentally, you may be wondering why there is a Find object and not simply a Find method. The main reason is that, like the Font object, the Find object retains its settings until they are changed by the programmer and can therefore be used repeatedly. This is especially important when we consider the fact that the Find object has 25 properties. If Find were a method instead of an object, we would potentially need to set all 25 properties each time we made a call to this method!
Here is an example of finding some text using the Find object from a Selection object:
With Selection.Find
.ClearFormatting
.Text = "To be or not to be"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
Note the use of the With construct, which saves some coding when we need to set several properties (or execute methods) for a single object. As we have discussed, the expression
With Object
.Property1 = Value1
.Property2 = Value2
. . .
.Propertyn = Valuen
End With
is equivalent to
Object.Property1 = Value1
Object.Property2 = Value2
. . .
Object.Propertyn = Valuen
The main properties and methods of the Find object are shown in Table 14.1.
Table 14-1.Find Object Members
ClearFormatting |
MatchAllWordForms |
Replacement |
Execute |
MatchCase |
Style |
Font |
MatchSoundsLike |
Text |
Format |
MatchWholeWord |
Wrap |
Forward |
MatchWildcards |
|
Found |
ParagraphFormat |
Note that several of these properties mirror check boxes found in the Find dialog box, as shown in Table 14.2
Table 14-2.Find Properties and the Find Dialog
Find Property |
Check Box in Find Dialog |
MatchCase |
MatchCase |
MatchSoundsLike |
Sounds like |
MatchWholeWord |
Find whole words only |
MatchWildcards |
Use wildcards |
Note also that the ClearFormatting method is equivalent to pressing the No Formatting button on the Find dialog box. It is a good idea to execute this method before searching to avoid limiting the search to formatting that may be left over from a previous search.
It is important to understand that Word initially searches only the given selection when the Find object comes from a Selection object or the given range when the Find object comes from a Range object. The behavior of the search with regard to the rest of the document depends upon the setting of the Wrap property, which is discussed in the following section.
Incidentally, the Word help files do not seem to state the default values for the various properties of the Find object, but some experimenting indicates that the Boolean properties
MatchCase
MatchWholeWord
MatchWildcards
MatchSoundsLike
MatchAllWordForms
default to False, but that the Boolean property Forward defaults to True. (This also makes sense.) Thus, we could omit them (at our own peril) to shorten the syntax quite a bit:
With Selection.Find
.ClearFormatting
.Text = "To be or not to be"
.Forward = True
.Wrap = wdFindContinue
End With
Selection.Find.Execute
The Wrap Property
The Wrap property can be one of the following WdFindWrap constants:
wdFindAsk
After searching the selection or range, Word will display a message asking whether to search the remainder of the document.
wdFindContinue
The find operation continues when either the beginning (Forward = False) or end (Forward = True) of the search range or selection is reached.
WdFindStop
The find operation ends when the beginning or end of the search range or selection is reached.
The Found Property
After a search, Word will set the Found property of the Find object to reflect the success or failure of the search. This is a Boolean property, so it will be set to either True or False. As an example, the following code acts on the results of the search
With Selection.Find
.ClearFormatting
.Text = "find text"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
If Selection.Find.Found then
MsgBox "Text found"
Else
MsgBox "Text not found"
End
Consequences of a Successful Search
It is important to understand that a successful search will have an effect on the current selection or the range object. If a search is successful, then the consequences depend upon whether the Find object comes from a Range object or a Selection object. (If the search is not successful, then nothing happens.)
If the Find object comes from a Selection object, then the found text is selected. Hence, the original selection is no longer selected. If the Find object comes from a Range object, as shown in the following example:
Dim rng As Range
' Search entire document
Set rng = ActiveDocument.Content
With rng.Find
.ClearFormatting
.Text = "Find text"
.Forward = True
.Wrap = wdFindStop
.Execute
End With
rng.Select
then the range is redefined to include just the newly found text. Thus, the original range is lost.
The following example illustrates how to search for formatting (or formatted text). In this case, we search for any italicized text that is centered. As you can see, this is done by setting the corresponding formatting properties of the Find object (Font and ParagraphFormat) and then setting the Format property of the Find object to True. We must also not forget to clear the formatting first, so that only our formatting will be in effect. (By setting the Text property of the Find object to the empty string, all text is found.)
Selection.Find.ClearFormatting
Selection.Find.Font.Italic = True
Selection.Find.ParagraphFormat.Alignment = _
wdAlignParagraphCenter
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = ""
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
The following example searches for a particular style, in this case the Heading 1 style:
Selection.Find.ClearFormatting
Selection.Find.Style = _
ActiveDocument.Styles("Heading 1")
With Selection.Find
.Text = ""
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
Performing a search and replace is not much more complicated than performing a simple search. The Find object has a Replacement child object, accessible through the Replacement property. We just set the properties of the Replacement object, including any text or formatting that we want to use for the replacement. Then we run the Execute method of the Find object, using the named parameter Replace, which can take on one of three values: wdReplaceAll, wdReplaceNone, or wdReplaceOne.
Here is an example that replaces each occurrence of the word "find" with the word "replace". Note that we must clear the formatting of both the Find and the Replacement objects.
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "find"
.Replacement.Text = "replace"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
The Execute method of the Find object actually has a number of parameters that allow an alternative syntax for doing a search and replace. The full syntax of the Execute method is:
FindObject.Execute(FindText, MatchCase, MatchWholeWord, _
MatchWildcards, MatchSoundsLike, MatchAllWordForms, _
Forward, Wrap, Format, ReplaceWith, Replace)
For instance, the previous search and replace could have been coded as follows:
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
Selection.Find.Execute _
FindText:="find", _
ReplaceWith:="replace", _
Forward:=True, _
Wrap:=wdFindContinue, _
MatchCase:=False, _
MatchWholeWord:=False, _
MatchWildcards:=False, _
MatchSoundsLike:=False, _
MatchAllWordForms:=False, _
replace:=wdReplaceAll
This syntax seems to be a bit less readable than the previous one, but you may run across it when reading other code.
Since the range is redefined after a successful search, the process of repeated searching has a slight complication. The problem is that we cannot simply define a Range object called rng to denote the range to search and then use the Find object of rng, because a successful search would then change the search range for the next search!
Perhaps it would have been simpler if Microsoft had included a ResultRange object that represented the range of the successful search and left the original search range alone. The ResultRange object could have the Found property, which would be queried to determine whether or not the search was successful. In any case, in doing a repeated search, we need to save the original search range (especially its end point) and also keep track of where the next search operation should begin.
The macro in Example 14-1 illustrates one approach to repeatedly searching through a portion of a document. To explain the purpose of this macro, let me set the stage. In the manuscript for this book, the chapter titles have the form
Chapter XX -
where XX is a chapter number and - is an en-dash. Also, the titles are formatted with style Heading 1. For quickly navigating through the manuscript, I wanted to add a bookmark of the form CXX in front of each chapter title. For instance, the bookmark for Chapter 12 should be named C12.
Of course, I could do this manually (for about 20 chapters), but the problem is that as the manuscript develops, I may add chapters in the middle, thus changing the chapter numbers. So the best solution is a macro that inserts the bookmarks for me. I can run this whenever the chapter numbers change.
The macro in Example 14-1 also illustrates the use of pattern matching. In particular, the pattern
Chapter?{3,4}^=
search for the word "Chapter" followed by 3 or 4 characters, followed by an en-dash. (I used the Find dialog box to help me determine the correct pattern.)
Example 14-1. A macro to locate chapter titles
Sub AddChapterBookmarks()
' Insert a bookmark in front of all
' chapter titles. Chapter XX gets
' bookmark named CXX.
Dim iChapNum As Integer
Dim sChapNum As String
Dim rngToSrch As Range
Dim rngResult As Range
' Set search ranges
Set rngToSrch = ActiveDocument.Range
Set rngResult = rngToSrch.Duplicate
' Loop to find all chapter titles, which
' have style Heading 1 and form
' "Chapter XX -" where XX is a
' number (1 or 2 digits)
' and - is an en-dash
Do
With rngResult.Find
.ClearFormatting
.Style = "Heading 1"
.Text = "Chapter?{3,4}^="
.Forward = True
.Wrap = wdFindStop
.MatchWildcards = True
.Execute
End With
' Exit loop if not found
If Not rngResult.Find.Found Then Exit Do
' Select the chapter title
rngResult.Select
' Get the chapter number
iChapNum = Val(Mid(Selection.Text, 8))
sChapNum = Format(iChapNum)
' Add bookmark using chapter number
Selection.Collapse wdCollapseStart
Selection.Bookmarks.Add "C" & sChapNum
' Prepare for next search by
' moving the start position over one word
rngResult.MoveStart wdWord
' and extending the end of rngResult
rngResult.End = rngToSrch.End
Loop Until Not rngResult.Find.Found
End Sub
There are a few subtle points in this code, so let us go over them carefully.
First, the code uses two Range objects. The range rngToSrch refers to the search range and does not change. The range rngResult is used to do the searching and thus changes each time there is a successful search. (Actually, if our code inserts new text into the range rngToSrch, then this range will expand to accommodate the new text, so in this case it does change.)
Accordingly, we must first set rngResult to point to a range with the same endpoints as the range pointed to by rngToSrch. Since we need two different range objects with the same start and end points, the appropriate code is
Set rngResult = rngToSrch.Duplicate
and not
Set rngResult = rngToSrch
(We have discussed this issue before.)
The next issue is how to prepare for the second (and subsequent) searches. This is done using the code
' Prepare for next search by
' moving the start position over one word
rngResult.MoveStart wdWord
' and extending the end of rngResult
rngResult.End = rngToSrch.End
The issue here is that after a successful search, rngResult refers only to the word found. We cannot simply restore rngResult to its original range, because then we would just repeat the same search and return the same word. Instead, the new search range should begin one word after rngResult, so as not to return the same word over and over again. Also, the endpoint of the new search range must be restored to its original value, which is the endpoint of the range rngToSrch.
Finally, note that it would not do for us to use a Long variable to capture the search range endpoint, as in
iEnd = rngToSrch.End
and then use this to restore the rngResult endpoint after each search, as in
rngResult.End = iEnd
The reason that this does not work in general is that if our code were to insert or remove some text from the search range, the value iEnd would no longer represent the last character in that search range. (Since we are not changing any text in this example, it will work here.) Put another way, we need to save the search range object itself (in rngToSrch) and not just the character position of the endpoint of this range.