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

Delete paragraph end spaces

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

We are building several functions to streamline our previous move sentence macro. Toward this end, we implement a function to delete any spaces at the end of a given paragraph, but an additional version is included for a given range.

Thanks for your interest

This content is part of a paid plan.

The Problem

In the original article, the base macro to move a sentence forward or backward in a document is awkwardly long. In this function, we start the process of simplifying the longer macro by separating out the steps to delete any undesired spaces at the end of a given paragraph.

Delete Paragraph End Spaces

To create a function, we usually identify a group of steps in a macro that performs a specific, usually repeated, task. When writing the move sentences macros, we had to check and delete any spaces at the end of a paragraph. This task occurred four times (two possible paragraphs per direction), so that seems like a good place to start a candidate function.

The steps to delete unwanted spaces at the end of a paragraph was as follows:

' Select and delete all ending paragraph spaces at source sentence
' Use first character of range to detect end of paragraph
FirstCharacter = rSentence.Characters.First.Text
If FirstCharacter = vbCr Then
rSentence.MoveStartWhile Cset:=" ", Count:=wdBackward
' Delete preceding spaces
rSentence.Delete

' Delete straggling space Word reinserts sometimes
FirstCharacter = rSentence.Characters.First.Text
If FirstCharacter = " " Then
rSentence.Delete
End If
End If

This particular set of steps worked with a range variable marking the original sentence to move, but while we’re generalizing the steps to a function, we’ll convert it to work with a paragraph since that was the most common case (and it's easier).

Create function skeleton

We start by creating the function skeleton.

Function DeleteParagraphEndSpaces() As Long
' Include function steps ...

DeleteParagraphEndSpaces = 0
End Function

We indicate the returned data type with the “As Long” statement after the Function name and parentheses. Counting numbers in Word are usually Integer or Long, but a Long number type is simply a bigger form of an Integer with a much higher maximum number.

In this function, we’re returning how many spaces were deleted which is a counting number, so we add a step at the end to return that value which we accomplish by just assigning a value to the function name.

DeleteParagraphEndSpaces = 0

We just set a value of zero for now because we don’t know what it is yet.

What parameters to use?

Often, we want to reuse a function with other macros, so we create it to receive generic document elements (or whatever data is necessary). With this one, we’ll work on a given paragraph. Although we could design it using Ranges instead (see extended content below).

We just have to make a choice. Applying it to ranges is probably the more general purpose case, but sometimes there is an easier approach. I chose to use a paragraph since I know the exact structure of the paragraph. Specifically, a paragraph ends with a paragraph mark and not some random character like would happen with a generic range.

So, we add a Paragraph parameter, which we'll call TargetParagraph, to the function inside the parentheses.

Function DeleteParagraphEndSpaces(TargetParagraph As Paragraph) As Long
' Include function steps ...

DeleteParagraphEndSpaces = 0
End Function

We add "As Paragraph" after the variable name to tell the function that the data type is a Paragraph. A Paragraph is an object type in Word VBA which has its own methods (actions) and properties (data). It's almost like a document range but more specific.

Fill the subroutine

Now fill the empty function. In this function, we’re now assuming the user gives us a valid, whole paragraph which simplifies the logic.

Sometimes I create subroutines from scratch since it can be easier to think through the steps like that, but other times I copy the steps over from an existing macro to “save some time.” Neither approach is wrong, but you’ll probably modify any copied steps since you’re usually implementing the function with slightly different assumptions as opposed to creating it inside the original macro.

Define the working Range

We first set a working range to span the given TargetParagraph.

Why a range?

Huh? Wait.

Why a range when I thought you just said something about a paragraph—

Just hold on. You might wonder why I wanted a Paragraph parameter for the function if we’re just going to define a working range based on it anyhow.

If we ask the user for a Paragraph, we know it includes the whole paragraph including the paragraph mark. We don’t have to check anything extra and worry about any special cases. A range version is good if you want to do that, but I liked the simplified logic. It gives us a more targeted macro with fewer options to consider.

While we can perform some actions using the Paragraph directly, we define a working range since Ranges allow us to navigate and modify document content more generally.

Define the range variable

We want that entire paragraph range to begin the function.

Set MyRange = TargetParagraph.Range

Recall we have to Set Ranges since they are more than just pure values like a number or plain text.

Omit the paragraph mark

Remove the paragraph mark from the End of the Range.

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

This is an example of knowing a specific starting state and how we can take a specific action based on it. If we had a general target range parameter, we would need to add some tests to verify our starting state.

Collapse the range

We collapse the working range since we’re planning on selecting the spaces using the range.

MyRange.Collapse Direction:=wdCollapseEnd

We collapse toward the end using the Direction option since we want to eliminate any spaces there. Now our empty range is positioned just before the paragraph mark and presumably just after any spaces that may exist.

Select any spaces

Select any spaces before the range by moving the Start of the Range backward over them.

MyRange.MoveStartWhile Cset:=" ", Count:=wdBackward

Remember MoveStartWhile doesn’t change the End position of the Range, so if any spaces exist, we’ll be spanning text after this step.

Get number of characters moved

However, I need to track how many characters the Start position moved, so I can check whether any spaces were spanned. Thus, we modify the command to store the result in a variable.

CharactersMoved = MyRange.MoveStartWhile(Cset:=" ", Count:=wdBackward)

Don’t forget we need to include the arguments in parentheses if we use the method like a function and store the returned value.

Delete any spaces

We want to delete any spaces contained in MyRange, but we can’t just use

MyRange.Delete

Why not?

What if there were no spaces at the end of the paragraph?

Then this Delete command would delete the first character to the right of the empty MyRange, so we need to first check whether the MyRange Start position changed. We previously stored the number of characters in the CharactersMoved variable.

If CharactersMoved < 0 Then
' Delete any spaces found in the range
MyRange.Delete
End If

The CharactersMoved variable will have a negative value if the Start position of MyRange moved backward in the document.

Delete a straggling space

Word sometimes reinserts a space as a helpful feature during real-time editing. I like it there, but for macros, it’s … well, not as a helpful.

We need to check whether Word actually inserted the space and delete it if so. If it is there, it will be reinserted to the right of the now empty MyRange.

FirstCharacter = MyRange.Characters.First.Text
If FirstCharacter = " " Then
MyRange.Delete
End If

Remember the “next” character after an empty range (when the Start and End positions are the same) is actually the “First” character. This is not true if the Range spans any text (see extended content below for an example).

Change return value

We now know how many spaces were spanned, so we can set the return value at the end of the function appropriately.

DeleteParagraphEndSpaces = Abs(CharactersMoved)

This value is passed back to the macro that called the function.

The Abs() is the absolute value function. Recall an absolute value is the positive value of whatever number is given to it. It is used here since the MoveStartWhile method will return negative values for moves backward in the document, but the number of spaces deleted is inherently a positive number. It’s a preference.

Final function

Now let’s put the function steps together. This one returns how many spaces were deleted.

Function DeleteParagraphEndSpaces(TargetParagraph As Paragraph) As Long
' Delete all spaces at the end of the target Paragraph
' Returns how many spaces were deleted

' Create working range
Set MyRange = TargetParagraph.Range

' Omit paragraph mark
MyRange.MoveEnd Unit:=wdCharacter, Count:=-1
' Collapse to end of range before paragraph mark
MyRange.Collapse Direction:=wdCollapseEnd
' Extend range Start over previous spaces
CharactersMoved = MyRange.MoveStartWhile(Cset:=" ", Count:=wdBackward)

' Delete preceding spaces if found
If CharactersMoved < 0 Then
MyRange.Delete
End If

' Delete straggling space Word sometimes reinserts
FirstCharacter = MyRange.Characters.First.Text
If FirstCharacter = " " Then
MyRange.Delete
End If

DeleteParagraphEndSpaces = Abs(CharactersMoved)
End Function

Notice it looks a lot like a regular macro (because it is) except we require a Paragraph argument when it’s used, the function requires a return type, and we return a value at the end. Subroutines even skip that last two parts.

The steps are similar to what we did in the original macro, but we made some changes based on the assumptions when applying it to a known paragraph.

In practice, I prefer functions since we can send some information back about what just happened during the function, but the calling macro can just ignore the value by not using or storing it.

Range Version

What would change if we implemented the subroutine using a Range parameter instead?

Delete paragraph end spaces function using a Range parameter

This is an interesting example of how small choices affect our solution and the work involved in implementing them. Sometimes choosing the simpler option is better.

The result (changes made off screen) is as follows.

Function DeleteRangeEndSpaces(TargetRange As Range) As Long
' Delete all spaces at end of TargetRange
' Omits ending paragraph marks

' Create a duplicate range so we do not change original other than
' deleting any ending spaces
Set MyRange = TargetRange.Duplicate

' Omit paragraph mark if it exists (preference)
LastCharacter = MyRange.Characters.Last.Text
If LastCharacter = vbCr Then
MyRange.MoveEndWhile Cset:=vbCr, Count:=wdBackward
End If

' Store current next character in intuitive sense for later space check
If MyRange.Start = MyRange.End Then
' Range is empty, so get first character
NextCharacter = MyRange.Characters.First.Text
Else
' Get regular next character after spanned text
NextCharacter = MyRange.Next(Unit:=wdCharacter).Text
End If

' Collapse to end of range
MyRange.Collapse Direction:=wdCollapseEnd
' Extend range Start over previous spaces
CharactersMoved = MyRange.MoveStartWhile(Cset:=" ", Count:=wdBackward)

' Delete any spaces found
If CharactersMoved < 0 Then
MyRange.Delete
End If

' Delete straggling space Word reinserts but only if original NextCharacter
' was not a space
FirstCharacter = MyRange.Characters.First.Text
If FirstCharacter = " " And NextCharacter <> " " Then
MyRange.Delete
End If

DeleteRangeEndSpaces = Abs(CharactersMoved)
End Function

Of course, the basic features and many of the steps are the same as in the Paragraph parameter version. It gets rid any spaces at the end of the given Range.

This version is more general since it accepts any Range, not just paragraphs, so it is preferable in that regard, but …

Let’s talk about the extra work

Generality usually comes at a price. Fortunately, the price to pay is relatively small here, and it also gave us a springboard to talk about a few more things.

What about spaces outside end of range?

We need to at least briefly consider other conditions that may affect how we write the macro. For example, do we worry about any ending spaces outside the end of the given TargetRange?

Generally speaking, I would say no (don't change anything outside the given TargetRange) mostly because that’s not what the name of the function implies "DeleteRangeEndSpaces" (I do have exceptions though), so we won’t do anything with this.

We didn’t have to worry about this with the Paragraph parameter version.

Check for paragraph mark

Since we have a general TargetRange, we now need to check whether the paragraph mark is present before removing it from the range. This is a preference that seems obvious to exclude (to me at least) since paragraphs will be a common use case, but we don't want to force the user to always manually remove the ending paragraph mark.

If you're unsure, you could also set up an extra Optional parameter to give the user a choice, but we'll cover those later.

New gotcha with ending space

This version introduces a small gotcha if the next character after the TargetRange is already a space. The last steps of the subroutine would delete a trailing space because the check for the last space can’t tell the difference between one that was already present in the document and one that Word reinserted during the macro.

Avoid side effects

It’s not the end of the world if we ignore this gotcha since we’re just deleting an extra unintended space to the right of the TargetRange, but such “side effects” should generally be avoided because we’ll forget about them as time passes. The user just doesn’t expect a macro to act like that … so it shouldn’t.

Next character issue

We have another small compounding problem.

In the process of fixing the gotcha, we run up against another small issue where the next character in the intuitive sense is different for empty Ranges compared to Ranges that span any text. This pops up several times in other macros, so we’ll eventually turn this into a separate small function.

Since we now allow the user to provide any TargetRange to the subroutine, we have to allow for ranges whether they are empty or not. On top of that, storing the next character must happen after the paragraph mark check, or we could get a third spurious result in isolated cases.

See what’s happening …

Additional next character check

We finally add the extra next character check to the condition in the last macro steps.

Just a little messier

Yikes. That’s more than such a simple change from a Paragraph to a Range parameter might suggest is necessary.

None of the above was particularly difficult individually, but on the whole it was just messier to implement which is why I initially created the simpler version using a Paragraph parameter.

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.