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

Parsing voice commands in VBA to run a Word macro

Word • Macros • Dragon • Voice Commands
Peter Ronhovde
45
min read

Natural language commands complement our writing process by allowing the computer to accomplish tasks for us more efficiently. Dragon Professional includes some natural language voice command features out of the box, but they need a boost with more flexible command phrasing as well as improvement in how Dragon carries out task details.

Thanks for your interest

This content is part of a paid plan.

Basic Keyword Voice Command Searches in VBA using Like

So, you have a nice script for your new voice command. You’ve even incorporated a list variable or two to generalize it (see related article), but then your mixes get worded up when you say it and …

Huh?

Nothing happens.

Arghhh.

Sure would be nice if we could bake our own …

We can. At least with a little VBA elbow grease and some attention to detail.

Toward that end, we’ll extend a previous custom Dragon script to allow a wider variety of phrasings when invoking a voice command. In the process, we’ll also gain more detailed control over how the commands are performed because we can use our own Word macros to accomplish any necessary tasks.

Getting started

This article leans on three previous functions:

Many voice commands are relatively simple grammatically speaking, so it’s easier to parse them using simpler search tools like Like. No sense breaking out a pickaxe when a toothpick will do. We will, however, branch out into using regular expressions (regex) for more detailed command scripts in upcoming articles.

We’re using Dragon Professional advanced scripting for the voice commands since Dragon Professional integrates nicely with Microsoft Word, and both applications use VBA for their scripting/macros. Dragon Professional is a paid application with a significant sticker price, but I think it’s worth the investment since I make a living with my fingers … and hopefully my voice as I move forward. Just don’t ask me to sing.

Why bake our own?

Doesn’t Dragon Professional already do that?

Yes, Dragon Professional scripting has some natural language voice commands out-of-the-box, and it allows more custom commands and scripts to be created within its command browser. However, there are limitations.

Command word limitations

With Dragon, every word order variation must be its own custom command. List variables allow word categories like direction or different document elements (although the categorizations are not enforced) providing some generality and flexibility, but the word placement must be preserved in the spoken command.

Words also cannot be omitted. We can sometimes get around this limitation with expanded list variables, but it only works for certain variations. If there is a list variable present, you must say something at that position in the command.

If we wish to expand on the phrasing variety to make it more natural, we’re stuck with duplicating commands with small phrasing changes. They’ll multiply faster than you think, and keeping track of which ones are similar or different can be confusing if you need to make any changes. As your list of custom voice commands grows, including steps to distinguish between different versions in a single script is better than having multiple slightly different custom scripts.

Examples

By the time we’re done, all these voice commands (and more) in Dragon Professional will run from within a single custom script.

These commands would require at least six custom script variations in the Dragon Professional command editor with only minor changes between them. It’s not the end of the world but keeping up with and updating so many nearly duplicate scripts with any changes is annoying.

"Select to the beginning of that heading content"
"Select the previous five words"
"Select that paragraph to the end"
"Select the entire heading"
"Select those sentences"
"Select to the heading start"
"Select by three paragraphs down"

Just six or seven command variations ... who cares?

It also makes sense to implement cut, copy, or delete versions. Now you're up to at least two dozen scripts just getting started.

Controlling the details

When baking our own command interpretations, we can also control all the details of how the task is performed in Word rather than relying on Dragon Professional’s stock treatment. I suspect you’ll eventually like that more than you realize.

For example, you might want your own sentence manipulation macros to exclude double quotes when working with dialog in a novel or parentheses in other documents.

I’m also not a fan of how “Select the next six words” and similar natural language commands are implemented literally by Dragon. When editing a document, “most people” (pardon my generalization) would consider the selection to start with the current word not the literal previous or next word (at least to me; I even researched it, and that seemed to be the consensus). I’ve done a lot of math and programming where logical statements are strictly evaluated, but the literal interpretation when editing was jarring for me. I had to move over a word before invoking the command. Ughhh.

Create parsing script skeleton

Since we covered this in more detail in a previous article, we’ll be brief here.

Dragon’s advanced scripting editor is mostly a plain text editor where we type commands just like in Word VBA macros. The Dragon editor has fewer features than the already meager Word VBA editor, but it does the job, albeit with some idiosyncrasies.

Open the script editor

Open the command editor using the Dragon Bar menu: Tools → Command Center → Add new command …

In the middle, open the combo box for the Command Type and select Advanced Scripting. This will add some lines to the script editor below.

What you’ll see

After selecting the new command option, it’ll create a default empty VBA script like the following in the script editor.

'#Language "WWB-COM"
Option Explicit
Sub Main

End Sub

Dragon includes Option Explicit by default which means we must declare all our variables (no implicit definition). Our script steps will begin under Sub Main.

Examples with more natural language voice commands

Let’s begin with a simple example. Suppose we wanted to act on a command phrase like:

"Select that paragraph"

Dragon will already carry out this command, but we're building something. We might as well make it general purpose and allow any of the following:

"Select that sentence"
"Select that heading content"

And others.

What about list variables?

If these were the only cases of interest, a list variable in Dragon’s custom command editor would handle these commands easily and more clearly. For example, we could create a command named:

Select that <element> ' Example not used today

We then store the list of potential document elements in the <element> list variable and use the spoken word identified by Dragon when it’s used to run the appropriate macro within the script. See this previous article if you prefer a list variable approach for your problem.

Hang on though because we’re building something.

Static portion of the command

“Select that” will be static part of the command for now, so we only need to focus interpreting the last word(s). Later we’ll make “that” a list variable to increase the command flexibility by allowing “this” or “those” as well as other options.

Dragon voice command name

With this tentative voice command, the name would be:

Select that <dictation>
Dictation list variable

We’re parsing the command ourselves, so we use a <dictation> list variable. The <dictation> list variable does not correspond to a list of words or phrases like other custom list variables. Instead, it encapsulates everything a user says after that point in the command. With that in mind, it must be placed at the end of the command name.

Get the command text

We need a way to store the words said for the <dictation> portion of the command, so we can determine what was said inside our script.

Store the dictation text

Any list variable in Dragon Professional is plain text, so we store the result in a String variable named sPhrase.

Dim sPhrase As String

I like to precede my String variables with an “s” just to remind myself what type of variable it is, but this convention is not required.

Store the dictation phrase text

Now we store the “dictated” command text. Since <dictation> is only one list variable so far, it occurs in the zeroth position of the context values.

sPhrase = UtilityProvider.ContextValue(0)

The zeroth (0) element of ContextValue corresponds to the first list variable in the command. Counting indices starting from zero is common in programming, so we’ll need to get used to it.

This notation isn’t pretty or clear (to me at least), but this is how we access the words or text in list variables.

Parse document element

Let’s assume we’re storing the relevant document element in a string variable called sElement.

Dim sElement As String

How do we parse the command?

Brief review of Like

We’re looking for certain document elements when spoken as part of the command phrase. We stored the phrase above in a text variable sPhrase. A quick example of using Like to search is for specific text looks like:

sPhrase Like "*paragraph*"

Like compares the search text on the left sPhrase to the search pattern on the right "paragraph". It returns a True or False value based on whether the pattern is found within the search text. The asterisks * are wildcard characters that tell Like to allow any or no characters on either side of our keyword.

Like only tells us whether a match was found, so we must separate various conditions into different document elements using conditional statements. Once we identify a match, we can assign the found element in the command to a variable for later use.

If sPhrase Like "*paragraph*" Then
' Assign document element here
End If

We can add other cases to the above conditional statement using ElseIf statements. See our brief review of conditional statements in VBA.

Document element parsing function example

We spent a whole article breaking down how to do this, so we’ll refer to that one and just use the result here. Specifically, the function to get the stated document element is GetPhraseElement.

GetPhraseElement(SomeCommandPhrase, SomeDefaultElement)

The first argument should be the command phrase which is the sPhrase variable for us. The second argument is a default element as plain text. If we wish, we can specify something like a “paragraph”, but this argument can also be omitted. If so, it will default to an empty string “”.

We want to store any function result in the aforementioned sElement variable, so we literally just use an equals = to assign the returned document element from the function to our text variable.

sElement = GetPhraseElement(sPhrase)
What do I do with it?

We can use the identified element to run a specific Word macro related to that element. With simpler tasks, we could make the change directly from within Dragon Professional using a Word application object (don’t worry if that makes no sense at the moment), but I prefer to just keep all the macro activity inside Word and use the Dragon script as more of a manager than a worker.

That is, we'll define all macros to carry out specific tasks in Word, so the Dragon command scripts just tell Word which macro to run. I think that is the cleanest approach that allows the least room for unexpected problems.

Which macro?

Which Word macro do we run?

We’ve identified the element above, so we need to select an appropriate macro to run. We’re not covering any details of specific Word macros here. We just want to identify which one we need and run it from our Dragon script.

Previous script to run a Word macro

We previously introduced a script function to run a Word macro using a voice command. The function name is RunWordMacro which takes the macro name to run within Word as plain text. For example, if we want to run the macro named SelectSentence, we would use the command:

Call RunWordMacro("SelectSentence")

The first parameter is the macro name in plain text or a String variable that stores the name. The second parameter is optional, but it corresponds to a possible macro parameter. Not all macros will require it, but when used, it allows us to send a bit of information to the macro we’re running. For example, we’ll use this parameter later to specify the number of paragraphs to select.

The function returns a True or False (Boolean) value based on whether it was able to run the Word macro, but True does not directly indicate whether the macro successfully executed its task in Word.

This function will also catch an error when trying to execute the macro, so the overall script doesn’t crash.

Unfortunately, given no library exists in Dragon scripting, we need to copy this between scripts. This function allows us to easily run any Word VBA macro we have from within a custom Dragon script.

Tentative Word macros

For our purposes today, we’ll assume our Word macros are named:

SelectSentence
SelectParagraph
SelectHeading
SelectHeadingContent

With more variations as desired.

Each macro acts much as its name suggests. One exception is when selecting sentences, our version will account for double quotes or parentheses.

Selecting the document element

We need to select between at least four different possibilities. This can be done with If statements, but that’s a little messy.

Select Case statement basics

That’s why Select Case statements were invented. A simple version of a Select Case statement is:

' Select Case simple example
Select Case SomeVariable
Case Value1
' Do these steps for Value1
Case Value2, Value3
' Do these steps for Value2 or Value3
Case Else
' Do these steps if no matches are found
End Select

Let’s break it down.

Select Case just defines the start of the command. Then VBA checks the value of SomeVariable we provide. If it has a Value1, then the steps underneath that Case are run. Otherwise in this example, if SomeVariable has either a Value2 or Value3, then the steps under that Case are run.

If no matches are found, the Case Else steps are run, but the Case Else can be omitted, if desired. If no matches are found and no Else part exists, then nothing happens.

We can get fancier, but this statement allows us to make decisions more concisely when there are many options in a script or macro.

Selecting a macro for our document element

For our current toy problem, we’re selecting the macro based on the identified element. We then store this value in the sElement variable.

' Select which macro to run based on the element
Select Case sElement
' Cases go in here ...
End Select

Our current Cases are: “paragraph”, “sentence”, “heading”, and “heading content”.

' Select which macro to run based on the element
Select Case sElement
Case "sentence"
' Run sentence macro ...
Case "paragraph"
' Run paragraph macro ...
Case "heading content"
' Run heading content macro ...
Case "heading"
' Run heading macro ...
Case Else
' No element found, so give an error message
End Select
Select Case statement to run a macro

Putting it together for each tentative macro to run, we have:

' Select which macro to run based on the element
Select Case sElement
Case "sentence"
Call RunWordMacro("SelectSentence")
Case "paragraph"
Call RunWordMacro("SelectParagraph")
Case "heading"
Call RunWordMacro("SelectHeading")
Case "heading content"
Call RunWordMacro("SelectHeadingContent")
Case Else
' No element found, so give an error message
End Select

This Select statement combines at least four different macros under one concise voice command.

Notice the cases "heading" and "heading content" are separated. One might wonder if these would overlap, but string comparisons require exact matches to be equal, so these are different cases as far as the Select Case statement is concerned.

Error message

I included an error message in the Case Else because I don’t want to run any macros if I don’t identify a specific command. You can omit the error message if you wish, but it is convenient to know if something went wrong which is especially true when you’re creating the script.

MsgBox function

A standard function to pop up a dialog box with an error (or any kind of) message is the VBA function MsgBox.

MsgBox "Some text message"

MsgBox works in both Mac and Windows, but Dragon Professional only works on Windows.

Adding text strings

We can add strings together to create a more specific message like:

MsgBox "Could not run the selection command with the element " + sElement

I used a plus + sign to add the sElement variable as a String to the plain text note in double quotes. It mimics addition operations with numbers, but it’s called concatenation when you’re working with strings. Essentially, we're just joining the two strings into a new larger string. We could also use an ampersand &. There is a slight but subtle difference between the two operators, but we don’t care about them here.

The actual error message in the function below is a little more complicated.

Warning on error messages

Assuming you’re not a perfect person and don’t always do everything right the first time, don’t be skimpy with your error messages since they are a significant firewall against unforeseen errors. A few weeks ago, I lost a day of work because I skimped on a clear and detailed error message for a better version of the RunWordMacro function mentioned earlier. In the end, the error was almost trivial, and it would have easily been detected the first time it occurred … if I had included a more descriptive error message.

Baby Script … do, do, do, oooh

Putting all this together, our baby Dragon Professional custom voice command script is as follows:

'#Language "WWB-COM"
Option Explicit
Sub Main
' Run a Word macro to select the indicated document element

' sPhrase is the text after static part of command
' sElement is document element read from the command and used to Select
' a Word macro below
Dim sPhrase As String, sElement As String

' Store dictated portion of voice command phrase
sPhrase = UtilityProvider.ContextValue(0)

' Detect any document element based on sPhrase
sElement = GetPhraseElement(sPhrase)

' Select which macro to run based on a detected element
' Macros are assumed names which may vary in practice
Select Case sElement
Case "sentence"
Call RunWordMacro("SelectSentence")
Case "paragraph"
Call RunWordMacro("SelectParagraph")
Case "heading"
Call RunWordMacro("SelectHeading")
Case "heading content"
Call RunWordMacro("SelectHeadingContent")
Case Else
' No command found error notice ...
Dim sMessage As String
sMessage = "Sorry, I did not understand the command for element: " _
+ Chr(34) + sElement + Chr(34) + vbCr + _
"in Select that <dictation> voice command"
MsgBox sMessage
End Select
End Sub

Remember, we’re building something here. If this was all we could do with command parsing using keywords, then we would just use a list variable in the custom voice command which would require far less effort.

Improved natural language voice commands using keywords

The point is to create better “natural language” commands, so you don’t interrupt your workflow when writing or editing. You’ll be surprised how much your wording may vary when you give a voice command in real time.

Suppose we want to act on a more general command where we say something like the following:

"Select the next three paragraphs"

In today’s script, we’re not worried about the details of the Word macro that carries out this command. We’re trying to interpret a text version of the voice command and select the correct Word macro to run.

New command information

This new example command provides four pieces of information.

  • The static part of the command “Select …” which indicates something should be selected.
  • We want to select a “paragraph” document element as we did with the baby script above.
  • The first bit of new information we need to interpret is an implied direction with “next”.
  • The number of paragraphs was specified as “three”.

Fortunately, we’ve previously created several functions to facilitate the last three items (see Getting started section at the top).

The versus That

Our previous example command used “that” as the second word, but it is also quite natural to say “Select the …” as is done here. We need to allow these simple variations.

Which list variable

To solve this problem without creating duplicate commands, we can create a new list variable to allow any of several convenient articles or prepositions. I called my list variable <which> which contains words like:

the

those

these

that

by

to the

by the

of the

and several more variations. Each word or phrase should be placed on a separate line. We also don’t need to restrict ourselves to individual words as shown by “to the” and below. Some variations like “of the” apply more when the list variable is used later in a command (see earlier example commands).

List variables are nice but a little bit of a mixed bag in my opinion. They extend the utility of custom scripts without having to create redundant versions, but they are a little awkward to manage. See this previous article for more about using list variables in Dragon Professional advanced scripting.

Revised command name

The new custom voice command name is:

Select <which> <dictation>

In the command editor, we literally type this as the voice command name.

The static part of the voice command must be stated exactly to run the script. Dragon will automatically detect any of these <which> variations as the next word(s) in the command. Then anything after that is stored as the <dictation> text.

Revised command phrase assignment

We previously stored the <dictation> command text in the sPhrase variable in order to parse it for the relevant information. One small change to the above baby script is <dictation> is now the second list variable. We need to revise the command phrase assignment to reference element (1) of the context values.

sPhrase = UtilityProvider.ContextValue(1)

Remember the list variable context values are indexed starting from zero which is common in programming, so (1) is the second list variable. We do not care about which word was used for the <which> list variable. It’s just present to allow the natural variations at that position in the command.

We don’t get everything we want since we must state something at that position. We can’t omit a word in the command unless it falls within the <dictation> variable text.

Get document element with a function

As with the above baby script, we get the document element using the GetPhraseElement function from a previous article. We “pass” the function our command text sPhrase and store the result of the function in the sElement variable.

' Detect any document element based on sPhrase
sElement = GetPhraseElement(sPhrase)

We decline to specify a default element type as a second argument since the document element is a required part of the command if we are to properly interpret the instruction.

Get the direction with a function

We also previously introduced a GetPhraseDirection function to automatically parse a stated or implied direction from the text version of the voice command. We’ll pass the function our command phrase and store the result in a String variable named sDirection.

Dim sDirection As String
sDirection = GetPhraseDirection(sPhrase, "forward")

The function normally returns “forward”, “backward”, or a default empty string “” if no direction was found in the command. We overrode the default value for clarity using the second function argument “forward”. It’s not strictly required, but we probably need at least an implied direction. Using forward as a default value makes sense in this script.

Get the stated number with a function

We previously introduced another function GetPhraseNumber to parse a stated number from a text command. We’ll store the result in a number variable named nElements.

Dim nElements As Long

We define nElements as a Long value (basically a bigger Integer or “counting number” in VBA) corresponding to the number type returned by the function.

We pass the command phrase to the function and store the value in the nElements variable.

nElements = GetPhraseNumber(sPhrase)

If no number is stated or implied, the default value is zero. Any stated number is assumed to be positive in the function, but we’ll account for the direction next.

Account for direction with the number of elements

In Word VBA, backward in the document almost always corresponds to negative values and forward to positive in various VBA commands. This makes more sense when we remember the document grows (more characters) as we add content.

The sign of the above nElements number needs to be adjusted based on the direction specified in the document. If the user indicates forward direction in the document, our nElements variable is already positive, so we do nothing. On the other hand, if the user indicates a backward direction, we make nElements negative.

We’ve already identified the direction and stored the result in the sDirection variable. Since we assigned one of two possible text values to sDirection, “forward” or “backward”, we can check for a match directly without using a search comparison such as Like.

' Assumes we found the direction earlier
If sDirection = "backward" Then
nElements = -nElements ' Negative value is backward
End If

Using -nElements is the same thing as multiplying by -1. If no direction was given, then nElements is zero from the GetPhraseNumber function. “Negative zero” is still zero, so nothing happens if so.

Since the command is so short, we can shorten it into one brief line:

' Assumes sDirection was found earlier (negative is backward in the document)
If sDirection = "backward" Then nElements = -nElements

I tend to prefer this more concise notation when it’s clear.

Command keyword summary

Let’s collect the four keyword assignments based on the respective functions.

' Use existing functions to parse any command keywords
' Not all voice commands will use every keyword
sElement = GetPhraseElement(sPhrase)
sDirection = GetPhraseDirection(sPhrase, "forward")
sLocation = GetPhraseLocation(sPhrase)
nElements = GetPhraseNumber(sPhrase)

Based on keywords, we’ve parsed the command for a mentioned document element, any direction, a possible document location, and a stated number. These keywords account for many voice command variations without binding us to any specific command phrasings.

These short four steps do so much while also being easy to understand. They’re even more convenient when we reuse them in other scripts without having to reinvent the wheel every time.

Selecting the correct macro to run

There are several ways to select the correct Word macro to run. Assuming the command category is obvious (move, select, copy, etc.), we have three main pieces of information: the direction, a possible location, and how many elements (paragraphs, sentences, etc.) to select.

Messy macro selection … (not used)

The baby script above only considered the document element, which was stored in the sElement variable, to pick the correct Word macro to run. Our extended version is a little more complicated since the command might restrict the selection to a specified direction or only to the “start” or “end” of the stated document element. The command may further specify a number of elements.

Uhhh … where are you—

Hey! Get back here.

It’s not as bad as it sounds. Plus, I’m just making a point here about why I’m using simpler macro selection process below.

Start with the location for macro selection

A logically simple approach is to nest the decisions. That is, decide one part and then decide the specific macro based on that branch. There are a few ways to structure it, so we’ll start with the location and work toward the other elements.

Some of my Word macros are targeted specifically toward the beginning or end of a document element (paragraph, heading, etc.). This corresponds to our location variable sLocation.

We can use a basic If statement with the location whether it's the "start", "end", or anything else (usually an empty string ""). Our conditions look like:

sLocation = "start"
sLocation = "end"

These are intended to be True-False (Boolean) conditions as they’re used below. That is, does the plain text stored in sLocation exactly match “start” or “end”? It would be preferable if these two conditions didn’t read exactly like a variable assignment (store the text value “start” in the variable sLocation) even in isolation. Some languages instead use a double equals sign == for comparisons, but VBA distinguishes them by context. It's not the clearest approach, but we’re stuck with it.

We construct an If statement based on these two potential locations.

' Narrow the choice of which macro to run based on the location
If sLocation = "start" Then
' Start location given, so do these steps
ElseIf sLocation = "end" Then
' End location given, so do these steps
Else
' No location given, so do these steps
End If

It’s not too bad so far. It’s no more complicated than what we’ve done in previous scripts or macros, but it gets messy quick.

Include the document element in the macro selection

Our previous decision based a macro decision on the document element. It used a Select Case statement something like:

' Previous decision based on document element
Select Case sElement
Case "paragraph"
' Run select paragraph macro
Case "sentence"
' Run select sentence macro
'
' Add more cases as needed ...
'
Case Else
' Give error message, if desired
End Select

Unfortunately, the correct element macro to run will be different for each location. We need to insert a copy of this skeleton into each part of the above location If statement:

' This is messy (not used)!
' Select which macro to run based on the location
If sLocation = "start" Then
Select Case sElement
Case "paragraph"
' Run select paragraph to start macro
Case "sentence"
' Run select sentence to start macro
'
' Add more cases as needed ...
'
Case Else
' Give error message, if desired
End Select
ElseIf sLocation = "end" Then
Select Case sElement
Case "paragraph"
' Run select paragraph to end macro
Case "sentence"
' Run select sentence to end macro
'
' Add more cases as needed ...
'
Case Else
' Give error message, if desired
End Select
Else
Select Case sElement
Case "paragraph"
' Run select paragraph(s) macro
Case "sentence"
' Run select sentence(s) macro
'
' Add more cases as needed ...
'
Case Else
' Give error message, if desired
End Select
End If

Ughhh … see what I’m saying.

I don’t even like looking at that mess much less trying to figure out what it’s doing. Perhaps in another macro this lengthy blob of VBA might be the best solution, but let’s find a better way to solve our current problem.

Flattened Case Selection

The above macro selection process would’ve worked, but it would have “nested” the conditional statements into a two-level decision tree. If that’s the only solution, okay, but it’s a little confusing and harder to follow than just a sequential list of cases.

We can make it easier to read if we create our own little concise baby command using the various keywords we’ve identified above. Then we use that constructed command to choose the correct Word macro with a simple, albeit longer, "flattened" Select Case statement.

Command variable

We first create a plain text variable called sCommand to store our abbreviated command.

Dim sCommand As String
Create the simple abbreviated command

The main information to construct our command is the stated document element followed by the location, if available. The easiest thing to do is to just start the command with the identified element.

sCommand = sElement

Now we check whether the location exists and add it to the command. If sLocation is not an empty string “” (the default value returned from the GetPhraseLocation function), then we add the location to the command.

If sLocation <> "" Then
sCommand = sCommand + " " + sLocation
End If

Remember <> is the not equal symbol in VBA (literally less than or greater than). This symbol along with the equals = sign are used mostly with number comparisons, but they also work for exact text comparisons.

Alternative approach (just use one)

If you don’t like using not equals <> to compare text strings, then you could just test for the value of sLocation. Notice “start” and “end” both have the same command structure. That is, either could be True, and we add the document element and the location variables. That corresponds to an Or compound conditional statement.

sLocation = "start" Or sLocation = "end"

Only when sLocation is empty does the command change. Thus, we can construct an alternative abbreviated command assignment. Putting it into a conditional statement, we have:

If sLocation = "start" Or sLocation = "end" Then
sCommand = sElement + " " + sLocation
Else
sCommand = sElement
End If

But only use one of them. I prefer the former because it is more concise, but not everybody likes mint chocolate chip ice cream (yum) either.

Note also we could not just always add them. If no location is given, sLocation would be an empty string "" which would add nothing. That seems okay at first glance, but then our command phrase might be something like "paragraph ". The extra space at the end would not be an exact match for "paragraph" as listed in the Select Case statement below.

Leave out direction and number from abbreviated command

We don’t include number variable nElements or the direction variable sDirection in the above abbreviated command because we’ve already accounted for the effect of the direction by changing the sign of nElements when it’s used. Specifically, negative values of nElements correspond to backward selections, and positive values are forward in the document.

Example abbreviated commands

Suppose our document element is a paragraph. This will create three possible abbreviated commands:

"paragraph"
"paragraph start"
"paragraph end"

Of course, we have variations for sentences, headings, etc. as desired.

Since we’ve set the sElement and sLocation variable values to specific values, we know what any valid abbreviated commands must look like. This allows us to construct a simpler decision tree below that is flat and easier to read than the nested version above.

This isn’t the only solution to the problem. It’s just clean and relatively easy to understand even if it takes a few extra steps to set up.

Parsed command examples

We also have more variations for sentences, words, and headings. Let’s practice a few examples to be clear.

Example 1

What if we stated the following voice command?

"Select to the end of that heading"

The variable sCommand would store the value “heading end”, and nElements would have a value of zero because no number was mentioned.

' Variable values will be
sCommand = "heading end"
nElements = 0

I’m not assigning these values here. I’m just illustrating what the values would be based on all the above steps.

A direction is implied with “end” in the voice command, but the selection macro handles that detail, and changing the sign of a zero in nElements would not matter.

Example 2

What about this command?

"Select the previous five words"

The command values are:

sCommand = "word"
nElements = -5

Here “word” is clearly mentioned, and the stated number 5 is negative because “previous” was given which is backward in the document.

Example 3

Another one:

"Select those paragraphs"

The command values are:

sCommand = "paragraph"
nElements = 0

Here paragraph is clearly stated, but no number was given so nElements defaults to zero.

Selecting the Word macro

We’ve constructed our command, so now we’ll use a Select Case statement to pick the appropriate Word macro to run. A Select Case statement is better than an If statement here because there are about a dozen options depending on which document elements you include. The code will still be long, but a Select Case statement allows us to organize them more clearly.

Select to Start of End of the current paragraph

For the start and end of the paragraph, we’ll assume we have a simple macro call to make.

Call RunWordMacro("SelectParagraphToStart")
Call RunWordMacro("SelectParagraphToEnd")

These use the previously presented function RunWordMacro, or see the earlier section for brief explanation. We’re not covering the details of any Word macros mentioned in this article.

We needed to include “Call” because we’re providing (called “passing”) and argument to the macro. The returned function result (a Boolean value indicating whether the function ran successfully) is ignored since we don’t store it in a variable.

Select current paragraphs

The SelectParagraphs macro has a parameter to indicate the number of paragraphs to select. We previously stored that value in the nElements variable, so we add that as a second argument inside the RunWordMacro parentheses.

Call RunWordMacro("SelectParagraphs", nElements)

If you have more complicated macros with more parameters, we’ll cover that function in upcoming articles. It’s a little more complicated.

A similar set of macros are needed for the other options with sentences, headings, etc.

Skeleton to select a macro based on the abbreviated command

We chose the Case based on our constructed abbreviated sCommand.

Select Case sCommand
'
' Insert abbreviated command cases here ...
'
Case Else
' No command match found ...
End Select
Selecting correct Word macro to run using the abbreviated command

Now insert out possible constructed commands as cases:

Select Case sCommand
Case "paragraph start"
Call RunWordMacro("SelectParagraphToStart")
Case "paragraph end"
Call RunWordMacro("SelectParagraphToEnd")
Case "paragraph"
Call RunWordMacro("SelectParagraphs", nElements)
'
' And other cases as desired ...
'
Case Else
' No command match found. Notify about error.
End Select

We can add cases for “sentence”, “sentence start”, and “sentence end” as well as those for heading, words, etc. as desired. It stretches out, but the overall macro keeps our collection of voice commands relatively easy to read. The point was to flatten the Select Case statement into one level rather than it being nested two levels.

Gotchas

What could go wrong?

Command conflicts?

With more command variations, there are more chances for command conflicts where the same voice command inadvertently refers to two different scripts. For example, I had a command that overlapped one of the default Dragon Professional natural language commands. It took me a while to realize Dragon was preferring its own native command over mine.

Arghhh.

I wish Dragon had a way to toggle individual commands to active or inactive, but I eventually disabled Dragon’s natural language commands entirely since I was writing my own (I like controlling the details).

As far as I can tell, Dragon Professional handles command conflicts well enough (except for the blip above), so it’s not a big deal, but it is something to keep in mind as you expand your command library. Having no conflicts is better than relying on Dragon to pick your intended command.

Number pronouns

The number “one” sometimes counts as a pronoun such as:

"Select by three paragraphs down from this one"

Three is the intended number of paragraphs, but one is a pronoun referring to the current paragraph. Our current number function would not distinguish between “three” and “one” in this phrase. It's not the biggest problem in the world, but a partial solution will be covered in upcoming articles.

Function results?

Some of the internal logic in the above script assumes specific standard values (such as “forward” or “backward” or “start” or “end”) or specific default values (such as 0 or an empty string “”) as the only possible results. The final script below doesn’t check for or validate every possible result before making decisions about which Word macro to run. This keeps this script smaller, but it does push us to make sure the possible function results and the script expectations are in sync. We guard ourselves against some mistakes by only running a macro when very specific abbreviated commands are detected.

A side lesson here is we should be clear and specific when we write functions. Clear input and output should also be explained in dedicated comments, so we can buttress our other scripts and macros against problems.

Final Keyword Command Script Using Like

The final script looks messier than it is logically. In this version, we use the above functions to parse the command phrase for specific information. Recall the main subroutine in a Dragon custom script is literally called Main.

'#Language "WWB-COM"
Option Explicit
Sub Main
' Run a Word macro to select the indicated document element

' sPhrase includes any text after the static part of command
' sCommand is the contructed abbreviated command used to pick a macro to run
Dim sPhrase As String, sCommand As String
' sElement is document element read from the command
' sLocation corresponds to a stated start or end position for the command
' nElements indicates a stated number of elements, if given
' These data are used to Select a Word macro below
Dim sElement As String, sDirection As String, sLocation As String
Dim nElements As Long

' Use functions to parse the command keywords
' Not all voice commands will use every keyword

' Assign document element for command below
sElement = GetPhraseElement(sPhrase)
' Direction is either forward or backward
sDirection = GetPhraseDirection(sPhrase, "forward")

' Location can be either start or end with no default value because it
' changes which macro is run below
sLocation = GetPhraseLocation(sPhrase)

' Get number of elements which defaults to zero
nElements = GetPhraseNumber(sPhrase)
' Account for backward direction on number (negative is backward in Word VBA)
If sDirection = "backward" Then nElements = -nElements

' Generate abbreviated command starting with the element
sCommand = sElement
' Add location to command if it exists in voice command
If sLocation <> "" Then sCommand = sCommand + " " + sLocation

' Select which macro to run based on abbreviated command
Select Case sCommand
Case "paragraph start"
Call RunWordMacro("SelectParagraphToStart")
Case "paragraph end"
Call RunWordMacro("SelectParagraphToEnd")
Case "paragraph"
Call RunWordMacro("SelectParagraphs", nElements)

Case "sentence start"
Call RunWordMacro("SelectSentenceToStart")
Case " sentence end"
Call RunWordMacro("SelectSentenceToEnd")
Case "sentence"
Call RunWordMacro("SelectSentences", nElements")
'
' Include any other cases of interest ...
'
Case Else
' No command found error message
Dim sMessage As String
sMessage = "Sorry, I did not understand the command: " + _
Chr(34) + sCommand + Chr(34) + vbCr + _
"in Select <which> <dictation> voice command"
MsgBox sMessage
End Select
End Sub

The Getting started section at the top links to the functions used to parse the keywords. Breaking the script down into smaller pieces makes it more manageable and readable. The Word macros would be created separately, and this blog has several examples of such macros at different levels of difficulty.

Is it safe?

I get it. You don’t want something bad happen to your novel text. That sends shivers of fear through an author.

This final script is basically safe because the Select Case statement combined with the abbreviated commands only run a Word macro if one of the specific constructed commands is detected. Of course, this script does not guarantee the Word macro is correct.

What do we gain?

Exact wording is quite flexible

The nice thing about baking our own natural language command is just how flexible the phrasing can be starting from the <dictation> list variable.

We don’t have to worry about exact placements of the the’s, that’s, to’s, etc. except for the word immediately after “Select …” (that position is occupied by a list variable <which>, so it is required). Omitted words and even some word mangling will all work provided the proper keywords are present.

Other commands?

This script can easily be duplicated and modified to work for cutting, deleting, or copying the same document elements. You just need corresponding macros to exist in Word. Change the static part of the command to “Copy <which> …” for whichever variation you wish. Then make the trivial macro name changes in the Select Case statement of the script.

Controlling the details

I mentioned this at the top, but one of the best features in my opinion is we can control all the details of each respective task by using our own Word macros. We don’t need to rely on Dragon’s implementation of a natural language voice command.

Condensed voice commands

We could just create variations of the same macro for each phrase type, but this approach condenses the script variations into a smaller number of custom voice commands. This seems like “no big deal,” but your custom command list will grow faster than you think, and near-duplicate commands are annoying to keep up to date if you ever make changes.

Comments comment

I stretch the script with lots of comments because I like being extra clear. (Yes, I really do this in my own scripts and macros also but maybe not quite as many.) I try to include relevant information that might be important if I come back to the macro after months or more. Comments are more important for more obscure script steps, but I still recommend them for any significant step or related group of steps. The temptation exists to skip adding comments while you’re working on a script, but you will save yourself time in the long run if you or someone else ever returns to the macro or script.

One script to rule them all (natural language commands)?

Is this keyword approach the solution to all voice command problems?

No.

It’s just one attempt that is relatively easy to understand but also generalizes several voice command possibilities. I understand some may prefer dedicated commands for each type of document element, but as your list of voice commands grows, the consolidation is a nice side effect.

The above solution allows many more naturally stated command variations. We don’t have to worry as much about exact phrasing. Of course, this comes with a few issues, but it’s a win overall for improved natural language processing with less up front effort.

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.