Move text using headings - vba

I am trying to move text in a MS-word document based on a user input, using Headings to find what to move and where to move it.
For example let's say my document is organized like this:
Section 1
Section 2
Section 3
Annex 1
With "Section 1", "Section 2", "Section 3" and "Annex" being defined as Headings 1 style.
In each Section (and annex) you have a mixed batch of text, tables, pictures, etc.
Let's assume users get asked the following question through VBA (triggered either via a button click event or document open event, does not matter - this I know how to do). Depending on their answer, I would like to either
a) do nothing
b) do the following actions:
select the entire "Section 1", including Heading and all the text, figures, tables, etc, in it (in other words - until "Section 2" starts)
move it between Section 3 and Annex 1 so that the document structure now looks like this:
Section 2 Section 3 Section 1 Annex 1
Dim answer as Integer
answer = MsgBox("Do you like cookies?", vbQuestion + vbYesNo + vbDefaultButton2, "The big question")
if answer = vbYes Then
' e.g. do nothing or end sub
else
' move text as described above
I explored / read quite a few posts on the selection.find, selection.move and range.move methods.
I reached a stage where I find and select the Section I am interested in using the following code;
Dim answer as Integer
answer = MsgBox("Do you like cookies?", vbQuestion + vbYesNo + vbDefaultButton2, "The big question")
if answer = vbYes Then
' e.g. do nothing or end sub
else
Selection.WholeStory
Selection.Collapse wdCollapseStart
Selection.Find.ClearFormatting
Selection.Find.Style = ActiveDocument.Styles("Heading 1")
With Selection.Find
.Text = "Section 1"
.Forward = True
.Wrap = wdFindContinue
.Format = True
.MatchCase = False
.MatchWholeWord = True
End With
Selection.Find.Execute
Selection.Collapse wdCollapseStart
Dim r1 As Range
Set r1 = Selection.Range
' keep format settings, only change text
Selection.Find.Text = "Section 2"
If Selection.Find.Execute Then
Selection.Collapse wdCollapseStart
Else
Selection.WholeStory
Selection.Collapse wdCollapseEnd
End If
Dim r2 As Range
Set r2 = ActiveDocument.Range(r1.Start, Selection.Start)
r2.Select
I struggle to now move this range (or this selection) to another position in the document based on Headings (in this case, to insert this section between "Section 3" and "Annex 1").

You are on the right lines but need to avoid using the Selection object. There are rare circumstances when using Selection is unavoidable but this isn't one of them.
Word has a number of hidden predefined bookmarks, one of which returns the full range of a heading level. This is used in the GetHeadingBlock function below.
A Range also has a FormattedText property that can be used instead of the clipboard.
Sub MoveSection()
Dim moveRange As Range, destRange As Range
Set moveRange = GetHeadingBlock("Section 1", wdStyleHeading1)
If Not moveRange Is Nothing Then
Set destRange = GetHeadingBlock("Section 3", wdStyleHeading1)
If Not destRange Is Nothing Then
destRange.Collapse wdCollapseEnd
destRange.FormattedText = moveRange.FormattedText
moveRange.Delete
End If
End If
End Sub
Public Function GetHeadingBlock(headingText As String, headingStyle As WdBuiltinStyle) As Range
Dim findRange As Range
Set findRange = ActiveDocument.Content
With findRange.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = headingText
.Style = headingStyle
.Replacement.Text = ""
.Forward = True
.Format = True
.Wrap = wdFindStop
If .Execute Then Set GetHeadingBlock = _
findRange.GoTo(What:=wdGoToBookmark, Name:="\HeadingLevel")
End With
End Function

Related

How to add links to all Heading 1 style text?

The code is intended to add the same hyperlink to all Heading 1 style text. (Purpose: clicking any heading brings you to the top of the document).
It works for the first Heading Style text. It does not advance to the next instance.
I found this was due to the line which adds the hyperlink. When this line is removed, all the Heading 1 style text is found (but of course then I can't add the link).
Sub addLinksToAllTextHavingCertainStyle()
Dim r As Range
Set r = ActiveDocument.Content
r.Find.ClearFormatting
Do
With r.Find
.Text = ""
.Replacement.Text = ""
.Style = "Heading 1"
.Forward = True
.Wrap = wdFindStop
.Format = True
.Execute
End With
r.Select 'for testing
ActiveDocument.Hyperlinks.Add Anchor:=r, Address:="", SubAddress:="_top", ScreenTip:=""
Loop
End Sub
You're looping the wrong part of the code. As written your code loops the entire find, which means it just starts over from the beginning each time.
It is only the execution of the Find that needs to be looped, the parameters you have set will remain. When Find is executed the range that the Find is executed on is redefined to the found match, so in a loop you need to collapse the range to the end to avoid the match being endlessly re-found.
Sub addLinksToAllTextHavingCertainStyle()
Dim r As Range
Set r = ActiveDocument.Content
With r.Find
.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Style = "Heading 1"
.Forward = True
.Wrap = wdFindStop
.Format = True
End With
Do While r.Find.Execute = True
ActiveDocument.Hyperlinks.Add Anchor:=r, Address:="", SubAddress:="_top", ScreenTip:=""
r.Collapse wdCollapseEnd
Loop
End Sub

Howto set Range to third Heading1 style match?

I have a document where I want to start start searching in section 3 to the end of the document and bold text based on my user-defined style since other styles used may contain the same word I don't want to bold - including the possibility of the Heading/section.
I do have VBA code that works by finding my user-defined style throughout the document and bolding it for one style match only. However, the .Style in the .find doesn't allow one to specify a wildcard style i.e. my user-defined style set of "Requirement1 thru Requirement9" which are based off the Heading1-9 style. I assume I'll have to create a for loop outside of the "with" that finds my user-defined style that is passed one at a time to bold the text to match one style at a time i.e Requirement1 thru Requirement9. Is there a better way? see code below.
The next issue is to focus the search of this text to bold to start in a particular section to the end of the document. For me, its section 3 to the end of the document or till it encounters another style defined as Appendix1 and stop. I do have user-defined styles Appendix1 thru Appendix9 that are also based off of Heading1-9. The reason to search on a style as a starting point is because these other sections before section3, or descriptive text in Section 3 and beyond may contain the word I'm trying to bold - which I don't want to do except when my particular user-defined style Requirment1-9 was applied that is used in section 3 and beyond. In trying to implement this part before doing the word searching and bolding part, I've been searching based on the "Heading1" style and when I found the third one, I know I'm in section 3. I can't use the VBA "sections" search since it includes all the subsections of a document which can vary from document to document and specifying sections.item(3) doesn't mean I'll get the third section of my document that corresponds to the third Heading1 style.
Since I'm not that adept at the various VBA sections,paragraphs,sentences, characters and everything else when applying the "range" either before, after, or both on any of these (never seems to work the way one expects. Even the VBA hint tools says one can do it, but runtime says error) I've started with the specifying the start/end of the whole activedocument.range. Then I perform a ".find" of the "Heading1" style in a for loop three times which does find them. I then try to assign the last Heading1 found range value as the start value to use in the next embedded "find" that will search and apply the bold to the "Requirement1" style. (May need to create a loop to search Requirement1-9 styles as stated above). To help me know where I'm at, I'm trying to dump via debug.print the range position, but it dumps the all whole document text to the debug window and not the integer value I thought I'd get. Whats the issue with doing this? I've even tried to use a selection.range and dump the retrieved text to the debug.print window without success. It makes it worse since the processing timing seems to run forever. The wdFindStop doesn't force the find to stop on the third find either.
So how to:
a). find and dump the range position to the debug window along with the text at this position. Both the number and text will tell me where I'm at.
c). How to assign the range position based on find of the third "Heading1" position in the document so that that the second "with" can search and apply the bolding to the text I want to bold.
d). How to do a wildcard search on the .Style user-defined Requirement1-9 style instead of having to create a for loop to search one at a time?
The following is the VBA code I have written.
Attribute VB_Name = "BoldMustShall2_M"
Option Explicit
Public Sub BoldMustShall2()
' If .Parent.Bold = True is used with wdReplaceall, the whole
' document is bolded even when the sentence doesn't have any of the words
' being searched for.
Dim myRange, rngSel As Range
Dim oDoc As Word.Document
Dim pos1, pos2 As Long
Dim numchars As Long
Set oDoc = ActiveDocument
Set rngSel = Selection.Range
Application.ScreenUpdating = False
'Set the starting Heading to search as Section 3
pos1 = oDoc.Range.Start
pos2 = oDoc.Range.End
Set myRange = oDoc.Range(Start:=pos1, End:=pos2)
'Another way is to find the "Heading1" style and set the range start to the third one found which is section 3.
'Counting Word sections can vary alot so its not the best way.
'The same would apply if Appendix1 style is used and assuming that requiremens start in the third one.
Debug.Print "My Start Range " + myRange
With myRange
.TextRetrievalMode.IncludeFieldCodes = False ' don't want to search fieldcodes for must/shall
.TextRetrievalMode.IncludeHiddenText = False ' don't want to search hiddentext for must/shall
' Get the range position for Heading1 style for section 3 of PRD.
Dim i As Integer
Dim ReqHDR As Range
Dim bFind As Boolean
With .Find
.ClearFormatting
.Forward = True
'.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.Style = "Heading1"
'Loop three times for Style
For i = 1 To 3 Step 1
'bFind = .Execute
.Execute Wrap:=wdFindStop
If .Found = True Then
rngSel = oDoc.Range 'This should be the current position in the Document for the requirements.
' When found it outputs the message once
Debug.Print "Found a Heading1 style "
.Replacement.Font.Size = 20
End If
'.Wrap = wdFindStop
Next i
End With
Debug.Print "End of Heading1 Search "
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.Replacement.Font.Bold = True
.Replacement.Font.Name = "Times New Roman"
.Replacement.Font.Size = 12
.Replacement.Font.Italic = True
'.Style = "Requirement1" ' A loop is needed to cycle thru all the Requirement1-9 styles. No wildcard.
'.Style = wdStyleNormal
.Replacement.Text = "^&" ' This is the contents of the find what box in word.
' In this case the .find.text "must" statement.
' Alternate is to specify "must" or "shall" but this
' would require two replace.text statements instead of
' just this one. The ^& is a special command that eliminates
' the need to set the replacement.Text info.
'.Text = "must"
.Execute FindText:="must", Replace:=wdReplaceAll
If .Found = True Then
' When found it outputs the message once
Debug.Print "Found one or more must "
End If
'.Text = "shall"
.Execute FindText:="shall", Replace:=wdReplaceAll
If .Found = True Then
' When found it outputs the message once
Debug.Print "Found one or more shall "
End If
End With
End With
Application.ScreenUpdating = True
Debug.Print "Completed searching for must/shall in document."
End Sub
Assuming that, when you refer to 'Sections', you're referring to parts of your document delineated by Section breaks, perhaps something along the lines of:
Sub Demo()
Application.ScreenUpdating = False
Dim Rng As Range, i As Long
With ActiveDocument
Set Rng = .Range(.Sections(3).Range.Start, .Range.End)
With .Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = True
.Format = True
.Wrap = wdFindContinue
.Style = "Appendix1"
.Execute
End With
Rng.End = .Start
End With
With Rng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Forward = True
.Format = True
.Replacement.Style = "Strong"
.Wrap = wdFindStop
.Replacement.Text = "^&"
.Text = "must"
For i = 1 To 9
.Style = "Requirement" & i
.Execute Replace:=wdReplaceAll
Next
.Text = "shall"
For i = 1 To 9
.Style = "Requirement" & i
.Execute Replace:=wdReplaceAll
Next
End With
End With
Application.ScreenUpdating = True
End Sub
Note that, instead of using hard formatting for the replacement, I've simply applied Word's built-in 'Strong' Style. Your own code should use a Style, too, whether it's Word's 'Strong' Style or another character Style of your own definition.

Word VBA: Find a string of text and change all instances of it to title case

I'm trying to write a macro to change the case of "section 1", "section 2", etc. to title case, so they all read "Section 1" etc. I've adapted Variatus' very helpful code here:
Dim Rng As Range
Dim Fnd As Boolean
Set Rng = Selection.Range
With Rng.Find
.ClearFormatting
.Execute FindText:="section [0-9]", Forward:=True, _
Format:=False, Wrap:=wdFindContinue, MatchWildcards:=True
Fnd = .Found
End With
If Fnd = True Then
Rng.Case = wdNextCase
End If
The issue I'm having with this is that it only changes one instance at a time. Ideally it would change the case of all instances with one keypress.
As a bonus, I'd also like it to register the change of case as a tracked change. As I'm using tracked changes, I can't do a simple replace text macro with section ([0-9]) for Section \1, as the bug with tracked makes it "1Section ", "2Section ", etc. This isn't essential but would be a really nice bonus. The .Case function doesn't get tracked, and .Font only has an option for .AllCaps.
Perhaps there's a way it can find section [0-9], move the cursor to the beginning of the word and select the first letter, set .Font.AllCaps = True, and loop until there are no more instances of section [0-9]? Just an idea, but that's way beyond my macro ability at the moment. The main thing for now is getting the above code to apply to all instances of section [0-9].
Cheers!
Try:
Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "section [0-9]"
.Replacement.Text = ""
.Format = False
.Forward = True
.Wrap = wdFindStop
.MatchWildcards = True
End With
Do While .Find.Execute
.Characters.First.Text = "S"
.Collapse wdCollapseEnd
Loop
End With
Application.ScreenUpdating = True
End Sub

VBA copying to one document in Word from specified place in another Word document (including)

I am trying to copy exact part of text from one Word document to another. Here is an example of text:
——————————————————
About the company
Bla bla bla bla
Bla bla bla
Bla bla
Thank you for attention
——————————————————-
Imagine that text is located in the end of the Word.
So I want to copy the whole text from “About the company” till “Thank you for attention” including both.
My code below copies only what is between “About the company” and “Thank you for attention” but I need them also to be copied (please don't suggest to add extra words to make the code find them, it is impossible in my case). Any ideas?
Dim Pos As Word.Document
Set Pos = Documents(1)
Set myRange = Pos.Content
Dim IngStart As Long
Dim IngEnd As Long
With myRange.Find
.ClearFormatting
.Wrap = wdFindStop
.MatchCase = False
.Text = "About the company"
If .Execute = False Then
MsgBox "'About the company' not found.", vbExclamation
Exit Sub
End If
myRange.Collapse Direction:=wdCollapseEnd
IngStart = myRange.End
.Text = "Thank you for attention"
If .Execute = False Then
MsgBox "'Thank you for attention' not found.", vbExclamation
Exit Sub
End If
IngEnd = myRange.Start
End With
Pos.Range(lngStart, lngEnd).Copy
objWrdDoc.ContentControls(18).Range.PasteSpecial DataType:=2
Thank you in advance!
Really, all you need for this is a wildcard Find, where:
Find = About the company*Thank you for attention
You don't even need a macro! That said:
Sub Demo()
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Format = False
.Forward = True
.MatchWildcards = True
.Wrap = wdFindContinue
.Text = "About the company*Thank you for attention"
.Execute
End With
If .Find.Found = True Then .Copy
End With
End Sub
If I understand correctly you want to include the first search text "About the company" and the second search text "Thank you for attention" in the range you ultimately process.
Your current code is collapsing MyRange too soon after the first find and on the second find you are picking up the wrong ending address. I have made the modifications belong and it should now work as you desire it to.
Dim Pos As Word.Document
Set Pos = Documents(1)
Set myRange = Pos.Content
Dim IngStart As Long
Dim IngEnd As Long
With myRange.Find
.ClearFormatting
.Wrap = wdFindStop
.MatchCase = False
.Text = "About the company"
If .Execute = False Then
MsgBox "'About the company' not found.", vbExclamation
Exit Sub
End If
IngStart = myRange.Start
myRange.Collapse Direction:=wdCollapseEnd
.Text = "Thank you for attention"
If .Execute = False Then
MsgBox "'Thank you for attention' not found.", vbExclamation
Exit Sub
End If
IngEnd = myRange.End
End With
Pos.Range(lngStart, lngEnd).Copy
objWrdDoc.ContentControls(18).Range.PasteSpecial DataType:=2

Remove all text between 2 headers Word 2010, using VBA

I have 2 headers or markers that are a part of my RTF document. In my example I am showing a sentence when in reality it will be multiple sentences or paragraphs. I have used brackets instead of less than and greater than signs as they disappear in my question. All I want to do is replace the text between the 2 markers with the following sentence, "text goes here", without quotation marks.
[EmbeddedReport]Lots of text, thousands of character, multiple paragraphs[/EmbeddedReport]
I want replace all the text between the 2 markers replaced with "text goes here".
It would end up looking like this...
"[EmbeddedReport]text goes here[/EmbeddedReport]"
I've literally spent 2 days trying to solve this. Any help would be appreciated.
This is the last thing I tried...
Sub RemoveReport()
Dim c As Range
Dim StartWord As String, EndWord As String
Selection.HomeKey Unit:=wdStory
StartWord = "<ImageTable>"
EndWord = "</ImageTable>"
Set c = ActiveDocument.Content
c.Find.ClearFormatting
c.Find.Replacement.ClearFormatting
With c.Find
.Text = StartWord & "*" & EndWord
' MsgBox (.Text)
.Replacement.Text = "<ImageTable>text goes here</ImageTable>"
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = True
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
c.Find.Execute
While c.Find.Found
Debug.Print c.Text
'~~> I am assuming that the start word and the end word will only
'~~> be in the start and end respectively and not in the middle
Debug.Print Replace(Replace(c.Text, StartWord, ""), EndWord, "")
c.Find.Execute
Wend
End Sub
Word VBA is not my area of expertise, but it seems similar to a question I answered a few days ago.
Turns out the wildcard match was not doing what I hoped it would do, or at least it was not reliable. Also, I ran in to some trouble using angle brackets, so this uses square brackets. I suspect that word treats the angle brackets as markup/syntax, and thus does not interpret them as text in the Find object. There is probably a way around this, but Word VBA is not my specialty. There is also probably a more elegant solution, but again, Word VBA is not my specialty :)
Try something like this:
Option Explicit
Sub Test()
Dim doc As Document
Dim txtRange As Range
Dim startTag As String
Dim endTag As String
Dim s As Long
Dim e As Long
startTag = "[EmbeddedReport]"
endTag = "[/EmbeddedReport]"
Set doc = ActiveDocument
Set txtRange = doc.Content
'Find the opening tag
With txtRange.Find
.Text = startTag
.Forward = True
.Execute
If .Found Then
s = txtRange.Start
Else
GoTo EarlyExit
End If
End With
'Find the closing tag
Set txtRange = doc.Range(txtRange.End, doc.Content.End)
With txtRange.Find
.Text = endTag
.Forward = True
.Execute
If .Found Then
e = txtRange.End
Else
GoTo EarlyExit
End If
End With
Set txtRange = doc.Range(s, e)
txtRange.Text = startTag & "text goes here" & endTag
Exit Sub
EarlyExit:
MsgBox "Header not found in this document!", vbInformation
End Sub
It takes some time to figure it out at first, but learning to navigate the object model reference documentation for VBA will make these tasks a lot easier to figure out in the future.