How to add exceptions list to a string search? - vba

I'm trying to create a macro that can flag up instances of the prefix 'pre' being used. I can create one that highlights all instances of 'pre' appearing but this then flags words like "present", "pretend" etc.
My plan to go about this was to create an array with words that I wouldn't want to be flagged (like "present") and then use an AND operation so the text equals "pre" and is NOT equal to the words I don't want flagged. My code is below, when running there is a syntax error on the .Text <> Exceptions line. Is there a better way to go about this? Thanks!
Sub NeedPrefix()
Dim range As range
Dim i As Long
Dim TargetList
Dim Exception
TargetList = Array(" pre")
Exceptions = Array("prepare", "preparation", "present", "presentation", "presented", "prepared", "pretense", "pretend")
For i = 0 To UBound(TargetList)
Set range = ActiveDocument.range
With range.Find
.Text = TargetList(i)
.Text <> Exceptions
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
Do While .Execute(Forward:=True) = True
ActiveDocument.Comments.Add range, "Is the use of a prefix appropriate?"
Loop
End With
Next
End Sub

Here's one possibility to compare a list of terms with a found Range.
Looping an array for each "hit" would be possible, although time-consuming. Instead, it's possible to compare the found Range with the list using Instr. This returns 0 if the found Range isn't present in the string.
In order for this to work, the found Range needs to be extended to encompass the entire word, so Range's end-point is extended until a space is found.
If you don't need to do anything if a term in the list is found you can, of course, leave out the Else part of the code snippet.
range.Collapse wdCollapseEnd puts the starting point for the next Find loop after the found term - otherwise the loop would repeat endlessly on the same " pre".
I've changed the variable name from range to rng - it's always a bad idea in VBA to use a reserved word (the name of an object, method or property belonging to Word or VBA) as a variable name. Notice, also, the inclusion of .Wrap = wdFindStop - this is important as otherwise the code could continue again from the start of the document.
Sub NeedPrefix()
Dim rng As Range
Dim i As Long
Dim TargetList
Dim Exceptions As String
Dim theException As String
TargetList = Array(" pre")
Exceptions = "prepare preparation present presentation presented prepared pretense pretend"
For i = 0 To UBound(TargetList)
Set rng = ActiveDocument.Content
With rng.Find
.Text = TargetList(i)
.Format = False
.Wrap = wdFindStop
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
Do While .Execute(Forward:=True) = True
Debug.Print "Characters moved: " & rng.MoveEndUntil(" " & Chr(13))
If InStr(Exceptions, rng.Text) = 0 Then
ActiveDocument.Comments.Add rng, "Is the use of a prefix appropriate?"
Else
theException = Mid(Exceptions, InStr(Exceptions, rng.Text))
theException = Mid(theException, 2)
theException = Left(theException, InStr(theException, " ") - 1)
Debug.Print "An exception was found: " & theException
End If
rng.Collapse wdCollapseEnd
Loop
End With
Next
End Sub

Related

How to delete a string of text before a Bookmark if a condition is met?

First and foremost, I'm a novice at this.
The situation is as follows:
A Word template is being edited by a VBA macro upon generating a document. I need to improve the VBA macro by deleting a set string of text (A) and replacing it with a different string of text (B) upon the condition that another specific string of text (C) can be found in the document.
There's a Boolean function
Function findrange(tekst As String) As Boolean
Set place = Documents(ActiveDocument.Name).Content
If place.Find.Execute(findtext:=tekst) = True Then
findrange = True
Else
findrange = False
End If
End Function
By which I can identify if that string of text (A) is found in the document. So far so good.
What I need is to delete that string of text (A), if it is found in the document, upon the condition that another string of text (C) can be found in the document.
How do I go about it? I've tried
If findrange("C") = True Then
If findrange("A") = True Then place.Text = ""
But If True Then doesn't allow nesting Ifs apparently.
You need to set up two Range variables.
Dim rngA as Word.Range
Dim rngC as Word.Range
Then upon finding the Text(A) using your findRange function you need to store the found range in the rngA variable.
Set rngA = place.Range
Next, run the findRange function again using Text(C), and assuming it is found you can then replace the rngA.text with your Text(B) data.
For example:
Sub Demo()
Application.ScreenUpdating = False
Dim RngA As Range, RngB As Range
With ActiveDocument
Set RngA = .Range: Set RngB = .Range
With RngA.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "String C"
.Execute
If .Found = True Then
With RngB.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "String A"
.Replacement.Text = "String B"
.Format = False
.Forward = True
.Wrap = wdFindContinue
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute Replace:=wdReplaceAll
End With
End If
End With
End With
Application.ScreenUpdating = True
End Sub

Visual Basic in Word: compare selection within range

UPDATE: Following the suggestion of Cindy below, I used the InRange function. My function iterates fine through the Find operation. But the function is failing to return FALSE when the selection is outside the named range. See "FAILING HERE" below. Thanks.
Using Visual Basic, I need to validate whether the selection location in a Word document is within a named range. Many years ago, I used this code to do that:
ActiveDocument.Bookmarks("typdef").Select
While ((WordBasic.CmpBookmarks("\Sel", "typedef") = 8 _
Or WordBasic.CmpBookmarks("\Sel", "typedef") = 6 _
Or WordBasic.CmpBookmarks("\Sel", "typedef") = 10) _
And leaveloop <> 1
...
If WordBasic.CmpBookmarks("\Sel", "\EndOfDoc") = 0 Then
leaveloop = 1
End If
Wend
Here's the updated function I wrote:
Function FormatSpecHeadReturn(strStyle)
Dim rngBookmark As Word.Range
Dim rngSelection As Word.Range
Set rngBookmark = ActiveDocument.Bookmarks("SpecBodyPairRange").Range
Set rngSelection = Selection.Range
var = rngSelection.InRange(rngBookmark)
Debug.Print var
Do While rngSelection.InRange(rngBookmark) = True
Selection.Find.Style = ActiveDocument.Styles(strStyle)
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "^p"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindAsk
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
Selection.HomeKey
' FAILING HERE: Returns TRUE when selection point
' is outside SpecBodyPairRange
var = rngSelection.InRange(rngBookmark)
Debug.Print var
Selection.HomeKey Unit:=wdLine, Extend:=wdMove
Selection.InsertBefore Chr(182)
Selection.EndKey
Selection.InsertAfter vbTab
Selection.MoveDown Unit:=wdLine, Count:=1, Extend:=wdMove
If rngSelection.InRange(rngBookmark) <> True Then Exit Do
Loop
End Function
I was using CmpBookmarks in this current project, but it did not reliably returning the value of the current location. When the selection point is within the named range, it returns 8 for two loops, and then returns 6. When the selection point is outside the named range, CmpBookmarks returns 6.
Obviously, CmpBookmarks is deprecated. I can't find the return values that CmpBookmarks produces, and I can't find a modern equivalent function.
I confess I don't understand the difference between the named "SpecBodyPairRange" range and the range assigned to r, here:
Dim r As Range
I can see that "r" in this instance appears to hold the entire document. I studied Range Interface and Selection Interface on Microsoft.Office.Interop.Word, which I don't yet fully understand. I'm not a programmer, only a semi-technical writer self-taught in some coding who has the task of automating document conversion.
There must be a better way to compare the selection point to validate if it's within a named range, but I can't find it. Any pointers you can give me are sincerely appreciated!
Not a big Word VBA person but can you just compare the Start and End properties?
Dim bm As Bookmark
Set bm = ActiveDocument.Bookmarks("tester")
Debug.Print "Bookmark", bm.Start, bm.End
Debug.Print "Selection", Selection.Start, Selection.End
In order to determine whether one Range is within another use the InRange method:
Dim rngBookmark as Word.Range
Dim rngSelection as Word.Range
Set rngBookmark = ActiveDocument.Bookmarks("typeDef").Range
Set rngSelection = Selection.Range
If rngSelection.InRange(rngBookmark) = True Then
'Do something
End If
You could use VBA's InRange Method. For example:
Function FormatSpecHeadReturn(strStyle)
Dim Rng As Range
With ActiveDocument
Set Rng = .Bookmarks("SpecBodyPairRange").Range
With .Bookmarks("SpecBodyPairRange").Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "^p"
.Replacement.Text = ""
.Style = strStyle
.Format = True
.Forward = True
.Wrap = wdFindStop
.MatchWildcards = False
.Execute
End With
Do While .Find.Found
If .InRange(Rng) = False Then Exit Do
.Style = "SpecHead"
.Paragraphs.First.InsertBefore Chr(182)
.InsertAfter vbTab
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
End With
End Function

How to select list, if the list contains specific text using word VBA

I have a vba code for find the specific string found in table, as well as i need a vba code for select the list, if specified text found.
The code was got from here,
Microsoft Word VBA - Select table if cell contains specified string,
Sub Find_Text_in_table()
selection.Find.ClearFormatting
With selection.Find
.Text = "figure id:"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindAsk
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While selection.Find.Execute
If selection.Information(wdWithInTable) Then
MsgBox "Figure ID Found in Table"
Exit Sub
'now you are in table with text you searched
'be careful with changing Selection Object
'do what you need here
End If
Loop
Application.ScreenUpdating = True
End Sub
as well, if the text "Figure ID:" found in any list type, throw an alert message.
this is the list
this is the list
this is the list
Figure Id:
On the whole, it's preferable to work with Range objects instead of Selection. There can be only one selection, but code can work with as many Ranges as necessary. I've altered the original code accordingly. I also changed the Find.Wrap to wdFindStop so that the code searches the entire document, then stops.
The Range object has a ListParagraphs property that will return the ListParagraph object(s) of the Range. In this case, that would be paragraph in which the Find term is located if it belongs to a numbered list. If it does, the Count will be greater than 0 and the code continues to get the Paragraph.Range, from which it's possible to extract all paragraphs that belong to the list using Rnage.ListFormat.List.ListParagraphs.
In order to select the entire list it's necessary to get the Start point of the first list entry and the End point of the last list entry. In the code below, the range of the paragraph in which "Figure Id" was found is extended to these points so that it covers the entire list. Note that it's not clear what you want to do with this, once you have it, since the code loops. It may be that it should not be selected at all but that the action should be performed on the Range object, instead...
Sub Find_Text_withList_in_table()
Dim rngFind As Word.Range, rngFigureList As Word.Range
Dim lstParas As Word.ListParagraphs
Dim lFindCounter As Long 'for testing / debugging
Set rngFind = ActiveDocument.content
rngFind.Find.ClearFormatting
With rngFind.Find
.Text = "figure id:"
.Replacement.Text = ""
.Forward = True
.wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While rngFind.Find.Execute
lFindCounter = lFindCounter + 1
If rngFind.Information(wdWithInTable) Then
Debug.Print "Figure ID Found in Table"
Set lstParas = rngFind.ListParagraphs
If lstParas.Count > 0 Then
Set rngFigureList = lstParas.Item(1).Range
Set lstAllParas = rngFigureList.ListFormat.List.ListParagraphs
Debug.Print "Nr paragraphs in the list: " & lstAllParas.Count
rngFigureList.Start = lstAllParas(1).Range.Start
rngFigureList.End = lstAllParas(lstAllParas.Count).Range.End
rngFigureList.Select
MsgBox "Figure Id is in a numbered list, in a table"
End If
End If
Loop
Debug.Print "Nr Figure ID found: " & lFindCounter
Application.ScreenUpdating = True
End Sub

Replace text with matching Mail Merge Field

I would like to create a macro in MS Word that when run searches the document for text that appears in the body of the document that matches the mail merge field name. Once identified it would change the text in the document to the actual matching mail merge field name. For example, if there was a mail merge field named "project_date" and in the Word document there was the text "project_date" the macro would turn the text into the actual mail merge field "project_date".
Ideally, the macro would do this for all mail merge fields that exists at once.
Below is as far as I have come with formulating my desired code.
I found this code here ( https://answers.microsoft.com/en-us/msoffice/forum/msoffice_word-mso_other-mso_2007/how-do-i-replace-words-in-a-document-with-a-mail/da323980-7c7d-e011-9b4b-68b599b31bf5 ) but it only will do one specified mail merge field at a time.
Dim oRng As Range
Set oRng = ActiveDocument.Range
With oRng.Find
Do While .Execute(FindText:="(Player 1)")
oRng.Fields.Add oRng, wdFieldMergeField, "Player_1", False
oRng.Collapse wdCollapseEnd
Loop
End With
I recorded this myself, but am not sure how to search and replace text with desired merge field.
With Selection.Find
.Text = "project_name"
.Replacement.Text = "project_name"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
The solution for this combines the code for inserting all merge fields into a document with the basic code you found / recorded. Inserting the merge field is moved into the Function that searches the field names in the document. I've set the function up to return the number of times the field is inserted.
The tricky, or special, part of the Function is setting up the Range after a successful Find to continue the search. The end-point of a merge field is still within the merge field, thus the line oRng.MoveStart wdCharacter, 2 is required after collapsing the Range. If the Range stays within the field, the merge field name inside it will be found again, and again, and again...
Sub InsertAllMergeFieldsAtPlaceholders()
Dim doc As word.Document
Dim rng As word.Range
Dim mm As word.MailMergeDataField
Set doc = ActiveDocument
Set rng = doc.content
If doc.MailMerge.MainDocumentType <> wdNotAMergeDocument Then
For Each mm In doc.MailMerge.DataSource.DataFields
Debug.Print ReplaceTextWithMergeField(mm.NAME, rng) & " merge fields inserted for " & mm.NAME
Set rng = doc.content
Next
End If
End Sub
Function ReplaceTextWithMergeField(sFieldName As String, _
ByRef oRng As word.Range) As Long
Dim iFieldCounter As Long
Dim fldMerge As word.Field
Dim bFound As Boolean
With oRng.Find
.ClearFormatting
.Forward = True
.wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
bFound = .Execute(findText:=sFieldName)
End With
Do While bFound
iFieldCounter = iFieldCounter + 1
Set fldMerge = oRng.Fields.Add(oRng, wdFieldMergeField, sFieldName, False)
Set oRng = fldMerge.result
oRng.Collapse wdCollapseEnd
oRng.MoveStart wdCharacter, 2
oRng.End = oRng.Document.content.End
bFound = oRng.Find.Execute(findText:=sFieldName)
Loop
ReplaceTextWithMergeField = iFieldCounter
End Function

To delete everything except for words between a start and end point

I happen to have problems trying to manipulate the below code to my liking.
First off, the code below deletes everything in between the start and end conditions I have stipulated in my program.
I would like to change this, to delete everything besides those stipulated between the start and end words.
Sub SomeSub()
Dim StartWord As String, EndWord As String
Dim Find1stRange As Range, FindEndRange As Range
Dim DelRange As Range, DelStartRange As Range, DelEndRange As Range
Application.ScreenUpdating = False
Application.DisplayAlerts = False
'Setting up the Ranges
Set Find1stRange = ActiveDocument.Range
Set FindEndRange = ActiveDocument.Range
Set DelRange = ActiveDocument.Range
'Set your Start and End Find words here to cleanup the script
StartWord = "From: Research.TA#traditionanalytics.com|Tradition Analytics Commentary| | |"
EndWord = "This message has been scanned for malware by Websense. www.websense.com"
'Starting the Find First Word
With Find1stRange.Find
.Text = StartWord
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindAsk
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
'Execute the Find
Do While .Execute
'If Found then do extra script
If .Found = True Then
'Setting the Found range to the DelStartRange
Set DelStartRange = Find1stRange
'Having these Selections during testing is benificial to test your script
DelStartRange.Select
'Setting the FindEndRange up for the remainder of the document form the end of the StartWord
FindEndRange.Start = DelStartRange.End
FindEndRange.End = ActiveDocument.Content.End
'Having these Selections during testing is benificial to test your script
FindEndRange.Select
'Setting the Find to look for the End Word
With FindEndRange.Find
.Text = EndWord
.Execute
'If Found then do extra script
If .Found = True Then
'Setting the Found range to the DelEndRange
Set DelEndRange = FindEndRange
'Having these Selections during testing is benificial to test your script
DelEndRange.Select
End If
End With
'Selecting the delete range
DelRange.Start = DelStartRange.Start
DelRange.End = DelEndRange.End
'Having these Selections during testing is benificial to test your script
DelRange.Select
'Remove comment to actually delete
DelRange.Delete
End If 'Ending the If Find1stRange .Found = True
Loop 'Ending the Do While .Execute Loop
End With 'Ending the Find1stRange.Find With Statement
End Sub
Hah! That's a new twist There's certainly more than one way to go about it; my inclination would be to work with (at least) three Ranges. Something like this:
Sub FindAndDeleteEverythingElse()
Dim strFind1 As String, strFind2 As String
Dim rngDoc As word.Range, rngFind1 As word.Range
Dim rngFind2 As word.Range
Dim bFound As Boolean
strFind1 = "You"
strFind2 = "directly."
Set rngDoc = ActiveDocument.content
Set rngFind1 = rngDoc.Duplicate
Set rngFind2 = rngDoc.Duplicate
With rngFind1.Find
.Text = strFind1
bFound = .Execute
End With
If bFound Then
With rngFind2.Find
.Text = strFind2
bFound = .Execute
End With
If bFound Then
rngDoc.End = rngFind1.Start
rngDoc.Delete
rngDoc.Start = rngFind2.End
rngDoc.End = ActiveDocument.content.End
rngDoc.Delete
End If
End If
End Sub
The "main" Range is that of the entire document: ActiveDocument.Content. The Range object is a bit different than other objects, if you set one Range to another it becomes that Range, not a copy. So you need the Duplicate method to make a copy of a Range. This lets you use Find independently for the various Ranges.
If the first Find is successful, the second one is executed. If that is also successful then the Document Range's end-point is set to the starting point of the successful Find and the content of the Range deleted. The Document Range is then re-defined to start at the end-point of the second found Range and end at the end of the Document, and deleted.
You will probably have to set more Range.Find properties than I did in this code snippet - I used the absolute minimum to make working with the Ranges clearer.
There maybe another way but till then you can do this.
try to add dummy character after and before your string like this
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchWildcards = False
.Text = "From: Research.TA#traditionanalytics.com|Tradition Analytics Commentary| | |"
.Replacement.Text = "From: Research.TA#traditionanalytics.com|Tradition Analytics Commentary| | |######"
.Execute Replace:=wdReplaceAll
.Text = "This message has been scanned for malware by Websense. www.websense.com"
.Replacement.Text = "######This message has been scanned for malware by Websense. www.websense.com"
.Execute Replace:=wdReplaceAll
End With
End With
End Sub
Then try to set range between ###### and ######
this is best answer to set range select a range of text from one Word document and copy into another Word document
Please note that in my word 2007 it is not possible to find within hyperlinks. Try to remove all hyperlink or within range before doing replacement.
another best answer for that: How do you remove hyperlinks from a Microsoft Word document?