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.

The Find and Replace Objects

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

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


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

wdFindContinue

WdFindStop

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.


Searching for Formatting

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

The Replace Operation

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

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.


Example: Repeated Searching

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.