How to find text in Word, insert additional values before text with VBA - vba

I'm entirely new to VBA. I need to write a macro to do as the following pseudo code describes. Any references to VBA code are from looking at examples I've found so far from googling. Many thanks for any guidance you can offer.
Dim myText as string;
Dim myAutoTextFieldValue as string;
Set myText='Figure';
Set myAutoTextFieldValue = 'fignum';
// fignum is a autotext value that will insert a sequence type field
.Find text which matches this Word expression \[[0-9]*[0-9]*[0-9]\]
// this expression works in the Find what function in Word, not strictly regex
For each
.InsertBefore (myText + myTextAutoFieldValue);
// I'm guessing I'll need a With expression and a Do While.
EDIT:
I now have the following but I get "Method or Data Member not found" when I try to run it.
Sub EditFindLoop()
'find text where the string equals [00:00:00] or numeric sequence as per input mask
'then insert myText and myAutoTextFieldValue before it
Dim myText As String
Dim myAutoTextFieldValue As String
Dim myFind As String
myFind = "\[[0-9]*[0-9]*[0-9]\]"
myAutoTextFieldValue = "fignum"
myText = "Figure"
With ActiveDocument.Content.Find
'.Text = myFind
'.ClearFormatting
.MatchWildcards = True
Do While .Execute(findText:=myFind, Forward:=True) = True
.InsertBefore myText & myAutoTextFieldValue
Loop
End With
End Sub

And here's the answer to my own question, should anyone else require a similar piece of code.
Sub EditFindLoop()
Dim myText As String
Dim myFind As String
Dim x As Integer
myFind = "\[[0-9]*[0-9]*[0-9]\]"
myText = "Figure "
mySpace = ". "
x = 1
Dim oRange As Word.Range
Set oRange = ActiveDocument.Range
With oRange.Find
.Text = myFind
.ClearFormatting
.MatchWildcards = True
.MatchCase = False
.MatchWholeWord = False
Do While .Execute = True
If .Found Then
oRange.InsertBefore (myText & x & mySpace)
End If
oRange.Start = oRange.End
oRange.End = ActiveDocument.Range.End
x = x + 1
Loop
End With
End Sub

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

How do I search and highlight multiple terms in Microsoft Word?

My goal is to be able to run this script and have the document search for and highlight a set number of terms, typically 10+ terms. I figured out how to do this with another script I found here, but every time I use it Word crashes.
Below is a simpler version I have pieced together from different forums/videos I found online. It does exactly what I want it to do except I can't figure out how to make it look for more than one term.
The .Text = "Text" works great but only for one term. If I list multiple then it only looks for the one I listed last. I have tested other chunks of code I found online but I can't figure it out.
I am hoping it is a simple fix, especially since the rest of the code does what I want. TIA!
Sub UsingTheFindObject_Medium()
'Declare Variables.
Dim wrdFind As Find
Dim wrdRng As range
Dim wrdDoc As Document
'Grab the ActiveDocument.
Set wrdDoc = Application.ActiveDocument
'Define the Content in the document
Set wrdRng = wrdDoc.Content
'Define the Find Object based on the Range.
Set wrdFind = wrdRng.Find
'Define the parameters of the Search.
With wrdFind
'Search the text for the following term(s)
.Text="Test"
.Format = True
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While wrdFind.Execute = True
'Change the color to Yellow.
wrdRng.HighlightColorIndex = wdYellow
Loop
End Sub
This will do what you want.
Sub HighlightMultipleWords()
Dim sArr() As String
Dim rTmp As Range
Dim x As Long
sArr = Split("highlight specific words") ' your list
Options.DefaultHighlightColorIndex = wdYellow
For x = 0 To UBound(sArr)
Set rTmp = ActiveDocument.Range
With rTmp.Find
.Text = sArr(x)
.Replacement.Text = sArr(x)
.Replacement.Highlight = True
.Execute Replace:=wdReplaceAll
End With
Next
End Sub
Before:
After:
Use your current routine as a function.
Here is an example.
Function FindAndMark(sText As String) ' UsingTheFindObject_Medium()
' https://stackoverflow.com/questions/69633517/how-do-i-search-and-highlight-multiple-terms-in-microsoft-word
' Charles Kenyon
'Declare Variables.
Dim wrdFind As Find
Dim wrdRng As Range
Dim wrdDoc As Document
'Grab the ActiveDocument.
Set wrdDoc = Application.ActiveDocument
'Define the Content in the document
Set wrdRng = wrdDoc.Content
'Define the Find Object based on the Range.
Set wrdFind = wrdRng.Find
'Define the parameters of the Search.
With wrdFind
'Search the text for the following term(s)
.Text = sText
.Format = True
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
' Mark text
Do While wrdFind.Execute = True
'Change the color to Yellow.
wrdRng.HighlightColorIndex = wdYellow
Loop
Set wrdFind = Nothing
Set wrdRng = Nothing
Set wrdDoc = Nothing
End Function
Sub MultiFindMark()
' https://stackoverflow.com/questions/69633517/how-do-i-search-and-highlight-multiple-terms-in-microsoft-word
' Charles Kenyon
Dim i As Integer
Const n As Integer = 4 ' set number (n) of terms in search
Dim sArray(n) As String ' Create array to hold terms
' Assign values, starting at 0 and going to n-1
Let sArray(0) = "Aenean"
Let sArray(1) = "Pellentesque"
Let sArray(2) = "libero"
Let sArray(3) = "pharetra"
For i = 0 To n - 1
FindAndMark (sArray(i))
Next i
End Sub
Here is a revision using the code from ASH to handle the Array
Sub MultiFindMark2()
' https://stackoverflow.com/questions/69633517/how-do-i-search-and-highlight-multiple-terms-in-microsoft-word
' Charles Kenyon
' modified to use methods proposed by ASH
Dim i As Long
Dim sArray() As String ' Create array to hold terms
' Assign values, starting at 0 and going to n-1
sArray = Split("Aenean Pellentesque libero pharetra") ' your list separated by spaces
For i = 0 To UBound(sArray)
FindAndMark (sArray(i))
Next i
End Sub
With some of the changes showing as comments:
Sub MultiFindMark2()
' https://stackoverflow.com/questions/69633517/how-do-i-search-and-highlight-multiple-terms-in-microsoft-word
' Charles Kenyon
' modified to use methods proposed by ASH
Dim i As Long
' Const n As Integer = 4 ' set number (n) of terms in search
Dim sArray() As String ' Create array to hold terms
' Assign values, starting at 0 and going to n-1
sArray = Split("Aenean Pellentesque libero pharetra") ' your list separated by spaces
' Let sArray(0) = "Aenean"
' Let sArray(1) = "Pellentesque"
' Let sArray(2) = "libero"
' Let sArray(3) = "pharetra"
For i = 0 To UBound(sArray)
FindAndMark (sArray(i))
Next i
End Sub
Note, this still requires the function.

Iterate And add ContentControls in Word VBA Macro

I have a hundreds of word documents that have multiple tables. Each table row has a specific custom style that was applied that identifies the data that goes in the cell. The need is to iterate through the word document, find the style, and add a ContentControl on that item. The issue that I have is the Selection.Find command restarts at the beginning of the document, so it ends up nesting ContentControls. I have tried adding in some counting mechanism, but while it fixes most of the issues, it leaves off at least some of the ContentControls and does have a few nests. I have tried only searching on a specific table but the Selection.Find overrides the selected table. Is there a way to iterate from the beginning of the document to the end so that I can dynamically add the content controls? Each document has 2 different types of tables. There will be only 1 of the following tables:
There can be 1 to 100 of this table:
The contentControl is supposed to encapsulate the data in the Document Level Metadata column. Here is the code I have up to this point
Option Explicit
Sub FindStyleReplaceWithCC()
Dim CCtrl As ContentControl
Do While ActiveDocument.ContentControls.Count > 0
For Each CCtrl In ActiveDocument.ContentControls
If CCtrl.LockContentControl = True Then CCtrl.LockContentControl = False
CCtrl.Delete False
Next
Loop
'For Each CCtrl In ActiveDocument.ContentControls
'For Each CCtrl In ActiveDocument.ContentControls
' MsgBox (CCtrl.Range)
'Next
'Dim CCtrl As ContentControl
Dim sty As Style
Dim oTbl As Table
''''''''''''''''''''''''''''''''''''''''
'Table 1
Dim thearray(1 To 13, 1 To 2)
Dim element As Variant
Dim arrWsNames() As Variant
Dim I As Integer
arrWsNames = Array("Sensitive Information Protection", "Applies To", "Functional Org", "Functional Process Owner", _
"Topic Owner", "Subject Matter Experts", "Author", "Corporate Source ID", "Superior Source", "CIPS Legacy Document", _
"Meta-Roles(DocLvl)", "SME Reviewer", "SourceDocs")
For I = 1 To 13
thearray(I, 1) = arrWsNames(I - 1)
thearray(I, 2) = 0
Next
Dim howmany As Integer
howmany = 0
For Each element In arrWsNames
Dim iterations As Integer
With Selection.Find
.ClearFormatting
.Style = ActiveDocument.Styles(element)
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = False
.Wrap = wdFindContinue
End With
Selection.Find.Execute
Selection.Range.ContentControls.Add (wdContentControlRichText)
Selection.ParentContentControl.Title = element
Next
'''''''''''''''''''''''''''''''''''''
'Table 2
Dim thearray2(1 To 8, 1 To 2)
Dim arrWsNames2() As Variant
arrWsNames2 = Array("Meta-ReqType", "Meta-Roles", "Meta-Input", "Meta-Output", "Meta-Toolset", _
"Meta-Sources", "Meta-Traced", "Meta-Objective_Evidence")
For I = 1 To 8
thearray2(I, 1) = arrWsNames2(I - 1)
thearray2(I, 2) = 0
Next
howmany = 0
For Each element In arrWsNames2
iterations = 1
For Each oTbl In ActiveDocument.Tables
oTbl.Select
With Selection.Find
.ClearFormatting
.Style = ActiveDocument.Styles(element)
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = False
.Wrap = wdFindContinue
End With
Selection.Find.Execute
If howmany + 1 = iterations Then
Selection.Range.ContentControls.Add (wdContentControlRichText)
Selection.ParentContentControl.Title = element
howmany = howmany + 1
iterations = iterations - 1
Else
iterations = iterations + 1
End If
Next
Next
MsgBox ("Done")
End Sub
If this can't be done in VBA, can it be done in .net?
This can definitely be done in VBA.
The first thing you need to do is to stop using the Selection object. Although there are occasions when Selection has to be used most things can be accomplished by using Range instead.
The next thing I recommend is breaking your code down into separate routines that only perform one element of the solution. This will not only enable you to simplify your code it will result in reusable routines.
I have edited your code as below and tested it in O365 on a document with a subset or your styles.
Sub AddContentControlsForMetadata()
RemoveContentControls ActiveDocument
Dim element As Variant
Dim arrWsNames() As Variant
arrWsNames = Array("Sensitive Information Protection", "Applies To", "Functional Org", "Functional Process Owner", _
"Topic Owner", "Subject Matter Experts", "Author", "Corporate Source ID", "Superior Source", "CIPS Legacy Document", _
"Meta-Roles(DocLvl)", "SME Reviewer", "SourceDocs", "Meta-ReqType", "Meta-Roles", "Meta-Input", "Meta-Output", "Meta-Toolset", _
"Meta-Sources", "Meta-Traced", "Meta-Objective_Evidence")
For Each element In arrWsNames
FindStyleReplaceWithCC ActiveDocument, CStr(element)
Next element
End Sub
Sub RemoveContentControls(docTarget As Document)
Dim ccIndex As Long
For ccIndex = docTarget.ContentControls.Count To 1 Step -1
With docTarget.ContentControls(ccIndex)
If .LockContentControl = True Then .LockContentControl = False
.Delete False
End With
Next ccIndex
End Sub
Sub FindStyleReplaceWithCC(searchDoc As Document, styleName As String)
Dim findRange As Range
Dim ccRange As Range
Set findRange = searchDoc.Range
With findRange.Find
.ClearFormatting
.Style = ActiveDocument.Styles(styleName)
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
Do While .Execute = True
If findRange.Information(wdWithInTable) Then
findRange.Expand wdCell
End If
Set ccRange = findRange.Duplicate
AddContentControlToRange ccRange, styleName
'need to collapse the findRange so that Find can continue without finding the same location again
findRange.Collapse wdCollapseEnd
Loop
End With
End Sub
Sub AddContentControlToRange(ByVal ccLocation As Range, ByVal ccTitle As String)
ccLocation.ContentControls.Add(wdContentControlRichText).Title = ccTitle
End Sub
EDIT:
To add both a tag and a title to the content control:
Sub AddContentControlToRange(ByVal ccLocation As Range, ByVal ccTitle As String, ByVal ccTag as String)
With ccLocation.ContentControls.Add(wdContentControlRichText)
.Title = ccTitle
.Tag = ccTag
End With
End Sub

Replace all instances of a string but the first one

I need to clean up text in a MS Word file.
I receive text from a web form like this and then get it as a Word file.
Confirmed: Something
Confirmed: Else
Confirmed: every
Confirmed: time
I would like to get rid of all the "Confirmed" but for the first one, to get something like the following.
Confirmed:
Something
Else
every
time
I count all the words with
Function CountOccurrences(ByVal strToCount As String) As Integer
Dim iCount As Integer
iCount = 0
With ActiveDocument.Content.Find
.Text = strToCount
.Format = False
.Wrap = wdFindStop
Do While .Execute
iCount = iCount + 1
Loop
End With
CountOccurrences = iCount
End Function
I found articles on how to delete just the first one, or the last one, but can't figure out, how to delete all but the first one.
Try this:
Sub tester()
Dim col As Collection, i As Long
Set col = AllOccurrences("Confirmed:")
For i = 2 To col.Count
col(i).Text = vbTab 'replace the text with a tab
Next i
End Sub
'Return a collection with all instances of strToMatch in the activedocument
Function AllOccurrences(ByVal strToMatch As String) As Collection
Dim rv As New Collection, rng As Range
Set rng = ActiveDocument.Range
With rng.Find
.Text = strToMatch
.Format = False
.Wrap = wdFindStop
Do While .Execute
rv.Add ActiveDocument.Range(rng.Start, rng.End)
Loop
End With
Set AllOccurrences = rv
End Function

VBA Word macro not working as expected with field results in document

I have a word document (report) and in that document, I'm importing many text files with fields like this:
{INCLUDETEXT "C:\\PATH\\TOXMLFILES\\Request.xml" \*CHARFORMAT}
Also I'm updating all those fields with a macro on opening the document...
Sub AutoOpen()
With Options
.UpdateFieldsAtPrint = True
.UpdateLinksAtPrint = True
End With
ActiveDocument.Fields.Update
End Sub
Now I need to highlight the text of those imported XMLs (in the IncludeText fields) between <faultstring></faultstring> tags
Here is code I got here on stackoverflow for highlighting text (making it bold)
Sub BoldBetweenQuotes()
' base for a quotes finding macro
Dim blnSearchAgain As Boolean
Dim blnFindStart As Boolean
Dim blnFindEnd As Boolean
Dim rngFind As word.Range
Dim rngFindStart As word.Range
Dim rngFindEnd As word.Range
Set rngFind = ActiveDocument.content
Set rngFindStart = rngFind.Duplicate
Do
' set up find of first of quote pair
With rngFindStart.Find
.ClearFormatting
.Text = "<faultstring>"
.Replacement.Text = ""
.Forward = True
.wrap = wdFindStop
blnFindStart = .Execute
End With
If blnFindStart Then
rngFindStart.Collapse wdCollapseEnd
Set rngFindEnd = rngFindStart.Duplicate
rngFindEnd.Find.Text = "</faultstring>"
blnFindEnd = rngFindEnd.Find.Execute
If blnFindEnd Then
rngFindStart.End = rngFindEnd.Start
' make it bold
rngFindStart.Font.Bold = True
rngFindStart.Start = rngFindEnd.End
rngFindStart.End = rngFind.End
blnSearchAgain = True
Else
blnSearchAgain = False
End If
Else
blnSearchAgain = False
End If
Loop While blnSearchAgain = True
End Sub
Problem is, when I run the macro in my Word document (with the IncludeText fields) it keeps cycling and bolding just the first appearance of text between faultstring tags. When I run it in a new Word document with some random text and faultrstring tags it works well...
EDIT: It turns out the problem is due to the faultstring tags being inside the IncludeText fields. I need to turn the fields into static text after opening the document and updating the fields. How can I do that?
In order to convert dynamic field content to static text using Word's object model (such as VBA) the Fields.Unlink method is required. For the entire document:
ActiveDocument.Fields.Unlink
This is also possible for any given Range; to remove the fields in the last paragraph, for example:
ActiveDocument.Paragraphs.Last.Range.Fields.Unlink
In order to unlink only a certain type of field, loop the Fields collection, test the Field.Type and unlink accordingly. For example, for IncludeText:
Sub DeleteIncludeTextFields()
Dim doc As word.Document
Set doc = ActiveDocument
Debug.Print DeleteFieldType(wdFieldIncludeText, doc)
End Sub
Function DeleteFieldType(fldType As word.WdFieldType, doc As word.Document) _
As Long
Dim fld As word.Field
Dim counter As Long
counter = 0
For Each fld In doc.Fields
If fld.Type = wdFieldIncludeText Then
fld.Unlink
counter = counter + 1
End If
Next
DeleteFieldType = counter
End Function
Assuming you want to do this for all the fields in your document, after updating it:
Sub AutoOpen()
With Options
.UpdateFieldsAtPrint = True
.UpdateLinksAtPrint = True
End With
ActiveDocument.Fields.Update
ActiveDocument.Fields.Unlink
End Sub