Replace text strings with incremented named bookmarks

  • Thread starter Maritza van den Heuvel
  • Start date
M

Maritza van den Heuvel

Hello everyone,

Up to now, my VBA has been limited to basic Find and Replace macros, or
updating links and other formatting issues. Now I'm trying to do
something a bit more complex involving looping and a counter, and I'm
coming unstuck completely. Any help would be much appreciated!

My apologies if this is quite a long post, but I thought the more detail
the more likely that someone may be able to see what I'm trying to do
and have some answers that will help me learn how to do this. Part of my
problem is that with all the reading up I've done (MVPS, various other
sites) that I can't see the forest for the trees anymore.

MTIA!
Maritza van den Heuvel
Team Lead: Documentation and QA
STT Trainer : Kaplan IT Learning


MACRO SCENARIO:
I have two placeholder strings that appear in a document, call them
"StringA" and "StringB" for now. I need to loop through the document
and do the following:

All occurrences of StringA need to be replaced with a named bookmark,
let's call it "FirstBookmarkName". Each of these bookmarks needs to
increment for each occurrence of the string, so if there were ten
occurrences of "StringA", I'm left with 10 bookmarks in the document,
starting with "FirstBookmarkName0" and ending with "FirstBookmarkName9".
Each bookmark must be inserted in the position where each occurrence of
"StringA" was replaced. The original text strings must be deleted.

I need to do exactly the same with "StringB", but instead the
replacement should be "SecondBookmarkName" throughout.

My attempt so far:
I have two main subs, each using selection.find to find the relevant
string, and replace it with an empty string (""), while adding the
required bookmark (with the incremented counter concatenated onto the
bookmark name) to the ActiveDocument. After adding the bookmark, the
counter is supposed to increment by one as the starting point for the
next pass. There is a third sub that simply calls these two subs.

The bad news is, that the two subs compile without errors, but none of
the replacement or incrementing is happening, and only one instance of
each bookmark is being added ...

Since my initial attempt, I have done some more reading up, and have
come to some conclusions/assumptions:

1) From the error about "bad bookmark name" I keep getting, I'm starting
to suspect that I cannot use the str function to concatenate the counter
value as a string onto the bookmark name?

2) That perhaps with Find.Execute I can only use text strings as
replacements, and not bookmarks? I read something that the only way to
use a non-text entity as the replacement value is to copy this entity to
the clipboard first and then insert it from the clipboard during the
find and replace? (If that's true, then HOW do I do this?)

3) I found some sample code which led me to the conclusion that I first
need to use selection.find to find the required string, and then with
selection.find execute the replace and bookmarks.add commands as part of
a Do While loop. Trouble is ... I can't seem to get the syntax right
with this. :-(

Here's the code that I've pieced together so far on my own in one sub.
This is the sub that compiles, but only the Bookmarks.Add works - ONCE:

Sub InsertMarkerA()
Dim Counter As Integer
Counter = 0
With Selection.Find
.Text = "StringA"
.Replacement.Text = ""
.Execute Replace:=wdReplaceOne
ActiveDocument.Bookmarks.Add Name:="FirstBookmarkName"+Str(Counter)
Counter = Counter + 1
End With
End Sub


Here's the other sub, but with the changes to the selection.find and Do
loop that I found tonight. This gives a compile error (external name not
defined) on the ReplaceWith variant, but I don't know how to fix it. :-(
So I don't know yet if this comes even close to solving (some/more) of
my problem.

Sub InsertMarkerB()
Dim Counter As Integer
Counter = 0
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "StringB"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While Selection.Find.Execute([ReplaceWith=""],
[Replace:=wdReplaceOne])
ActiveDocument.Bookmarks.Add Name:="CreateResultStep" +
Str(Counter)
Counter = Counter + 1
Loop
End Sub
 
J

Jay Freedman

You're headed in the right direction, but you've been tripped up by
some similar-looking things.

For example, instead of the Str function you should use the CStr
function or the Format function. The Str function always puts a space
character at the beginning of positive numbers, and that makes the
bookmark name invalid. Also, to put two strings together, use the &
operator instead of the + operator.

In the .Execute call, don't put square brackets around arguments.
That's just a typographic convention used in the Help topics to show
that the arguments are optional; they aren't meant to be typed
literally in the code. Also, don't bother to include the ReplaceWith
argument, which does the same thing as setting .Replacement.Text.

Here's an example of how to code the replacement of one set of strings
with the corresponding bookmarks:

Sub Demo1()
' replace occurrences of "String A" with numbered bookmarks
Dim Counter As Long
Counter = 0
Selection.HomeKey wdStory
With Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "String A"
.Replacement.Text = ""
.Format = False
.Forward = True
.MatchWildcards = False
.Wrap = wdFindStop

Do While .Execute(Replace:=wdReplaceOne)
ActiveDocument.Bookmarks.Add _
Name:="FirstBookmarkName" & CStr(Counter), _
Range:=Selection.Range
Counter = Counter + 1
Loop
End With
End Sub

Rather than make two complete copies of this code and just change the
strings and bookmark names, it's more efficient to write a single copy
and pass the strings and bookmarks as parameters. Then you can call
that single copy twice, once for each set. The following example also
uses a Range object and its .Find method instead of using the
Selection object. The advantage of this is that the cursor (insertion
point) never moves, so Word doesn't have to spend time redrawing the
screen as the macro runs, and it's much faster.

Sub Demo2()
' Call the subroutine twice -- once for each string to replace
DoReplacements "String A", "FirstBookmarkName"
DoReplacements "String B", "SecondBookmarkName"
End Sub

Private Sub DoReplacements(strToReplace As String, bkName As String)
Dim Counter As Long
Dim Rg As Range
Counter = 0
Set Rg = ActiveDocument.Range
With Rg.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = strToReplace
.Replacement.Text = ""
.Format = False
.Forward = True
.MatchWildcards = False
.Wrap = wdFindStop

Do While .Execute(Replace:=wdReplaceOne)
ActiveDocument.Bookmarks.Add _
Name:=bkName & CStr(Counter), _
Range:=Rg
Counter = Counter + 1
Loop
End With
End Sub

--
Regards,
Jay Freedman
Microsoft Word MVP
Email cannot be acknowledged; please post all follow-ups to the
newsgroup so all may benefit.

Hello everyone,

Up to now, my VBA has been limited to basic Find and Replace macros, or
updating links and other formatting issues. Now I'm trying to do
something a bit more complex involving looping and a counter, and I'm
coming unstuck completely. Any help would be much appreciated!

My apologies if this is quite a long post, but I thought the more detail
the more likely that someone may be able to see what I'm trying to do
and have some answers that will help me learn how to do this. Part of my
problem is that with all the reading up I've done (MVPS, various other
sites) that I can't see the forest for the trees anymore.

MTIA!
Maritza van den Heuvel
Team Lead: Documentation and QA
STT Trainer : Kaplan IT Learning


MACRO SCENARIO:
I have two placeholder strings that appear in a document, call them
"StringA" and "StringB" for now. I need to loop through the document
and do the following:

All occurrences of StringA need to be replaced with a named bookmark,
let's call it "FirstBookmarkName". Each of these bookmarks needs to
increment for each occurrence of the string, so if there were ten
occurrences of "StringA", I'm left with 10 bookmarks in the document,
starting with "FirstBookmarkName0" and ending with "FirstBookmarkName9".
Each bookmark must be inserted in the position where each occurrence of
"StringA" was replaced. The original text strings must be deleted.

I need to do exactly the same with "StringB", but instead the
replacement should be "SecondBookmarkName" throughout.

My attempt so far:
I have two main subs, each using selection.find to find the relevant
string, and replace it with an empty string (""), while adding the
required bookmark (with the incremented counter concatenated onto the
bookmark name) to the ActiveDocument. After adding the bookmark, the
counter is supposed to increment by one as the starting point for the
next pass. There is a third sub that simply calls these two subs.

The bad news is, that the two subs compile without errors, but none of
the replacement or incrementing is happening, and only one instance of
each bookmark is being added ...

Since my initial attempt, I have done some more reading up, and have
come to some conclusions/assumptions:

1) From the error about "bad bookmark name" I keep getting, I'm starting
to suspect that I cannot use the str function to concatenate the counter
value as a string onto the bookmark name?

2) That perhaps with Find.Execute I can only use text strings as
replacements, and not bookmarks? I read something that the only way to
use a non-text entity as the replacement value is to copy this entity to
the clipboard first and then insert it from the clipboard during the
find and replace? (If that's true, then HOW do I do this?)

3) I found some sample code which led me to the conclusion that I first
need to use selection.find to find the required string, and then with
selection.find execute the replace and bookmarks.add commands as part of
a Do While loop. Trouble is ... I can't seem to get the syntax right
with this. :-(

Here's the code that I've pieced together so far on my own in one sub.
This is the sub that compiles, but only the Bookmarks.Add works - ONCE:

Sub InsertMarkerA()
Dim Counter As Integer
Counter = 0
With Selection.Find
.Text = "StringA"
.Replacement.Text = ""
.Execute Replace:=wdReplaceOne
ActiveDocument.Bookmarks.Add Name:="FirstBookmarkName"+Str(Counter)
Counter = Counter + 1
End With
End Sub


Here's the other sub, but with the changes to the selection.find and Do
loop that I found tonight. This gives a compile error (external name not
defined) on the ReplaceWith variant, but I don't know how to fix it. :-(
So I don't know yet if this comes even close to solving (some/more) of
my problem.

Sub InsertMarkerB()
Dim Counter As Integer
Counter = 0
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "StringB"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While Selection.Find.Execute([ReplaceWith=""],
[Replace:=wdReplaceOne])
ActiveDocument.Bookmarks.Add Name:="CreateResultStep" +
Str(Counter)
Counter = Counter + 1
Loop
End Sub
 
M

Maritza van den Heuvel

Thanks a million for your help, Jay! This was exactly what I needed.

Just one question - when would you then use the Str function instead of
CStr?

Regards,
Maritza

Jay said:
You're headed in the right direction, but you've been tripped up by
some similar-looking things.

For example, instead of the Str function you should use the CStr
function or the Format function. The Str function always puts a space
character at the beginning of positive numbers, and that makes the
bookmark name invalid. Also, to put two strings together, use the &
operator instead of the + operator.
<rest cut>
 
J

Jay Freedman

I don't think I would use Str for anything. :) It's one of those things
that have been in various versions of the Basic language since the
beginning, but there are better alternatives.

I suppose it might be useful if you're trying to create a column of numbers
that align properly in a list box or something of that sort, and some of
them might be negative. I'd probably use the Format function instead,
though.

Jay
 
M

Maritza van den Heuvel

Well, I have actually found a use for it similar to what you describe.
May not be the best way, but I think it will do what I need. I ran into
another problem in the document, where I have to build up my own
numbered sequence of steps in a table column because Word AUTONUM fields
break the integration with the application I'm trying to export to. Each
step number must be in the format "Step 1", "Step 2", etc. And Str looks
to be ideal for this format. Except that I'm having trouble with my looping.

With the macro below, I get this to work ONCE only. If I change
wdFindStop to wdFindContinue, then I get n x Step 1. The same happens if
I change wdReplaceOne with wdReplaceAll, which makes sense.

I thought that incrementing the counter in the loop would take care of
this, but I must be missing something?


Sub ReplaceNumbers()
Dim Counter As Long
Dim Rg As Range
Counter = 1
Set Rg = ActiveDocument.Range
With Rg.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "StepNumber"
.Replacement.Text = "Step" & Str(Counter)
.Format = False
.Forward = True
.MatchWildcards = False
.Wrap = wdFindStop

Do While .Execute(Replace:=wdReplaceOne)
Counter = Counter + 1
Loop
End With
End Sub

Maritza
 
J

Jay Freedman

The way you've ordered your statements, the .Replacement.Text is
computed only once, before any of the replacements are made. That's
why all the replacements say "Step 1" -- you never change it to
anything else.

Copy the .Replacement.Text statement and paste the copy between the
Counter = Counter + 1 and the Loop; use the .Wrap = wdFindContinue
value. You should get what you want. Keep wdReplaceOne; you can't make
this work with wdReplaceAll.

--
Regards,
Jay Freedman
Microsoft Word MVP
Email cannot be acknowledged; please post all follow-ups to the
newsgroup so all may benefit.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top