Text manipulation macros occasionally need to know the next character immediately after a range. While the task sounds trivial, Word treats an empty range a little differently than one that spans text. The question arises often enough that it's convenient to have a dedicated function, so we can obtain the correct character without prior knowledge of the range's extent.
Thanks for your interest
This content is part of a paid plan.
Get the next character after a range
If a range spans any text in the document, the next character is obviously the one that immediately follows the end of the range. That makes sense. Unfortunately, the “next” character for a range depends on whether it's empty or not.
A Word Range is a contiguous region of document content with Start and End positions, and an empty range is one where those positions are equal. The next character of an empty range would intuitively be the character immediately after the end position, but VBA considers that character to be the “first” one in the empty range. This is counterintuitive and a little inconsistent. It's also inconvenient when writing some decision logic based on that character if we don't have prior knowledge of whether the range spans any document content. This function fixes this inconvenience.
This article is part of a series to streamline a move sentence macro, but it finds uses in other macros and functions as well. See the little brother article if you only need the character text, but this function can be trivially used to get the text as well. Either function saves steps.
Create the empty function
Open the VBA editor with Option+F11 in Word for Mac (or Alt+F11 in Windows) and create the following empty function.
The single quotes are comment characters where any text on the same line after them is ignored by VBA.
What parameter do we use?
The function needs the document range after which we want the next character. We'll call the Range parameter rTarget to be clear.
Use an optional range parameter
We make the argument Optional, so we can default to the current document position when the argument is omitted. As such, we need a default fixed value of Nothing. See our introduction to functions for more explanation.
What is the return result?
Half the point of a function is to return a result. This one returns the range of next character. It may seem inefficient to work with individual character ranges, but a Range is a fundamental data type in VBA and also probably the most commonly used data type in editing macros.
If the target range doesn’t exist, the function will return Nothing. This is a special value is assigned to object variables that have not yet been assigned to a valid document element. For the moment, we'll also use it a placeholder since we don't know the correct result yet.
What is a Range?
Word VBA can represent contiguous regions of document content using a virtual object (in the everyday sense) called a Range. Other data types exist such as a Paragraph, but a Range allows us to work with more general regions. Such objects define various actions called methods and data called properties, so we can track and manipulate them in the document. Typical properties include its Start and End positions, but they also contain collections of spanned content including Sentences, Paragraphs, Characters, and more.
When we say a parameter is a Range data type, the function requires a variable declared as a Range and assigned to a valid document range. In this function, we also need to identify the correct character range to send back to the main macro as the function result.
What’s the plan?
We need to test whether the range is empty and return the correct next character range. The range returned will be based on the range r given by the user.
- If r is invalid, it defaults to the current Selection as a Range and then checks the character as usual.
- If r is empty, it returns the first character range as the intuitive next character.
- If r is valid, it returns the next character range.
The invalid check must be done first, so the default range can be assigned if the target range is invalid.
Declare a working range
Since we may change the range inside the function when we assign a default value, we need to create a working duplicate of the target range. We refer to the Duplicate property of the target range argument.
This working range r begin with a value of Nothing. If a duplicate were not made, any changes to rTarget would also change the argument outside the function. While not technically wrong, it's usually unexpected, so we take the extra precaution to avoid any confusion later.
Check for the default target range
We need to check whether a valid range was given. If not we assign the default range based on the initial cursor position in the document. A rough conditional statement could be:
What is Nothing?
VBA assigns a value of Nothing to an object that is not yet assigned to a valid document element (or whatever the data type represents). Nothing is not a value in the normal sense like 0 or -1. We can also "unassign" a variable by assigning Nothing to it.
Check whether the working range is already assigned
Typical data types use an equals = sign to check for equality, but objects in VBA are different thingies. Instead, we use the keyword "Is" to check whether two objects refer to the same thing. We'll avoid the details of what that means for now. The main point is Nothing indicates an object variable is not yet assigned, so we also use Is to check for it. Thus, we literally use the phrase “Is Nothing” as a condition to check whether a variable is assigned to something valid in the document.
The target range parameter was declared as Optional and given a default value of Nothing. If the user omits the argument when using the function, rTarget will be assigned the default value of Nothing. It is also possible that someone used the function with a target range argument that was not properly assigned to a valid document range. In this case, rTarget would again have a value of Nothing. This range validation catches either case, but the distinction between the two cases is not important in this function.
Assign the default value
If the target range is Nothing, then we assign our working range based on the initial cursor position. We refer to the Range property of the Selection.
All object assignments require the keyword Set. The Selection represents the current literal selection or insertion point in the document. We need the Range of the Selection because we're assigning it to a Range variable. While the Selection is like a Range, it is also more than a Range.
Assign the duplicate range
If the target range is valid as far as we can tell, we assign a duplicate of it to the working range r. We refer to the Duplicate property of the target range.
Working range assignment conditional statement
Putting the above steps together, the conditional statement to assign the desired working range r is:
At this point in the macro, the working range variable is assigned to valid document range either way.
Target range validation placement
We should to check whether the target range is Nothing before we try to use it for anything else in the function, so we are assured we have a valid working range. Otherwise, any other use of the variable will cause an error. This extra validation illustrates some of the extra machinery required in a function compared to including similar steps inside the main macro. While the extra stuff clutters the function some, our main macros are clearer because all the steps are tucked out of sight.
Rough conditional statement for an empty range
We have a valid working range, so we turn toward detecting the correct next character range. The basic decision logic would look something like the following:
This seems simple, but the details get a little tricky.
How do we check if a range empty?
An empty range is a valid range in the sense that the variable is properly assigned to a specific, contiguous region of the document. It just doesn't span any document content.
VBA is not consistent with how it treats empty range variables compared to single character ranges. We need a reliable way to detect an empty range, so we can pick the correct next character. What's the problem?
Character count does not work
A logical condition to detect an empty range would be to check the number of characters it contains. Zero characters would indicate an empty range, and one character would indicate the range variable spans a single character.
Sounds like a plan …
We can access the number of characters with the Count property of the Characters collection.
Unfortunately, Count doesn't work as expected. A range literally spanning one character also is considered to contain one character, but … an empty range also contains one character. They even have the same "next" character.
Arghhh.
So, we can’t check whether a range is empty by checking how many characters are spanned. It's another small example of how we need to be careful when working with empty ranges in Word VBA.
Use Start and End positions
Fortunately, we can check whether the range is empty using its Start and End positions. In Word VBA, Start and End are literal character positions counting from 0 at the beginning of the document. If the two position values have the same value, the range is empty since it spans no text in the intuitive sense.
This is a True or False (Boolean) condition checking whether the two numbers are equal. VBA will interpret it as a Boolean value when it's used in a conditional statement.
Get the first character
If the working range r is empty, we need the first character as the intuitive next character. Its range is found using the First property of the Characters collection.
First returns the range of the character not its text, so its already the result we need for this case.
Assign the first character result
We assign this character range to the function name as the final result.
Get the next character
If the range spans any text in the document, we use the Next method of a range.
The Next command returns a range corresponding to the Unit given. The unit wdCharacter is from a table of unit constants, but a character is the default unit, so we can omit the option and the messy parentheses (which were required because we were assigning the range to a variable).
Next returns the unit range not the text, so this is again the result we need for this function.
Assign the next character result
We again need to assign this to the function name as the result.
Intuitive next character conditional statement
Putting the conditional steps together, we get the intuitive next character.
Gotchas
While this function kind of simple, we should still think about potential problems.
What if the given range doesn’t exist?
What if the user gives us a range variable that isn’t yet assigned to a document range? This is a common consideration with any function or subroutine that receives external data.
Fortunately, if the target range was not yet assigned to a valid working range in the document, then it will have a value of Nothing. That is the same as our default value which we handled above., so an invalid target range is already handled by the function.
The only issue would be if the argument was unassigned by mistake, but that is a logical error outside the purview (oooh, fancy word) of this function. If fact, since Nothing is the default value, we cannot currently tell the difference between an omitted argument and an invalid one (there is a way, but it's rarely necessary).
Final get next character range function
This range version is not as simple as its little brother that returns the plain text, but it's not too bad. The final function to return the range of the next character is.
All the mess is neatly contained in the function, so a user can just use it and know they'll get the correct character range.
How do we use the functions?
These functions are easy to use. We just call them where needed. Let's declare a Range variable for the example.
Assign the next character range to a variable
We can simply assign the function result to the variable.
Since the argument was omitted, it defaults to the range of the current cursor position in the document. We can further omit the parentheses because no arguments were given. This works regardless of whether the initial selection spanned any text or not.
Now, assume the main macro has a valid Range variable SomeRange declared and assigned somewhere earlier in the main macro.
This stores the character range immediately after SomeRange regardless of whether SomeRange is empty or not.
Range argument gotcha
A small gotcha exists here where VBA expects SomeRange to be an actual Range variable. It cannot be a Variant meaning it cannot be an undeclared variable. VBA automatically assigns those as a generic Variant data type that can store any data. See the introduction to functions and subroutines article for more explanation.
Use the next character range in conditional statement
We can also just use the next character directly in a conditional statement. This example uses the text to make a decision based on whether the character is a close parenthesis or not.
This usage omits the target range argument, so it defaults a range spanned by the initial cursor position. Then, we refer to the Text property of that range.