Heading

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

Toggle characters around range

Word • Macros • Functions
Peter Ronhovde
18
min read

As we build our macro toolbox, we’ll find cases where the same macro steps readily apply to different situations. A previous article focused on toggling double quotes around a range, which is useful, but it’s clumsy to just copy the same steps over to work with parentheses or single quotes. This function generalizes the original version to work with any given single characters.

Thanks for your interest

This content is part of a paid plan.

Toggle Characters around Range

It’s natural to apply double quotes to a paragraph, a sentence, some selected text to manipulate dialog. Similarly, we might want to add or remove parentheses from around a range, single quotes, or even square brackets occasionally. It’s messy and clunky to copy a previous function focused on double quotes and make only small changes, so why not create a tool we can use for any of these cases?

With that in mind, we generalize the previous function to add or remove any single characters as appropriate on their respective side of a given range.

This article has significant overlap with the more specific variation toggling double quotes, but this article stands on its own.

Other functions

This function makes use of a smaller function TrimRange to trim any blank space off both ends of the initial range. Also, left and right double quotes are different characters in Word for Mac and Windows, so in the examples at the end, we use another pair of simple functions to access them.

What’s the plan?

Essentially, we’re taking the previous version for only double quotes and changing the LeftDQ function call to a general LeftCharacter and the corresponding RightDQ function call to a RightCharacter. If that were all, this article would really not be necessary, but this variation also shows how generalizing a macro or function can be a little more involved than you might expect.

So how do we detect and then add or remove a specific character from both sides of a given range?

  • Trim any spaces or paragraph marks off either side of the range to simplify the later tests.
  • Check whether the characters are outside the range on either side. If so, include them in the range.
  • Test whether the left and right characters both exist on their respective sides. If so, delete them. If not, add them to the side on which they’re missing or perhaps both sides.

The initial step of trimming any blank space on either side sets up a clear starting condition for the rest of the function. It’s easier to work with the given range when we know a regular character is present on either side since there are fewer variations to consider.

Create the function skeleton

Start by creating the function skeleton.

Function ToggleCharactersRange(rTarget As Range, ByVal LeftCharacter As String, _
ByVal RightCharacter As String) As Range
' Include function steps ...

' Return modified range
Set ToggleCharactersRange = rTarget ' Temporary
End Function

We include an underscore "_" line continuation character at the end of the first line since it is so long. With it, the VBA editor treats both lines as one. This is a common trick to make your macros or functions more readable for human eyes. The VBA editor does not care either way.

What parameters to use?

This function requires three arguments to be given.

Target range

We accept the target range rTarget around which we’ll toggle the characters. This variable is not changed while the function runs (range variables might be changed; see this previous article on ranges for a brief explanation).

Two characters

We also accept two distinct characters as strings to toggle on either side of our target range. Strings can be more than one character long, so we’ll need to check for this inside the function and use only the first character.

What is ByVal?

Both character variables include a ByVal statement in front of their declaration because I don’t want to create more string variables inside the function. The abbreviation literally means “by value,” and it prevents any changes we make to the variables inside the function from escaping back outside to the original variables given by the user. This is only a problem with certain data types like ranges and strings (where it is called passing “by reference”).

Not using optional character parameters

Including Optional parameter values for the characters would be natural in this function, so users could leave them off if they prefer. My preference would be to use double quotes as the default since they would be the most common use case, but the internal representations for curly quote characters vary between Word for Mac and Windows.

Inside functions, I generally use another pair of short functions LeftDQ or RightDQ to automatically pick the correct special characters. However, we can’t use functions for default values with Optional parameters. Any default values must be constants of some kind. I could use open and close parentheses “(“ and “)” specifically, but I don’t like that option, so I just chose to require the characters to be passed to the function every time.

Return type

We return the changed range including the original range but with or without any additional characters added or removed by the end of the finished function.

What about invalid input?

This might normally go in a gotchas section where we consider possible problems, but we have to account for invalid input, or our function could crash and end any macro using it immediately.

Invalid target range

What if a macro provides our function with an invalid range?

This is usually indicated by a Nothing value for the range variable (see previous article on ranges for more explanation), so we add an initial check to validate whether the range exists. If not, we quit the function.

If rTarget Is Nothing Then
' Invalid range, so let user know and exit ...
End If

The “Is” keyword compares objects in VBA similar to an equals = sign for regular values, and we use “Is Nothing” because Nothing is not an actual value like a 0, -1, or even "abc.".

Empty toggle characters

For this function, we might be given an empty string by a macro, but it isn’t a valid input for this function.

Why?

What do we check against if there is no character given for the comparisons? An empty string is a valid thing, but in this function, it confuses the logic, so we’ll treat it as invalid input. The condition checking whether either input character is an empty string is:

LeftCharacter = "" Or RightCharacter = ""

If we wanted to be even more careful, we could restrict the input to allow only specific characters or character types such as only alphabetic characters or punctuation marks. That’s a little too much for this article, but it's not a bad idea overall.

Return Nothing for invalid input

If we detect something amiss with any of the input values, we first Set the return value via the function name to Nothing to indicate an invalid result.

Set ToggleCharactersRange = Nothing

Exit Function

Then we exit the function immediately.

Exit Function

No steps after this command are run. The function ends immediately.

Input validation check

Putting all three of the above together, our validation check for the function input is:

If rTarget Is Nothing Or LeftCharacter = "" Or RightCharacter = "" Then
' Invalid input, so let user know and exit
Set ToggleCharactersRange = Nothing
Exit Function
End If

We use Or operators between the individual conditions because any of them are invalid input, and the Or operator returns True if either condition is True. That's exactly what we want here.

After this, we at least know the target range is valid, and we’ve been given characters.

Restricting to a single character

Any string comparisons with and equals sign = would demand an exact match of all given characters, so we need a way to get only the first character.

Left is a general string function that returns subset of characters from a given string starting from the left side.

Left(SomeString, NumberOfCharacters)

Using the string variable and the Left function above, we get the first character.

LeftCharacter = Left(LeftCharacter, 1)
RightCharacter = Left(RightCharacter, 1)

Now we know LeftCharacter and RightCharacter are both only one character long. We saved them back to the same variable names to avoid creating unnecessary new variables. This works without any issues because we declared them using ByVal, so these changes only apply to inside the function.

Create duplicate working range

In order to avoid any changes to the rTarget range, we immediately create a duplicate of it which we call r just to keep the name short.

Dim r As Range
Set r = rTarget.Duplicate

Now our working range r is independent of the given range rTarget, so a user won’t be unpleasantly surprised by the target range being changed during the function.

We also needed to declare the range explicitly using a Dim command since we're passing it to a function below. VBA is picky about knowing for sure what the argument type is.

Why not ByVal with the range?

Now—

What did you say? ByVal?

Oh ... I was hoping I could slip that past you. Ummm, strangely ByVal doesn't actually work in an effective sense with ranges because the range reference won't be changed, but any changes to the properties of the range, including its Start or End positions, will still make it outside the function.

Ughhh.

That kind of defeats the purpose of ByVal when used with ranges, but that's why we immediately create a duplicate. VBA is great overall for its English-like commands and approachability for basically everyone who's willing to dig a little past their comfort zone, but this behavior ought not be so.

Who cares whether changes escape the function?

Really, it's just to be nice to whoever is using the function. If unintentional changes leak out of the function through the arguments, it might catch the user off guard and burn through hours of their time before they discover what was wrong ... and that it wasn't their fault. Ooops.

Trim the blank space

Using a previous function, we quickly trim any blank space off either side of the working range and reassign the result back to same variable name r.

Set r = TrimRange(r)

This task is common enough that we created a separate function. Recall we used "Set" because a range is an object containing more data just a plain value.

Check whether left and right characters already exist

We start by checking whether the left and right characters already exist on their respective side just outside the range. If so, we include them in the range.

This is a preference, and some could argue it logically shouldn’t be done since the user “specifically” provided the target range. I think including them makes the function a little more convenient to use but omit these steps if you don’t like them. It won’t affect the rest of the function.

Get previous and next characters

We need the previous and next characters before and after the working range.

PreviousCharacter = r.Previous(Unit:=wdCharacter).Text
NextCharacter = r.Next(Unit:=wdCharacter).Text

These methods Previous and Next both return a range outside our working range r corresponding to the previous or next document Unit specified. We assigned wdCharacter here for the obvious reason. We then accessed the Text property to get the character for the range.

We had to use parentheses around the Unit argument here as (Unit:=wdCharacter) for two reasons. First, we're assigning the function result to a variable, but accessing any property or method of the resulting range would also require the parentheses.

Now, we can test the characters just outside the working range and extend the range to include them if they match our given LeftCharacter or RightCharacter arguments.

Why bother?

Mostly for convenience and clarity with the following steps, but in this function, we’re also maintaining some consistency with the early version. There we automatically included them, so we probably should here as well.

At this point, we know our toggled characters could be at none, one, or both sides of the working range.

Include left character

For the first character of the range, we check whether it is our given left character. If so, extend the Start position of the range backward over it.

If PreviousCharacter = LeftCharacterThen
r.MoveStart Count:=-1
End If

The default move Unit is by character, so we don’t need to specify it. The Count option is negative 1 to tell the command to move the Start position backward in the document.

Since we only have one brief command here, we can further shorten the conditional statement.

If PreviousCharacter = LeftCharacter Then r.MoveStart Count:=-1

It makes the steps a little harder to read, but it helps keep our function smaller and cleaner. I usually prefer this presentation if the steps are clear enough.

Include right character

For the rightmost character of the range, we check whether it is the given right character. If so, we extend the End position of the range forward over it.

If NextCharacter = RightCharacter Then
r.MoveEnd
End If

The default move Unit is again by character, and the default Count is by 1 unit, so we don’t need to specify either of them. Again, we can shorten the conditional statement if we wish.

If NextCharacter = RightCharacter Then r.MoveEnd

See this previous article if you would like to review conditional statements.

Check for double quotes

Given the current first or last characters of the range, we have three cases:

  • Characters are on both sides → delete both of them
  • Characters are only on one side → add the missing character
  • Missing both characters → add respective character on both sides

Let’s translate these cases into VBA.

Get first and last characters

For all cases above, we need the first and last characters of the range to do our checking.

FirstCharacter = r.Characters.First.Text
LastCharacter = r.Characters.Last.Text

These commands use the Characters collection of the range. The First or Last references return ranges corresponding to the respective character positions. We finally refer to the Text property to get the actual plain text character which we then store (using the equals = assignments) in the respective variable, FirstCharacter or LastCharacter.

As a brief aside, these First and Last references work a little differently for some document element types. For example, Paragraphs are their own kind of object internally in Word, so we would need to access the Range of the Paragraph before referencing the Text property.

Characters already exist on both sides

Similar to the above conditions for the previous and next characters, we now check whether the first or last characters are the respective left or right characters given to the function.

If FirstCharacter = LeftCharacter And LastCharacter = RightCharacter Then
' Characters are on both sides, so delete them ...
End If

In this case, both left and right characters must be present before we delete them, so we use an And operator. Recall And requires both conditions to be True before the composite condition is True.

Note the “FirstCharacter = LastCharacter” notation in this statement is actually a condition not an assignment to a variable. It is an unfortunate ambiguity in VBA, but we don’t need to think much about it because VBA determines the proper usage by context.

Delete characters

To delete the characters, we again refer to the range of the First and Last ranges in the Characters collection and delete the two single-character ranges.

r.Characters.First.Delete
r.Characters.Last.Delete

Every valid range has a Delete method, so we use it for these tiny little one-character ranges.

Cannot delete the Text property

Note we are not deleting the “Text” of the range because Text is a property of the range.

r.Characters.First.Text.Delete ' Does not work

This doesn’t work because the Text property of a range has no Delete method. It just represents the string of plain text characters spanned by the range in the document. We must refer to the range given by First or Last items in the Characters collection and use the Delete method for those ranges.

Change characters on which side?

There are a few ways to detect and change the characters if they're on both, either, or neither sides of the range. I went with a shorter solution.

If FirstCharacter = LeftCharacter And LastCharacter = RightCharacter Then
' Function characters on both ends, so delete them ...
Else
' No function characters or only on one side of the range
' Check individually and add to either or both sides of range ...
End If
Left character

To add the left or right characters, we check each in turn and add it to the side that doesn’t already have it.

If FirstCharacter <> LeftCharacter Then
r.InsertBefore Text:=LeftCharacter
End If

Recall the <> symbol is “not equal,” so we basically add the left double quote if it doesn’t already exist on the left side.

InsertBefore inserts the given plain text in the Text option on the left side of the range, and the range automatically extends backward to include the new text.

Right character

Similarly, we can check whether the last character of the range is correct and add the right character if not.

If LastCharacter <> RightCharacter Then
r.InsertAfter Text:=RightCharacter
End If

InsertAfter works just like InsertBefore except for the obvious text insertion on the right side of the range. The range again automatically extends forward to include the new text.

Independent checks

Both conditions need to be checked since we don’t know which one is missing or if both are needed. These conditional statements are also relatively concise, so we can shorten them.

If FirstCharacter <> LeftCharacter Then r.InsertBefore Text:=LeftDQ
If LastCharacter <> RightCharacter Then r.InsertAfter Text:=RightDQ

Return the modified range

We need to return a range for the range for the function (by assigning a range to the function name). In this case, we return the working range after any modifications in case the user wants to do something with it.

Set ToggleCharactersRange = r

I often don’t care about the returned range, meaning I’ll ignore the result other than the desired toggled characters, but it’s good to be general if it makes sense for the function.

Gotchas

Where could all this go wrong?

This function modifies the document, so as a general rule, we should be careful. However, this one is relatively safe since we’re only adding or removing specific individual characters.

What about an empty range?

This function just inserts the characters around the empty range as normal, so we’re okay here.

Duplicated characters?

We don’t check for whether the given characters are duplicated at either side of the range.

Since this function accepts any pair of characters, duplication on either side could be a problem. However, so many possibilities exist, it’s difficult to narrow down to a choice that makes sense for most cases. With that in mind, it’s a reasonable compromise to not bloat the function by checking and correcting for special cases.

Final Function

Putting it all together, our new function to toggle any character around the range is:

Function ToggleCharactersRange(rTarget As Range, ByVal LeftCharacter As String, _
ByVal RightCharacter As String) As Range
' Toggle the given characters around given range r and return resulting range

' Return Nothing if rTarget is invalid or either string is empty
If rTarget Is Nothing Or LeftCharacter = "" Or RightCharacter = "" Then
Set ToggleCharactersRange = Nothing
Exit Function
End If

' Only consider first character of toggle strings
LeftCharacter = Left(LeftCharacter, 1)
RightCharacter = Left(RightCharacter, 1)

' Define a working range independent of rTarget
Dim r As Range
Set r = rTarget.Duplicate

' Trim spaces or paragraph marks from both ends
Set r = TrimRange(r)

' Check for left and right characters outside range and include them if so
PreviousCharacter = r.Previous(Unit:=wdCharacter).Text
NextCharacter = r.Next(Unit:=wdCharacter).Text
If PreviousCharacter = LeftCharacter Then r.MoveStart Count:=-1
If NextCharacter = RightCharacter Then r.MoveEnd

' Get first and last characters to check for matches
FirstCharacter = r.Characters.First.Text
LastCharacter = r.Characters.Last.Text

' Add or remove characters as appropriate
If FirstCharacter = LeftCharacter And LastCharacter = RightCharacter Then
' Delete double quotes from both sides of range
r.Characters.First.Delete
r.Characters.Last.Delete
Else
' No character matches or only match on one side of range
' Check each side for respective character and add if needed
If FirstCharacter <> LeftCharacter Then r.InsertBefore Text:=LeftCharacter
If LastCharacter <> RightCharacter Then r.InsertAfter Text:=RightCharacter
End If

' Return the possibly modified range
Set ToggleCharactersRange = r
End Function

This more general function is nicer than the previous one, but it isn't meant to handle everything. We picked reasonable improvements that make sense and do not dramatically increase the complexity of the function. If more complicated situations are required, we could revisit it at that time.

Examples

Assume our range of interest is stored in a variable SomeRange. Calling the above function to add double quotes around the range while ignoring any result would look like:

Call ToggleCharactersRange(SomeRange, LeftDQ, RightDQ)

This uses two previous tiny functions LeftDQ and RightDQ because they’re different in Word for Mac or Windows.

If you prefer to store the returned range for later use, the command would look like:

Set Result = ToggleCharactersRange(SomeRange, LeftDQ, RightDQ)

Result is a valid range variable, and the range determined by the function would be stored in it. Recall we have to use Set because Range is an object containing more information than a single value like 0, True, or “xyz.”

If we instead wanted to toggle parentheses on either side of a range, we could use:

Set Result = ToggleCharactersRange(SomeRange, "(", ")")

That's it.

Now go forth and edit faster!

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.