Visual Basic in Word: compare selection within range - vba

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

Related

Range.Find Word VBA: finding heading with specific heading number only works if heading style is specified

I'd like to find the location of a heading that has a specific heading number. E.g. "2.3."
For some reason, I can only find the location of the heading if i specify what Style that heading is going to be. If i don't specify the heading style then I don't get any matches (i.e. .Execute is never True).
How can I find the location of a heading without having to specify it's style?
Code that works:
Function FindHeadingPos(oRng As Word.Range) As Long
Dim rng As Word.Range
With oRng.Find
.ClearAllFuzzyOptions
.ClearHitHighlight
.ClearFormatting
.Text = ""
.Forward = True
.Wrap = 2
.Format = True
.Style = "Heading 2,H2 Numb"
Do While .Execute
'.Parent contains the found range
Set rng = .Parent
If rng.ListFormat.ListString = "2.3." Then
FindHeadingPos = rng.Start
Exit Do
End If
'below statement seems to prevent code hanging on some headings.
rng.Collapse Direction:=wdCollapseEnd
Loop
End With
End Function
Code that doesn't work:
Function FindHeadingPos(oRng As Word.Range) As Long
Dim rng As Word.Range
With oRng.Find
.ClearAllFuzzyOptions
.ClearHitHighlight
.ClearFormatting
.Text = ""
.Forward = True
.Wrap = 2
'.Format = True
'.Style = "Heading 2,H2 Numb"
Do While .Execute
'.Parent contains the found range
Set rng = .Parent
If rng.ListFormat.ListString = "2.3." Then
FindHeadingPos = rng.Start
Exit Do
End If
'below statement seems to prevent code hanging on some headings.
rng.Collapse Direction:=wdCollapseEnd
Loop
End With
End Function
Thanks #GSerg for suggesting the .ParagraphFormat.OutlineLevel property.
The code below seems to solve my problem in case it helps anyone else.
Function getParaOutlineLevel(headNumberRaw As String) As Integer
Dim numberOfDecimals As Integer
numberOfDecimals = Len(headNumberRaw) - Len(Replace(headNumberRaw, ".", ""))
If Not IsNumeric(Left(headNumberRaw, 1)) Then
getParaOutlineLevel = numberOfDecimals + 5
Else
getParaOutlineLevel = numberOfDecimals
End If
End Function
Function FindHeadingPos(oRng As Word.Range) As Long
Dim headNumber As String
Dim rng As Word.Range
headNumber = "2.3."
With oRng.Find
.ClearAllFuzzyOptions
.ClearHitHighlight
.ClearFormatting
.Text = ""
.Forward = True
.Wrap = 2
.Format = True
.ParagraphFormat.OutlineLevel = getParaOutlineLevel(headNumber)
Do While .Execute
'.Parent contains the found range
Set rng = .Parent
If rng.ListFormat.ListString = headNumber Then
FindHeadingPos = rng.Start
Exit Do
End If
'below statement seems to prevent it hanging on some headings.
rng.Collapse Direction:=wdCollapseEnd
Loop
End With
End Function

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

vba Word create a variable with value of text between 2 cursor points

We are trying to revise rtf docs that are created by Molecular Device software.
Here is an example of part of one of these documents:
Protocol 'C:\ALL USERS\Params\Current\2017 Opto Params\0 VoltageClampContinuous.pro' opened.
C:\ALL USERS\Alan\2018_07_11\2018_07_11_0000.abf started at 00:19:48 stopwatch time.
So for right now - all I am trying to do is automatically find the experiment date (in this case = "2018_07_11_")
My sub so far can find the correct cursor positions but how do I select the text between 2 cursor positions?
Below is what I have the CursorPosition statement is of course wrong - this is what I am looking to correct.
Sub FindfilenameDate()
txt_prior_to_expDate = "\"
txt_after_expDate = "0000"
With ActiveDocument.Content.Find
.Text = txt_after_expDate
.Forward = True
.Execute
If .Found = True Then
.Parent.Select
Set after_rng = Selection.Range
expDateEnd_cursorPos = after_rng.Start - 1
Selection.HomeKey Unit:=wdStory, Extend:=wdExtend
With Selection.Find
.Text = txt_prior_to_expDate
.Forward = False
.Execute
If .Found = True Then
.Parent.Select
Set charBefore_expDate = Selection.Range
expDateStart_cursorPos = charBefore_expDate.Start + 1
End If
End With
End If
End With
'expDate = CursorPosition(expDateStart_cursorPos, expDateEnd_cursorPos)
'MsgBox ("expDate = " & expDate) 'DELETEMSGBOX
End Sub
The trick to something like this is to work with multiple Range objects. My personal preference is to declare a Range for each separate thing to be worked with, rather than trying to figure out the minimum and re-use a Range - at least for the initial code and testing purposes.
For this task, then, I use four Ranges: 1) For the original search, 2) for the end of the "cursor position" that's wanted, 3) For the second search, 4) for the final "cursor position".
The other important concepts are how to "collapse" a Range and how to "copy" one.
Collapsing a Range is like pressing the right- or left-arrow key with a selection, so that it is a "point" and doesn't contain anything. A Range can be collapsed to its start or end position.
Copying a Range (setting one Range to another) needs to be done using the Duplicate property so that the copy is independent of the original. Otherwise, when one is changed the other changes, as well.
Sub FindfilenameDate()
Dim rngFind As Word.Range, rngBefore As Word.Range
Dim rngAfter As Word.Range, rngFound As Word.Range
txt_prior_to_expDate = "\"
txt_after_expDate = "0000"
Set rngFind = ActiveDocument.content
With rngFind.Find
.Text = txt_after_expDate
.Forward = True
.Execute
If .found = True Then
Set rngAfter = rngFind.Duplicate
rngAfter.Collapse wdCollapseStart
Set rngBefore = rngFind.Duplicate
rngBefore.Collapse wdCollapseStart
With rngBefore.Find
.Text = txt_prior_to_expDate
.Forward = False
.Execute
If .found = True Then
Set rngFound = rngBefore.Duplicate
rngFound.Collapse wdCollapseEnd
rngFound.End = rngAfter.Start
'rngFound.Select
End If
End With
End If
End With
'expDate = CursorPosition(expDateStart_cursorPos, expDateEnd_cursorPos)
MsgBox ("expDate = " & rngFound.Text) 'DELETEMSGBOX
End Sub
Though it's not apparent why you're after the date string ending in _0000 rather than the date that is the parent folder name, a much simpler approach for a single date would be:
Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "[0-9]{4}_[0-9]{2}_[0-9]{2}_0000"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
.Execute
End With
If .Find.Found = True Then MsgBox "expDate = " & Split(.Text, "_0000")(0)
End With
Application.ScreenUpdating = True
End Sub
And, for all such dates in a document:
Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "[0-9]{4}_[0-9]{2}_[0-9]{2}_0000"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
.Execute
End With
Do While .Find.Found = True
MsgBox "expDate = " & Split(.Text, "_0000")(0)
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
Application.ScreenUpdating = True
End Sub

How to make a VBA word code run faster?

I found this code online to search and highlight multiple words. It takes roughly about 10 min to run it on a 15 page document. I was wondering if it could be made to run any faster.
Sub HighlightMultipleWords()
Dim Word As Range
Dim WordCollection(2) As String
Dim Words As Variant
'Define list.
'If you add or delete, change value above in Dim statement.
WordCollection(0) = "word1"
WordCollection(1) = "word2"
WordCollection(2) = "word3"
'Set highlight color.
Options.DefaultHighlightColorIndex = wdYellow
'Clear existing formatting and settings in Find feature.
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
'Set highlight to replace setting.
Selection.Find.Replacement.Highlight = True
'Cycle through document and find words in collection.
'Highlight words when found.
For Each Word In ActiveDocument.Words
For Each Words In WordCollection
With Selection.Find
.Text = Words
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
Next
Next
End Sub
The comments are all correct here, you only need to run the find and replace once per item in your list, you are running it multiple times by the amount of words in the document.
Option Explicit
Sub HighlightMultipleWords()
Dim AryWords(2) As String
Dim VntStore As Variant
'Define list.
'If you add or delete, change value above in Dim statement.
AryWords(0) = "word1"
AryWords(1) = "word2"
AryWords(2) = "word3"
'Set highlight color.
Options.DefaultHighlightColorIndex = wdYellow
With Selection.Find
'Clear existing formatting and settings in Find feature.
.ClearFormatting
.Replacement.ClearFormatting
'Set highlight to replace setting.
Selection.Find.Replacement.Highlight = True
'Process the array
For Each VntStore In AryWords
.Execute FindText:=VntStore, _
MatchCase:=False, _
MatchWholeWord:=False, _
MatchWildcards:=False, _
MatchSoundsLike:=False, _
MatchAllWordForms:=False, _
Forward:=True, _
Wrap:=wdFindContinue, _
Format:=True, _
Replace:=wdReplaceAll
Next
End With
End Sub

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?