PDA

View Full Version : [SOLVED:] wdReplaceAll seems to corrupt my Range



erros84
09-05-2013, 10:35 PM
Hi all, I have a weird response from VBA when I use rng.Find.Execute Replace:=wdReplaceOne.
Program/Platform: Word 2010 x64, Windows 7 x64

This is a snippet of code from a much larger project, but basically I want the process to replace ONLY the first search result in the range on each loop until done. In my full program, the .Replacement.Text is a much more clever result from another process, and multiple different find-replaces are done per loop on the same paragraph.

I've been told that it's better to use ranges than selections...and it's been nothing short of exhausting making this work. Ranges just don't behave the way I expect them to.

When I use wdReplaceAll in the first snippet, the range (the current paragraph) stays the same, and it does exactly what I expect. The range and the paragraph in the document reflect the changes of a standard replace all.

Sample Document:
et at ot et

Working code:

Sub CleanReplace()
Dim re2 As Object
Dim i As Integer
Dim Reference As Range
Dim myMatches, ResultString
Set re2 = CreateObject("vbscript.regexp")
re2.pattern = "(a|e)t"
re2.IgnoreCase = True
re2.Global = True
re2.MultiLine = False
Dim para As Paragraph
For Each para In ActiveDocument.Paragraphs
Dim CurrentStyle As Object
Set rng = para.Range
Dim FoundMatch
If rng <> "" Then
FoundMatch = re2.Test(rng.Text)
If FoundMatch = True Then
Set myMatches = re2.Execute(rng)
If myMatches.count >= 1 Then
'ResultString = myMatches(0).Value
'do replaces on array
For i = 0 To (myMatches.count - 1)
tempstring = myMatches(i).Value
rangeStart = Len(rng)
' Selection.Find.ClearFormatting
' Selection.Find.Replacement.ClearFormatting
With rng.Find
.Text = tempstring
.Replacement.Text = "est"
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
End With
rng.Find.Execute Replace:=wdReplaceAll
MsgBox (rng.Text)
Next i
End If
End If
End If
Next para
End Sub

As expected, the message box appears with "est est ot est".

Unfortunately, when I use .wdReplaceOne (as in the second snippet) the document reflects the change of a replace one, BUT then the range contracts down, showing only the replacement text from that operation. At this point, I can no longer continue replacements in this paragraph because my range no longer contains the whole paragraph. Shouldn't the msgbox in the second code snippet show the result "est at ot et" instead of simply "est"?

I have a watch on the variable "rng", and it's driving me insane why a find/replace with wdReplaceOne corrupts the range and wd_ReplaceAll does not.

What am I misunderstanding?

Broken Code:

Sub CleanReplace()
Dim re2 As Object
Dim i As Integer
Dim Reference As Range
Dim myMatches, ResultString
Set re2 = CreateObject("vbscript.regexp")
re2.pattern = "(a|e)t"
re2.IgnoreCase = True
re2.Global = True
re2.MultiLine = False
Dim para As Paragraph
For Each para In ActiveDocument.Paragraphs
Dim CurrentStyle As Object
Set rng = para.Range
Dim FoundMatch
If rng <> "" Then
FoundMatch = re2.Test(rng.Text)
If FoundMatch = True Then
Set myMatches = re2.Execute(rng)
If myMatches.count >= 1 Then
'ResultString = myMatches(0).Value
'do replaces on array
For i = 0 To (myMatches.count - 1)
tempstring = myMatches(i).Value
rangeStart = Len(rng)
' Selection.Find.ClearFormatting
' Selection.Find.Replacement.ClearFormatting
With rng.Find
.Text = tempstring
.Replacement.Text = "est"
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
End With
rng.Find.Execute Replace:=wdReplaceOne
MsgBox (rng.Text)
Next i
End If
End If
End If
Next para
End Sub


The messagebox appears with only "est", losing the rest of the paragraph.

erros84
09-06-2013, 07:19 AM
After all that description...my title is backwards...the title should be "wdReplaceOne seems to corrupt my Range"

Jay Freedman
09-06-2013, 07:11 PM
Between the MsgBox and the Next i statement, insert


rng.Collapse wdCollapseEnd


The behavior you're seeing is a consequence of the fact that, when a Range object's .Find.Execute succeeds in finding (and maybe replacing) the .Text string, the Range object is redefined to just the found text. (This is barely mentioned at the end of http://msdn.microsoft.com/en-us/library/office/aa221467%28v=office.11%29.aspx.) To allow the loop to continue to find the rest of the occurrences in the original range, you need to collapse the range to its endpoint (wdCollapseEnd).

In the wdReplaceAll scenario, the range isn't redefined -- I guess whoever designed this mess couldn't figure out which occurrence should be the one to grab the redefinition, or some such. :banghead:

erros84
09-06-2013, 11:40 PM
THANK YOU THANK YOU!
Now that I had some idea what was going on in the first place, I had the key to find the rest of the solution.

After the first replace-one, I wanted the range to start after the previous replace-one and go to the end of the paragraph. I used your code, plus another line to expand the range back to the end of the paragraph.

rng.Collapse wdCollapseEnd
rng.MoveEnd unit:=wdParagraph, count:=1


Now I can search the rest of the paragraph using this range, or define another range if I want to do a different search on the whole paragraph.
Solved!

gmaxey
09-08-2013, 06:35 AM
I realize your immediate problem is resolved, however you might find something useful here: http://gregmaxey.mvps.org/word_tip_pages/words_fickle_vba_find_property.html