Find all the verbs after "that" and highlight them - vba

Basically I am trying to change the past tense into present tense in a word document by finding verbs after the word "that" and replace them with present participle(-ing). The following code working correctly with regular verbs but cannot handle other type of verbs.
The code uses a wildcard search to find instances of "that" followed by a verb in the past tense, and highlights those instances. if I use
.TEXT = "that [a-z]#ed>"
it only matches regular verbs in the past tense (verbs that end in "-ed"), so it won't match irregular verbs, which can have different forms in the past tense.
To handle irregular verbs, I need to add additional wildcard search patterns to account for the different ways that irregular verbs can be spelled in the past tense. For example, a search pattern for "-t" (for verbs like "left" and "slept"), "-d" (for verbs like "bend" and "send"), "-en" (for verbs like "broken" and "eaten"), etc.
Here is an example of modified code to handle both regular and irregular verbs, This code now search for different past-tense suffixes as well like "-t", "-d" and "-en" and not just "-ed" and this way it can handle all type of verbs.
so to accommodate all the verbs I uses OR with find.
Sub Hi_That_Word_AI()
Dim AD As Range
Dim CR As Range
Dim t, x
Dim pText$
Application.ScreenUpdating = False
t = Timer
x = 0
Set AD = ActiveDocument.Range
With AD.Find
.MatchWildcards = True
.TEXT = "that [a-z]#ed|that [a-z]#t|that [a-z]#d|that [a-z]#en>"
Do While .Execute
If Not AD.Sentences(1).TEXT Like "*[.?]”*" Then
.Parent.Select
AD.HighlightColorIndex = wdYellow
x = x + 1
End If
.Parent.Collapse wdCollapseEnd
Loop
End With
MsgBox Round(Timer - t, 2) & " Seconds. There are " & x & " instances HighLighted in " & AD.Parent.Name, vbOKOnly + vbInformation
Application.ScreenUpdating = True
End Sub
Above code does not return any instances due to use of OR with Find method. what is the right way of doing that kind of searched what are the alternate methods?

Related

Finding Endnote number with wdRestartSection NumberingRule

I am writing a VBA script to convert endnotes to plain text. This is fairly straightforward when the endnotes have continuous numbers (copy all the end notes to the end of the text, number them using the index, and replace all the references with the indexes).
In this case, however, the endnote numbers are configured to reset every section (NumberingRule=wdRestartSection). This means the index is not the number. I've tried to get the number using endnote.Reference.Text, but this is empty. I haven't found anywhere in the object model that has the actual number for each Endnote.
Is this information available?
Is there a way to walk Endnotes per-section rather than for the entire document so that I could track the index myself?
I'm currently trying to fetch it this way:
For Each objEndnote In ActiveDocument.Endnotes
print(objEndnote.Reference.Text)
Next
This just prints empty strings.
Looks like there is no number per section - weird. So you have to count it yourself per section:
Option Explicit
Sub getAllEndnotesWithNumbers()
Dim e As Endnote, section As Long, eCounter As Long
For Each e In ThisDocument.Endnotes
If section <> endnoteSection(e) Then
section = endnoteSection(e)
eCounter = 1
Debug.Print "--- Section " & section & " ----------"
End If
Debug.Print eCounter, e.Range.Text
eCounter = eCounter + 1
Next
End Sub
Private Function endnoteSection(e As Endnote) As Long
endnoteSection = e.Range.Sections(1).Index
End Function

Whats the difference between WorksheetFunction.IsNumber() and IsNumeric() in VBA?

Whats the different between using these two functions in VBA? Is there a speed advantage of using one over the other?
Application.WorksheetFunction.IsNumber(myVar)
IsNumeric(myVar)
IsNumeric is a VBA standard library function located in the VBA.Information module, like IsArray, IsDate, IsError, Err, VarType, and more:
As part of the VBA standard library, it works regardless of the host application: the standard library is safe to presume as existing on a machine that runs VBA.
Application.WorksheetFunction.IsNumber, on the other hand, is found in the Microsoft Excel object model library:
As such, the code can only work if the Excel type library is referenced. If you're already in Excel, that's not a problem. But if you're in Word and you would like to use a function that returns a Boolean when the given String is numeric, you don't want to reference the whole Excel type library just for that.
The WorksheetFunction interface is a gateway to Excel's calculation engine: with it, you can write VBA code that can now use INDEX/MATCH against carefully crafted plain-VBA arrays - it's completely awesome. But there's no guarantee that Excel's calculation engine works exactly the same way VBA does. If you want to go by Excel's rules, use WorksheetFunction.
I would probably presume VBA.Information.IsNumeric to be faster than Excel.WorksheetFunction.IsNumber though: I could be wrong, but it seems like fewer moving parts are involved with just the VBA standard library.
But the only way to find out:
You have two horses - race them!
;-)
TL;DR Unless you have a specific reason to use Excel's semantics for the function (i.e. replicating the result it would give in a cell, don't use IsNumber if you care about performance.
First of all, the result (and performance) is going to differ based on the data type that is passed into the function. If you're pulling values directly from the Worksheet, you have to be aware of the fact that Excel is going to cast the value differently depending on how you access it:
Public Sub Foo()
Debug.Print IsNumeric("1e12") 'True
Debug.Print Application.WorksheetFunction.IsNumber("1e12") 'False
[A1] = "1e12"
'The next 2 are the same, but the first is an implicit call to .Value
Debug.Print Application.WorksheetFunction.IsNumber([A1]) 'True
Debug.Print Application.WorksheetFunction.IsNumber([A1].Value) 'True
Debug.Print Application.WorksheetFunction.IsNumber([A1].Text) 'False
End Sub
Next, there is a small overhead of the dereferencing calls against WorksheetFunction, so I'll control for that in the benchmarks below by wrapping it in a With block. Note also the difference in performance (and return value o_O) between handling a string and a number:
Private Const ITERATIONS As Long = 1000000
Private Sub RunAll()
Test 1000
Test "1000"
End Sub
Private Sub Test(testValue As Variant)
IsNumericBenchMark testValue
IsNumberBenchMark testValue
End Sub
Private Sub IsNumericBenchMark(inputValue As Variant)
Dim start As Single, i As Long
start = Timer
For i = 1 To ITERATIONS
IsNumeric inputValue
Next
Debug.Print "IsNumeric" & vbTab & Timer - start & vbTab & IsNumeric(inputValue)
End Sub
Private Sub IsNumberBenchMark(inputValue As Variant)
Dim start As Single, i As Long
start = Timer
With WorksheetFunction
For i = 1 To ITERATIONS
.IsNumber inputValue
Next
End With
Debug.Print "IsNumber" & vbTab & Timer - start & vbTab & WorksheetFunction.IsNumber(inputValue)
End Sub
Results:
IsNumeric 0.09375 True
IsNumber 5.664063 True
IsNumeric 0.6796875 True
IsNumber 6.796875 False
It's official, IsNumber is not exactly a top performer.

VBA Parameter Error

I have been fiddling with this code for a few hours now and I can't get rid of the compile error.
Set objWord = CreateObject("Word.Application")
''more code
objWord.Selection.TypeText (FDS.Cells(2, 8).Value)
objWord.Selection.HomeKey Unit:=wdLine, Extend:=wdMove (error occurs here)
ActiveDocument.Indexes.MarkEntry Range:=objWord.Selection.Range, Entry:=("Device")
objWord.Selection.EndKey Unit:=wdLine
''more code
(I left out a lot of code since the module is huge)
What I want the code to do in the end is look in a specific excel cell and take that value and place it in the currently open word document. Then that value has to be selected and given a marking and then going to the end of the line.
I have tried all solutions that I know of and changing Extend:=wdMove to Extend:=wdExtend but nothing seems to work.
The only way I can get rid of the error is removing Unit:=wdLine. But this also makes sure it doesn't select the Value anymore.
Im hopeless at this point. So I will take any help.
Thnx in advance.
Trying using 5 instead of wdLine and 0 in place of wdMove. – Darren Bartrup-Cook
If you don't have a reference to the Microsoft Word type library, wdLine and wdMove are undefined.
If Option Explicit isn't specified at the top of the module, then they become undeclared variables with a default type of Variant, and since they're passed as arguments to members that expect enum values, they'll be passed as 0 - which may or may not be a valid value for the invoked member, and may or may not work as intended.
If Option Explicit is specified at the top of the module, the code doesn't compile, and the VBE highlights wdLine and wdMove as undeclared identifiers.
Since more people are going to use this I'm sticking with the numerical ones. But I appreciate the help! – MateoVD
The best solution (assuming you're keeping the MS Word reference late-bound) is to maintain the readability of wdLine and wdMove values. Inlining their respective underlying integer value will work, but then you have 5 and 0 and no idea whatsoever about what these values mean: these are known as magic numbers, and they're a great source of hard-to-debug issues.
Declare the constants yourself. wdLine and wdMove are members of the WdUnits and WdMovementType enumerations, respectively.
You can define these enums yourself, at the top of any module - I'd recommend defining them in their own standard/procedural module (.bas), so that these values are accessible from anywhere in your code (they wouldn't be, if you defined them in e.g. a Worksheet class module):
'Module: WordConsts
'#Folder("Microsoft Word Constants")
Option Explicit
Public Enum WdUnits
wdCell = 12
wdCharacter = 1
wdCharacterFormatting = 13
wdColumn = 9
wdItem = 16
wdLine = 5
wdParagraph = 4
wdParagraphFormatting = 14
wdRow = 10
wdScreen = 7
wdSection = 8
wdSentence = 3
wdStory = 6
wdTable = 15
wdWindow = 11
wdWord = 2
End Enum
Public Enum WdMovementType
wdMove = 0
wdExtend = 1
End Enum
And now you can keep your code as-is, and use wdMove and wdLine and any other possible legal values, without having to dig them and their underlying values up online.
If you then add a reference to the Microsoft Word type library, the constants defined in your WordConsts module will take precedence over the same-name identifiers defined in the type library: this is called shadowing, or hiding - anything defined in your VBA project will always have higher precedence in identifier resolution.
TL;DR: Write code that says what it does, and does what it says. Magic numbers don't say anything - avoid them. If a number means something, give it a meaningful name. When a name exists for that value, use it.

Counting words in Word document, including footnores

I periodically receive long documents that include footnotes and am trying to find a way using VBA to count the number of words on each page, including footnotes. It doesn't matter if a footnote spills over onto the next page, I just the word count including footnotes that are anchored on the page.
I have a macro that correctly counts the number of words in the body of the text, using the command:
WordCount = ActiveDocument.Range(Start:=pos1, End:=pos2).ComputeStatistics(wdStatisticWords)
The variables pos1 and pos2 have been set to the first and last characters of the page being counted.
However, when I add the True parameter to ComputeStatistics(wdStatisticWords, True), to IncludeFootnotesAndEndnotes, as in:
WordCount = ActiveDocument.Range(Start:=pos1, End:=pos2).ComputeStatistics(wdStatisticWords, True)
it doesn't work, giving an error that there are too many parameters. It appears that when using a Range, the IncludeFootnotesAndEndnotes parameter is not available.
How do you count the words within footnotes contained in a range?
I think what you will need to do is iterate into each of the StoryRanges and update a counter. Here is a small example that should serve as an example, however, you will likely need to tweak it for your specific case (review my note about the enum for StoryRanges)
Here's the code:
Public Sub Count_All_Words()
Dim Story_Ranges As Variant: Set Story_Ranges = ActiveDocument.StoryRanges
Dim Story_Range As Object
Dim WordCount As Long
'Loop through each story range and only include the footer and Main story to the word count
For Each Story_Range In Story_Ranges
'You may need to check additional types, lookup the enumerations for StoryType here:
'https://msdn.microsoft.com/en-us/library/bb238219(v=office.12).aspx
If Story_Range.StoryType = wdMainTextStory Or Story_Range.StoryType = wdFootnoteSeparatorStory Then
'Add to the word count
WordCount = WordCount + Story_Range.ComputeStatistics(wdStatisticWords)
End If
Next
Debug.Print "The word count is: " & WordCount
End Sub

What defines a sentence in MS-Word?

I know that the sentences collection is just a just a bunch of ranges, but I have not been able to determine exactly what criteria are used to decide where those ranges begin and end. I have been able to determine that a period (.) a question mark (?) or an exclamation point (!) followed by one or more spaces is the end of a sentence and that the spaces are included in the sentence range. I have also determined that if there are no spaces between what you and I would consider two sentences MS-Word considers it as only one sentence.
The problem is when you start putting in things like tabs, page breaks, new line characters etc. things become unclear. Can anyone explain precisely or point me to some reference material what criteria MS-Word uses to decide where one sentence ends and another begins?
Seems to be based on a delimiter of a sentence ending type (e.g. ".","!","?"). If you explain what you are trying to do or post some code more people will be willing to help.
If you are concerned about combined sentences (e.g. This is a single Sentence.Even though it is deliminated) you could expand upon this basic methodology. Special Characters seem to be much harder to handle. So positn what you are trying to do would be suggested
Sub sent_counter()
Dim s As Integer
For s = 1 To ActiveDocument.Sentences.Count
ActiveDocument.Sentences(s) = splitSentences(ActiveDocument.Sentences(s))
Next s
End Sub
Function splitSentences(s As String) As String
Dim delims As New Collection
Dim delim As Variant
delims.Add "."
delims.Add "!"
delims.Add "?"
Dim ender As String
Dim sub_s As String
s = Trim(s)
ender = Right(s, 1)
sub_s = Left(s, Len(s) - 1)
For Each delim In delims
If InStr(1, sub_s, delim) Then
sub_s = Replace(sub_s, delim, delim & " ")
End If
Next delim
splitSentences = sub_s & ender
End Function