Word 2016 VBA loop until end of document - vba

I've looked at many different answers online but have not been able to find a solution that fits my code. This is my first time writing VBA in Word (have some moderate experience in Excel).
I thought this post might be what I need but it doesn't stop the loop at the end of the document for me.
I'm trying to insert a continuous section break before the start of a new section, which I designate as text that is formatted with style Heading 1. I'm totally open to doing this another way and would be grateful for your insights!
Sub InsertSectionBreak()
' Go to start of document
Selection.HomeKey Unit:=wdStory
' Find next section based on header formatting, insert continuous section break just before
'
Selection.Find.ClearFormatting
Selection.Find.Style = ActiveDocument.Styles("Heading 1")
With Selection.Find
.Text = ""
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While Selection.Find.Execute = True
Selection.Find.Execute
Selection.MoveLeft Unit:=wdCharacter, Count:=1
Selection.InsertBreak Type:=wdSectionBreakContinuous
Loop
End Sub

The code in the question is not bad, but has a major problem: The Selection is being moved towards the front of the document in order to insert the Section Break. This means that next time Find runs it again finds the same Heading 1 and thus repeatedly inserts Section Breaks in the same place.
The other problem is that the code is executing Find as part of the Do While criterium (which is why it's not finding the first instance of Heading 1 in the document).
The following code sample works with Range objects instead of the Selection. You can think of a Range like an invisible selection with a very important difference: there can be multiple Ranges; there can be only one selection.
The suggested code uses two ranges: one for the Find and the other for inserting the Section Break. The Find range is set to the entire document. Whether the Find is successful is stored in a boolean variable (bFound).
If Find is successful the found range is duplicated to the range for the Section break. Duplicate makes an independent "copy" of the original range so that they can be manipulated independently of one another. The range for the section break is then collapsed to its starting point (think of it like pressing left-arrow), then the section break is inserted.
The Find range, however, is collapsed to its end point in order to move it beyond the text formatted with Heading 1 so that the next Heading 1 can be targeted. Find is then executed again and the loop repeats until no more instances of Heading 1 are found.
Sub InsertSectionBreak()
Dim rngFind As Word.Range, rngSection As Word.Range
Dim bFound As Boolean
Set rngFind = ActiveDocument.content
' Find next section based on header formatting, insert continuous section break just before
'
rngFind.Find.ClearFormatting
rngFind.Find.style = ActiveDocument.styles("Heading 1")
With rngFind.Find
.text = ""
.Replacement.text = ""
.Forward = True
.wrap = wdFindStop
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
bFound = .Execute
End With
Do While bFound
Set rngSection = rngFind.Duplicate
rngSection.Collapse wdCollapseStart
rngSection.InsertBreak Type:=wdSectionBreakContinuous
rngFind.Collapse wdCollapseEnd
bFound = rngFind.Find.Execute
Loop
End Sub

If the content you're interested is related to a heading, you can obtain all the content under that heading without the need for Section breaks. For example:
Sub GetHeadingSpanText()
Application.ScreenUpdating = False
Dim Rng As Range
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = InputBox("What is the text to find?")
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
End With
If .Find.Found = True Then
Set Rng = .Paragraphs(1).Range
Set Rng = Rng.GoTo(What:=wdGoToBookmark, Name:="\HeadingLevel")
MsgBox Rng.Text
End If
End With
Set Rng = Nothing
Application.ScreenUpdating = True
End Sub
Note that this approach get's all the content associated with the nearest heading, regardless of its level; a more sophisticated approach can be use to get all the content associated with a particular heading level so that, if the match is found under a sub-heading, the prior major heading is used to determine the range spanned.

Related

How to Find a Specific Keyword from the beginning of the Word Document in VBA?

I am using Word VBA. I want to find a specific keyword "MyTest" from the beginning of the document, and then repeat until all of occurrences are found. How to do so?
I use macro record, and get the following codes:
Selection.Find.ClearFormatting
With Selection.Find
.Text = "MyTest"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchByte = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
This seems only start the find from the current position and will return one instance of the keyword?
The macro recorder will not give you the best code as it can only record what you do on screen. This means that it always works with the Selection object, i.e. whatever you have selected on screen.
Instead you should use a Range object set to the the part of the document you want to work with. Unless you are using ReplaceAll you also need to repeatedly execute the Find until you have found all the matches.
Below is a generic routine that you can modify.
Sub FindSomeTextAndDoSomething(textToFind As String)
Dim findRange As Range
Set findRange = ActiveDocument.Content
With findRange.Find
.ClearFormatting
.Text = textToFind
.Replacement.Text = ""
.Wrap = wdFindStop
.Format = False
Do While .Execute = True
'add code here to do something with the found text
'collapse range to continue
findRange.Collapse wdCollapseEnd
Loop
End With
End Sub

Store multiple selections in Array and later do select all the selections in the Array

I am trying to find a word and save the selection in an array and then find again and then save the next selection in the array. And in the end try to select all the selections in the array.
I am trying this but its with half knowledge. I am not able to get it. Can some one help.
Sub Macro6()
'
' Macro6 Macro
'
'
Selection.HomeKey Unit:=wdStory
Dim selecttest(2) As Selection
For I = 1 To 2
Selection.Find.ClearFormatting
With Selection.Find
.Text = "PQXY"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = True
End With
Selection.Find.Execute
Set selecttest(I) = Selection
Selection.MoveRight Unit:=wdCharacter, Count:=1
Next I
For I = 1 To 2
selecttest(I).Select
Next I
End Sub
I want to keep the selection in the loop and show them in the end.
Solution i tried:
Sub Macro61()
'
' Macro6 Macro
'
'
Selection.HomeKey Unit:=wdStory
Dim selecttest(2) As Range
For i = 1 To 2
Selection.Find.ClearFormatting
With Selection.Find
.Text = "PQXY"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = True
End With
Selection.Find.Execute
Set selecttest(i) = Selection.Range
Selection.MoveRight Unit:=wdCharacter, Count:=1
Next i
For i = 1 To 2
selecttest(i).Select
Next i
End Sub
Problem above is selecttest(2) is only selected. I want the end result with both selecttest(1) and selecttest(2) selected
I also tried
Dim totalselect as Range
For i = 1 to 2
set totalselect = totalselect + selectest(i)
Next i
totalselect.select
It shows error that "+" (plus) operation does not exist
Solution: Not Possible
Found some articles regarding discontinous range selections is not possible by VBA whereas its possible by FindAll
Find All in VBA: https://forums.windowssecrets.com/showthread.php/124485-Find-All-in-VBA
Reason 1: which talks about findall
Unfortunately, Microsoft omitted to add support for "Find All" in the
VBA object model for Word. In other words, Find All cannot be executed
from a macro.
You can loop through all occurrences of the search text in VBA, but
that's not the same as Find All.
Reason 2: findall is inderectly related to discontiguous selections which is not possible
Probably the reason a Find All isn't in VBA is that VBA also has never
had any way to deal with discontiguous selections (the kind you can
make with Ctrl and the mouse), which is what Find All would produce.
The KB article here explains the few things that can be done. Every
version since 2002 (including 2010) has made no changes in this area.
Reason 3: computationally expensive, in terms of both processing and memory. if done by VBA
I suspect this omission was intentional and carefully considered. In
the visual context of an open document window, Find All is a perfectly
sensible concept. However, in the procedural world of VBA, it is a tad
more difficult to work with sets of things, and, often,
computationally less efficient.
This isn't to say that it can't be done in VBA, only that I can
understand why it wasn't done. For what it's worth, the same is true
of the Find object in Excel, with which I have much more intimate, and
recent, experience.
To support Find All in VBA would require the Execute method to return
a collection of Range objects, which could be computationally
expensive, in terms of both processing and memory
Try with two simple changes replacing Selection into Range object:
Sub Macro6()
...
...
Dim selecttest(2) As Range 'not Selection
...
...
Set selecttest(i) = Selection.Range 'not just a Selection
...
End sub
If all you want to do is show the matches, you only need:
ActiveDocument.Range.Find.HitHighlight FindText:="PQXY"
Dim oRng As Word.Range
Set oRng = Selection.Range
oRng.Find.ClearFormatting
With oRng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "shhada"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchKashida = False
.MatchDiacritics = False
.MatchAlefHamza = False
.MatchControl = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
While .Execute
oRng.Editors.Add wdEditorEveryone
Wend
ActiveDocument.SelectAllEditableRanges wdEditorEveryone
ActiveDocument.DeleteAllEditableRanges wdEditorEveryone
End With
End Sub

How to select list, if the list contains specific text using word VBA

I have a vba code for find the specific string found in table, as well as i need a vba code for select the list, if specified text found.
The code was got from here,
Microsoft Word VBA - Select table if cell contains specified string,
Sub Find_Text_in_table()
selection.Find.ClearFormatting
With selection.Find
.Text = "figure id:"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindAsk
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While selection.Find.Execute
If selection.Information(wdWithInTable) Then
MsgBox "Figure ID Found in Table"
Exit Sub
'now you are in table with text you searched
'be careful with changing Selection Object
'do what you need here
End If
Loop
Application.ScreenUpdating = True
End Sub
as well, if the text "Figure ID:" found in any list type, throw an alert message.
this is the list
this is the list
this is the list
Figure Id:
On the whole, it's preferable to work with Range objects instead of Selection. There can be only one selection, but code can work with as many Ranges as necessary. I've altered the original code accordingly. I also changed the Find.Wrap to wdFindStop so that the code searches the entire document, then stops.
The Range object has a ListParagraphs property that will return the ListParagraph object(s) of the Range. In this case, that would be paragraph in which the Find term is located if it belongs to a numbered list. If it does, the Count will be greater than 0 and the code continues to get the Paragraph.Range, from which it's possible to extract all paragraphs that belong to the list using Rnage.ListFormat.List.ListParagraphs.
In order to select the entire list it's necessary to get the Start point of the first list entry and the End point of the last list entry. In the code below, the range of the paragraph in which "Figure Id" was found is extended to these points so that it covers the entire list. Note that it's not clear what you want to do with this, once you have it, since the code loops. It may be that it should not be selected at all but that the action should be performed on the Range object, instead...
Sub Find_Text_withList_in_table()
Dim rngFind As Word.Range, rngFigureList As Word.Range
Dim lstParas As Word.ListParagraphs
Dim lFindCounter As Long 'for testing / debugging
Set rngFind = ActiveDocument.content
rngFind.Find.ClearFormatting
With rngFind.Find
.Text = "figure id:"
.Replacement.Text = ""
.Forward = True
.wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While rngFind.Find.Execute
lFindCounter = lFindCounter + 1
If rngFind.Information(wdWithInTable) Then
Debug.Print "Figure ID Found in Table"
Set lstParas = rngFind.ListParagraphs
If lstParas.Count > 0 Then
Set rngFigureList = lstParas.Item(1).Range
Set lstAllParas = rngFigureList.ListFormat.List.ListParagraphs
Debug.Print "Nr paragraphs in the list: " & lstAllParas.Count
rngFigureList.Start = lstAllParas(1).Range.Start
rngFigureList.End = lstAllParas(lstAllParas.Count).Range.End
rngFigureList.Select
MsgBox "Figure Id is in a numbered list, in a table"
End If
End If
Loop
Debug.Print "Nr Figure ID found: " & lFindCounter
Application.ScreenUpdating = True
End Sub

MS Word table -macro to find row containing specific text then move entire row to last row in the table

Looking to create a macro that will search down MS Word table, find a specific word, then move that entire row to the bottom of the table, then repeat for the next occurrence of that word.
The specs you've given # vbaexpress are somewhat different to what you gave here. Assuming the vbaexpress specs are correct, try:
Sub Demo()
Application.ScreenUpdating = False
Dim TblRng As Range, TmpRng As Range
With ActiveDocument.Tables(1)
Set TblRng = .Range: Set TmpRng = .Range
With .Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "DENIED"
.Replacement.Text = ""
.Forward = True
.Format = False
.Wrap = wdFindStop
.MatchCase = True
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
End With
Do While .Find.Found
If .InRange(TblRng) Then
TmpRng.Collapse wdCollapseEnd
TmpRng.FormattedText = .Rows(1).Range.FormattedText
.Rows(1).Delete
End If
.Find.Execute
Loop
End With
If .Rows.Count > TblRng.Rows.Count Then
.Split .Rows(TblRng.Rows.Count + 1)
End If
End With
Application.ScreenUpdating = True
End Sub
Note: the above code assumes you're processing just the first table in the document; if it's a different table, change the 1 in .Tables(1) to suit.

Ensure Replace Text function only looks at a specific Word table VBA

I have a Word table where I apply a routine that replaces paragraph marks with a comma and a space. However, in doing so there is now some text like '..., There...' and my client wanted to replace the Upper Case to Lower Case as much as possible.
So, I wrote some secondary code that I call from the previous routine as follows:
Sub LowerCaseAfterComma()
With Selection.Find
.ClearFormatting
.Text = ", ([A-Z])"
.Forward = True
.Wrap = wdFindStop
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = True
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
While .Found
Selection.Range.Case = wdLowerCase
Selection.Collapse Direction:=wdCollapseEnd
.Execute
Wend
End With
End Sub
Although this identifies the instances of Upper Case characters in the table and replaces them accordingly, the code then looks for all other instances outside the table in the document, which I don't want the code to do. I have tried using the Range object in Word for the table I want edited but haven't been successful in the syntax needed.
NB. I have problems in ensuring the editing stays within the specific table. There can be a differing number of tables prior to the one I wish to edit so
ActiveDocument.Tables() specifying the number of the table doesn't seem to work.
I am sure I doing some basically wrong and the code just needs a little tinkering. However, I am just a novice developer learning.
Try
Sub Demo()
Application.ScreenUpdating = False
Dim Rng As Range
With ActiveDocument.Tables(1)
Set Rng = .Range
With .Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ", ([A-Z])"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
.Execute
End With
Do While .Find.Found
If .InRange(Rng) Then
.Case = wdLowerCase
Else
Exit Do
End If
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
End With
Application.ScreenUpdating = True
End Sub