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

Select sentence more

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

We create a function to get the current sentence range while allowing naturally including double quotes or parentheses if they are present on both sides of the initial sentence.

Thanks for your interest

This content is part of a paid plan.

Select sentence with dialog or parentheses

This is another in a series of functions intended to streamline our previous move sentence macro.

Sometimes it’s convenient to isolate specific tasks even when they aren’t complicated on their own, and selecting a sentence range is one such function. It's also a general enough task that it makes a good candidate function for use in other text manipulation macros.

In the process of encapsulating the function as its own separate thing, we can often enhance it without complicating the original macro. Then we can leverage it to make other useful macros even easier to implement.

The Problem

In the initial article, we created two macros to move a sentence forward or backward in the document. Both are awkwardly long, so we are breaking them down into smaller chunks. In practice, we would do this while creating the original macros to make the work easier, but we’re attacking the problem after the fact this time.

What’s the plan?

It’s a good practice to think about some details regarding how the macro works. It doesn’t mean you’ll get everything exactly right, but you’ll go into the process with a plan which is usually better than winging it (another life lesson beckons, but I shall restrain myself ...).

Roughly speaking, the function should work as follows:

  • Start in a general paragraph with any sentence which could be just regular text, text within dialog, or text between parentheses.
  • Set the working sentence range based on the current selection or insertion point (a blinking I-bar with no text selected).
  • Trim any paragraph marks or spaces from the right side of the range.
  • Trim double quotes or parentheses if they only occur on one side of the range.

We remove any paragraph marks because they shouldn’t be part of a sentence (even though Word includes them in selections). We omit spaces because we need to check for punctuation at the end of the range, and that’s easier to do when we remove the spaces..

Let’s translate those assumptions into a function.

Create function skeleton

The function skeleton is basically the same as previous ones we’ve created.

Function GetCurrentSentenceRange() As Range
' Include function steps ...

' Return whether paragraph was deleted
Set GetCurrentSentenceRange = Nothing
End Function

What parameters to use?

In this function, we want the current sentence range, so there are no parameters to accept. We instead based our starting range on the initial selection or insertion point in the document.

Return type

We’re returning the sentence range after properly accounting for double quotes or parentheses, so I added “As Range” at the end of the Function declaration line. The return value for the sentence range is temporarily set to Nothing because we don’t know what it is yet.

Set GetCurrentSentenceRange = Nothing

See a previous article for a description of the Nothing value, but briefly, it is assigned to invalid objects that have no regular assignment yet.

Get initial sentence range

Let’s review the sequence for the original sentence selection steps.

Define initial range

We first define the working range for based on the current Selection’s Range property.

Set r = Selection.Range

This command sets our working range equal to the current selection or insertion point in the document. In Word, an insertion point is just an empty Selection spanning no content. Specifically for this corresponding working range, it's Start and End positions in the document would be the same value.

We’re using a simple working range name r, so it’s easier to type.

Collapse the range

What happens if there was an initial selection of spanned text when the user runs the macro? Let’s avoid any issues by collapsing the selection right off the bat.

' Call function with a specific Paragraph variable
Call DeleteEmptyParagraph(Selection.Paragraphs.First) ' Does not work

Why?

The range is now known to be empty specifically positioned at the beginning of the document Selection.

Basic Expand command

Now we can expand the range as needed with some confidence since we have a consistent starting point for our macro. The command to expand the range over the current sentence is:

r.Expand Unit:=wdSentence

Remember the Expand command expands the selection by the given Unit but no more or less even if you run the command twice in a row.

Omit paragraph mark

Word includes an ending paragraph mark after the sentence by default. In fact, Word will include every trailing paragraph mark (along with the empty paragraphs) until it encounters some regular text. This behavior isn’t intuitive to me, so let’s get rid of them.

Remove one paragraph mark?

The vast majority of the time, there will only be one paragraph mark, so the earliest version of the move sentence macro removed a single paragraph mark character using the MoveEnd method.

' Remove one paragraph mark
r.MoveEnd Unit:=wdCharacter, Count:=-1 ' Not used

MoveEnd literally moves the End position of the range by the Unit size given, and a negative count value indicates the End position should be moved backward in the document.

Remove all paragraph marks

Better though, we can remove any number of paragraph marks with a single command using the MoveEndWhile method.

' Remove all paragraph marks from sentence range
r.MoveEndWhile Cset:=vbCr, Count:=wdBackward

This command keeps moving the End position of the range backward (with the Count option) as long as it keeps finding paragraph marks. Remember vbCr is a special character for a paragraph mark in Word, so we set that character search option Cset equal to it.

In my opinion, omitting paragraph marks from a sentence selection is more intuitive and consistent with the meaning of a sentence within a paragraph.

Original macro sentence selection steps

Put together, the steps to span the initial sentence range are:

' Select the current sentence
Set r = Selection.Range
r.Collapse ' Avoid starting selection issues
r.Expand Unit:=wdSentence
' Remove any paragraph marks from sentence range
r.MoveEndWhile Cset:=vbCr, Count:=wdBackward

This sequence occurs only once at the beginning of each move sentence macro, but it can easily occur in other macros where we want to accomplish related tasks such as copying or cutting a sentence.

This wouldn’t be a bad function all by itself since it encapsulates several intuitive steps, and we can run them as a single command in whichever macro we need.

If this is all you want, see the simple version of the macro below. However, as an author using macros to edit faster, we can do better.

Allowing dialog

I like my macros to work as intuitively as possible (I suppose within reason based upon the work involved). They shouldn’t work against or frustrate me by making me clean up text after they finish. They should just work.

In novels, sentences are often included inside double quotes, so if I’m moving dialog sentences around, I want the macro to naturally interpret whether the dialog is a single sentence or just one of several inside the double quotes.

A similar effect occurs with parentheses in novel notes or work documents. I generally want to exclude a parenthesis if it occurs only on one side of a sentence. For the remainder of this article, I’ll work with double quotes since the logic extends trivially to parentheses.

Use double quote functions

To make this macro easier to read, I’ll assume you’ve implemented the double quote functions in a separate article. In this function, I will refer to the respective left and right double quote characters as LeftDQ and RightDQ knowing these are functions that will return the correct character.

Why?

Left and right double quote characters are different in Word for Mac or Windows systems, so the mentioned functions will give us the correct characters without having to think about it. Both functions are simple, and I will not need to repeatedly rephrase the commands below depending on an assumed operating system. Plus it just looks much nicer.

If you prefer not to use these functions, see the comments after the final function below for the respective standard character references.

What is the dialog situation?

Dialog can occur in various cases, so how do we handle the double quotes? The following includes examples with corresponding explanations.

No dialog

Example: This is a regular sentence in a novel. Another regular sentence follows that one.

The function should just span the whole individual sentence range like normal, so our new steps shouldn’t mess anything up.

Single sentence of dialog

Example: “This is dialog text inside a pair of double quotes.”

Since the sentence is entirely dialog, the function should span the whole sentence range including the double quotes on both sides.

Multiple sentences of dialog

Example: “This is the first sentence of some dialog text. Another sentence follows it inside the double quotes like this.”

Regardless of which sentence is picked, it would have at most one double quote bordering it. The function should span only the current sentence range and exclude the beginning or ending double quote as appropriate.

Summary

So, the double quotes should be included in the range only when they are on both sides of a single sentence.

Hmmm.

See where this gets a little tricky? Glad you have me along for the ride?

Trim spaces

We first trim any spaces from the end of the range, so we can properly test for double quotes. Word never includes (to my knowledge) any spaces on the left side of an automatic selection, so we’ll just trim any spaces from the right side of the range.

r.MoveEndWhile Cset:=" ", Count:=wdBackward

Wait a second …

This uses the same MoveEndWhile command as before, even moving backward, except it removes spaces now.

Just combine with removing paragraph marks step

We could just combine it with the earlier command by including a space along with the paragraph mark in the Cset character search string:

r.MoveEndWhile Cset:=" " + vbCr, Count:=wdBackward

We’re adding the two characters together to create a revised Cset search string. This is called string concatenation which we explained in more detail in a previous article.

Get first and last characters

Now we need to get the characters at each end of the range using the characters collection.

r.Characters.First

Remember the First character is actually a range, so we need to reference its Text property for the actual character. We store this in a variable using the equals = sign for later use.

FirstCharacter = r.Characters.First.Text

Similarly, we get the last character of the range.

LastCharacter = r.Characters.Last.Text

Presumably, the range is not empty, but the logic below still handles everything correctly even if so.

Remove left double quote

To start the process of removing the left double quote, if appropriate, we compare the FirstCharacter to a left double quote.

First condition

The initial condition is whether the first character is a left double quote. Specifically, the True-False (Boolean) value is:

FirstCharacter = LeftDQ
Not assigning a value

We’re not assigning FirstCharacter the value of LeftDQ. It’s a True-False value to be used in an If statement below to make a decision.

It is unfortunate that VBA has overlapping notation between assigning a value and determining a True-False condition (some other languages use double equals == for True-False values), but we’re stuck with it.

So, the If statement is:

If FirstCharacter = LeftDQ Then
' Remove left double quote? But not quite correct ...
End If

But this isn’t quite correct because we want to keep the double quotes if they are on both sides of the range.

Arghhh.

Second condition

So, if it’s on the left but not on the right, then remove it from the range. The second condition is:

Not LastCharacter = RightDQ

We include the preceding Not statement because we want the opposite True-False value of LastCharacter = RightDQ.

Compound condition

For the compound condition, we use “And” between the individual conditions meaning both must be True to give an overall True result.

If FirstCharacter = LeftDQ And Not LastCharacter = RightDQ Then
' Remove left double quote from range ...
End If

Omit left double quote from range

The command to remove the double quote from the left side of the range is:

r.MoveStart Unit:=wdCharacter

The MoveStart command moves the Start position of the range one character past the left double quote without changing the End position (unless Start exceeds End). The default is to move by a Unit of wdCharacter (included to be clear), and the default Count option is by 1 Unit.

Resulting steps for left double quote

So the If statement to remove a left double quote when appropriate is:

If FirstCharacter = LeftDQ And Not LastCharacter = RightDQ Then
' Remove left double quote from range
r.MoveStart Unit:=wdCharacter
End If

Remove right double quote

Similarly, we may need remove the right double quote.

We initially check the LastCharacter, but similar to the previous case, we need to make sure the left double quote doesn’t exist before we remove the right one from the range.

If Not FirstCharacter = LeftDQ And LastCharacter = RightDQ Then
' Remove right double quote from range ...
End If

Omit right double quote from range

The command to remove the double quote on the right side of the range is:

r.MoveEnd Unit:=wdCharacter, Count:=-1

The MoveEnd command moves the End position of the range without changing the Start position (unless End precedes Start). The negative Count value moves backward in the document by 1 Unit.

Resulting steps for right double quote

So the If statement to possibly remove a right double quote, if appropriate, is:

If Not FirstCharacter = LeftDQ And LastCharacter = RightDQ Then
' Remove right double quote from range
r.MoveEnd Unit:=wdCharacter, Count:=-1
End If

Allowing parentheses

We can do the same thing for parentheses simply by swapping out the respective left and right characters in the above conditions.

' Remove left parenthesis if on one side
If LeftCharacter = "(" And Not RightCharacter = ")" Then
r.MoveStart Unit:=wdCharacter
End If

' Remove right parenthesis if on one side
If Not LeftCharacter = "(" And RightCharacter = ")" Then
r.MoveEnd Unit:=wdCharacter, Count:=-1
End If

Both of these are a preference, but they are definitely convenient as you use the macros more.

Include ending spaces

Since Word normally includes space on the end of a selection, our range function should probably mimic that behavior. That’s what users are probably expecting in Word even if they’re not aware of it.

' Re-include any spaces at end of range
r.MoveEndWhile Cset:=" "

Now if the user selects the returned range, it will look normal.

Gotchas

We should get in the habit of considering potential problems.

What if the range ends up empty?

During the function, we remove several characters from the range: excess spaces, paragraph marks, and possibly a double quote or parenthesis. If the range ends up being empty by the time the macro finishes, is that a problem?

Not really because we will just exit the function as normal and return the empty range. In fact, the returned empty range serves as a clue to the user that something did not follow through not as expected, but this function isn’t responsible for correcting anything like that.

Basically, there doesn’t seem to be a problem here, but that doesn’t mean we shouldn’t consider it when creating the macro because sometimes issues like this can return to bite us.

Simple Function

The simpler version of the function without considering double quotes or parentheses is:

Function GetCurrentSentenceRangeSimple() As Range
' Get range corresponding to current sentence excluding paragraph marks

' Get working range r based on current selection
Set r = Selection.Range
r.Collapse ' Avoid starting selection issues

' Select current sentence
r.Expand Unit:=wdSentence
' Remove paragraph marks from end of range
r.MoveEndWhile Cset:=vbCr, Count:=wdBackward

Set GetCurrentSentenceRangeSimple = r
End Function

This version just acts like a regular sentence range with the exception that it omits any ending paragraph marks that Word likes to include. I find this behavior more intuitive for sentence ranges/selections.

Final Function

Putting it all together, the function to get the current sentence range while accounting for dialog or parentheses is:

Function GetCurrentSentenceRange() As Range
' Get range corresponding to current sentence excluding paragraph marks
' Excludes double quotes or parentheses if they only occur on one side of range

' Get working range r based on current selection
Set r = Selection.Range
r.Collapse ' Avoid starting selection issues
' Select current sentence
r.Expand Unit:=wdSentence
' Remove paragraph marks or spaces from end of range
r.MoveEndWhile Cset:=" " + vbCr, Count:=wdBackward

' Get first and last characters for testing below
FirstCharacter = r.Characters.First.Text
LastCharacter = r.Characters.Last.Text

' Remove left double quote if only on one side
If FirstCharacter = LeftDQ And Not LastCharacter = RightDQ Then
r.MoveStart Unit:=wdCharacter
End If

' Remove right double quote if only on one side
If Not FirstCharacter = LeftDQ And LastCharacter = RightDQ Then
r.MoveEnd Unit:=wdCharacter, Count:=-1
End If

' Remove left parenthesis if only on one side
If LeftCharacter = "(" And Not RightCharacter = ")" Then
r.MoveStart Unit:=wdCharacter
End If

' Remove right parenthesis if only on one side
If Not LeftCharacter = "(" And RightCharacter = ")" Then
r.MoveEnd Unit:=wdCharacter, Count:=-1
End If

' Re-include any spaces at end of range
r.MoveEndWhile Cset:=" "

Set GetCurrentSentenceRange = r
End Function

These functions require the LeftDQ and RightDQ functions given in a separate article. These additional functions return the correct double quote characters regardless of whether you’re working in Word for Mac or Windows.

If you prefer not to use the double quote functions, replace every LeftDQ with a Chr(210) on Mac and Chr(147) on Windows. Similarly replace every RightDQ with a Chr(211) on Mac and Chr(148) on Windows.

We could combine the double quotes and parentheses character comparisons (the same steps are inside the If statements) before we shrink the range, but the compound If conditions start to look quite cumbersome. It’s not really necessary either.

What are the uses?

Clearly this function is used with the aforementioned move sentence macros. Where else might it be used?

We’ve previously created a delete sentence macro, and we could simplify and generalize that macro at the same time. We could further create macros to select, cut, copy, and italicize (for internal dialog, but probably with some modifications) sentences. These macros would be nearly trivial to create now that we have this workhorse function.

Improvements?

Given the messy conditions and the similarity in the steps, we could further extract the steps that remove the double quotes or parentheses when appropriate into a separate function. This would condense and even generalize the steps.

It’s not a bad idea, and I would probably do so with my own macros. This would be mostly for aesthetics though, so I’m leaving it out of the public macros …

But I can’t seem to help myself.

Character Removal Function

I tried to leave this function out, but the perfectionist in me wouldn’t cooperate. Here is a function to encapsulate the double quotes omission from the working range. It’s presented without explanation other than the comments in the macro.

Function TrimRangeCharacterXor(TargetRange As Range, LeftCharacter As String, _
RightCharacter As String) As Range
' Exclude given left or right character if they only occur on one side of the range
' Ignores any characters after first in input strings

' Check whether TargetRange is valid before continuing
If TargetRange Is Nothing Then
Set TrimRangeCharacterXor = Nothing
Exit Function
End If

' Verify strings are not empty
If Len(LeftCharacter) = 0 Or Len(RightCharacter) = 0 Then
Set TrimRangeCharacterXor = Nothing
Exit Function
End If

' Duplicate TargetRange as a working range r
Dim r As Range
Set r = TargetRange.Duplicate

' Use just first character of each given string
LeftCharacter = Left(LeftCharacter, 1)
RightCharacter = Left(RightCharacter, 1)

' Get first and last characters of working range
FirstCharacter = r.Characters.First.Text
LastCharacter = r.Characters.Last.Text

' Remove left character if only on one side
If FirstCharacter = LeftCharacter And Not LastCharacter = RightCharacter Then
r.MoveStart Unit:=wdCharacter
End If

' Remove right character if only on one side
If Not FirstCharacter = LeftCharacter And LastCharacter = RightCharacter Then
r.MoveEnd Unit:=wdCharacter, Count:=-1
End If

Set TrimRangeCharacterXor = r
End Function

For those that are unsure about the working range r appearing inside both functions. It will be treated in each function as a different range. This is called the “scope” of the variable, but more explanation is beyond the scope (no pun intended) of this article.

If you’re curious, the “Xor” suffix in the function name refers to the Boolean operator Xor. It’s like Or, but it’s only True if one of the conditions is True, not both like with the Or operator.

Declare range

In order to use the range with the function, we need to declare it explicitly. Otherwise, we'll get an error when we try to pass it to the above function.

Dim r As Range

We didn't need to do this previously because VBA will infer the type of the variable (using a generic type), but VBA is pickier when we send data to functions.

Revised Final Function

Let’s see if you like the result and agree it might be appealing to write additional functions to clean up your macros.

Function GetCurrentSentenceRange() As Range
' Returns range corresponding to current sentence excluding paragraph marks
' Excludes double quotes or parentheses if they only occur on one side of range

' Get working range r based on current selection
Dim r As Range
Set r = Selection.Range
r.Collapse ' Avoid starting selection issues

' Select current sentence
r.Expand Unit:=wdSentence
' Remove paragraph marks or spaces from end of range
r.MoveEndWhile Cset:=" " + vbCr, Count:=wdBackward

' Remove double quote if only on one side
Set r = TrimRangeCharacterXor(r, LeftDQ, RightDQ)
' Remove parentheses if only on one side
Set r = TrimRangeCharacterXor(r, "(", ")")

' Re-include any spaces at end of range
r.MoveEndWhile Cset:=" "

Set GetCurrentSentenceRange = r
End Function

Just by extracting the steps to exclude a double quote or parenthesis, this version is much easier to read. This version looks so much cleaner than the main function. The tradeoff is we have functions within functions.

Is it worth the extra work?

It’s not really that much extra work, and I cannot assert the extra function will be generally useful in other macros, but I still prefer the cleaner looking macro.

Only you can decide if you like it.

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.