MS Word, VBA, How to select a paragraph within a cell within a table? - vba

I'm new to using VBA to write macros within MS Word. I've worked out how to select the cell within the table, but it doesn't appear I can use the paragraph object with it... or, more likely, I'm doing it wrong.
Essentially, what I'm trying to do, it look for phrase "as follows:" within all the paragraphs of Cell (13,2) of Table(1). If it finds it, I want to see if the next thing that happens after that phrase is a new paragraph with a bullet. If it is, great, nothing more to do. If it isn't, then do a new paragraph with a bullet.
I'm just not sure how to go about this, particularly determining if there is already a bullet or not.
Hoping someone can throw some light on the subject. I'll keep plugging away in the meantime. :)
UPDATE: I've gotten this far where it inserts a return and I was hoping would insert a bullet but it is inserting a bullet in numerous spaces in that Cell rather than after the vbCr:
Dim BIOCell As range
With ActiveDocument
Set BIOCell = .range(Start:=.Tables(1).Cell(13, 2).range.Start, _
End:=.Tables(1).Cell(13, 2).range.End)
BIOCell.Select
End With
With ActiveDocument.Tables(1)
If .Cell(13, 2).range.Text Like "*as follows:*" Then
With Selection.Find
.Text = "as follows: "
.Replacement.Text = "as follows:" & vbCr
Selection.range.ListFormat.ApplyListTemplateWithLevel ListTemplate:= _
ListGalleries(wdBulletGallery).ListTemplates(1), ContinuePreviousList:= _
False, ApplyTo:=wdListApplyToWholeList, DefaultListBehavior:= _
wdWord10ListBehavior
.Execute Replace:=wdReplaceAll
End With
Else
MsgBox "couldn't find it"
End If
End With

I've modified your code sample and this works for me. Since you already declare and assign a Range to BIOCell you can use that throughout your macro to identify the cell contents. There's no need to use the "Like" test since Range.Find.Execute returns True if successful, otherwise False. When Find is successful, the Range will change to what has been found (in other words it's no longer the entire cell).
Trying to replace with a paragraph mark isn't working as you wish. Since you need to do something that can't be done with Find/Replace anyway (the bullets) simply add the paragraph mark if Find is successful, put the Range focus at the end of the cell, than apply the Bullets formatting. (Note that there's no need to use Selection if you have the Range object.)
Sub FindInCellAppendBullets()
Dim BIOCell As Range
Dim found As Boolean
With ActiveDocument
Set BIOCell = .Range(Start:=.Tables(1).Cell(13, 2).Range.Start, _
End:=.Tables(1).Cell(13, 2).Range.End)
BIOCell.Select
End With
With BIOCell.Find
.Text = "as follows: "
found = .Execute
If found Then
BIOCell.InsertParagraphAfter
BIOCell.Collapse wdCollapseEnd
BIOCell.ListFormat.ApplyListTemplateWithLevel ListTemplate:= _
ListGalleries(wdBulletGallery).ListTemplates(1), ContinuePreviousList:= _
False, ApplyTo:=wdListApplyToWholeList, DefaultListBehavior:= _
wdWord10ListBehavior
Else
MsgBox "couldn't find it"
End If
End With
End Sub
If the table cell already has paragraphs of text and you want everything after the Find term to be bulleted, then the code could look like the example that follows.
In this case, a second Range object is used to perform the Find, while BIOCell remains assigned to the entire cell. (Always use the Duplicate property to make a "copy" of a Range that can be used independently. Range is an anamoly in the Office object models: Range=Range makes both Ranges identical - if you change the position of one, the position of the other changes, as well.)
Once Find is successful, the findRange is collapsed to the end of the Find term and moved one paragraph further (to the first paragraph following the found text). The end of the Range is then extended to the end of the cell (end of BIOCell), then moved back a couple of characters so that it doesn't include the end-of-cell markers. (Otherwise the bullets would be applied to the entire cell instead of up through the last paragraph of the cell.)
Sub FindInCellFormatWithBullets()
Dim BIOCell As Range
Dim findRange As Range
Dim found As Boolean
With ActiveDocument
Set BIOCell = .Range(Start:=.Tables(1).Cell(13, 2).Range.Start, _
End:=.Tables(1).Cell(13, 2).Range.End)
Set findRange = BIOCell.Duplicate
BIOCell.Select
End With
With findRange.Find
.Text = "as follows: "
found = .Execute
If found Then
findRange.MoveStart wdParagraph, 1
findRange.End = BIOCell.End - 2
findRange.ListFormat.ApplyListTemplateWithLevel ListTemplate:= _
ListGalleries(wdBulletGallery).ListTemplates(1), ContinuePreviousList:= _
False, ApplyTo:=wdListApplyToWholeList, DefaultListBehavior:= _
wdWord10ListBehavior
Else
MsgBox "couldn't find it"
End If
End With
End Sub

Try:
Sub Demo()
Application.ScreenUpdating = False
Dim Rng As Range, i As Long
With ActiveDocument.Tables(1).Cell(13, 2)
Set Rng = .Range
With .Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "as follows:"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
End With
If .Find.Found = False Then
MsgBox "couldn't find it"
Exit Sub
End If
Do While .Find.Found
If .InRange(Rng) Then
If .Characters.Last.Next <> vbCr Then .InsertAfter vbCr & vbCr
If .Paragraphs.Last.Next.Range.ListFormat.ListType <> wdListBullet Then
If Len(.Paragraphs.Last.Next.Range.Text) > 1 Then .InsertAfter vbCr
.Paragraphs.Last.Next.Range.ListFormat.ApplyListTemplateWithLevel _
ListTemplate:=ListGalleries(wdBulletGallery).ListTemplates(1), _
ContinuePreviousList:=False, ApplyTo:=wdListApplyToWholeList, _
DefaultListBehavior:=wdWord10ListBehavior
End If
Else
Exit Do
End If
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
End With
Application.ScreenUpdating = True
End Sub
Unlike Cindy's code, the above will insert a bullet paragraph regardless of whether the 'as follows:' string terminates with a paragraph break (or anything other than a space) when the following paragraph isn't a bulleted one.

Related

Word VBA: Modify range based on conditional statement

I'm working on a script that extracts parenthetical in-text citations like '(Author, 1992)' from a word doc and copies them to another doc. The code works well to extract all parentheticals, but there are cases where a citation is in the form of "as quoted in Author (1992)..." Where a parenthetical starts with a number, I would like to pull the previous word into the range and copy both over into the new doc. In the above scenario then, we would get 'Author (1992)' rather than just '(1992)' as the code currently operates. I have tried writing a conditional to modify the range using .MoveStart, but it is not capturing the preceding word when the range copies over to the new doc. I know I'm missing some small, significant piece here but can't find any obvious (to me) solutions on other forums questions. Thanks
Sub CopyRefs()
Dim SearchRange As Range, DestinationDoc$, SourceDoc$
DestinationDoc$ = "Extracted_References.doc"
SourceDoc$ = ActiveDocument.Name
Documents.Add DocumentType:=wdNewBlankDocument
ActiveDocument.SaveAs DestinationDoc$, wdFormatDocument
Documents(SourceDoc$).Activate
Set SearchRange = ActiveDocument.Range
With SearchRange.Find
Do While .Execute(findText:="\(*\)", _
MatchWildcards:=True, _
Wrap:=wdFindStop, Forward:=True) = True
'this part doesn't seem to work when condition is satisfied
If SearchRange.Text Like "\(#*" Then
SearchRange.MoveStart wdWord, -1
End If
'a parenthetical number like (1992) will copy over to new doc but seems_
'like range not updating to include preceding word in previous step
Documents(DestinationDoc$).Range.InsertAfter SearchRange.Text & vbCr
Loop
End With
End Sub
Thanks to jonsson for pointing out the syntax error. Code below functions as intended.
Sub CopyRefs()
Dim SearchRange As Range, DestinationDoc$, SourceDoc$
DestinationDoc$ = "Extracted_References.doc"
SourceDoc$ = ActiveDocument.Name
Documents.Add DocumentType:=wdNewBlankDocument
ActiveDocument.SaveAs DestinationDoc$, wdFormatDocument
Documents(SourceDoc$).Activate
Set SearchRange = ActiveDocument.Range
With SearchRange.Find
Do While .Execute(findText:="\(*\)", _
MatchWildcards:=True, _
Wrap:=wdFindStop, Forward:=True) = True
'Removed backslash since "(" doesn't need to be escaped
If SearchRange.Text Like "(#*" Then
SearchRange.MoveStart wdWord, -1
End If
'copies range to new doc
Documents(DestinationDoc$).Range.InsertAfter SearchRange.Text & vbCr
'collapses range to prevent infinite loop when If condition met
SearchRange.Collapse wdCollapseEnd
Loop
End With
End Sub

How to refer to a line or table row I've just inserted

I feel I must be missing something obvious. I'm using VBA to build a Word document by writing lines to it one at a time. Once I've written a line, I need to format it - this could be bolding, setting tabstops, etc. But in order to format a line, I have to be able to refer to it. All the formatting facilities operate on a Range or a Selection - how do I identify the line I've just inserted as the Range I want to operate on? (Also, same question for table rows, as the doc also includes tables I'm building one row at a time, and I need to format cells as I go).
This is how to insert text and format it as you go, using a Range object. It's better to not try to simulate how a user works by using Selection and TypeText. The code runs more slowly and it's more difficult to work precisely. There can be only one Selection, but code can work with many Ranges...
The other important point to remember is to declare and instantiate objects as they're created - tables and table rows, for example.
Dim rng1 as Word.Range, rng2 as Word.Range
Set rng1 = ActiveDocument.Content
rng1.Text = "line one" & vbCr
rng1.Font.Bold = True
rng1.Collapse wdCollapseEnd
rng1.Text = "line two" & vbCr
rng1.Font.Bold = False
rng1.Collapse wdCollapseEnd
Set rng2 = rng1.Duplicate
rng2.Text = "line three" & vbCr
rng2.Font.Italic = True
'You can still work with the first range
rng1.ParagraphFormat.Alignment = wdAlignParagraphCenter
'
Dim tbl as Word.Table, rw1 as Word.Row, rw2 as Word.Row
Set tbl = ActiveDocument.Tables.Add
Set rw1 = tbl.Rows(1)
Set r2 = tbl.Rows.Add
Sub FormatBold()
Dim StartWord As String, EndWord As String
StartWord = "STARTSTART"
EndWord = "ENDEND"
With ActiveDocument.Content.Duplicate
.Find.Execute Findtext:=StartWord & "*" & EndWord, MatchWildcards:=True
.MoveStart wdCharacter, Len(StartWord)
.MoveEnd wdCharacter, -Len(EndWord)
.Font.Bold = True ' Or whatever you want to do
End With
End Sub
Format the text while you write it:
Sub StartTyping()
Selection.TypeText Text:="This is the "
Selection.Font.Bold = wdToggle
Selection.TypeText Text:="sentence"
Selection.Font.Bold = wdToggle
Selection.TypeText Text:=" I am inserting." & vbCr
End Sub

word vba range not repositioning using a loop

I am working on a macro in word. It pulls cell contents from some cells in an excel doc, puts part of them at the end of the word doc, bolds the first part, then puts the rest of the string and unbolds it.Then it looks for the next match in the excel doc and repeats until there are no matches.
On the second pass through the loop, it continues to affect the content added in the first pass. The font with block also affects the previous line and ends up bolding the entire thing. I set the object to Nothing at the end of the function so I wouldn't expect it to see the first part of the loop as part of the range any longer.
Do
x = AssembleSentence(Last, First, Rank)
Set Loc = .FindNext(Loc)
Loop While Not Loc Is Nothing And Loc.Address <> sFirstFind
Function AssembleSentence(Last, First, Rank)
Dim sText0 As String, sText As String, oText As Object
Set oText = ActiveDocument.Content
sText0 = First & " " & Last
sText = ", " & Rank & " Professor at College of Hard Knocks."
Set oText = ActiveDocument.Content.Paragraphs.Add
oText.Range.SetRange Start:=ActiveDocument.Range.End, End:=ActiveDocument.Range.End
Selection.EndKey Unit:=wdStory
With oText.Range
.InsertAfter (sText0)
With .Font
.Bold = True
End With
End With
Selection.EndKey Unit:=wdStory
With Selection
.Text = sText
With .Font
.Bold = False
End With
End With
Selection.EndKey Unit:=wdStory
Set oText = Nothing
End Function
Still unsure why the loop doesn't redo the range to the end on its own, but this fixes it so that it stops affecting prior looped content.
Looking at my oText.range start/end properties it looks like it is 1034/1035 with a length of 1036 on the first pass and then 1036/1209 with a length of 1210 on the second pass. That is the issue - I don't know why it isn't 1208/1209 on the second pass after setting the object to nothing at the end of the first pass, but the following edit fixes the issue.
With oText.Range
.SetRange Start:=oText.Range.End, End:=oText.Range.End
.InsertAfter (sText0)
With .Font
.Bold = True
End With
End With

VBA need Range assistance

the following is the function I have for adding the non underlined entry
(to simplify it a bit, there is also a function that does this twice adding 1 string underlined and then the string after it not underlined)
Function Add_Single_Entry(ByVal uEntry As String, ByVal ptime As String, ByVal crntValue As String)
uEntry = UCase( uEntry )
Call add_tList( ptime )
Dim rng1 As Word.Range
' Set Selection position however is appropriate
Set rng1 = Selection.Range
rng1.End = rng1.Start
rng1.Text = uEntry
Selection.Start = rng1.End
End Function
I need to be able to set my starting point to be the next line after the last instance of crntValue but I'm not sure how.
to clarify i would like the code to find the last instance of say "0000Z" (crntValue) in a Word document and then input a string on the next line.
In cases like this, the macro recorder is a good help - just record "goto end, find upwards, insert new line" and adapt the recorded code.
Something like
' goto end of document
Selection.EndKey Unit:=wdStory
With Selection.Find
.Text = crntValue
.Forward = False ' from bottom to top
.Format = False
' adapt to your needs
.MatchCase = False
.MatchWholeWord = False
End With
' Check if the string was found
If Selection.Find.Execute() Then
' goto end of line
Selection.EndKey Unit:=wdLine
' and insert new line
Selection.TypeParagraph
' now you're ready to insert your entry
Else
MsgBox "Sorry, " & crntValue & " was not found.", vbExclamation
End If

Word VBA: How to replace only the next instance of a string via Replacement Object

This is a silly question, but can't figure it out.
Straight from the Microsoft Site:
This example finds every instance of the word "Start" in the active document and replaces it with "End." The find operation ignores formatting but matches the case of the text to find ("Start").
Set myRange = ActiveDocument.Range(Start:=0, End:=0)
With myRange.Find
.ClearFormatting
.Text = "Start"
With .Replacement
.ClearFormatting
.Text = "End"
End With
.Execute Replace:=wdReplaceAll, _
Format:=True, MatchCase:=True, _
MatchWholeWord:=True
End With
I need to know how to make it so it only finds the next instance of Start and replace it with End. This will leave all other Ends intact throughout the document.
You should use wdReplaceOne in place of wdReplaceAll.
You should be able to adapt this:
Sub Tester()
Const FIND_WHAT as String = "Start"
Const REPLACE_WITH as String = "End"
Const REPLACE_WHICH As Long = 4 'which instance to replace?
Dim rng As Range, i As Long
i = 0
Set rng = ActiveDocument.Content
With rng.Find
.ClearFormatting
.Text = FIND_WHAT
Do While .Execute(Format:=True, MatchCase:=True, _
MatchWholeWord:=True)
i = i + 1
If i = REPLACE_WHICH Then
'Note - "rng" is now redefined as the found range
' This happens every time Execute returns True
rng.Text = REPLACE_WITH
Exit Do
End If
Loop
End With
End Sub
This discussion has some useful suggestions: Replace only last occurrence of match in a string in VBA. In brief, it's a case of looping through your search string from start until the first instance of the search argument is located and replacing just that.