VBA code to search for text and stop at next occurrence including footnotes, headers etc - vba

I am trying to write some code that will search through all the stories including headers, footers, footnotes etc and then stop at each occurrence so the user can make a decision about it (it may or may not change), then click a button again to move to the next occurrence (like Word's Find Next).
I am aware there is some pretty tricky code for performing a search and replace using the range object and I have that code working for another part of this project, but what I can't do is make it search and stop at the selected text, then carry on looking in the different stories, it just stops at the end of the main document.
The code below looks as though it should work but even if the footnote for example has the text to be searched for, it is ignoring it. I have done a thorough search of this site and others and have found several examples for search and replace, but none for search and stop/select.
Any advice gratefully received - thank you.
Sub TestSelection()
Dim rngStory As Range
Dim docDocument As Document
Set docDocument = ActiveDocument
With docDocument
For Each rngStory In .StoryRanges
Select Case rngStory.StoryType
Case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
Debug.Print rngStory.StoryType
With Selection.Find
.ClearFormatting
.Text = "XYZ"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
If Selection.Find.Found = True Then
Exit Sub
End If
End Select
Next rngStory
End With
End Sub

Whether this is your problem in this case I don't know, but while your loop iterates over all the ranges returned by StoryRanges, it does not process the entire document. It only includes the first part of each story. (So, for example, if there are several sections in your document, it will only include the header & footer from the first section).
You need to use the NextStoryRange method in order to access the entire story. Look that up in VBA help for an example loop construct. (It's a horrible API - just as bad as Range.Find!).
Also, be aware that executing a search will change the selection, so Selection.Find will suddenly be searching in the last result, rather than the entire range.

Related

Consistent syntax error while attempting to install macros in Word 365

I'm not technologically oriented at all, but I'm attempting to install some macros to Word for proofreading and editing. I've followed every tutorial but keep getting a syntax error on the very first line. Like Sub Confusables() or Sub PassiveWords() both keep having syntax errors. I've checked multiple times that the spelling of the macro name is correct and that I followed the tutorials exactly, but I keep getting the same error. Please help!
I've done everything I know how/that I saw advised for syntax errors online: Triple-checked Macro name. Tried deleting extraneous text identifying source of Macro. Tried using Step Into to debug. All resulted in same syntax error.
Here is one of the macros I'm trying to run:
Sub PassiveWords()
‘ Highlights passive words
‘ Written by Roger Mortis, revised by Subcortical, adapted by Jami Gold and tweaked by C.K. MacLeod; words selected from Ryan Macklin’s passive words list at http://ryanmacklin.com/2012/05/passive-voice-words/
Dim range As range
Dim i As Long
Dim TargetList
TargetList = Array(“be”, “being”, “been”, “am”, “is”, “are”, “was”, “were”, “has”, “have”, “had”, “do”, “did”, “does”, “can”, “could”, “shall”, “should”, “will”, “would”, “might”, “must”, “may”)
For i = 0 To UBound(TargetList)
Set range = ActiveDocument.range
With range.Find
.Text = TargetList(i)
.Format = True
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
Do While .Execute(Forward:=True) = True
range.HighlightColorIndex = wdBrightGreen
Loop
End With
Next
End Sub

Find a selection of text but not if part of other text

I am trying to find a selection of text in a Word document and then lock the content control.
I have one search text 'Our Ref:' and another 'Your Ref:'.
When the second sub searches for 'Our Ref:' it also highlights 'Your Ref:'.
Screenshot of search result for 'our ref:'
I tried to add .MatchPrefix which works within the advanced find in Word, but not in the macro.
Is there a way to either skip the first result or narrow the search?
Private Sub LockOurRef()
With Selection.find
.Text = "Our Ref:"
.MatchWholeWord = True
.Forward = True
.Execute
Selection.Range.ContentControls.Add (wdContentControlGroup)
Selection.ParentContentControl.LockContentControl = True
End With
End Sub

MS Word VBA Find Variable-Length Pattern String

Question: Is there a way to specify a repeating pattern of variable but bounded length in the Find.Text argument?
Background:
I have a collection of Word documents, each containing several hundred pages of numbered text blocks. I want to copy each block of text into its own cell in a spreadsheet, but the text blocks aren't in Ordered or Multi-Level Lists and each block of text may contain multiple paragraphs, so I can't simply select and copy each paragraph in the document. To work around this, I've tried to use the Range.Find method to locate two adjacent number headings and copy all the characters between them. For testing purposes, I'm using the following sample document:
The paragraph header numbers can be 2-5 levels deep, with 1-2 digits in each level (i.e. "x.x." through "xx.xx.xx.xx.xx."). I'm using a wildcard search of the form "xx.xx.", relying on the placement of the decimal points to identify the headers. Here's my code:
'Open the Word document
Doc = CStr(folderPath & objFile.Name)
Set wDoc = wApp.Documents.Open(Doc)
Set wRange = wDoc.Range
RngEnd = wRange.End
'Search for text block
With wRange
Do While i < 7 And subRngStart2 < RngEnd
With .Find 'Search for starting keyword
.ClearFormatting
.Text = "[0-9]{1,2}.[0-9]{1,2}."
.Forward = True
'.Format = True
.MatchWildcards = True
.MatchCase = False
.Execute
End With
If .Find.Found = True Then
subRngStart1 = wRange.Start 'Mark starting position
wRange.SetRange Start:=subRngStart1 + 6, End:=RngEnd 'Reset range starting at end of keyword
contentFlag = True
Else
contentFlag = False
End If
With .Find 'Search for ending keyword
.ClearFormatting
.Text = "[0-9]{1,2}.[0-9]{1,2}."
.Forward = True
.MatchWildcards = True
.MatchCase = False
.Execute
End With
If .Find.Found = True Then
subRngStart2 = wRange.Start 'Mark ending position
Else
subRngStart2 = RngEnd
End If
wRange.SetRange Start:=subRngStart1, End:=subRngStart2 'Set range between first and second keywords
'Copy text in range to Excel
If contentFlag = True Then
Cells(i + 1, 1) = wRange.Text
End If
wRange.SetRange Start:=subRngStart2 - 3, End:=RngEnd 'Reset range starting at last keyword
i = i + 1
Loop
End With
This works fine for headers up to 3 levels but breaks down beyond that: the "Long Headers" example gets split in half because the search thinks the first two levels in the string form a complete text block (Row 7 in the output sample below).
I could just increase the starting offset (first IF statement, second line) from 6 to 10 to "skip over" long number strings, but this can cause problems with very short headers. I think the proper way to fix this is to search for a pattern of the form "xx.xx." which may repeat up to 4 consecutive times. I've tried a couple of variations on the wildcard string to achieve this, including:
.Text = "[0-9]{1,2}.*[0-9]{1,2}."
.Text = "[0-9]{1,2}.[0-9]{0,2}[.]{0,1}[0-9]{1,2}."
But these either don't do what I want or fail to compile (I'm guessing a min length of zero isn't allowed in wildcard charlists). Is it possible to specify variable-length patterns in Find.Text, or do I need to take a completely different approach?

vba ".find" stopped working

Currently this code runs with no error message but does not make the requested replacement:
Private Sub TestingButton_Click()
Dim RngFound As Range
Dim FileToProcess As Word.Document
Dim WordInstance As Object
Set WordInstance = CreateObject("Word.Application") 'For these tests I close Word first.
Set FileToProcess = WordInstance.Documents.Open("c:\sarah\junk\Attach.doc")
WordInstance.ActiveDocument.Range.Select 'Gets the whole document
Toolbox.SetupFind (WordInstance.ActiveDocument.Range)
With WordInstance.ActiveDocument.Range.Find
.Text = "rock"
.Replacement.Text = "found it!"
.Execute
End With
End Sub
In the Toolbox module:
Public Function SetupFind(ByRef RngPassed As Word.Range)
With RngPassed.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
End With
End Function
The code above is from a test database that I created for troubleshooting. My destination is an Access form whose purpose is to extract some information from a set of documents, and place the information in a database. It was working fine. It stopped working after a particularly spectacular crash. I tried to remove a label while the form was in break mode. (I know,... deep regret) I couldn't close Access even with ctrl-break. Probably the wierdest part is that similar code now no longer works in back-up copies of the database either.
Here are things I've tried that haven't worked:
Re-import all objects into a new database.Similarly, rebuild the backend database. Break the form's code (a couple thousand lines)
into modules. Copy all the code into Notepad, save it, then create a
button in a new empty database. Recreate subs & functions by
typing, then once they exist, paste in the code from Wordpad. Reset
the form's references, which include MSWord. Make a brand new form
in the new database with one button that has only the displayed
code. Use a defined range: This all started when I got a persistent
error in RngToSearch.find.execute findtext:="reason". The error
highlighted .find and said 'Argument not optional.' So among other
things, I switched to a selection rather than a defined range in my
attempts to isolate the problem. But working with a range rather
than a selection is where I really need to end up again.
Possibly relevant observations:
Other forms in the same database with lots of backend code work fine. So do other routines in the same form.
Only one document is open. As far as I can tell, there's nothing unusual about the document. I have tried multiple documents.
The text to find exists in the document, outside of a table.
Things that have worked, as they might be clues:
In Word straight up, no code, no nothing, use 'find' to select the targeted word.
WordInstance.ActiveDocument.Range.Text = Replace(WordInstance.ActiveDocument.Range.Text, "rock", "Found it!"). Trouble is, what I ultimately need to do again is way more complex than Replace can handle.
Specify the scope for the replacement(s). The code worked for me from Access 2010 with this change in TestingButton_Click() ...
'.Execute
.Execute Replace:=wdReplaceAll
Toolbox.SetupFind also calls .Execute. Since the find and replacement text are both empty strings at that point and no scope is specified, .Execute doesn't cause harm ... but it doesn't seem useful either.

VBA Compare positions of two words in a Word document

I'm writing a module for defining acronyms in Word documents. The script gets the acronym and definition from an Excel document. What I'm having trouble with is comparing the location of the first instance of the acronym with the location of the first instance of the full definition.
Ultimately, I need to make sure the first instance of the acronym occurs immediately after the first instance of the definition, enclosed in parentheses. After this is done, the script will need to remove subsequent instances of the definition, so I also need to figure out how to remove all but the first instance of a definition.
The end result should look something like this:
....This document is about software as a service (SaaS). SaaS is software that is hosted by someone else. Rather than installing it on your own computer, you access it through a Web browser. There are many types of SaaS.
....
How can I get the positions of these two elements and or compare their positions?
In the example above, how would I find the first instance of "SaaS" and make sure it occurred exactly two positions after (space, open parentheses) the definition (assuming the definition actually appears in the document)?
'Selects first instance of acronym. Get start and end positions of first instance of acronym.
Selection.HomeKey Unit:=wdStory
Selection.Find.Execute Acronym 'Acronym is a variable. Now that it's selected, I need to get it's start position (or the position of the cursor if the cursor is at the start of the acronym) or find a way to compare it's position to the UserSelection variable.
'Is the definition in the document?
'If no, add definition before first instance of acronym.
'If yes, get start and end positions of first instance of definition.
'Is end position of first instance of definition adjacent to start position of first instance of acronym? If not, which is first?
'If definition is first, add acronym behind definition.
'If acronym is first, add definition in front of acronym and delete remaining instances of definition.
'Highlights all instances of the acronym in green
With ActiveDocument.Content.Find
.MatchCase = True
.MatchWholeWord = False
With .Replacement
.Highlight = True
End With
.Execute Replace:=wdReplaceAll, Wrap:=wdFindContinue, FindText:=Acronym, ReplaceWith:=Acronym
End With
Any help or insight would be appreciated, as I'm completely at a loss and having no luck with Google.
-Vince
I think the following code snippet can help you:
Sub example(acronym, definition)
Selection.Find.ClearFormatting
With Selection.Find
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchAllWordForms = False
End With
ActiveDocument.Range(0, 0).Select ' go to beginning of document
Selection.Find.Text = acronym
Selection.MatchSoundsLike = False
If (Not Selection.Find.Execute) Then
' acronym not found in this document
Exit Sub
End If
ActiveDocument.Range(0, 0).Select ' go to beginning of document
Selection.Find.Text = definition
Selection.MatchCase = False
Selection.MatchSoundsLike = True
While (Selection.Find.Execute)
'
Selection.Delete ' delete all definitions
'
Wend
ActiveDocument.Range(0, 0).Select ' go to beginning of document
Selection.Find.Text = acronym
Selection.MatchSoundsLike = False
If (Selection.Find.Execute) Then
Selection.InsertBefore "(" & definition & ")"
End If
End Sub
Note: I also found that authors make mistakes in the definitions (minor variations) and even an extra unintended space messes up the find.