Heading

This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
min read

Toggle quotes around multiple paragraphs

Word • Macros • Editing • Functions
Peter Ronhovde
16
min read

Editing is a time consuming task, but it’s annoying to make tedious or repetitive changes that a computer could easily do. You might have some notes about a conversation between characters. Having a quick toggle to apply double quotes for the entire conversation would be convenient. Let the computer do computer stuff and save your human brain and fingers for author stuff.

Thanks for your interest

This content is part of a paid plan.

Toggle Quotes around Multiple Paragraphs

Toggle double quotes around each paragraph in the current selection. This is an extension but independent of a previous single-paragraph version. Due to the multiple possible document edits, we further add an undo record to make it easier to reverse the changes, and we allow “sloppy” paragraph selections to make the macro easier to use.

What’s the plan?

We generalize an earlier macro that toggled double quotes around a paragraph. This is a more practical variation since we’ll probably at some time need to toggle quotes for a whole segment of dialog across multiple paragraphs at once.

What are the basic steps?

  • Loop over all paragraphs in the current selection, partial or full.
  • Run the toggle quotes function on each paragraph range.

We haven’t had much need for loops thus far on this blog, but it’s central to the action of this macro. Let’s work through the details, but we also add some icing on the cake afterward.

Other functions

With this in mind, we leverage the aforementioned previously created a function that toggles double quotes around any document range. That function also uses two double quote functions because the special character for left and right double quotes is different between Word for Mac and Windows as well as a function to trim blank space from a range.

Create macro skeleton

Open the VBA editor (Option+ or Alt+F11) and create two empty macros.

Sub ToggleQuotesParagraphs()
' Insert macro steps ...

End Sub

The macro can’t accept any parameters or return a value (it’s a subroutine not a function) since we’re assigning it to a keyboard shortcut.

Set up the loop

There are several ways to loop over macro steps in VBA. This isn’t a loop tutorial, so let’s focus on the one we need.

What are collections?

Collections are a general kind of list which can store any type of document element (or any type of data really). Commonly used examples in Word macros include Characters, Sentences, Paragraphs, etc., but the concept is quite general.

Simple For Each loop

We can loop over any collection of document elements using a For-Each loop. The meaning here is:

For Each SomeElement In SomeCollection
' Do something with current element ...
Next

A For-Each loop literally cycles through each element in the given collection. We can do anything we want in the loop, but it usually relates somehow to the current element of the loop.

Collection

SomeCollection is the collection you want to loop over and do something with each element.

The In keyword between the element variable name and the collection name is part of the command, so don’t forget it.

Element variable

We give a variable name, SomeElement in this case, which refers to the specific element of the collection in each cycle of the loop. Of course, the variable must match the element type of the collection. Some collections store document ranges. Others store paragraphs, hyperlinks, etc.

Next element

The Next command at the end cycles the loop to the next element in the collection. The loop ends when all elements have been used.

Nested loops

For completeness, if there are “nested loops” (loops within loops), we can add the element identifier after the Next statement.

Next SomeElement ' Used with nested loops

We could include the identifier in today’s macro also, but we don’t need it since we only have one loop.

Create the paragraphs loop

For clarity, we’ll refer to Paragraphs from now for this macro, but the basic logic applies to any loop variable along with a corresponding collection.

Loop variables

We’ll call our current element MyParagraph. If we want to be extra clear, we can declare this variable explicitly before the loop.

Dim MyParagraph As Paragraph ' Not required

This step isn’t required (unless Option Explicit is used) or you’re passing the variable directly into a function.

As a reminder, MyParagraph changes as the For-Each loops steps through all elements of the collection.

Paragraphs collection

We’re using the Paragraphs collection which is a property of the current Selection.

Selection.Paragraphs

Fortunately for this macro, this collection includes even partially selected paragraphs. This includes the current paragraph even if there is only an insertion point (no text spanned by a selection). In this case, only one paragraph would be in the collection.

Loop over all Paragraphs

Our loop over the Paragraphs collection is:

For MyParagraph In Selection.Paragraphs
' Do something with MyParagraph ...
Next

Even though we're doing something much more complex than a single command, this loop still reads almost like English. I like how VBA (and Visual Basic in general) tries to make programming look more approachable.

Toggle quotes for current paragraph range

In the loop, we want to toggle the quotes for the current paragraph but be specific with the function argument. For example, this doesn’t work.

Call ToggleQuotesRange(MyParagraph) ' Does not work

This doesn’t work for this function because we wrote the function to use a given range, not a paragraph. We could’ve written that function to work on a given paragraph, but using a range parameter made the function more generally useful.

Instead, we need the range of the current paragraph in the loop. A Paragraph object has a Range property that refers to the paragraph’s spanned content.

MyParagraph.Range

So “pass” this range to the ToggleQuotesRange function.

Call ToggleQuotesRange(MyParagraph.Range)

We already wrote the ToggleQuotesRange to exclude paragraph marks because they are also included as part of the paragraph and thus the range.

Gotcha

What could go wrong?

What if no paragraphs exist in the current Selection?

Uhhh … well, that can’t really happen.

If a document is open and active, there must be a Selection in it. Even an empty Selection, called an “insertion point” (the blinking I-bar waiting for you to type text), is associated with the paragraph in which it is located, so it looks like we’re okay here.

In general, a For-Each loop will only start if at least one element is in the given collection, so we would’ve been okay anyhow.

Final Macro

Putting this together, our extended macro to toggle double quotes around any number of selected paragraphs is:

Sub ToggleQuotesParagraphs()
' Toggle quotes individually around multiple paragraphs
For MyParagraph In Selection.Paragraphs
Call ToggleQuotesRange(MyParagraph.Range)
Next
End Sub

And done.

[Mic drop. The crowd goes wild …]

This is the advantage of functions. We just run it for each paragraph range letting Word do the work of looping over them and applying the function individually to each paragraph in the current Selection. This version even works for a single paragraph since it “loops” over the one paragraph running the toggle function just once.

I’ve assigned my versions of these macros to Command+Q or Control+Q.

However, the error extension below is important. Please don’t skip it.

Improvements

But by now you know I can’t leave it plain and … well I can’t say the above version is boring, but it could be better. I want to improve it three different ways.

  • Add an undo record, so all changes are undone together, if desired
  • Add an error handler just in case there are problems (mostly for the undo record)
  • Check for an empty paragraph and add plain double quotes inside it

This macro definitely benefits from an Undo Record (see previous article) since potentially many changes are made during the macro.

The error handler is to keep us out of trouble if the macro ever crashes while running.

I also add a condition to check for an empty paragraph, so I can tell the macro to automatically place the insertion point between the double quotes. That way, I can just start typing the new dialog.

Add undo record

This function has the potential to make many changes in different paragraphs, so it would definitely benefit from an undo record (see previous article for more explanation).

Start undo record

An undo record stores up all the changes made while it is on and will reverse them all at once on an Undo command usually by pressing Command+Z or Control+Z in Mac or Windows, respectively.

' Set start of macro undo record
Dim MyUndo As UndoRecord
Set MyUndo = Application.UndoRecord

An UndoRecord is a specific type of object which Word uses to keep track of actions under the hood. MyUndo is the current record variable name.

After we use the StartCustomRecord method, all commands will be aggregated together for any potential undo action.

MyUndo.StartCustomRecord("VBA Toggle paragraph quotes")

It’s quite handy, and the record will also be labeled in the edit menu list of recent document actions with the name you give it. Nice.

End undo record

Definitely end the undo the custom record at the end of the macro, but don’t omit the error handler below.

MyUndo.EndCustomRecord

Undo record danger points

Microsoft says don’t switch documents while using undo records. Also don’t use breakpoints (during macro testing) or the VBA editor will end the undo record mid-stream. This could cause issues, but you probably shouldn’t be testing your macros on your novels until they’re working correctly anyhow.

More importantly, see the error handler below.

Error handler

I try not to get overly complicated (which is a matter of perspective, I understand) with the presented macros, but undo records can cause significant issues if not properly done (see previous article for more explanation).

Why?

If an error occurs while the loop is running over the paragraphs in the selection, the macro quits immediately, and the undo record may not be properly closed.

I’ve had times where it works fine, but at others …

Bad. Definitely don’t do that.

Based on the terse documentation (as i understand it), VBA is supposed to close the undo record anyhow, but experience says otherwise. To help control this serious possibility, we can add an On Error statement.

' Catch any errors and end Undo record below
On Error GoTo ErrorUndo

This command tells VBA if an error occurs to go to a specific line identified by the name ErrorUndo and continue running the macro from there but skipping all steps in between. That way we can properly end the undo record.

Our “error steps” are as follows. We start the line label.

ErrorUndo:

Then we add any steps that are still run after an error is detected.

' End Undo record even if there is an error
MyUndo.EndCustomRecord

Don’t leave this out.

You could lose precious words (and writing time) which is a fearful thing for an author, and yes, I have lost novel content a few times because I wasn’t careful enough.

There are two downsides of using an error handler. One is the macro looks messier and more like real programming. Also, you need to wait to enable it until after you’re sure the macro is running properly. Otherwise, you won’t get an error message like normal since VBA will handle the error automatically because you told it to.

No Exit Sub before error steps?

If the error steps should only be run when an error occurs, it is common to include an Exit Sub statement just before the error line identifier.

On Error GoTo ErrorLine
' ... regular macro steps ...
Exit Sub ' Common if error steps are only for errors

ErrorLine:
' Include error-only steps ...
End Sub

For our macro, even if the main macro steps finish properly, we still want to close the undo record, so I omitted an Exit Sub command. The macro just continues on through the ErrorLine: label to run the last step.

Why?

I don’t want to repeat the same command above the error label. It just looks messy to me.

Insert empty double quotes on empty paragraph

I like little boosts of efficiency. Like I said, I don’t like typing double quotes, so I would like the macro to recognize an empty paragraph and position my insertion point in between my new double quotes ready for me to type some amazing dialog.

Conditional statement branch

How do we check for an empty paragraph and do the special case?

If current paragraph is empty Then
' Found empty paragraph, so add quotes and move between them ...
ElseIf Regular selection or no selection Then
' Toggle quotes for all paragraphs in selection ...
End If

We can’t forget the main purpose of this macro, so the ElseIf part adds the double quotes for regular selected paragraphs or no selection (meaning a single paragraph) as normal. See a previous article for a brief introduction to conditional statements.

Empty paragraph condition

Let’s take a short detour to explore some insufficient logic just to give you a flavor of little bugs that could creep into your macros if you’re not careful.

Insufficient case 1

Will testing for an empty Selection work?

We can’t use an empty Selection by itself to detect an empty paragraph since an insertion point can be anywhere in a paragraph even if the paragraph has text.

Selection.Type = wdSelectionIP ' Not enough ...

See a previous article for a brief review of Selection Types.

Insufficient case 2

What about testing for a paragraph mark?

A paragraph mark is included in the Paragraph’s text, so we could check the current paragraph’s text and see if it is just a paragraph mark special character vbCr. This is better, but it still won’t properly detect an empty paragraph by itself.

Selection.Text = vbCr ' Not enough by itself

Why?

What if the user has strangely only selected a paragraph mark in a regular paragraph? Admittedly, this would be odd, but this condition would still be True even if the paragraph isn’t empty. This condition could also be true if the insertion point just happens to be at the last position in any paragraph. This unfortunately second issue is because VBA considers the character immediately after an empty range (via Selection.Range since Selection.Text is just shorthand for Selection.Range.Text) to be the range's Text.

Insufficient case 3

Okay, what about checking the full paragraph text?

We can refer to the first full first paragraph text instead when checking for a paragraph marker.

Selection.Paragraphs.First.Range.Text = vbCr ' Still not quite enough

This one is trickier to see why it doesn’t work, but if the first paragraph of a multi-paragraph selection is empty, the condition is still true.

What condition do we use then?

Better condition

We can avoid each problem case above by validating whether the Selection is empty in addition to verifying the full paragraph text is only a paragraph mark.

Selection.Type = wdSelectionIP And Selection.Paragraphs.First.Range.Text = vbCr

It requires an And operator because both conditions must be True before we are sure the paragraph is empty. This condition will even be False if the paragraph contains only spaces since its text is more than just a paragraph mark.

Check for valid selection on paragraphs loop

We should probably validate whether we have a normal selection before trying to loop over any paragraphs.

Selection.Type = wdSelectionNormal Or Selection.Type = wdSelectionIP

We check whether the Selection Type indicates a normal selection using wdSelectionNormal, but we also need to allow for no selection (indicating we apply quotes to the current paragraph), so we include a condition for wdSelectionIP. Either condition can be true to loop over the paragraphs, so we use an Or operator.

Our revised conditional statement is:

If Selection.Type = wdSelectionIP And Selection.Paragraphs.First.Range.Text = vbCr Then
' Found empty paragraph, so add quotes and move between them ...
ElseIf Selection.Type = wdSelectionNormal Or Selection.Type = wdSelectionIP Then
' Toggle quotes for all paragraphs in selection ...
End If

We do not include an Else part, so nothing happens in the conditional statement if neither of these conditions is True.

Add double quotes and adjust insertion point position

Since we know there is an empty paragraph, we can just add the double quotes manually using the Selection’s InsertBefore method.

Selection.InsertBefore Text:=LeftDQ + RightDQ

These double quote functions are used because the special characters differ between Word for Mac and Windows.

Now move the insertion point between the double quotes, so we can just start writing the dialog.

Selection.Collapse
Selection.MoveRight ' Move between double quotes

We start by collapsing the Selection, so it does not move from the end. It seems in this situation the "active" side of the Selection is the End position, so the move begins from there. The collapse ensures the move begins from the left side.

MoveRight has a default Unit of characters and Count of 1 Unit, so we don't need to add any options.

I would probably also add the extra move steps for empty paragraphs in the previous versions of the toggle quotes functions.

Final Macro for Multiple Paragraphs

Adding the above bells and whistles, here is a more complete version of the macro.

Sub ToggleQuotesParagraphs()
' Toggle double quotes individually around multiple paragraphs
' Works even for partially selected paragraphs
' Inserts empty double quotes in an empty paragraph

' Set start of macro undo record
Dim MyUndo As UndoRecord
Set MyUndo = Application.UndoRecord
MyUndo.StartCustomRecord("VBA Toggle paragraphs quotes")

' Catch any errors and end Undo record below
On Error GoTo ErrorUndo

' Check for empty paragraph
If Selection.Type = wdSelectionIP And Selection.Paragraphs.First.Range.Text = vbCr Then
' Selection is insertion point in an empty paragraph
' Add double quotes and move between them
Selection.InsertBefore Text:=LeftDQ + RightDQ
Selection.Collapse
Selection.MoveRight ' Move between double quotes
ElseIf Selection.Type = wdSelectionNormal Or Selection.Type = wdSelectionIP Then
' Toggle double quotes for all paragraphs in selection
For Each MyParagraph In Selection.Paragraphs
Call ToggleQuotesRange(MyParagraph.Range)
Next
End If

' Continue into end undo record
ErrorUndo:
' End Undo block even if there is an error
MyUndo.EndCustomRecord
End Sub

I’d like to have one macro that does everything, but this one can’t apply block quotes across multiple paragraphs as a group nor to specific text like the previous versions. It only works on whole paragraphs. There is no way to discern those two other cases from whether the user wants to apply double quotes to each individual paragraph.

I don’t see a need for a similar variation with sentences since a writer would rarely add double quotes around each individual sentence in a selection.

I’ve assigned my version of this macro to Command+Q in Word for Mac and Control+Q Windows.

I have implemented further extensions to account for or remove dialog tags, etc. but this will have to do for now.

Affiliate Links

If you're interested in using Word or another tool related to the article, check out these affiliate links. I may make a small commission if you purchase when using them, but there is no increase in cost for you, and it helps to support this site and associated content.

I've been using Microsoft for Business for commercial use (that's us writers) on one of the lower pricing tiers for years. I get to use my macros, have online storage, and don't have to worry about software updates.