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:
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.
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).
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.
Paragraph object works
What if we give the function a known Paragraph object?
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.
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.
If you have a range variable, you could also access a valid paragraph object with it.
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:
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.
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.
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.
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 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.
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.
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.
We set up to Delete the paragraph if it is empty.
We get the first paragraph of the range r using the Paragraphs collection. This is redundant since there is only one paragraph.
This doesn’t work because Paragraphs don’t have a Delete method directly. Instead use the paragraph’s range property.
Putting the conditional statement together, we get:
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:
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.
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:
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.
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
We could save an extra line by putting the GetRangeNextCharacter(r) function call inside the condition check since it already returns the next character.