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 empty paragraph

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

A menial task in many text manipulation macros is cleaning up after any changes. Removing an empty paragraph is one such trivial, but necessary task, and we might as well get the macro to do it for us while it’s running. We shouldn’t force ourselves to edit manually after using a macro.

Thanks for your interest

This content is part of a paid plan.

Delete empty paragraph

This is another in a series of functions intended to streamline our previous move sentence macro.

The Problem

In the original article, the macros to move a sentence forward or backward in the document are both awkwardly long, so we are breaking them down into smaller chunks. In practice, we would do this while creating the original macros to make the work easier, but we’re attacking the problem from the other side this time.

Original Macro Sample

Deleting an empty paragraph is a general task that makes a good candidate function for use in other text manipulation macros. The original steps in the move sentence macros are as follows:

' Delete empty paragraph if we moved only sentence
' Checked in addition to deleting spaces above
PreviousCharacter = rSentence.Previous(Unit:=wdCharacter).Text
FirstCharacter = rSentence.Characters.First.Text
If PreviousCharacter = vbCr And FirstCharacter = vbCr Then
' Delete empty paragraph
rSentence.Delete
End If

Each move sentence macro has one occurrence of this sequence of steps because we would only need to delete an empty paragraph if we moved a lone sentence in the starting paragraph.

What’s the plan?

It’s good to think about some specifics on how the macro works. We’ll work under these assumptions:

  • We’re given a general paragraph as a candidate for deletion.
  • The target paragraph may have text, just whitespace (only spaces or tabs), or no text at all. We need to detect the difference between them and take the appropriate action.
  • Delete any paragraph that is empty (only a paragraph mark) or contains only whitespace.

Let’s translate those assumptions into a function.

Create function skeleton

The function skeleton is basically the same as previous ones we’ve created.

Function DeleteEmptyParagraph(TargetParagraph As Paragraph) As Boolean
' Include function steps ...

' Return whether paragraph was deleted
DeleteEmptyParagraph = False
End Function

Return type

In this function, a proper return value isn’t as obvious as with some functions. We don’t necessarily have any whitespace to delete, so we can’t return how many characters were deleted or anything similar. Also, an empty paragraph is gone by the end of the macro, so we can’t return a corresponding range.

One might simply omit the return type entirely (making it a Subroutine instead), but it’s useful to return some kind of information indicating a successfully completed task or not. In this case, the only other return value that makes sense is a simple True-False (Boolean) value indicating whether the paragraph was deleted.

With that in mind, I declared the returned data type “As Boolean” after the Function name and parentheses. For the moment, I gave it a value of False since we don’t have any logic yet to decide what the value is.

Since a Boolean variable just holds a value, we only need an equals = sign to assign it a value. We don’t use “Set” like we do when storing a Range or Paragraph.

What parameters to use?

This original sequence of macro steps earlier used a range variable rSentence that marked the position of the deleted sentence, but in this function, we can be more specific with the accepted parameter type. Being more specific (often more restrictive) about the accepted data usually simplifies the function steps since you know more about what to expect.

In this case, we’ll require a Paragraph parameter since we’re trying to delete a whole empty paragraph. Inside the function, we’ll identify it as the TargetParagraph.

Function argument types

When someone uses this function, they must supply an explicitly declared Paragraph variable.

Huh? What does that mean?

Generic argument types won’t work

It's a little confusing what works and what doesn't but let me try to shed a little light on it.

Using generic undeclared types (called Variant) won’t work for the most part (but see below).

' Generic type does not work for function arguments
Dim SomeVariable

SomeVariable is not given a type, so VBA assumes it is a generic, Variant type. These can even be assigned a known Paragraph, of course, but we still can’t call the function like this.

Set SomeVariable = Selection.Paragraphs.First
' Call function with a specific Paragraph variable
Call DeleteEmptyParagraph(SomeVariable) ' Still does not work

Paragraph object works

What if we give the function a known Paragraph object?

' Call function with a specific Paragraph property
Call DeleteEmptyParagraph(Selection.Paragraphs.First) ' This works

This one seems tricky since we already attempted a specific paragraph Selection.Paragraphs.First via an intermediate generic variable above, but this version works because VBA knows Selection.Paragraphs.First is a Paragraph.

Arghhh, but the difference is the generic variable used.

Use explicit argument variable types

So, the easiest solution is to always use an explicit Paragraph (or whatever needed type) variable for VBA to accept the argument for the function.

' Declare a Paragraph variable
Dim SomeParagraph As Paragraph
Set SomeParagraph = Selection.Paragraphs.First

Recall we must “Set” a Paragraph since it is more than just a value in Word.

Calling a function

Now we can call the function using the known Paragraph variable.

' Call function with a specific Paragraph variable
Call DeleteEmptyParagraph(SomeParagraph) ' This works

If you have a range variable, you could also access a valid paragraph object with it.

' Declare a Range variable
Dim SomeRange As Range
Set SomeRange = Selection.Range
Call DeleteEmptyParagraph(SomeRange.Paragraphs.First) ' This works

This also works because we explicitly declare a range variable before accessing the First paragraph of the range's Paragraphs collection. We could get a little sloppy here by not declaring the range, but rather than remembering any exceptions, it's probably easier to just explicitly declare variables passed to functions or at least reference a known object with the correct type.

Discarding the function result

By the way, since we’re using “Call” statement here, we’re basically ignoring the Boolean (True-False) returned value of the DeleteEmptyParagraph function.

Storing the function result

As we've done many times, if you need the value, you could instead use:

WasDeleted = DeleteEmptyParagraph(SomeParagraph)

The WasDeleted variable would hold a True or False value depending on whether the given paragraph was deleted. We could then use this value as a condition in an If statement if we had some steps we only needed to carry out if it worked or not.

Fill the function

This is a function where it’s definitely easier to just create it from scratch since it’s easier to think through the logic that way. Even if you copy steps from the main macro, they require significant modification this time.

Define the working Range

We set a working range using the TargetParagraph’s range property as a starting point. We’ll manipulate our working range when detecting the type of text in the paragraph as well as deleting the paragraph, if necessary.

Set r = TargetParagraph.Range.Duplicate

Why do this?

Using a range variable gives us more control over any necessary text manipulation or navigation, but accepting a Paragraph parameter ensures us of the starting state of the function. We know we’re given a whole paragraph rather than some arbitrary range we then must test to determine what we’ve been given.

I often use a short range variable name like a plain “r” since it’s easier to type any commands.

Collapse the range

We collapse the working range since we’re planning to check the paragraph text.

r.Collapse

The testing logic is a little simpler if we start at the beginning of the paragraph.

Extend over any existing spaces or tabs

Extend the working range over any spaces or tabs.

' Extend over spaces or tabs
r.MoveEndWhile Cset:=" " + vbTab

Here we create the character list by adding a special character vbTab to a space in double quotes.

String concatenation

This is called “concatenation,” and it’s similar to what the plus + sign implies. Adding two sequences of characters (called strings) together.

However, rather than adding in the sense of mathematical addition with numbers, string concatenation just joins the strings together. For example, we can add two strings like this:

SomeString = "abc" + "def"

SomeString would then have the value “abcdef”.

In the MoveEndUntil command above, each string for Cset is only one character long, so our extension of the working range would continue as long as either character is found.

Stop character determines whether paragraph is empty

With this command, the End position of the working range r will extend as long as it keeps finding spaces or tabs. If the paragraph is empty or filled with only whitespace, the command will stop at the next paragraph mark, specifically a vbCr special character.

If the paragraph is not empty, it will stop at some character other than a paragraph mark. We don’t care what the specific character is. It just informs us the paragraph is not empty.

Get next character

We need the next character after the extended working range to check whether it’s a paragraph mark.

Unfortunately, we don’t know whether the working range is empty or not, and it matters as far as what VBA considers is the “next” character.

' Check next character immediately after working range
If r.Start = r.End Then
' Range is empty, so use first character
NextCharacter = r.Characters.First.Text
Else
' Range is not empty, so use regular next character
NextCharacter = r.Next(Unit:=wdCharacter).Text
End If

Briefly, if the range is empty, VBA considers the “first” character to be part of the range, but a human would probably intuitively consider this the “next” character of an empty range. On the other hand, if the range spans some text, the next character is the one immediately after the range’s End position.

The If statement is just checking whether the range is empty (Start and End positions are the same) and deciding on what the next intuitive character is.

We’ve seen this logic in a previous function (see the get range next character article for more explanation). It pops up frequently enough that we’ll use a separate small function to get it even here.

NextCharacter = GetRangeNextCharacter(r)

This will make our current function more concise, but I give both versions below.

Delete any spaces at beginning of paragraph

We use an If Else statement to verify the presence of the immediate end of the paragraph.

If NextCharacter = vbCr Then
' Empty paragraph, so delete it
Else
' Not an empty paragraph, so do nothing else
End If

We set up to Delete the paragraph if it is empty.

r.Paragraphs.First

We get the first paragraph of the range r using the Paragraphs collection. This is redundant since there is only one paragraph.

' No Paragraph Delete command exists
r.Paragraphs.First.Delete ' Does not work

This doesn’t work because Paragraphs don’t have a Delete method directly. Instead use the paragraph’s range property.

r.Paragraphs.First.Range.Delete

Putting the conditional statement together, we get:

If NextCharacter = vbCr Then
' Empty paragraph, so delete it
r.Paragraphs.First.Range.Delete
Else
' Not an empty paragraph, so do nothing else
' Normally this Else would be omitted, but ...
End If

We would normally just omit the empty Else condition above, but it gives us a convenient way to assign the return value below.

Assign returned value

We need to assign a return value based on whether we successfully deleted the given paragraph. The command would be something like:

DeleteEmptyParagraph = True ' or False

The easiest way to accomplish this is to just assign the appropriate value in the same If statement above where we’re already deleting the paragraph or not.

If NextCharacter = vbCr Then
' Empty paragraph, so delete it
r.Paragraphs.First.Range.Delete
DeleteEmptyParagraph = True ' Yes, we deleted it
Else
' Not an empty paragraph, so do nothing else
DeleteEmptyParagraph = False ' No, not deleted
End If

Notice we didn’t wait until the end of the function like we did in previous functions. The returned value can be assigned anywhere in the function. However, we are guaranteed to return something valid since at least one of the assignments are done.

Final Function

Putting everything together, the function to delete a given paragraph if it is empty is:

Function DeleteEmptyParagraph(TargetParagraph As Paragraph) As Boolean
' Delete an empty paragraph if it only contains spaces, tabs, or the ending paragraph mark

' Define a working range and collapse toward start
Set r = TargetParagraph.Range.Duplicate
r.Collapse

' Extend range End over spaces or tabs
r.MoveEndWhile Cset:=" " + vbTab

' Get next character immediately after working range
If r.Start = r.End Then
' Range is empty, so use first character
NextCharacter = r.Characters.First.Text
Else
' Range is not empty, so use regular next character
NextCharacter = r.Next(Unit:=wdCharacter).Text
End If

' Check for end of TargetParagraph using next character
' Assign return value whether paragraph was deleted
If NextCharacter = vbCr Then
' Empty paragraph, so delete it
r.Paragraphs.First.Range.Delete
DeleteEmptyParagraph = True
Else
' Not an empty paragraph, so do nothing else
DeleteEmptyParagraph = False
End If
End Function

This function is better than the steps from the original move sentence macros. Since we have a stand-alone function, we added some steps to check whether the paragraph is only spaces or tabs without cluttering up the original macro. This change also makes the function more generally useful in other macros.

With get next character function

I prefer a more concise version using a function to get the next character after the working range (see the short article for more explanation). This version saves four lines and even makes the function easier read.

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

Delete empty paragraph with functions

Function DeleteEmptyParagraph(TargetParagraph As Paragraph) As Boolean
' Delete an empty paragraph if it contains no characters or only spaces or tabs

' Define a working range and collapse toward start
Dim r As Range
Set r = TargetParagraph.Range.Duplicate
r.Collapse

' Extend range End over spaces or tabs
r.MoveEndWhile Cset:=" " + vbTab

' Check for end of TargetParagraph
' Assign return value whether paragraph was deleted
NextCharacter = GetRangeNextCharacter(r)
If NextCharacter = vbCr Then
' Empty paragraph, so delete it
r.Paragraphs.First.Range.Delete
DeleteEmptyParagraph = True
Else
' Not an empty paragraph, so do nothing else
DeleteEmptyParagraph = False
End If
End Function

We could save an extra line by putting the GetRangeNextCharacter(r) function call inside the condition check since it already returns the next character.

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.