How can I find & replace text across multiple files inside text boxes? - vba

I would like to change text that repeats in .doc and .docx files.
I have this macro running at the moment:
Option Explicit
Public Sub BatchReplaceAll()
Dim FirstLoop As Boolean
Dim myFile As String
Dim PathToUse As String
Dim myDoc As Document
Dim Response As Long
PathToUse = "C:\Files\"
'Error handler to handle error generated whenever
'the FindReplace dialog is closed
On Error Resume Next
'Close all open documents before beginning
Documents.Close SaveChanges:=wdPromptToSaveChanges
'Boolean expression to test whether first loop
'This is used so that the FindReplace dialog will'only be displayed for the first document
FirstLoop = True
'Set the directory and type of file to batch process
myFile = Dir$(PathToUse & "*.docx")
While myFile <> ""
'Open document
Set myDoc = Documents.Open(PathToUse & myFile)
If FirstLoop Then
'Display dialog on first loop only
Dialogs(wdDialogEditReplace).Show
FirstLoop = False
Response = MsgBox("Do you want to process " & _
"the rest of the files in this folder", vbYesNo)
If Response = vbNo Then Exit Sub
Else
'On subsequent loops (files), a ReplaceAll is
'executed with the original settings and without
'displaying the dialog box again
With Dialogs(wdDialogEditReplace)
.ReplaceAll = 1
.Execute
End With
End If
'Close the modified document after saving changes
myDoc.Close SaveChanges:=wdSaveChanges
'Next file in folder
myFile = Dir$()
Wend
End Sub
It does work if replacing simple text.
How can I search and replace inside Text Boxes?
EDIT 1:
I changed this:
With Dialogs(wdDialogEditReplace)
.ReplaceAll = 1
.Execute
End With
To this:
With Dialogs(wdDialogEditReplace)
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "ORIGINAL_TEXT"
.Replacement.Text = "MODIFIED_TEXT"
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Do While Not (myStoryRange.NextStoryRange Is Nothing)
Set myStoryRange = myStoryRange.NextStoryRange
With myStoryRange.Find
.Text = "ORIGINAL_TEXT"
.Replacement.Text = "MODIFIED_TEXT"
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Loop
Next myStoryRange
End With
The problems with this new code is that sometimes it skips text boxes and it is slow.

In VBA, Word "Text Boxes" are known as a TextFrame Object. This may be able to point you in the right direction:
For Each s In ActiveDocument.Shapes
With s.TextFrame
If .HasText Then MsgBox .TextRange.Text
End With
Next
I will update my answer when I get more information on implementing it into your example.

Related

Update a FORMTEXT to a MERGEFIELD VBA

I have a project that I will likely be doing pretty regularly so I think a macro or vba module would be worth looking into.
The document has several [FORMTEXT] [FORMCHECKBOX] and such and I would like to automate replacing the [FORMTEXT] with {MERGEFIELD formtextname}. I've sone similar with {command} from Crystal to {mergefield } but that was just word replacement not field type. I found things about wdFormtextfield and wdMergeField just not sure how to .find type wdformtext. I assume if I can .find the type I can then .Replace with wdMergeField. I will be looping through the document. Any thoughts?
I might be going the wrong way but this is what I am thinking
Sub Change_FormTextToMergeField()
Application.ScreenUpdating = False
Dim StrFld As String 'change to a wd type for formtext
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "\{*\}"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
End With
Do While .Find.Execute
i = 1
'add .Replace code to replace wdFieldFormTextInput with wdFieldMergeField
MsgBox .Words.Parent
i = i + 1
Loop
End With
Application.ScreenUpdating = True
End Sub
Sample Doc
The following procedure converts all FormFields to MergeFields perserving bookmarks as the Mergefield name
Sub FormFieldToMergeField() '
' A scratch Word macro coded by Charles Kenyon - based on Greg Maxey format
' https://stackoverflow.com/questions/75393869/update-a-formtext-to-a-mergefield-vba
' 2023-02-10
' started with Doug Robbins macro https://answers.microsoft.com/en-us/msoffice/forum/all/convert-formtext-fields-into-bookmarked-content/1f2d7aa2-a335-4667-9955-998d0525ba09
' Converts FormFields to Mail Merge Fields - the Bookmark name becomes the mergefield name
' If no FormField bookmark, then mergefield says Unnamed
'
' DECLARE VARIABLES / CONSTANTS
Dim oRng As range
Dim oFld As Field
Dim strName As String
Dim i As Long
'================================================
' ACTIONS
Application.ScreenUpdating = False
On Error GoTo lbl_Exit
With ActiveDocument
For i = .FormFields.Count To 1 Step -1
Set oRng = .FormFields(i).range
strName = .FormFields(i).Name
If strName = "" Then strName = "Unnamed Field " & i
.FormFields(i).Delete
oRng.Select
Set oFld = .Fields.Add(range:=oRng, Type:=wdFieldMergeField, Text:=strName)
Selection.Collapse wdCollapseStart
Next i
End With
' EXIT PROCEDURE
lbl_Exit:
' CLEAR ERROR HANDLER AND OBJECTS
Application.ScreenUpdating = True
Application.ScreenRefresh
On Error GoTo -1
Set oRng = Nothing
Set oFld = Nothing
Exit Sub
End Sub
If there is no bookmark, the mergefield will be to Unnamed.
Instructions for Installing Macros from Forums or Websites by Word MVP Graham Mayor

Find and Replace Multiple Text Strings on Multiple Text Files from a folder

I'm working on the vba code to accomplish the following tasks
Word Document Open a Text file from the folder
Find and replace the text (multiple Text) based on a excel sheet (which have find what and replace with)
Process all text files in the folder and save it.
I would like to customize the below code for the above requirement,
I'm using Office 2016 and I think I have to replace Application.FileSearch in the script to ApplicationFileSearch for 2003 and prior office editions.
I try to accomplish using the word macro recorder and also used notepad++, I've recorded in Notepad++ also and it works for one file, I would like to do batch process all files in the folder and save it after replacing the text.
As there is too many lines there to replace more than 30 or more lines to replace, I would like the code to look for the text from a excel file like find what and replace with columns.
Sub FindReplaceAllDocsInFolder( )
Dim i As Integer
Dim doc As Document
Dim rng As Range
With Application.FileSearch
.NewSearch
.LookIn = "C:\My Documents"
.SearchSubFolders = False
.FileType = msoFileTypeWordDocuments
If Not .Execute( ) = 0 Then
For i = 1 To .FoundFiles.Count
Set doc = Documents.Open(.FoundFiles(i))
For Each rng In doc.StoryRanges
With rng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "Dewey & Cheatem"
.Replacement.Text = "Dewey, Cheatem & Howe"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute Replace:=wdReplaceAll
End With
Next rng
doc.Save
doc.Close
Set rng = Nothing
Set doc = Nothing
Next i
Else
MsgBox "No files matched " & .FileName
End If
End With
End Sub
Thanks
Jay
Borrowed from https://social.msdn.microsoft.com/Forums/en-US/62fceda5-b21a-40b6-857c-ad28f12c1b23/use-excel-vba-to-open-a-text-file-and-search-it-for-a-specific-string?forum=isvvba
Sub SearchTextFile()
Const strFileName = "C:\MyFiles\TextFile.txt"
Const strSearch = "Some Text"
Dim strLine As String
Dim f As Integer
Dim lngLine As Long
Dim blnFound As Boolean
f = FreeFile
Open strFileName For Input As #f
Do While Not EOF(f)
lngLine = lngLine + 1
Line Input #f, strLine
If InStr(1, strLine, strSearch, vbBinaryCompare) > 0 Then
MsgBox "Search string found in line " & lngLine, vbInformation
blnFound = True
Exit Do
End If
Loop
Close #f
If Not blnFound Then
MsgBox "Search string not found", vbInformation
End If
End Sub
This is simply just finding the match. You can use the built in function "Replace" which land the total fix. You would also have to fit in the "loop through files" code, which here is a snippet.
Dim StrFile As String
StrFile = Dir(pathtofile & "\*" & ".txt")
Do While Len(StrFile) > 0
Debug.Print StrFile
StrFile = Dir
Loop
I wouldve made this a comment, but it was too much text. This isnt meant to be a full blown answer, just giving you the pieces you need to put it all together on your own.
Thanks for all your help. I have found alternate solution using the below EXE. FART.exe (FART - Find and Replace Text). I have create a batch file with the below command example.
https://emtunc.org/blog/03/2011/farting-the-easy-way-find-and-replace-text/
http://fart-it.sourceforge.net/
Examples:
fart "C:\APRIL2011\Scripts\someFile.txt" oldText newText
This line instructs FART to simply replace the string oldText with newText.
fart -i -r "C:\APRIL2011\Scripts*.prm" march2011 APRIL2011
This line will instruct FART to recursively (-r) search for any file with the .prm extension in the \Scripts folder. The -i flag tells FART to ignore case-sensitivity when looking for the string.

Text Replacement in MS Word

In my MS-Word template, my client sends template with an error in the header section, as below,
VERMONT, ROBIN S. Date: 10/21/2017
File No: 312335 DOB: 05/02/1967Claim#: RE155B53452
DOI: 06/21/2017
The error being the ‘Claim#’ coming up right next to the DOB value, while it should come on the next line, as below:
VERMONT, ROBIN S. Date: 10/21/2017
File No: 312335 DOB: 05/02/1967
Claim#: RE155B53452 DOI: 06/21/2017
Also, this error comes up occasionally in some files and not all, so to solve it, I created a word macro, as below:
Sub ShiftClaimNumber2NextLine()
Dim rngStory As Word.Range
Dim lngJunk As Long
'Fix the skipped blank Header/Footer problem as provided by Peter Hewett
lngJunk = ActiveDocument.Sections(1).Headers(1).Range.StoryType
'Iterate through all story types in the current document
For Each rngStory In ActiveDocument.StoryRanges
'Iterate through all linked stories
Do
With rngStory.Find
.text = "Claim#:"
.Replacement.text = "^pClaim#:"
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'Get next linked story (if any)
Set rngStory = rngStory.NextStoryRange
Loop Until rngStory Is Nothing
Next
End Sub
And I run the above macro through a Group Macro, as below:
Sub ProcessAllDocumentsInFolder()
Dim file
Dim path As String
' Path to folder
path = "D:\Test\"
file = Dir(path & "*.doc")
Do While file <> ""
Documents.Open FileName:=path & file
' Call the macro to run on each file in the folder
Call ShiftClaimNumber2NextLine
' Saves the file
ActiveDocument.Save
ActiveDocument.Close
' set file to next in Dir
file = Dir()
Loop
End Sub
When I run the macro ProcessAllDocumentsInFolder(), for all files with such an error, it shifts the ‘Claim#’ to the next line.
However, the problem is, it also does so for files that do not have such a problem, thereby adding one enter line below the DOB, as below (as depicted by the yellow line):
What changes should I make to my macro ShiftClaimNumber2NextLine() , so that it does not make any change to files which DO NOT HAVE the ‘Claim#’ problem ?
This is a situation best suited to regular expressions see below link for similar issue, refer vba solution
https://superuser.com/questions/846646/is-there-a-way-to-search-for-a-pattern-in-a-ms-word-document
Sub RegexReplace()
Dim RegEx As Object
Set RegEx = CreateObject("VBScript.RegExp")
On Error Resume Next
RegEx.Global = True
RegEx.Pattern = "[0-9]Claim#:"
ActiveDocument.Range = _
RegEx.Replace(ActiveDocument.Range, "\rClaim#:")
End Sub

Word Userform won't open in second, currently active document after opened/unloaded in first document

The headline really says it all, but here's my situation: I have a userform set up to collect user input, then uses that input in a macro and executes it. That, in itself, works exactly like I want it to. The problem comes when more than one document is open.
To illustrate: I have two documents, 'doc a' and 'doc b'. I open both documents, then select 'doc a', open the userform using a show userform macro, input my data, and hit either 'Okay' or 'Cancel' (both of which are set to unload the userform once clicked). The macro runs, and then I select 'doc b' to do the same. This time, however, when I run my 'show userform' macro, 'doc a' is selected and the userform is opened there.
This seems like a pretty basic issue, but I haven't been able to figure out any fixes. After putting 'unload me' failed to work in my button-click subs, I tried creating an unload macro and calling it from those subs instead, but neither is working for me. Any thoughts? (Also, while I'm already here- are there any good tricks to autofill the Userform with the most recently filled data? Not between opening/closing word, which I've seen some solutions for, but just while word is open, and I'm switching between active documents)
Option Explicit
Option Compare Text
Private Sub UserForm_Initialize()
Folder_Name = ""
Tag_Name = ""
Checklist.Value = True
Site_Report.Value = False
Space_Check.Value = False
End Sub
Public Sub Okay_Click()
folder = Folder_Name.Text
tag = Tag_Name.Text
tagtxt = Tag_Name.Text & "[0-9]{1,}"
tagnum = Len(Tag_Name.Text)
If Checklist.Value = True Then
report_type = "cl"
Else
report_type = "sr"
End If
If Space_Check.Value = True Then
space = "yes"
Else
space = "no"
End If
If Len(Folder_Name.Text) > 0 Then
Application.Run "Mass_Hyperlink_v_5_0"
Application.Run "UnloadIt"
Else
Application.Run "UnloadIt"
End If
Unload Me
End Sub
Private Sub Cancel_Click()
Application.Run "UnloadIt"
Unload Me
End Sub
I don't think the issue is with the macros that this userform uses (it runs fine on its own, though the code is likely a bit hackneyed), but here's the code for good measure:
Option Explicit
Option Compare Text
Public tag As String
Public tagtxt As String
Public tagnum As String
Public folder As String
Public space As String
Public report_type As String
Public Sub Mass_Hyperlink_v_5_0()
Dim fileName As String
Dim filePath As String
Dim rng As Word.Range
Dim rng2 As Word.Range
Dim fileType As String
Dim start As String
Dim temp As String
Application.ScreenUpdating = False
fileType = "jpg"
If space = "Yes" Then
start = "%20("
Else: start = "("
End If
If report_type = "cl" Then
folder = "..\Images\" & folder
Set rng = ActiveDocument.Range
Else: folder = folder
End If
If report_type = "sr" Then
folder = "Images\" & folder
Set rng = Selection.Range
Else: folder = folder
End If
Set rng2 = rng.Duplicate
'tagtxt = tag & "[0-9]{1,}"
If Len(rng) > 0 And report_type = "sr" Then
With rng.Find
.Text = tagtxt
.Forward = False
.MatchWildcards = True
.Wrap = wdFindStop
Do While .Execute(findText:=tagtxt) = True
If rng.InRange(rng2) Then
rng.Select
'Selection.start = Selection.start + Len(tag)
Selection.start = Selection.start + tagnum
'ActiveDocument.Range(Selection.start - Len(tag), Selection.start).Delete
ActiveDocument.Range(Selection.start - tagnum, Selection.start).Delete
fileName = Selection.Text
filePath = folder & "\" & Hyperlinker.Tag_Name.Text & start & fileName & ")" & "." & fileType
ActiveDocument.Hyperlinks.Add Anchor:=Selection.Range, address:= _
filePath, SubAddress:="", ScreenTip:="", TextToDisplay:= _
(Hyperlinker.Tag_Name.Text & Selection.Text)
Else
Exit Sub
End If
rng.Collapse wdCollapseStart
Loop
End With
End If
If report_type = "cl" Then
With rng.Find
.Text = tagtxt
.Forward = False
.MatchWildcards = True
.Wrap = wdFindStop
Do While .Execute(findText:=tagtxt) = True
If rng.InRange(rng2) Then
rng.Select
'Selection.start = Selection.start + Len(tag)
Selection.start = Selection.start + tagnum
'ActiveDocument.Range(Selection.start - Len(tag), Selection.start).Delete
ActiveDocument.Range(Selection.start - tagnum, Selection.start).Delete
fileName = Selection.Text
filePath = folder & "\" & Hyperlinker.Tag_Name.Text & start & fileName & ")" & "." & fileType
ActiveDocument.Hyperlinks.Add Anchor:=Selection.Range, address:= _
filePath, SubAddress:="", ScreenTip:="", TextToDisplay:= _
(Hyperlinker.Tag_Name.Text & Selection.Text)
Else
Exit Sub
End If
rng.Collapse wdCollapseStart
Loop
End With
End If
Application.ScreenUpdating = True
End Sub
Sub Show_Linker()
Hyperlinker.Show
Hyperlinker.Folder_Name.SetFocus
End Sub
Sub UnloadIt()
Unload Hyperlinker
End Sub
Working with UserForms in VBA can be tricky, because they're actually a kind of Class. Since VBA tries to make everything exceptionally simple, classes are not obvious, nor is how to work with them correctly. There are situations where they become traps for the unwary.
So VBA makes it possible for you to work with an instance of a UserForm class without you needing to declare and instantiate a new object, as would normally be the case with a class object. The result being that the object can "hang around" and cause unexpected behavior, such as you're seeing.
The more correct way to work with a UserForm may seem like a lot more work (code to type and complexity), but it helps to keep things sorted. Indeed, this approach would theoretically allow you to have a separate UserForm for various documents.
Dim frmHyperlinker as Hyperlinker
Set frmHyperlinker = New Hyperlinker
frmHyperlinker.Folder_Name.SetFocus
frmHyperlinker.Show
'Execution waits...
'Now you're done with it, so clean up
Unload frmHyperlinker
Set frmHyperlinker = Nothing
There's an Answer in this discussion that goes into more technical detail, although the topic of that question is different from yours: Add Public Methods to a Userform Module in VBA

VBA loop won't stop/doesn't find the "\EndofDoc" marker

I am writing a vba macro to search a word document line by line and trying to find certain names in the document. The looping works fine except for when it gets to the end of the document, it just continues from the top and starts over. Here is the code:
Application.ScreenUpdating = False
Dim i As Integer, Rng As Range
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "?"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
.Execute
End With
Do While .Find.found
i = i + 1
Set Rng = .Duplicate
Set Rng = Rng.GoTo(What:=wdGoToBookmark, Name:="\line")
MsgBox "Line " & i & vbTab & Rng.Text
If Rng.Bookmarks.Exists("\EndOfDoc") Then Exit Do
.start = Rng.End
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
Set Rng = Nothing
Application.ScreenUpdating = True
I have also tried this piece of code:
Dim appWD As Word.Application
Dim docWD As Word.Document
Dim rngWD As Word.Range
Dim strDoc As String
Dim intVal As Integer
Dim strLine As String
Dim bolEOF As Boolean
bolEOF = False
' Set strDoc here to include the full
' file path and file name
On Error Resume Next
Set appWD = GetObject(, "Word.Application")
If Err.Number <> 0 Then
Set appWD = CreateObject("Word.Application")
End If
Err.Clear
On Error GoTo 0
strDoc = "c:\KenGraves\Project2\output\master.doc"
Set docWD = appWD.Documents.Open(strDoc)
appWD.Visible = True
docWD.Characters(1).Select
Do
appWD.Selection.MoveEnd Unit:=wdLine, Count:=1
strLine = appWD.Selection.Text
Debug.Print strLine
intVal = LineContainsDescendant(strLine)
If intVal = 1 Then
MsgBox strLine
End If
appWD.Selection.Collapse wdCollapseEnd
If appWD.Selection.Bookmarks.Exists("\EndOfDoc") Then bolEOF = True
Loop Until bolEOF = True
Neither seem to recognize the bookmark ("\EndOfDoc"). It doesn't matter which one gets working. Is it possible that my document does not contain this bookmark?
Not terribly elegant, but this change to one line of your first procedure seems to stop it at the appropriate time. I believe you actually have to insert bookmarks into your document if you want to reference them. They aren't automatically generated.
If i >= ActiveDocument.BuiltInProperties("NUMBER OF LINES") Then Exit Do
Cheers, LC
Unless you have a corrupted document, all Word documents should have the \EndOfDoc bookmark. You can check using simply ActiveDocument.Range.Bookmarks("\EndOfDoc").Exists. If it doesn't then you'll need to supply more details on the version of Word and if possible supply a sample document via Dropbox or the like.
I'm not sure why you're looping to the start of the Word document, when I run the code it works fine. However, if I put a footnote at the end of the document it runs into an endless loop, depending on your documents you may run into additional situations like this where your code fails to handle the document setup.
I would suggest modifying slightly how you check for the end of the document to make your code a bit more robust. I'd still use the bookmark "\EndOfDoc", however I'd check the limits of the range against your current search range.
So at the top of your code declare a range variable and set it to range of the end of the document eg:
Dim rEnd As Range
Set rEnd = ActiveDocument.Bookmarks("\EndOfDoc").Range
and then in your loop, instead of this line:
If Rng.Bookmarks.Exists("\EndOfDoc") Then Exit Do
use this line:
If Rng.End >= rEnd.End Then Exit Do