I am trying to find a string in my word document header table. I can find the range but I want to store the specific cell which this found range is located in into a table.cell variable.
How can I do it?
In VBA, for example, to retrieve the cell's table#, row # & column #:
Sub GetCellRef()
Application.ScreenUpdating = False
Dim Rng As Range, t As Long, r As Long, c As Long
With ActiveDocument.Sections.First.Headers(wdHeaderFooterPrimary)
Set Rng = .Range
With .Range
With .Find
.Text = "Text to Find"
.Forward = True
.Wrap = wdFindStop
.MatchCase = False
.MatchWholeWord = True
.Execute
End With
If .Find.Found = True Then
If .Information(wdWithInTable) = True Then
Rng.End = .Cells(1).Range.End - 1
t = Rng.Tables.Count
Rng.Start = .Start
r = .Cells(1).RowIndex
c = .Cells(1).ColumnIndex
MsgBox Chr(34) & Rng.Text & Chr(34) & vbCr & _
"Found in table " & t & " at row " & r & " column " & c
End If
End If
End With
End With
Application.ScreenUpdating = True
End Sub
I want to change all the cross-reference text that are in middle of the text to lowercase, but not the ones at the beginning of a sentence.
The problem is that ActiveDocument.Paragraphs(row).Range.Text is going through the original text, not the ShowFieldCodes (or Alt+F9 view), which generates longer paragraphs and rows.
Sub SetLowerCase()
Dim bBig As Boolean
Dim txt As String, row As String, pos As Integer
ActiveWindow.View.ShowFieldCodes = True
Selection.HomeKey unit:=wdStory
With Selection.Find
.ClearFormatting
.Text = "^d REF"
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = False
Do While .Execute
pos = Selection.Information(wdFirstCharacterColumnNumber)
row = Selection.Information(wdFirstCharacterLineNumber)
'The problem is that "ShowFieldCodes" generates longer paragraphs and more rows, which the next line does not take into account
txt = ActiveDocument.Paragraphs(row).Range.Text
If pos = 1 Then
bBig = True
ElseIf Mid(txt, pos - 2, 2) = ". " Then
bBig = True
ElseIf Mid(txt, pos - 1, 1) = "." Then
bBig = True
End If
If bBig = False Then
If Not Selection.Text Like "*Lower*" Then
With Selection
.MoveRight unit:=wdCharacter, Count:=1
.MoveLeft unit:=wdCharacter, Count:=1
.TypeText Text:="\*Lower "
.Fields.Update
End With
End If
Else
bBig = False
End If
Selection.Collapse wdCollapseEnd
Loop
End With
ActiveWindow.View.ShowFieldCodes = False
End Sub
For example:
Sub Demo()
Application.ScreenUpdating = False
Dim Fld As Field, Rng As Range, StrRef As String
With ActiveDocument
.ActiveWindow.View.ShowFieldCodes = False
For Each Fld In .Range.Fields
With Fld
If .Type = wdFieldRef Then
Set Rng = .Result: StrRef = Split(Trim(.Code.Text), " ")(1)
Rng.MoveStart wdSentence, -1
Rng.MoveEnd wdSentence, 1
If Rng.Sentences.Count = 1 Then
.Code.Text = "REF " & StrRef & " \* Lower \h"
Else
.Code.Text = "REF " & StrRef & " \* FirstCap \h"
End If
End If
End With
Next
.Fields.Update
End With
Application.ScreenUpdating = True
End Sub
Whichever approach you take, you'll run up against VBA's ignorance of what a grammatical sentence is. For example, consider the following:
Mr. Smith spent $1,234.56 at Dr. John's Grocery Store, to buy: 10.25kg of potatoes; 10kg of avocados; and 15.1kg of Mrs. Green's Mt. Pleasant macadamia nuts.
For you and me, that would count as one sentence; for VBA it counts as 5...
I want to loop through a document, and for each word, see if there is a match within 250 words (125 behind and 125 ahead).
If there is a match(s), highlight it. Certain words are excluded. These are stored in a dictionary.
To test the loop I am using,
For Each para In ActiveDocument.Paragraphs
For Each wrd In para.Range.Words
Debug.Print wrd & "----" & wrd.Start
Next wrd
Next para
The problem:
"World" in the sentence "I hate traveling to the spirit world", prints 32 when I am looking for 7.
I want to do something like:
If wrd < 125 Then
Set wrdRng = ActiveDocument.Range(Start:=wrd - 125, End:=ActiveDocument.Words(wrd + 125).End)
Else
Set wrdRng = ActiveDocument.Range(Start:=0, End:=ActiveDocument.Words(250 - wrd).End)
End if
Edit:
The current code I'm using completes a loop on a 50,000 word document in about 13 minutes. That is entirely too long. Anyone have a better alternative?
Sub Demo()
Application.ScreenUpdating = False
Dim i As Long
Dim StartTime As Double
Dim MinutesElapsed As String
StartTime = Timer
For Each Para In ActiveDocument.Paragraphs
For Each wrd In Para.Range.Words
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "<(McKnight)*\1>"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWildcards = True
End With
Do While .Find.Execute
If .ComputeStatistics(wdStatisticWords) < 100 Then
i = i + 1
.Words.First.HighlightColorIndex = wdBrightGreen
.Words.Last.HighlightColorIndex = wdBrightGreen
End If
.End = .End - Len(.Words.Last)
.Collapse wdCollapseEnd
Loop
End With
Application.ScreenUpdating = True
'MsgBox i & " instances found."
Debug.Print wrd
Next wrd
Next Para
MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
MsgBox "This code ran successfully in " & MinutesElapsed & " minutes", vbInformation
End Sub
EDIT:
Sub Demo()
Application.ScreenUpdating = False
Dim i As Long
Dim StartTime As Double
Dim MinutesElapsed As String
StartTime = Timer
For Each para In ActiveDocument.Paragraphs
For Each wrd In para.Range.Words
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "<(wrd)*\1>"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWildcards = True
End With
Do While .Find.Execute
If .ComputeStatistics(wdStatisticWords) < 100 Then
i = i + 1
.Words.First.HighlightColorIndex = wdBrightGreen
.Words.Last.HighlightColorIndex = wdBrightGreen
End If
.End = .End - Len(.Words.Last)
.Collapse wdCollapseEnd
Loop
End With
Application.ScreenUpdating = True
'MsgBox i & " instances found."
'Debug.Print wrd
Next wrd
Next para
MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
MsgBox "This code ran successfully in " & MinutesElapsed & " minutes", vbInformation
End Sub
Try the following. Amongst other things, it allows you to specify words to ignore (e.g. prepositions, articles, etc.). Additionally different highlights are used to identify all 'hits' on a given word. A progress report is given on the status bar. On my laptop, it takes about 6:40 for a 50,000 word 'lorem' document.
Option Explicit
Dim ArrOut() As String
Sub Demo()
' Turn Off Screen Updating
Application.ScreenUpdating = False
Dim eTime As Single
' Start Timing
eTime = Timer
Dim wdDoc As Document, StrFnd As String, StrTmp As String, Rng As Range
Dim SBar As Boolean, bTrk As Boolean, h As Long, i As Long, j As Long
' Store current Status Bar status, then switch on
SBar = Application.DisplayStatusBar
Application.DisplayStatusBar = True
Set wdDoc = ActiveDocument
With wdDoc
' Store current Track Changes status, then switch off
bTrk = .TrackRevisions: .TrackRevisions = False
'Display status
Application.StatusBar = "Building word list"
'Compile the Find list
Call BuildWordList(.Range.Text)
With .Range.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Format = False
.MatchCase = False
.MatchWholeWord = True
.Wrap = wdFindStop
.Execute
End With
End With
'Process all words in the concordance
For i = 0 To UBound(ArrOut())
StrFnd = ArrOut(i)
h = i Mod 14
If h < 6 Then
h = h + 2
Else
h = h + 3
End If
'Display current word
Application.StatusBar = "Processing: " & StrFnd
'Use wildcards, if possible, for extra speed
If Len(StrFnd) < 4 Then
StrTmp = ""
For j = 1 To Len(StrFnd)
StrTmp = StrTmp & "[" & UCase(Mid(StrFnd, j, 1)) & Mid(StrFnd, j, 1) & "]"
Next
StrFnd = StrTmp
With wdDoc.Range
With .Find
.MatchWildcards = True
.Text = "<(" & StrFnd & ")>*<(" & StrFnd & ")>"
.Forward = True
.Wrap = wdFindStop
End With
Do While .Find.Execute
If .ComputeStatistics(wdStatisticWords) < 100 Then
If .Words.First.HighlightColorIndex <> h Then .Words.First.HighlightColorIndex = h
.Words.Last.HighlightColorIndex = h
End If
.End = .End - Len(.Words.Last)
.Collapse wdCollapseEnd
Loop
End With
Else
With wdDoc.Range
With .Find
.MatchWildcards = False
.Text = StrFnd
.Forward = True
.Wrap = wdFindStop
.Execute
End With
Set Rng = .Duplicate
Do While .Find.Execute
Rng.End = .Duplicate.End
With Rng
If .ComputeStatistics(wdStatisticWords) < 100 Then
If .Words.First.HighlightColorIndex <> h Then .Words.First.HighlightColorIndex = h
.Words.Last.HighlightColorIndex = h
End If
End With
Set Rng = .Duplicate
.Collapse wdCollapseEnd
Loop
End With
End If
DoEvents
Next
' Restore original Track Changes status
wdDoc.TrackRevisions = bTrk
' Clear the Status Bar
Application.StatusBar = False
' Restore original Status Bar status
Application.DisplayStatusBar = SBar
' Restore Screen Updating
Application.ScreenUpdating = True
' Calculate elapsed time
eTime = (Timer - eTime + 86400) Mod 86400 ' Just in case execution time spans midnight
MsgBox "Execution took " & Format(eTime / 86400, "hh:mm:ss") & " to process"
End Sub
Sub BuildWordList(StrIn As String)
Dim StrFnd As String, i As Long, j As Long, k As Long
'Define the exlusions list
Const StrExcl As String = "a,am,and,are,as,at,be,but,by,can,cm,did,do,does,eg," & _
"en,eq,etc,for,get,go,got,has,have,he,her,him,how,i,ie,if,in,into,is," & _
"it,its,me,mi,mm,my,na,nb,no,not,of,off,ok,on,one,or,our,out,re,she," & _
"so,the,their,them,they,t,to,was,we,were,who,will,would,yd,you,your"
'Strip out unwanted characters
For i = 1 To 255
Select Case i
Case 1 To 31, 33 To 64, 91 To 96, 123 To 144, 147 To 191, 247
Do While InStr(StrIn, Chr(i)) > 0
StrIn = Replace(StrIn, Chr(i), " ")
Loop
End Select
Next
'Convert smart single quotes to plain single quotes & delete any at the start/end of a word
StrIn = Replace(Replace(Replace(Replace(StrIn, Chr(145), "'"), Chr(146), "'"), "' ", " "), " '", " ")
'Convert to lowercase
StrIn = " " & LCase(StrIn) & " "
'Process the exclusions list
For i = 0 To UBound(Split(StrExcl, ","))
StrFnd = " " & Split(StrExcl, ",")(i) & " "
Do While InStr(StrIn, StrFnd) > 0
StrIn = Replace(StrIn, " " & Split(StrExcl, ",")(i) & " ", " ")
Loop
Next
'Clean up any duplicate spaces
Do While InStr(StrIn, " ") > 0
StrIn = Replace(StrIn, " ", " ")
Loop
i = 0
Do While UBound(Split(StrIn, " ")) > 1
StrFnd = " " & Split(StrIn, " ")(1) & " ": j = Len(StrIn)
'Find how many occurences of each word there are in the document
StrIn = Replace(StrIn, StrFnd, " ")
k = (j - Len(StrIn)) / (Len(StrFnd) - 1)
'If there's more than one occurence, add the word to our Find list
If k > 1 Then
ReDim Preserve ArrOut(i)
ArrOut(i) = Trim(StrFnd)
i = i + 1
End If
Loop
WordBasic.SortArray ArrOut()
End Sub
I have a list of keywords that I need to search for in Word documents.
I made a script that searches the document and highlights all instances found, including matching all word forms (i.e. fix, fixed, fixing). After processing, a message box appears that is supposed to summarize the count of instances found for each word.
The problem is that although the highlighting routine allows for all word forms (.MatchAllWordForms = True), I'm missing something for the count, such that only exact matches are tallied.
Can you help me update this so that all words and word forms are both highlighted and summarized in the message box?
'
' Highlight Macro
Sub HighlightKeywords()
Dim range As range
Dim i As Long
Dim Keywords
' put list of terms to find here
Keywords = Array("wrong", "broke", "fix", "swap", "missing", "mistake", "revert", "oops", "backwards", "shatter", "drop")
For i = 0 To UBound(Keywords)
Set range = ActiveDocument.range
With range.Find
.Text = Keywords(i)
.Format = True
.MatchCase = True
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = True
Do While .Execute(Forward:=True) = True
range.HighlightColorIndex = wdYellow
Loop
End With
Next
ReDim numfound(0 To UBound(Keywords))
For Each wrd In ActiveDocument.Words
idx = 0
For Each var In Keywords
If Trim(wrd.Text) = Keywords(idx) Then
numfound(idx) = numfound(idx) + 1
End If
idx = idx + 1
Next var
Next wrd
idx = 0
For Each var In Keywords
strResults = strResults & Keywords(idx) & " : " & _
numfound(idx) & vbCr
idx = idx + 1
Next var
MsgBox strResults
End Sub
A message box has very limited output capacity. If you really want to go down that path, try:
Sub HighlightKeywords()
Application.ScreenUpdating = False
Dim i As Long, j As Long, Keywords, StrOut As String
' put list of terms to find here
Keywords = Array("wrong", "broke", "fix", "swap", "missing", "mistake", "revert", "oops", "backwards", "shatter", "drop")
For i = 0 To UBound(Keywords)
j = 0
With ActiveDocument.Range
With .Find
.Text = Keywords(i)
.Format = False
.MatchCase = True
.MatchAllWordForms = True
.MatchWildcards = False
.Forward = True
.Wrap = wdFindStop
.Execute
End With
Do While .Find.Found = True
j = j + 1
.Duplicate.HighlightColorIndex = wdYellow
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
StrOut = StrOut & vbCr & Keywords(i) & ": " & j
Next
Application.ScreenUpdating = True
MsgBox "Found the following:" & StrOut
End Sub
I am searching a long MS Word document for words that end in ly and highlighting them yellow.
However, certain words that end in ly are not adverbs (only, family) and I want to skip those.
How do I mod my code logic to do this:
If word=TargetList Then [If word=ExceptionList Then Skip ] Else [ highlight ]
Is a filter statement needed?
Sub FindAdverbs()
'
' FindAdverbs Macro
'
'
Dim range As range
Dim i As Long
Dim TargetList
TargetList = Array("ly") ' 'string to search for
ExceptionList = Array("family", "only") 'string of words to ignore
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
Please try this code. Apart from what #Peh said about using reserved words for naming variables, I have avoided the use of variants. MS Word is about text and text is contained in strings. To use them is both easier and more efficient when manipulating texts.
Sub FindAdverbs()
' 03 Jun 2017
Dim Targets() As String
Dim Exceptions As String
Dim Rng As Range
Dim LastFound As Long, RngEnd As Long
Dim n As Long
Dim i As Long
Targets = Split("ly,lo", ",") 'string to search for
Exceptions = "family,only" 'string of words to ignore
For i = 0 To UBound(Targets)
Set Rng = ActiveDocument.Content
With Rng.Find
.Text = "*" & Targets(i)
.MatchCase = True
.MatchWholeWord = True
.MatchWildcards = True
.Format = False
.Forward = True
.MatchSoundsLike = False
.MatchAllWordForms = False
Do
.Execute
With Rng
If (.End <= LastFound) Then Exit Do
LastFound = .End
RngEnd = .End
.Collapse wdCollapseEnd
.MoveStart wdWord, -1
n = .MoveEndUntil(".,;:!?" & Chr(9) & Chr(10) & Chr(11) & Chr(12) & Chr(13) & Chr(32))
If (InStr(1, Exceptions, Trim(.Text), vbTextCompare) = 0) And (n = 1) Then
.HighlightColorIndex = wdYellow
End If
.SetRange RngEnd, RngEnd
End With
Loop While .Found
End With
Next i
End Sub
As another change I made which is worthy of your attention is in the line .Text = "*" & Targets(i). This will ensure that words containing "ly" in a position other than at the end will not be turned up by the search.