We create a function to get a sentence range around on a given location in the document. It naturally accounts for double quotes or parentheses based on whether they are present on both sides of the text.
Thanks for your interest
This content is part of a paid plan.
Get any sentence range
Editing functions and macros should work intuitively and not make us clean up the text or a selection after they're done. With this in mind, they should should play nice with common document elements, and dialog is … let's use a fancy word, ubiquitous in fiction.
A function tackles a specific task which is generally useful in other macros, and finding a sentence range is a general enough task that it makes a good candidate function. Such a function should intelligently provide a sentence range while taking the dialog text into account if it's present. That is, should a sentence range include the dialog tag or just the regular sentence text? A similar argument applies to the parentheses in other documents.
Create the empty function
Open the VBA editor with Alt+F11 in Word for Windows (or Option+F11 on a Mac) and create the following empty function. It's much the same as others we've created except we add an optional object parameter.
All functions require the Function keyword followed by a unique name. Any external data are listed as parameters inside the parentheses. Functions always return a value, so we need to specify its returned data type at the end. The initial comment lines clearly describe what the function does.
What is the parameter?
We want the sentence range around a given target range, so we specify a parameter name TargetRange and give it a data type As Range.
A common use case is just getting the range around the current cursor position, so we'll make the argument Optional and assume the current position if it isn't given when the function is used.
Optional parameters require a fixed default value. Since a Range is an object, the only valid default value is Nothing.
Nothing is the value assigned to objects not yet assigned to a valid document element. See our introduction to functions and subroutines article for more information about optional parameters.
Other parameters?
If we want the function to be even more general, we could include two more optional parameters to control whether the function trims any dialog tag or parentheses. These are omitted from the current function for brevity.
What is the returned result?
We’re returning the sentence range, so we add As Range at the end of the header line. The empty function above includes a placeholder result assignment to be clear.
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 name, but it's temporarily set to Nothing since we do not yet have a sentence range.
What’s the plan?
Roughly speaking, the function should work as follows:
- We may begin the function with a given target range around which we want to know the sentence range.
- If a range is not given, the function defaults to the current selection or insertion point position in the document.
- Trim any paragraph marks and spaces from the right side of the range.
- Trim parentheses if they occur only on one side of the range. Similarly, trim any dialog tags or a double quote if both left and right double quotes are not present in the range.
Word automatically includes any trailing paragraph marks in automatic sentence range extensions, but we remove them because they shouldn’t be part of a sentence range. Spaces are also trimmed because we need to check for parentheses on either side which is easier to do after removing the spaces. The logic checking for double quotes is more complicated because we're further allowing dialog tags in the sentence range when appropriate.
Assign the initial working range
We need a separate working range variable inside the function. If we used the target range parameter directly, any changes to it would affect the argument on the outside of the function because object parameters are always passed to the function as a memory location (called "by reference").
Declare the working variables
We declare a working Range variable which we call simply r to make it easier to type.
Dim is the VBA keyword to declare a variable. We'll also declare several plain text variables to clarify the steps below.
Each variable needs its own data type, or it will default to a Variant type than can store anything. Using a variable would not be a problem in this function, but specifying a String type is clearer. Each variable is explained when it is used. Often variables are declared just before they are used the first time, but it seemed cleaner to group them.
What is the initial working range?
The initial working range depends on what was provided to the function as the target range. A rough conditional statement looks like:
We need to know whether the target range is valid. It may seem awkward to include the invalid case as the first condition, but this structure is easier to read later.
What is Nothing?
No, we're not delving into metaphysics or vacuum energy fluctuations (although, that's cool too).
VBA literally assigns a value called Nothing to any object not yet assigned to a valid document element. If a target range argument is not provided when this function is used, the target range parameter will default to Nothing.
Check for a default target range
Since the target range is an object parameter, we need to use the keyword "Is," rather than an equals = sign, to compare it to Nothing.
This is a True or False (Boolean) condition which we can use in a conditional statement to make a decision. More specifically, True indicates the target range is not assigned. This condition also catches an invalid target range argument if the user provides an unassigned range variable.
Assign the default working range
When the target range is Nothing, we assign the working range based on the Range property of the Selection.
Set is required in VBA when assigning any object variables. The Selection is a VBA object, kind of like a special Range, that represents the current selection or insertion point in the document. We need to refer to its Range property since the Selection is more than just a range of document content. In Word, an insertion point is just an empty Selection spanning no content meaning it's Start and End positions in the document are the same.
Assign the given target range
When the target range is valid, we use the Duplicate method to create a copy for our working range.
We need to duplicate the target range argument into a separate variable, or the two variable names will refer to the same range. That may seem like what we want with a copy, but they will not be independent range variables. Any changes made to the working range inside the function would still affect the target range variable outside the function. Using Duplicate ensures they are independent of each other.
Extend the range over the sentence
After the above assignments, the working range r is valid in either case, so we can proceed. We now extend it over the sentence range at its location.
Collapse the range (optional)
If the working range spans any text, the expansion below could extend the range across multiple sentences or even paragraphs depending on the starting range. While it's not inherently wrong, we can avoid any logical issues by simply using the Collapse method.
How does this help?
After the collapse, we know the working range is empty and positioned at the beginning of the initial target range. Only one sentence is possible for the following expansion step.
Allowing more than one sentence
If you prefer to allow multiple sentences, just omit the collapse command. Neither approach is wrong, but it could result in some extra gotchas. For the remainder of the function, we assume a single sentence range.
Expand over the sentence range
Now, we Expand the range over the sentence.
Expand accepts a Unit option. The default unit is by word, so we assign the sentence constant wdSentence (from a table of Word constants). All option assignments require a colon equals := symbol. The Expand command extends the range both directions to the respective beginning or ending of the given unit but no farther.
Omit any paragraph marks (suggested)
By default, Word includes an ending paragraph mark in an automatic sentence range extension (or selection) if it exists after a sentence. In fact, Word will include every trailing paragraph mark (empty paragraphs) until it encounters some regular text. This behavior isn’t intuitive, so let’s get rid of them.
The majority of the time, only one paragraph mark will be spanned, but we can remove all of them with a single command using the MoveEndWhile method.
This command literally moves the End position of the range backward or forward in the document. It requires a set of characters to include or exclude (depending on the direction) in any order or combination. A paragraph mark vbCr is a special character from a table of miscellaneous constants, so we assign that character to the Cset option.
An additional Count option specifies how many characters to check. The default is any number of characters forward (up to about a billion). We need to retract the End position backward, so we instead assign the constant wdBackward to the Count option. The two options must be separated by a comma.
Why omit paragraph marks?
A paragraph mark is a paragraph mark. It's in the name. In my opinion, omitting them from a sentence range is more intuitive and consistent with the meaning of a sentence within a paragraph.
Trim any ending spaces
Word automatically extends ranges over any trailing spaces. Detecting any parentheses on either side of the range will be easier if we trim any spaces. We again use the MoveEndWhile method.
This time, we assigned a space " " to the character set option. The double quotes are required to indicate a literal space character as a plain text string.
Wait a second …
This uses the same MoveEndWhile command as before, even moving backward, except it trims any spaces.
Combine with trimming any paragraph marks step
We can just combine this command with the previous one by including a space along with the paragraph mark in the character search string.
We concatenate the two characters together using the plus + sign to create a revised Cset search string. This just jams the two strings together to make a new, longer string.
A possible gotcha is this revised command will trim the two characters from the end of the range in any combination, but this behavior is still reasonable for this function.
Trim any starting spaces (optional)
Most automatic range extensions (or selections) do not include any preceding spaces, but it can happen for the first sentence of a paragraph. Just in case, we'll trim them using the MoveStartWhile method.
MoveStartWhile works just like MoveEndWhile mentioned above except it moves the Start position of the range. The default Word behavior for including any preceding spaces seems unintuitive, so if these spaces are present, we will not restore them to the working range as part of the function result.
Allow parentheses
Accounting for parenthetical text is easier than adjusting the sentence range for dialog, so we'll begin by intelligently selecting a sentence with or without any parentheses. More specifically, we'll exclude a parenthesis if it occurs only on one side of a sentence, specifically at the beginning or ending of the range. We'll extend the ideas below to return a general sentence range that may include dialog text.
We are not attempting to identify parenthetical text alone as is done in a separate macro. This function focuses on sentence ranges where the sentence may or may not be inside parentheses.
Examples of parenthetical text ranges
For illustration purposes, let's assume another macro uses this function to identify the sentence range and then selects the range. What are we expecting?
No parentheses in the text
If we're spanning a regular novel sentence without any parentheses, the selection would be:
The function should just return the individual sentence range like normal, so the steps to handle the parentheses shouldn’t cause any problems with the regular case.
Single sentence inside parentheses
If the entire sentence is parenthetical text, our function should span all of it including the parentheses on both sides.
Parentheses inside a sentence
If the sentence just includes some parenthetical text, this function should not adjust the sentence range based on it.
The sentence range is not affected by typical parenthetical text.
Multiple sentences of parenthetical text
If the text consists of multiple sentences, then the function should just return the sentence range inside the parentheses.
Regardless of which sentence is selected, it would have at most one parenthesis bordering it. The function should span only the current sentence range and exclude the parenthesis.
Trim parentheses logic
A rough conditional statement to detect a parenthesis only on the left side of the working range would look like:
Unfortunately, the open parenthesis test must be performed by itself because it will be trimmed from the left side of the range. The close parenthesis test is separate.
For both tests, we need to know the first and last characters of the range, but the respective conditions are different.
Check whether to trim an open parenthesis
We'll start with the open parenthesis and adapt it for a close parenthesis check.
Get the first and last characters
We need to get the characters at each end of the range using the Characters collection. The First property gives use the range of the first character in the range.
We need the text for the comparisons below, so we reference its Text property. We then store this character in a string variable sFirst.
Similarly, we get the Last character of the range and store it in a conveniently named variable.
Presumably, the working range r is not empty.
Open parenthesis condition
We check whether the first character is a left double quote.
This statement will be interpreted as a True-False (Boolean) value when it's used in an If statement to make a decision. The overlapping notation between assignments and conditions in VBA is unfortunate, but we’re stuck with it.
Missing close parenthesis condition
For the second condition, we need to know whether the close parenthesis is missing.
The not equals <> symbol literally reads as less than or greater than which is … not equal to something. For text, it checks whether the two strings are not exactly the same.
Compound parentheses condition
If the range only contains an open parenthesis, both of the above conditions must be True. We use “And” between them to create a compound condition for our budding conditional statement.
Trim the close parenthesis from the range
The command to remove the open parenthesis from the left side of the range is the MoveStart method:
As its name implies, the MoveStart command moves the Start position of the range. The default movement is forward by one character. That is exactly what we need at this point, so we can omit both the Unit and the Count options. The command does not change the End position unless Start exceeds the End position.
Since the If statement is relatively simple, we can condense it to one line.
Check whether to trim a close parenthesis
Similarly, we may need remove close parenthesis. We need to make sure the open parenthesis is not present at the start, but a close parenthesis is present at the end. If so, we trim the close parenthesis from the range.
Trim the close parenthesis from the range
We can trim a close parenthesis using the MoveEnd method.
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. The default movement unit is by a character, so we can omit the Unit option.
The If statement to remove a close parenthesis, if appropriate, is:
Allow dialog
A typical novel contains thousands of lines of dialog at about half of the total word count. If we're manipulating sentences, the macros should properly interpret whether it's a regular sentence, all dialog, or just one of several sentences inside the double quotes. We want this function to perform the grunt work when identifying the appropriate sentence range. The above section covers a simpler solution for parentheses which are more likely in other documents.
Use double quote constants
To make this function easier to read, we’ll use the double quote constants mentioned in a separate article. We'll refer to the left and right double quote characters as LeftDQ and RightDQ, respectively.
If you prefer not to use module-level constants (just constants declared at the top of the macro file outside any function or subroutine), copy and paste the constants into the function somewhere near the top or type and copy the actual text characters from a typical Word document. The latter approach is the most direct, but the characters are similar to straight double quotes, so it's easy to misread the strings.
Example dialog text selections
For sentence variations including dialog, we have several possibilities. For illustration purposes, let's view the function result as a selection.
No dialog in the text
If the sentence is just regular novel text with no dialog included, just span the sentence as normal.
Single sentence of dialog
If the entire sentence is dialog, our function should span all of it including the double quotes on both sides.
Multiple sentences of dialog
If the text consists of multiple sentences of dialog, then the function should just return the current sentence inside the double quotes.
Regardless of which sentence is selected, it would have at most one double quote bordering it. The function should span only the current sentence range and exclude the double quote.
Sentences with dialog tags
If the text contains any dialog tags, the same ideas from above apply, but we need some extra work to detect the respective double quote inside the sentence.
Dialog tag appears with a single sentence
The dialog tag often follows the text.
The dialog tag could also precede the text.
In either example, the function should include the dialog tag in the sentence range.
If the dialog tag is inside the sentence, we still select the whole sentence.
This variation takes advantage of a side effect our text searches in this function. It isn't checking for the number of left or right double quotes, so the validations just want to know they both exist.
Dialog tags with more than one sentence
The dialog could include more than on sentence. In this case, we only include the dialog text.
What about action tags?
As shown above, action tags (any character action taken during dialog) are treated like normal sentences because they are. They are not dialog text in the proper sense.
It gets a little tricky
See where this gets tricky?
We need to the detect the differences and span the range accordingly for every case (without messing up the others). Also, see the gotchas below for an exception where it gets even trickier.
Trim double quotes logic
A rough conditional statement to detect a double quote only on the left side of the range would look like:
Unfortunately, the left double quote test must be performed separately from the right double quote text because the trimmed text is different for each case.
In the earlier parenthetical conditions, we only needed the first and last characters of the range, but dialog can include tags. The generality makes the logic messier, but it follows a similar pattern.
Check whether to trim a left double quote
We'll start with a left double quote and then adapt the logic for a right double quote.
Is this text Like the other text?
The simplest way in VBA to detect some text within a another longer string of text is probably using Like.
For Like, we provide text to search on the left and the search pattern on the right. Both are plain text strings. The search text is often stored in a String variable, and the pattern is often just given in double quotes. Although, more complicated patterns might be stored in a String variable. Search patterns can be somewhat general but not as complex as we can do with an advanced Find search.
Why use Like?
Like automatically returns a True or False (Boolean) value that we can immediately use in a conditional statement to make a decision about our sentence range. Other commands such as MoveUntil, Find, or even a standard string function InStr(…) would also work, but each is a little more complicated than what we need for this function.
- The MoveUntil method would normally be a good solution since we're already working with a Range variable, but in this function, the decision logic would require more steps to avoid any gotchas. It would require an extra working range variable, and the command could the move it outside the original sentence.
- Word Range variables include a Find property, but it's like taking a hammer to a fly for simple text searches. We need to be clear about several Find settings to avoid any gotchas, and we're only searching for a single character.
- InStr(…) is a standard VBA function that searches for a substring in another string. It's perfectly functional (ha, ha, get it … a programming joke), but it's also clunky compared to using Like unless we also want to know where the double quote was found in the text.
None of the above are incorrect, but Like does what we need out of the box since we (mostly) just need a yes or no (True or False) result for our decision logic.
What is the search text?
We're looking for a left double quote in the range text, so the search text is found using its Text property.
We don't actually need an extra variable, but it makes the searches clearer.
What is the search pattern?
We previously defined a constant LeftDQ, so it might seem like we could just use Like like so:
Like compares the SearchText using LeftDQ as the pattern. The problem is it checks whether the search text is exactly a left double quote. Since this rarely happens, we need to allow for other characters on either side. A common wildcard character is an asterisk * which allows any number (zero or more) of plain text characters wherever the asterisk occurs in the pattern. Some special characters like a paragraph mark are not matched by an asterisk.
Left double quote condition
We include an asterisk as plain text in straight double quotes "*" on both sides to allow any characters on either side of the left double quote.
We concatenated the asterisks with the LeftDQ character using a plus + sign which just jams them together into a three-character string.
We could also use the plain text left double quote character search pattern "*“*" with the left double quote character.
This version is simpler, which is nice, but it could also easily be misread in a text editor. The potential confusion isn't worth the saved characters unless you prefer extra concise steps. Plus, if we happen to delete and retype the left double quote on the inside, it the VBA editor will replace it with a straight double quote. Just step around the mud puddle rather than trying to walk through it while keeping your shoes clean.
Missing right double quote condition
The right double quote pattern is almost the same.
Clean up on aisle five (simplify the conditions)
The conditional logic quickly gets messy because we need two conditions for each check. I don't like to bloat a function, but it will be clearer if we define two intermediate variables to store the search results.
The variables are Boolean data types because Like returns True or False values. I prefer to precede Boolean variables with a "B", but it seemed clumsy here.
Technically, this also avoids doing the searches four times in the conditional statements below, but the extra two searches would literally take less than a millisecond, so it's not the motivation for our function.
Check for a missing right double quote
One catch is we actually want to know if a right double quote is not present, so we precede the condition with a Not operator.
Not does exactly what it implies. It reverses the Boolean result that follows it. True becomes False and vice versa. This breaks the Englishy VBA phrasing, but correct is better.
Compound left double quote condition
If the range only contains a left double quote, both of the above conditions must be True. We use “And” between them to create a compound condition.
Trim the text up to the left double quote from the range
How do we trim all text up to the left double quote?
Trim the other text from the left side of the range
The MoveStartUntil method moves the Start position of a range based on the characters at that position.
MoveStartUntil works similarly to MoveStartWhile except it trims (or includes if moving backward) all text until it finds any character in the character set. We assign a LeftDQ character to the Cset option. Forward is the default direction, so we can omit the Count option. It does not change the End position unless Start exceeds the End position.
MoveStartUntil stops immediately at a left double quote
Technically, MoveStartUntil will move forward in the document until it finds the character even it moves outside the original range. In this function, we've verified it exists inside the range before using this command.
Also important for the logic of this function, if a left double quote begins the search text, the MoveStartUntil method will find it immediately and not move the Start position. Thus, the following trim step for the left double quote character still works as expected. In other words, we effectively inherit the original, simpler logic used with the parentheses above without needing any messy correction steps.
Trim the left double quote from the left side of the range
Then we can trim the left double quote from the left side of the range using the MoveStart method:
The MoveStart command moves the Start position of the range. The default movement is forward by one character. That is what we need at this step, so we can omit the Unit and the Count options.
Check whether to trim a right double quote
We may need remove the text after a right double quote if a left double quote is not present. Phrasing this in a rough conditional statement, we get:
Trim the other text from the right side of the range
We trim the text after the right double quote using the MoveEndUntil method.
MoveEndUntil works similarly to MoveEndWhile except it moves the End position across any text until it finds a character in the character set. We assign a RightDQ character to the Cset option. We need to move the End position backward, so we also assign the wdBackward constant to the Count option.
Trim the right double quote from the range
We trim the right double quote using the MoveEnd method.
MoveEnd moves the End position of the range. The negative Count value moves backward in the document by one Unit. The default movement unit is by a character, so we can omit the Unit option.
Extend over any ending spaces
Since Word normally includes spaces at the end of an automatic selection or a range extension, our sentence range function should mimic that behavior. We again use the MoveEndWhile method, but we need to extend over all spaces at the end of the working range.
We assigned a space " " character to the Cset option. The default movement is forward, so we can omit the Count option. Writers will probably expect this behavior in Word even if they’re not conscious of it.
Return the function result
The working range r now spans the intended sentence range, so we can assign it to the function name as the result.
Gotchas
We should get in the habit of considering potential problems, or skip to the final macro if you prefer to avoid the muddy details.
What if an initial selection exists?
This should probably be an automatic consideration in any editing function or macro. After assigning an initial working range based on a given (or omitted) target range, we immediately collapsed the range to avoid any logical issues, so no problem exists.
What if we like allowing multiple sentences?
If we omit the above collapse command, which is my preferred approach, the Expand method could span multiple sentences if the initial target range (or selection) crossed a sentence boundary.
Is this a problem?
Allowing multiple sentences is more of an advantage than a problem because we can leverage the function in more circumstances, but being more general also introduces more chances for gotchas that might cause trouble later. While useful, this extension is beyond the scope of this article.
What about the double quotes order?
Like only tells us whether the search pattern found a match. In this version, the function does not validate the order or number of the left and right double quotes. We could pivot to a different approach, but Like was used for its simplicity. The additional work required to snip this small gotcha is not difficult, but it is outside the scope of this article.
What if the working range ends the macro empty?
During the function, we remove several different characters. If the range ends up being empty by the time the macro finishes, is that a problem?
An empty range result by itself would just indicate no sentence was found, so it seems like a valid result on the surface.
The function would end and return the empty range as normal. The result would be a little strange but also reasonable, so adding any extra logic to catch an empty range seems superfluous. In fact, the returned empty range serves as a clue to the user that something did not work as expected, but this function isn’t responsible for correcting any external logical errors. It should just do what it does correctly without returning gibberish.
A similar argument applies to a final range that includes just spaces.
No problem exists here, but that doesn’t mean we shouldn’t consider it when creating the macro because sometimes issues like this can circle around to bite us.
What if the expanded range is only whitespace?
What happens if the expanded range contains only whitespace?
If the function begins with the target range in an empty paragraph or one containing only whitespace, the expansion step will extend the working range over whitespace. Ignoring tabs to keep the logic simple, the subsequent command to remove paragraph marks and spaces will remove all those characters. In fact, it will move the empty range backward to the end of a previous paragraph. It's an unintuitive result, so we need to correct for it.
We previously created a function to check whether a range contains only whitespace. If so, we want to exit the function. A reasonable return value is just the collapsed working range.
The collapsed range will indicate the function encountered some trouble in identifying a reasonable sentence range. This seems better than returning Nothing since Nothing indicates an invalid range rather than an inconclusive one. This conditional statement should be placed after the range expansion but before the step to trim paragraph marks and spaces.
We could alternatively check whether the initial paragraph is only whitespace. It's more direct since the condition can be checked at the top of the function, but it's also a little messier, so I used the above version.
Final function to get a sentence range
Putting it all together, the function to get the sentence range while accounting for dialog or parentheses is:
This function does not specifically return parenthetical or dialog text. Rather, it identifies sentences and validates whether to include any parentheses, double quotes, or dialog tags based on the included punctuation.
This function requires a separate function to check whether a range contains only whitespace. A separate article mentioned how to declare the LeftDQ and RightDQ constants for all macros in a module. If you prefer not to use the double quote constants, a quick and dirty solution is to replace every LeftDQ constant with a literal left double quote character "“" in straight double quotes and Every right double quote RightDQ constant with its plain text character "”". These strings are easy to misread, so the function uses the constants.
What are the uses?
A simpler version of this function was used with the move sentence macros. Where else might it be used?
We also 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) sentences. These additional macros would be nearly trivial to create using this workhorse function.
Examples of using the function
If we're creating a bigger macro, we can use this function as follows. We'll assume our macro has a valid range variable named SomeRange.
Parentheses are not required after the function name GetSentenceRange because no range argument was provided. The target range parameter was declared as Optional meaning we can omit the argument. In this case, we designed the function to assume the working range is the current position in the document.
We could also provide a range already assigned somewhere else in the macro. Without a lot of explanation, suppose we want to get the range of the last sentence of the current paragraph while adjusting for possible dialog or parentheses. We first define a current paragraph variable to keep the commands reasonably short. We then target the last sentence using the Last property of the Sentences collection.
The parentheses are required around AnotherRange because we assign the function result to a variable. The details are a little trickier than it seems here. The argument must explicitly be a Range variable or some other Range result because we declared the TargetRange parameter As Range.
Since the last character range is already a VBA Range, we could skip the intermediate range assignment.
It's a long command, but it's not hard to read. We could even skip the intermediate paragraph assignment, but the command becomes even longer.
Improvements
What could we do better?
Correct for double quote order or count
The above function uses Like for the text searches, so it does not check whether the right and left double quotes are in the correct order. It also does not catch multiple double quotes inside the same sentence. The order issue could be solve using the InStr function along with some messier logic. Catching multiple quotes would require even more steps. Both are good ideas for a foolproof function, but the work is beyond the scope of this article.
Catch sentence punctuation problems
Word's default sentence parsing algorithm will choke on common abbreviations and mistake them as the end of a sentence.
- Professional titles like Dr. Sally Doe will split a sentence.
- Latin abbreviations can be problematic like etc. or e.g.
- Many common abbreviations are also suspect such as Vol. or Inc.
- Geographical or address related abbreviations like Ave. or D.C. will do the same.
- Time or measurement based abbreviations like a.m. or ft. still include a pesky period.
- Names with middle initials will also cause trouble, but these are more difficult to catch, in general.
The list is probably much longer than you realized. We could include steps to correct the sentence range inconsistencies, and they would be neatly contained in the function.
We would need some logic to catch an incomplete sentence and correct the sentence range. The solution will probably stretch out more than you might think. The cleanest way to implement it would be with another function which would then be used with each desired abbreviation, but that's a lesson for another day and probably one for intermediate users.
Some of these may be worth the effort in certain work-related documents if a particular job deals with them frequently. Correcting for all of them every time would be cumbersome which is probably why Word does not do so by default.