In the Microsoft Word VBA editor, I'm trying to write a macro that finds and replaces a certain character with another character only within certain strings of text, not the whole document. For instance, I might want to replace decimal commas with decimal points (not all commas with periods), or a space with a hyphen in certain phrases. A big constraint is that the changes must be tracked via Track Changes, so finding and replacing the whole string of text isn't an option: Some customers think it looks weird and/or sloppy if I replace their numbers with themselves, and they have also worried that some of their data might have gotten changed. (It might also look like I let my computer make edits for me automatically, which I want to avoid.)
I can already do this clunkily by using Selection.Find to find certain strings (or patterns), doing Selection.Collapse, moving the cursor left or right, deleting a comma, and typing a period. I'm hoping there is a faster way to do this, possibly using ranges, but I have had little success finding or replacing anything using Word's Range object. Since I want to run several macros that total well over a hundred possible find-and-replace actions for each document, I'd like to streamline them all as much as possible.
What I've tried so far
For ease of illustration, I'll take the specific examples in which I want to find commas within statistical p-values written as "0,05", "0,01", or "0,001" and change them to periods, but not make this change anywhere else. I'm aware that in real life, searching for those strings could catch numbers in the thousands, millions, etc., but these are just simplified examples for learning/illustration purposes.
(1) The following works fine, it just strikes me as slow when done for many different Find strings in every document.
With Selection.Find
.ClearFormatting
.Text = "0,05"
.MatchWholeWord = True
.MatchWildcards = False
.Forward = True
.Wrap = wdFindContinue
End With
Do While Selection.Find.Execute
Selection.Collapse
Selection.MoveRight unit:=wdCharacter, count:=1
Selection.Delete unit:=wdCharacter, count:=1
Selection.TypeText (".")
Loop
(2) The most promising other way was adapted from VBA Word: I would like to find a phrase, select the words before it, and italicise the text:
Sub RangeTest()
Dim Rng As Range
Dim Fnd As Boolean
Set Rng = Selection.Range
With Rng.Find
.ClearFormatting
.Execute findText:="0,05", Forward:=True, _
format:=False, Wrap:=wdFindContinue
Fnd = .found
End With
If Fnd = True Then
With Rng
.Find.Wrap = wdFindContinue
.Find.Text = ","
.Find.Replacement.Text = "."
.Find.Execute Replace:=wdReplaceOne
End With
End If
End Sub
but it replaces the comma with a period in only the first "0,05" in the document, not all of them.
When I change wdReplaceOne to wdReplaceAll, then every comma in the document gets replaced with a period.
When I try every possible combination of wdFindContinue/wdFindStop (both times) and wdReplaceAll/wdReplaceOne, either one comma gets changed to a period or every one in the document does.
When I change the "If…Then" statement do a "Do While…Loop" statement, Word hangs:
Dim Rng As Range
Dim Fnd As Boolean
Set Rng = Selection.Range
With Rng.Find
.ClearFormatting
.Execute findText:="0,05", Forward:=True, _
format:=False, Wrap:=wdFindStop
Fnd = .found
End With
Do While Fnd = True
With Rng
.Find.Text = ","
.Find.Replacement.Text = "."
.Find.Execute Replace:=wdReplaceAll
End With
Loop
Is there any way to loop the "If…Then" statement or get the "Do While…Loop" method to work without hanging?
(3) I tried to adapt the code from this page https://www.techrepublic.com/article/macro-trick-how-to-highlight-multiple-search-strings-in-a-word-document/
Sub WordCollectionTest()
Dim Word As Word.Range
Dim WordCollection(2) As String
Dim Words As Variant
WordCollection(0) = "0,05"
WordCollection(1) = "0,01"
WordCollection(2) = "0,001"
'This macro behaves weirdly if insertions and deletions aren't hidden (more than one period gets inserted).
With ActiveWindow.view
.ShowInsertionsAndDeletions = False
For Each Word In ActiveDocument.Words
For Each Words In WordCollection
With Selection.Find
.ClearFormatting
.Text = Words
.Forward = True
.Wrap = wdFindContinue
.MatchWildcards = False
.MatchWholeWord = True
End With
Do While Selection.Find.Execute
Selection.Find.Text = ","
Selection.Find.Replacement.Text = "."
Selection.Find.Execute Replace:=wdReplaceAll
Loop
Next
Next
End With
End Sub
but this replaces every comma in the document with a period. (It's also kind of slow.)
(4) I tried putting the Find terms in an array rather than a word collection:
Sub ArrayTest()
Dim vDecimalCommas As Variant
Dim i As Long
vDecimalCommas = Array("0,05", "0,01", "0,001")
'This macro behaves weirdly if insertions and deletions aren't hidden:
With ActiveWindow.view
.ShowInsertionsAndDeletions = False
For i = LBound(vDecimalCommas) To UBound(vDecimalCommas)
With Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = vDecimalCommas(i)
.Forward = True
.Wrap = wdFindContinue
.matchcase = False
.MatchWholeWord = False
.MatchWildcards = True
End With
Do While Selection.Find.Execute
Selection.Find.Text = ","
Selection.Find.Replacement.Text = "."
Selection.Find.Execute Replace:=wdReplaceAll
Loop
Next
End With
End Sub
but this only replaces the comma with a period in the second of those numbers that it comes across, oddly enough.
I tried a variant of the Array method:
Sub ArrayTest()
For i = LBound(vDecimalCommas) To UBound(vDecimalCommas)
With Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ","
.Replacement.Text = "."
.Forward = True
.Wrap = wdFindContinue
.matchcase = False
.MatchWholeWord = False
.MatchWildcards = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
Next
End Sub
But this replaces every comma in the document with a period (which isn't surprising, since I don't think the For i statement has any bearing on the Find and Replace commands in this version).
I've tried lots of other variants that I haven't mentioned here. I've tried combining the Array method with the Range/Boolean method. I've tried every variant I know of of the Find, Selection, For i, For Each, If Then, and Do While commands. And every time, only one comma gets replaced with a period or every one does.
Is there some way to define a range that consists of a certain string of text so that word will find and replace commas with periods within that range, every time, and nowhere else? Is there a way to define many such strings in one array or some other kind of list? Or any other way to find and replace commas with periods only within certain strings? I'm far from an expert, so a tiny variation of one of the above methods might work.