MS Word VBA Find and Loop (NOT Replace) - vba

Sorry for the fundamental question but I am a LONGtime Access VBA developer who is getting my butt kicked by Word.
I am building a solution for someone who uses a Legal Case Management System with a specific numbering scheme (aaa-aaa-##-##-###). I need to search a document for hyphens and then grab the whole paragraph where a hyphen is found. I then need to send the text from that paragraph to a separate function which parses out the file number from the paragraph (this function already works).
If that function is successful, it returns the File Number, otherwise it returns "NOT FOUND". So, I need to:
Find all hyphens
Capture the paragraph with a hyphen
Pass that text to the function
GO TO THE NEXT HYPHEN IF function returns "NOT FOUND"
I have tried dozens of options without success. I typically get stuck in an infinite loop (and do not seem to move forward) or I get to the end of the process with a false failure.
I do not know how to move to the NEXT occurrence of a hyphen and repeat the process. I also do not know how to run the process to the end of the document or stop at the end (without starting all over - because the hyphens REMAIN since this is NOT a replace process).
I have tried so many different versions, but I included one below.
Thanks for any guidance. I do appreciate it.
DGP
Public Sub TestFind77() '
Selection.HomeKey Unit:=wdStory
With Selection.Find
.ClearFormatting
.Text = "-"
.Execute Forward:=True
Do While .Found = True
.Parent.Expand Unit:=wdParagraph
Dim strWTF As String
strWTF = .Parent
'MsgBox strWTF
strResult = fnGetFileNumberFromString(strWTF) ' This works
If strResult <> "NOT FOUND" Then
GoTo success
End If
.Execute Forward:=True
Loop
End With
success:
MsgBox strResult
End Sub

I understand... Good start and you're missing only tiny pieces.
One thing you need is Word's Range object. Best to use that with Find - unlike Selection you can work with multiple Ranges in your code.
Sometimes, when working with Find it's necessary to refer back to the original Range (the entire document, in your case). That doesn't appear to be the case, here, but I've built it in, anyway, on general principle - so you have it if it turns out you need it.
I've found it more reliable to save the result of Find.Execute in a boolean variable, rather than relying on .Found, so I've put that in, as well.
You can pick up the paragraph in which the Range is located using Range.Paragraphs(1). I tried to stick to what you have, but if you want to tighten up your code even more, you could do this as long as you don't need the paragraph for anything else:
strWTF = rngSearch.Paragraphs(1).Range.Text
Good luck!
Public Sub TestFind77()
Dim rngDoc as Word.Range
Dim rngSearch as Word.Range
Dim bFound as boolean
Dim para as Word.Paragraph
Set rngDoc = ActiveDocument.Range
Set rngSearch = rngDoc.Duplicate
With rngSearch.Find
.ClearFormatting
.Text = "-"
bFound = .Execute(Forward:=True)
Do While bFound = True
Set para = rngSearch.Paragraphs(1)
Dim strWTF As String
strWTF = para.Range.Text '???.Parent
'MsgBox strWTF
strResult = fnGetFileNumberFromString(strWTF) ' This works
If strResult <> "NOT FOUND" Then
GoTo success
End If
rngSearch.Collapse wdCollapseEnd 'search from after the found to the end of the doc
bFound = .Execute(Forward:=True)
Loop
End With
success:
MsgBox strResult
End Sub

Thanks Cindy,
I am still not sure that I understand the Word Object Model well enough to understand why...but your answer got it to work.
I did incorporate your suggestion re
strWTF = rngSearch.Paragraphs(1).Range.Text
Here is the final code, including that input:
Public Sub TestFind99()
Dim rngDoc As Word.Range
Dim rngSearch As Word.Range
Dim bFound As Boolean
Dim strWTF As String
Set rngDoc = ActiveDocument.Range
Set rngSearch = rngDoc.Duplicate
With rngSearch.Find
.ClearFormatting
.Text = "-"
bFound = .Execute(Forward:=True)
Do While bFound = True
strWTF = rngSearch.Paragraphs(1).Range.Text
strResult = fnGetFileNumberFromString(strWTF) ' This works
If strResult <> "NOT FOUND" Then
GoTo success
End If
rngSearch.Collapse wdCollapseEnd 'search from after the found to the end of the doc
bFound = .Execute(Forward:=True)
Loop
End With
success:
MsgBox strResult
End Sub

Related

VBA - Find paragraph starting with numbers

I'm using a VBA script to try to find the starting number of a paragraph (they are list items not formatted as such - not trying to format, just find the numbers).
1. First Item
2. Second Item
No number - don't include despite 61.5 in paragraph.
25 elephants should not be included
12. Item Twelve, but don't duplicate because of Susie's 35 items
Is there any way to say in VBA "If start of paragraph has 1-2 numbers, return those numbers". In regex, what I'm looking for is ^(\d\+)\.
Here is a working bit of VBA code - haven't figured out how to CREATE the excel file yet, so if you go to test create a blank test.xslx in your temp folder. Of course this may be simple enough that testing isn't necessary.
Sub FindWordCopySentence()
On Error Resume Next
Dim appExcel As Object
Dim objSheet As Object
Dim aRange As Range
Dim intRowCount As Integer
intRowCount = 1
' Open Excel File
If objSheet Is Nothing Then
Set appExcel = CreateObject("Excel.Application")
'Change the file path to match the location of your test.xls
Set objSheet = appExcel.workbooks.Open("C:\temp\test.xlsx").Sheets("Sheet1")
intRowCount = 1
End If
' Word Document Find
Set aRange = ActiveDocument.Range
With aRange.Find
Do
.ClearFormatting
' Find 1-2 digit number
.Text = "[0-9]{1,2}"
.MatchWildcards = True
.Execute
If .Found Then
' Copy to Excel file
aRange.Expand Unit:=wdSentence
aRange.Copy
aRange.Collapse wdCollapseEnd
objSheet.Cells(intRowCount, 1).Select
objSheet.Paste
intRowCount = intRowCount + 1
End If
Loop While .Found
End With
Set aRange = Nothing
If Not objSheet Is Nothing Then
appExcel.workbooks(1).Close True
appExcel.Quit
Set objSheet = Nothing
Set appExcel = Nothing
End If
End Sub
Thanks!
I would go quite a bit simpler and just check the first few characters of the paragraph:
Option Explicit
Sub test()
Dim para As Paragraph
For Each para In ThisDocument.Paragraphs
With para.Range
If (.Characters(2) = ".") Or (.Characters(3) = ".") Then
If IsNumeric(para.Range.Words(1)) Then
Debug.Print "Do something with paragraph number " & _
para.Range.Words(1) & "."
End If
End If
End With
Next para
End Sub
A more efficient approach, which obviates the need to test every paragraph:
Sub Demo()
Application.ScreenUpdating = False
Dim StrOut As String
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "^13[0-9.]{1,}" ' or: .Text = "^13[0-9]{1,}
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.MatchWildcards = True
.Execute
End With
Do While .Find.Found
StrOut = StrOut & .Text
' or: MsgBox Split(.Text, vbCr)(1)
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
Application.ScreenUpdating = True
MsgBox StrOut
End Sub
As coded, the macro returns the entire list strings where there may be multiple levels (e.g. 1.2). Comments show how to find just the first number where there may be multiple levels and how to extract that number for testing (the Find expression includes the preceding paragraph break).

How to print row of found string?

I'd like to find several strings within Word document and for each string found, I like to print (debug.print for example) the whole row content where the string is found, not the paragraph.
How can I do this? Thanks
Sub FindStrings
Dim StringsArr (1 to 3)
StringsArr = Array("string1","string2","string3")
For i=1 to 3
With
Selection.Find
.ClearFormatting
.Text = Strings(i)
Debug.Print CurrentRow 'here I need help
End With
Next
End Sub
The term Row in Word is used only in the context of a table. I assume the term you mean is Line, as in a line of text.
The Word object model has no concept of "line" (or "page") due to the dynamic layout algorithm: anything the user does, even changing the printer, could change where a line or a page breaks over. Since these things are dynamic, there's no object.
The only context where "line" can be used is in connection with a Selection. For example, it's possible to extend a Selection to the start and/or end of a line. Incorporating this into the code in the question it would look something like:
Sub FindStrings()
Dim StringsArr As Variant
Dim bFound As Boolean
Dim rng As Word.Range
Set rng = ActiveDocument.content
StringsArr = Array("string1", "string2", "string3")
For i = LBound(StringsArr) To UBound(StringsArr)
With rng.Find
.ClearFormatting
.Text = StringsArr(i)
.Wrap = wdFindStop
bFound = .Execute
'extend the selection to the start and end of the current line
Do While bFound
rng.Select
Selection.MoveStart wdLine, -1
Selection.MoveEnd wdLine, 1
Debug.Print Selection.Text
rng.Collapse wdCollapseEnd
bFound = .Execute
Loop
End With
Set rng = ActiveDocument.content
Next
End Sub
Notes
Since it's easier to control when having to loop numerous times, a Range object is used as the basic search object, rather than Selection. The found Range is only selected for the purpose of getting the entire line as these "Move" methods for lines only work on a Selection.
Before the loop can continue, the Range (or, if we were working with a selection, the selection) needs to be "collapsed" so that the code does not search and find the same instance of the search term, again. (This is also the reason for Wrap = wdFindStop).

Word VBA: finding a set of words and inserting predefined comments

I need to automate the insertion of comments into a word document: searching for a predefined set of words (sometimes word strings, and all non case-sensitive) each to which I add a predefined comment.
There are two word sets, with two goals:
Wordset 1: identical comment for each located word
Wordset 2: individual comments (I suggest new text based on the word identified)
I have been semi-automating this with a code that IDs all identified words and highlights them, helping me through the process (but I still need to enter all the comments manually - and I've also been able to enter comments - but only on one word at a time.) As my VBA skills are limited, my attempts to compile a robust macro from bits of other code with similar purposes has unfortunately led me nowhere.
Below are the bits of code I've been using.
Sub HighlightWordList()
Dim range As range
Dim i As Long
Dim TargetList
TargetList = Array("word1", "word2", "word3")
For i = 0 To UBound(TargetList)
Set range = ActiveDocument.range
With range.Find
.Text = TargetList(i)
.Format = True
.MatchCase = True
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
Do While .Execute(Forward:=True) = True
range.HighlightColorIndex = wdYellow
Loop
End With
Next
End Sub
The following code has been able to get me to insert bubbles directly
Sub CommentBubble()
'
'
Dim range As range
Set range = ActiveDocument.Content
Do While range.Find.Execute("Word x") = True
ActiveDocument.Comments.Add range, "my comment to enter in the bubble"
Loop
End Sub
I've tried to have the process repeat itself by doing as shown below, but for reasons I'm certain are evident to many of you (and completely unknown to me) - this strategy has failed, working for "word x" but failing to function for all subsequent words:
Sub CommentBubble()
'
'
Dim range As range
Set range = ActiveDocument.Content
Do While range.Find.Execute("Word x") = True
ActiveDocument.Comments.Add range, "my 1st comment to enter in the bubble"
Loop
Do While range.Find.Execute("Word y") = True
ActiveDocument.Comments.Add range, "my 2nd comment to enter in the bubble"
Loop
End Sub
I've mixed and matched bits of these codes to no avail. Any ideas to help me with either wordset?
Thanks for everyone's help!
Best regards
Benoit, you're almost there! All you need to do is redefine the range object after your first loop (because it would have been exhausted at that point). Like so:
Sub CommentBubble()
Dim rng As range
Set rng = ActiveDocument.Content
Do While rng.Find.Execute("Word x") = True
ActiveDocument.Comments.Add rng, "my 1st comment to enter in the bubble"
Loop
Set rng = ActiveDocument.Content ' <---------------Add This.
Do While rng.Find.Execute("Word y") = True
ActiveDocument.Comments.Add rng, "my 2nd comment to enter in the bubble"
Loop
End Sub
That should do the trick for you (it works on my end). If not, let me know.

Word VBA: How to replace only the next instance of a string via Replacement Object

This is a silly question, but can't figure it out.
Straight from the Microsoft Site:
This example finds every instance of the word "Start" in the active document and replaces it with "End." The find operation ignores formatting but matches the case of the text to find ("Start").
Set myRange = ActiveDocument.Range(Start:=0, End:=0)
With myRange.Find
.ClearFormatting
.Text = "Start"
With .Replacement
.ClearFormatting
.Text = "End"
End With
.Execute Replace:=wdReplaceAll, _
Format:=True, MatchCase:=True, _
MatchWholeWord:=True
End With
I need to know how to make it so it only finds the next instance of Start and replace it with End. This will leave all other Ends intact throughout the document.
You should use wdReplaceOne in place of wdReplaceAll.
You should be able to adapt this:
Sub Tester()
Const FIND_WHAT as String = "Start"
Const REPLACE_WITH as String = "End"
Const REPLACE_WHICH As Long = 4 'which instance to replace?
Dim rng As Range, i As Long
i = 0
Set rng = ActiveDocument.Content
With rng.Find
.ClearFormatting
.Text = FIND_WHAT
Do While .Execute(Format:=True, MatchCase:=True, _
MatchWholeWord:=True)
i = i + 1
If i = REPLACE_WHICH Then
'Note - "rng" is now redefined as the found range
' This happens every time Execute returns True
rng.Text = REPLACE_WITH
Exit Do
End If
Loop
End With
End Sub
This discussion has some useful suggestions: Replace only last occurrence of match in a string in VBA. In brief, it's a case of looping through your search string from start until the first instance of the search argument is located and replacing just that.

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