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

Get any sentence range [draft]

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

We create a function to get a sentence range based on a starting location. It naturally accounts for double quotes or parentheses based on whether they are present on both sides of the sentence.

Thanks for your interest

This content is part of a paid plan.

Get a sentence range (enhanced)

A function tackles a specific tasks which is generally useful to other macros. Finding the current sentence is a general enough task that it makes a good candidate function, but another advantage of functions is we can add a few tweaks to make them better while keeping the complexity tucked neatly out of sight.

It's important for editing macros to play nice with common document elements. For sentences in this macro, we'll include double quotes or parentheses if they appear on both sides of the sentence range. That is, if the sentence is all dialog, it makes sense to include it. If the sentence only has double quotes on one side, it is obviously just one sentence in the dialog, so it should be handled separately from the double quotes. A similar argument applies to the parentheses.

Move sentence article series

The member move sentence article uses several utility functions to streamline the overall task.

Each function is general enough to be useful in other macros, so they make great additions to an editing macro toolbox.

What’s the plan?

It’s a good practice to think about some details regarding how the macro works. We won't get everything exactly right, but we’ll begin the process with a plan which is usually better than winging it.

Whoa, a life lesson beckons, but I shall restrain myself ...

Roughly speaking, the function should work as follows:

  • We begin the macro with the cursor in a sentence which could be text, text within dialog, or text between parentheses. No assumption is made about whether we begin with a selection or an insertion point.
  • Assign 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 occur only 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 by default). We omit spaces because we need to check for punctuation at the end of the range, and that’s easier to do after removing the spaces. We'll extend a separate macro it to find the sentence range around any document position.

Let’s translate the plan into a function.

Create the empty function

The empty function is basically the same as previous ones we’ve created. Open the VBA editor with Alt+F11 in Word for Windows (or Option+F11 on a Mac) and type the following.

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

' Return the sentence range
Set GetCurrentSentenceRange = Nothing ' Placeholder value
End Function

The first line inside the function starts with a single quote (called a "comment character") to begin a descriptive comment about what the function does. We'll need to fill the function below.

No parameters are needed

In this simplified function, we want the current sentence range, so no parameters are needed, but the empty parentheses just after the name are still required in the function definition. We base our starting range on the initial selection or insertion point in the document.

Assign a returned data type and result

We’re returning the sentence range after properly accounting for double quotes or parentheses, so we need the return data type  “As Range” at the end of the Function header line.

Set GetCurrentSentenceRange = Nothing

Nothing is a value VBA assigns to object variables not yet associated with a valid document element (or whatever it represents). The returned result is assigned to the function hand, and it is temporarily set to  Nothing since we do not yet have a sentence range.

Get the 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 double quote if only on one side
If sFirst = LeftDQ And sLast <> RightDQ Then r.MoveStart
' Remove right double quote if only on one side
If sFirst <> LeftDQ And sLast = RightDQ Then r.MoveEnd Count:=-1

' 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

' Remove left parenthesis if only on one side
If sFirst = "(" And sLast <> ")" Then r.MoveStart
' Remove right parenthesis if only on one side
If sFirst <> "(" And sLast = ")" Then r.MoveEnd Count:=-1

' 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.

Declare a 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.

Function GetSentenceRange(Optional TargetRange As Range = Nothing _
Optional BDialog As Boolean = True, _
Optional BParentheses As Boolean = True) As Range
' Returns the sentence range corresponding to given range location
' excluding any paragraph marks
' Default target range is current document Selection range
' BDialog excludes any double quotes if they only occur on one side
' BParentheses excludes any parentheses if they only occur on one side
' occur on one side of range

' Assign the working range r based on the target range or default
' to the current Selection range
Dim r As Range
If TargetRange Is Nothing Then
Set r = Selection.Range ' Default to current Selection
Else
Set r = TargetRange.Duplicate ' Use target range argument
End If

' Span the current sentence (allows multiple sentences unless collapsed)
' r.Collapse ' Restrict to starting sentence (optional)
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
If BDialog Then Set r = TrimRangeCharacterXor(r, LeftDQ, RightDQ)
' Remove parentheses if only on one side
If BParentheses Then Set r = TrimRangeCharacterXor(r, "(", ")")
' Extend over any spaces at the end (mimic Word defaults)
r.MoveEndWhile Cset:=" "

Set GetSentenceRange = r
End Function

This function enables several one-line editing macros to select, copy, cut, or delete a sentence range.

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.

Improvements or extensions

Why would be go through the trouble?

Well, dialog is the obvious example for a writer, but other examples exist.

Perhaps you deal with a lot of professional titles in sentences, like Dr. Jognar Doe. Word's default sentence parsing will probably mistake Dr. as the end of the sentence. With a function, we can catch and correct such idiosyncrasies.

Personally, I don't think a paragraph mark (it is the default for the last sentence of a paragraph) should ever be included in a sentence selection, so my version could always exclude them and save me a step in the main macro.

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.