You may have the feeling that the Word macros are quite useless. Who needs them anyway? Well, sometimes they are used in malware... Moreover, they are written in a long forgotten VBA (Visual Basic for Applications, which is somehow limited Visual Basic 6 in an interpreted form), eeww!
But in fact, after all, many people may sometimes need to write a report in Word, or issue a paper, or write a CV before sending it to their dream company... For some people, their job is working in Word itself. Often, various documentation systems can export pages to doc
or docx
formats that your customers ask for. And it often happens that exported documents look awful, you have to fix them manually every time after export.
Working in Word often involves performing some repetitive actions, which can be sometimes (but not always!) solved by correctly setting and applying styles, as well as using templates. How do you automate everything else? This is where these very macros come to our aid.
What is so good about them? Well, for example, they can automatically and quickly enough do some work for you. Calculate something, reformat the document, mark suspicious words with annotations... Just everything that you program! They can even correct something in real time while you are writing a document. VBA in Word allows you to automate almost all actions that you can perform with a document manually.
Macros can be attached both to a specific document (which is of little use to us, but for Trojan writers this is the only option), and to the Word itself, which allows you to apply macros to any document which you work with.
Macros work in any version of Word and require minimal changes when porting from one version to another (and often do not require them at all). With macros you can even implement a full-fledged user interface with forms!
Let's dive into Visual Basic and write something useful! Our first example will be a macro that replaces two or more consecutive line breaks with a single one. This is often required when correcting documents after exporting them from documentation systems, or if you want to remove accidentally added extra line breaks in a document you have written yourself. We will make a solid macro, with a user interface and an operation progress display.
Firstly, to start writing or using macros, you need to make sure that the "Developer" panel is displayed in Word. If you don’t see it, after creating a new document open the "File" menu -> "Options" -> "Customize Ribbon", then find there and set the "Developer" checkbox.
After that, open the "Developer" tab and click the "Visual Basic" button.
On the left side of the opened VBA window you will see two projects: "Normal", and a project related to the current open document. Your "Normal" project can already contain some files in the "Modules" directory. In any case, create a new module by right-clicking on "Normal" and choosing "Insert" -> "Module".
This new module is a file, where we will put the macro code. You can rename the module (which has the "Module1" name by default) in the "Properties" window -> "Name". I will name my module "AllMacros". Now open the module code by double-clicking on its name. Let's proceed with creating the macro. Let me remind you that our goal is to replace two or more consecutive line breaks with a single one, performing such replacements throughout the document. Obviously, we need a function that searches a substring in text, because we want to find several consecutive line breaks. In Word, a line break is equivalent to the beginning of a paragraph. You can search for it using the regular search box by typing ^p
, ^13
or ^013
there (which corresponds to the ASCII code for line break). The function which do the same will look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Private Function FindNextText(text As String, useWildcards As Boolean) Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = text .Forward = True .Format = False .MatchCase = False .MatchWholeWord = False .MatchKashida = False .MatchDiacritics = False .MatchAlefHamza = False .MatchControl = False .MatchWildcards = useWildcards .MatchSoundsLike = False .MatchAllWordForms = False .Wrap = wdFindStop End With Selection.Find.Execute FindNextText = Selection.Find.Found End Function |
Let's see what happens here. We declare a function with two parameters. The first one is a String
- this is the text to search for, and the second one is a Boolean
, which tells whether to use wildcards. I’ll talk about them later. In the next two lines #2 and #3, we clear the formatting of the search string and the replacement string, in case it was earlier set by the user. Word allows you to specify the formatting of the search/replace string, but this is not required for our task. Next, we set some parameters for the Selection.Find
object: set some unused options to False
; the Text
parameter is the string we want to find; and the MatchWildcards
parameter indicates the use of wildcards. The Wrap
parameter tells whether to continue the search when it reaches the point from which it has begun, and we have its value set to wdFindStop
, since we want to stop when we get to the end of the document, and not to loop the search.
In general, all this abundance of properties and objects of the Word, Excel, and PowerPoint object models (yes, they support macros, too) is well described in MSDN. For example, this page lists the properties and methods of the Find object.
In addition, all available objects, their properties and methods can be viewed directly in the Word VBA editor. To do this, press F2
or select the "View" menu -> "Object browser", which will open the object browser, where you can scroll through all the available objects or search for something particular.
Back to our function. On line #19, we execute the search for the given text with the specified parameters. Line #20 is a statement, somewhat similar to the C-style return
. It sets the return value of the function, but does not return from the function itself. We return a boolean value Selection.Find.Found
, which indicates whether something was found.
Note that changing the Selection.Find
object properties will replace their values for the user, too, as this object is shared between the user and the macro. For example, if you were searching for something in Word with specific parameters, then executing the macro will replace your search parameters with those that we specified in the macro. Optionally, we can save them in macro, and restore them later, but I will not bother that much, just clean them up after our macro has finished execution. Let's write a function that resets the parameters to default values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Private Sub ClearFindAndReplaceParameters() With Selection.Find .ClearFormatting .Replacement.ClearFormatting .Text = "" .Replacement.Text = "" .Forward = True .Wrap = wdFindStop .Format = False .MatchCase = False .MatchWholeWord = False .MatchWildcards = False .MatchSoundsLike = False .MatchAllWordForms = False End With End Sub |
Please note that this is a Sub
, not a Function
, because we do not want to return any value from this procedure. This is just like a void
function in C language.
Now, how can we find two or more consecutive line breaks? To achieve this, we will need to utilize the wildcards, which were mentioned above. Word supports search wildcards, and they somewhat resemble regular expressions. By the way, you can use them outside of macros from the advanced search window:
I found a decent description of wildcard characters here. Let's write a wildcard to search for two or more consecutive line breaks: [^013]{2,}
. This is very similar to a classic Perl or PCRE regular expression, but square brackets specify a newline character in a unique Word style. Curly brackets indicate that there must be two or more characters in a row. By the way, there is a potential pitfall: not all Word versions/localizations will search with such a wildcard. In some cases, you need to specify a semicolon instead of a comma (yes, Microsoft sometimes does extremely strange things). To make our macro more universal, we will write a function that will return a wildcard suitable for searching in the version of Word where you execute this function:
1 2 3 4 5 6 7 8 9 |
Private Function GetLineBreakSearchRegExp() On Error GoTo Err FindNextText "[^013]{2,}", True GetLineBreakSearchRegExp = "[^013]{2,}" Exit Function Err: GetLineBreakSearchRegExp = "[^013]{2;}" End Function |
Here we first try to search using wildcard [^013]{2,}
. If everything is OK, then we return this wildcard from the function as a working one (line #4). Otherwise, an error occurs, but we are ready to handle it: we have set a handler for all errors in line #2. The execution will continue from the Err
label, and here we return the wildcard, which is suitable for other versions of Word (here the comma inside the curly braces is replaced by a semicolon).
Next, we write a function that replaces several consecutive line breaks with a single one:
1 2 3 4 |
Private Sub RemoveNextEnters() Selection.MoveStart wdWord, 1 If Selection.Range.Start <> Selection.Range.End Then Selection.Delete End Sub |
This function requires that it will be called when the Selection
object contains several line breaks (found earlier). In line #2, we move the selection start one character forward, and then in line #3, if the the selection start is not equal to its end, remove its contents. For example, if three line breaks were selected, we step 1 character forward (leaving one line break untouched), and then delete the remaining two (which are still selected).
Everything that remains is to write the last function that will manage everything.
1 2 3 4 5 6 7 8 9 10 |
Sub RemoveExcessiveEnters() Dim lineBreakSearchRegExp As String lineBreakSearchRegExp = GetLineBreakSearchRegExp() Selection.HomeKey Unit:=wdStory While FindNextText(lineBreakSearchRegExp, True) = True RemoveNextEnters Wend ClearFindAndReplaceParameters End Sub |
Here we just run previously written functions. First, we get the text of the wildcard to search for several line breaks in a row, then we emulate the Ctrl+HomeKey
press in line #5 to move the cusror to the very beginning of the document, and then in the loop we search for all the places we are interested in and delete the excessive line breaks. At the end, we reset the search parameters to default values.
That's it, you can now run the macro! Please note that we marked Private
all the functions except the last one. We do not want them to be called directly from Word (or from other VBA modules). Only the RemoveExcessiveEnters
function will be available for calling from the outside of our module. Before running, make sure that macros are enabled. If the following panel appears, then click "Enable content":
If there is no such panel, then you can open the "File" menu -> "Info", and enable macros from there:
You can enable macros for the duration of a single Word session (this is the default behavior when you click "Enable Content"), so that after restarting Word, macros will be disabled again. To run the macro, go back to the "Developer" panel in Word and click the "Macros" button, select our RemoveExcessiveEnters
macro and click "Run". Of course, you should have some experimental document open, with extra line breaks. As a bonus, our macro also removes empty list items, because they are exactly the same several line breaks in a row.
The macro runs quite fast. Its work can be undone (each step independently) by opening the menu of completed actions:
Macros can be debugged through the VBA window which we used to write the macro. By clicking to the left of the line of code, you can, like in other development environments, set a breakpoint, and then debug the macro with the "Run" button. The function with the cursor inside will be executed. If you place the cursor in a function which takes any arguments, a regular request to choose a macro to run will appear, as when you press the "Macros" button in Word.
I think, there's quite enough content for a single article. Of course, I promised user interface, progress tracking, and I will implement all of this (and even a little more), but in the next post.
The macro code can be downloaded here. You can import it into your Word by right-clicking on "Normal" -> "Import file..." and selecting the downloaded file.
P.S. By the way, I recommend creating backups of your macros by exporting them somewhere to your disk. Even licensed Word can sometimes behave strangely and delete them from the "Normal" template for some reason.