Working with the Wordnik API in VBA

wordnik logo

Update 3/2/11: The Wordnik API has been updated, and this code has not. I will be updating it as time permits. –JP

Wordnik is an API I discovered recently while browsing the Internet for APIs. What is Wordnik?

Wordnik is a place for all the words, and everything known about them.

Our goal is to show you as much information as possible, as fast as we can find it, for every word in English, and to give you a place where you can make your own opinions about words known.

Basically it's a dictionary, with an API so your applications can consume information about words, their definitions, sample sentences, and related words. This page will explore the available methods you can use to extract information from the API using VBA. You'll also find a sample application for Microsoft® Word.

As with many other API services, you'll need an API key to work with Wordnik's API. Visit the signup page to apply for one. I have my own API key, but in my code samples it has been removed.

Note that in the samples below, the API key is passed in the headers. Wordnik has since changed their API to allow passing the API key in the GET request. So you can do it as I have done, or change the code to send the API key in the request URL.

In all of the following code samples, I have declared objects As Object to avoid early binding issues. You should have msxml2.dll in your system32 directory. I did, however, preserve the early bound references so you can see how the objects would be declared if the code was early bound.

Also note that some of these methods may not be full implementations of the associated API method. For example, I might leave out some parameters that would otherwise change the output of the API.

To use some of the methods below, you'll need to first paste the following Enum section into a standard module (placed at the top), or into their own module. I prefer to put them into their own module so all Enums are in the same place and there are no placement issues.

Public Enum partOfSpeech
  noun
  Verb
  adjective
  adverb
  idiom
  article
  abbreviation
  preposition
  Prefix
  interjection
  suffix
End Enum

Public Enum wordType
  synonym
  antonym
  form
  equivalent
  hyponym
  variantType
End Enum

These are used by the GetDefinition and GetRelatedWords functions to get the proper part of speech and word type for a given word. You'll also need to copy the helper functions and place them into a standard module in the same project, since they are used by all of the functions below.

The GetWord Function

This function simply looks up a given word and returns that word. It is meant to verify that a given word is actually a word (as found in the Wordnik dictionary, of course). The perfect application I can think of for this is a Scrabble-type application, to verify that a submitted word is real.

It uses some of the more common techniques we've seen in articles like Airport Information, namely

  • checking for an existing XML file before (re-)querying the web,
  • grabbing a XML response from an API, and
  • parsing a temporary XML document for (a) sought after value(s) using the MSMXL object model.
Function GetWord(word As String, apiKey As String) As String

Dim xml As Object ' MSXML2.XMLHTTP
Dim result As String
Dim tempFile As String
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim wordNode As Object ' MSXML2.IXMLDOMNodeList

  tempFile = environ("temp") & "\" & word & ".xml"

  ' requery website if file doesn't exist
  If Len(Dir(tempFile)) = 0 Then

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/word.xml/" & word, False
    xml.setRequestHeader "api_key", apiKey
    xml.Send

    result = xml.responsetext

    ' create XML file from result
    Call CreateFile(tempFile, result)
  End If

  ' open XML file
  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set wordNode = GetChildNodes(xmlDocRoot)
  ' grab word
  GetWord = wordNode.Item(1).nodeTypedValue

End Function

Through experimentation (and by suggestion of Wordnik's API documentation), the API key is passed by header instead of in the HTTP GET request. Otherwise the function is similar to those we've seen in the article previously mentioned.

Sample usage

Sub TestGetWord()

Dim apiKey As String
Dim word As String

  apiKey = "your API key here"
  word = "blather"

  Debug.Print GetWord(word, apiKey)

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The GetDefinition Function

This function takes a given word, the part of speech and number of results desired and returns them as a String. The API returns all parts of speech, so we need to parse the XML for the one we want and only return results from that. Part of speech and number of results are passed as headers to the API.

Function GetDefinition(word As String, apiKey As String, _
                       Optional partOfSpeech As partOfSpeech = noun, _
                       Optional numberOfResults As Long = 1) As String

Dim xml As Object
Dim result As String
Dim pos As String
Dim i As Long
Dim tempFile As String
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim definitionNodes As Object ' MSXML2.IXMLDOMNodeList
Dim definitionNode As Object ' MSXML2.IXMLDOMNode

  tempFile = environ("temp") & "\" & word & "_definition.xml"

  pos = GetPartOfSpeech(partOfSpeech)

  ' requery website if file doesn't exist
  If Len(Dir(tempFile)) = 0 Then

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/word.xml/" & word & "/definitions", False
    xml.setRequestHeader "api_key", apiKey
    xml.setRequestHeader "partOfSpeech", pos
    xml.setRequestHeader "limit", numberOfResults
    xml.Send

    result = xml.responsetext

    ' create XML file from result
    Call CreateFile(tempFile, result)
  End If

  ' open XML file
  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set definitionNodes = GetChildNodes(xmlDocRoot)

  ' look for correct part of speech
  For i = 1 To definitionNodes.Length
    Set definitionNode = definitionNodes.Item(i - 1)

    If definitionNode.childNodes(3).nodeTypedValue = pos Then
      GetDefinition = definitionNode.childNodes(1).nodeTypedValue
      Exit For
    End If
  Next i

End Function

Sample usage

Sub TestGetDefinition()

Dim apiKey As String
Dim word As String

  apiKey = "your API key here"
  word = "tissue"

  Debug.Print GetDefinition(word, apiKey, noun)

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The GetRelatedWords Function

This function returns a String array of words related to the word passed to the function. It can return any of the following word types (per the Enum):

  • synonym
  • antonym
  • form
  • equivalent
  • hyponym
  • variant
Function GetRelatedWords(word As String, apiKey As String, _
                         Optional wordType As wordType = synonym, _
                         Optional numberOfResults As  Long = 1) As String()

Dim xml As Object
Dim result As String
Dim wdType As String
Dim tempFile As String
Dim i As Long, j As Long
Dim tempRelatedWords() As String
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim wordstrings As Object ' MSXML2.IXMLDOMNodeList
Dim wordstring As Object ' MSXML2.IXMLDOMNodeList

  tempFile = environ("temp") & "\" & word & "_related.xml"

  wdType = GetWordType(wordType)

  ' requery website if file doesn't exist
  If Len(Dir(tempFile)) = 0 Then

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/word.xml/" & word & "/related", False
    xml.setRequestHeader "api_key", apiKey
    xml.setRequestHeader "type", wdType
    xml.setRequestHeader "count", numberOfResults
    xml.Send

    result = xml.responsetext

    ' create XML file from result
    Call CreateFile(tempFile, result)
  End If

  ' open XML file
  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set wordstrings = GetChildNodes(xmlDocRoot)

  ' find the correct type
  For i = 1 To wordstrings.Length
    If wordstrings.Item(i - 1).Attributes.getNamedItem("relType").nodeTypedValue = wdType Then
      Set wordstring = wordstrings.Item(i - 1).childNodes

      ReDim tempRelatedWords(1 To wordstring.Item(i - 1).childNodes.Length)

      ' write words to temp array
      For j = 1 To wordstring.Item(i - 1).childNodes.Length
        tempRelatedWords(j) = wordstring.Item(0).childNodes.Item(j - 1).nodeTypedValue
      Next j
      Exit For
    End If
  Next i

  GetRelatedWords = tempRelatedWords

End Function

Sample usage

Sub TestGetRelated()

Dim apiKey As String
Dim word As String
Dim tempString() As String
Dim i As Long

  apiKey = "your API key here"
  word = "vexatious"

  tempString = GetRelatedWords(word, apiKey)
  For i = LBound(tempString) To UBound(tempString)
    Debug.Print tempString(i)
  Next i

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The GetWordFrequency Function

This function will return the frequency that a word appears in the Wordnik dictionary. Don't ask me what that means; I just work here :)

The API returns a few XML nodes with irrelevant information, so it has to be carefully parsed out. Unfortunately that means looping through the nodes twice; once to count the number of nodes (to size the array accordingly), then again to actually extract from the appropriate nodes.

Function GetWordFrequency(word As String, apiKey As String) As String()

Dim xml As Object
Dim result As String
Dim tempFile As String
Dim tempFreq() As String
Dim i As Long
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim frequencies As Object ' MSXML2.IXMLDOMNodeList
Dim frequency As Object ' MSXML2.IXMLDOMNodeList
Dim frequencyCount As Long

  tempFile = environ("temp") & "\" & word & "_frequency.xml"

  ' requery website if file doesn't exist
  If Len(Dir(tempFile)) = 0 Then

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/word.xml/" & word & "/frequency", False
    xml.setRequestHeader "api_key", apiKey
    xml.Send

    result = xml.responsetext

    ' create XML file from result
    Call CreateFile(tempFile, result)
  End If

  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set frequencies = GetChildNodes(xmlDocRoot)

  ' count the number of nodes named "frequency"
  ' there are extra nodes w/ useless info
  For i = 1 To frequencies.Length
    If frequencies.Item(i - 1).nodeName = "frequency" Then
      frequencyCount = frequencyCount + 1
    End If
  Next i

  ' resize array
  ReDim tempFreq(1 To frequencyCount, 1 To 2)

  ' loop
  For i = 1 To frequencies.Length
    Set frequency = frequencies.Item(i - 1).childNodes

    If frequencies.Item(i - 1).nodeName = "frequency" Then
      tempFreq(i, 1) = frequency.Item(0).nodeTypedValue
      tempFreq(i, 2) = frequency.Item(1).nodeTypedValue
    End If

  Next i

  GetWordFrequency = tempFreq

End Function

Sample usage

Sub TestGetFrequency()

Dim apiKey As String
Dim word As String
Dim tempString() As String
Dim i As Long

  apiKey = "your API key here"
  word = "litigious"

  tempString = GetWordFrequency(word, apiKey)
  For i = LBound(tempString) To UBound(tempString)
    Debug.Print tempString(i, 1) & tempString(i, 2)
  Next i

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The GetPunctuationFactor Function

This function returns the number of times the given word appears with punctuation in the Wordnik dictionary. I think.

Function GetPunctuationFactor(word As String, apiKey As String) As String()

Dim xml As Object
Dim result As String
Dim tempFile As String
Dim i As Long
Dim tempString() As String
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim punctuationNodes As Object ' MSXML2.IXMLDOMNodeList

  tempFile = environ("temp") & "\" & word & "_punctuation.xml"

  ' requery website if file doesn't exist
  If Len(Dir(tempFile)) = 0 Then
    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/word.xml/" & word & "/punctuationFactor", False
    xml.setRequestHeader "api_key", apiKey
    xml.Send

    result = xml.responsetext

    ' create XML file from result
    Call CreateFile(tempFile, result)

  End If

  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set punctuationNodes = GetChildNodes(xmlDocRoot)

  ReDim tempString(1 To punctuationNodes.Length)

  For i = 1 To punctuationNodes.Length
    tempString(i) = punctuationNodes.Item(i - 1).nodeTypedValue
  Next i

  GetPunctuationFactor = tempString

End Function

Sample usage

Sub TestPunctuationFactor()

Dim apiKey As String
Dim word As String
Dim tempString() As String
Dim i As Long

  apiKey = "your API key here"
  word = "blather"

  tempString = GetPunctuationFactor(word, apiKey)
  For i = LBound(tempString) To UBound(tempString)
    Debug.Print tempString(i)
  Next i

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The GetWordExamples Function

This function will return a String array of example sentences for a given word. I chose not to do so, but with a bit of tweaking of this function you can also return the source document name and URL to your VBA program.

Function GetWordExamples(word As String, apiKey As String) As String()

Dim xml As Object
Dim result As String
Dim tempFile As String
Dim i As Long
Dim tempString() As String
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim examples As Object ' MSXML2.IXMLDOMNodeList

  tempFile = environ("temp") & "\" & word & "_examples.xml"

  ' requery website if file doesn't exist
  If Len(Dir(tempFile)) = 0 Then

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/word.xml/" & word & "/examples", False
    xml.setRequestHeader "api_key", apiKey
    xml.Send

    result = xml.responsetext

    ' create XML file from result
    Call CreateFile(tempFile, result)

  End If

  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set examples = GetChildNodes(xmlDocRoot)

  ' resize array
  ReDim tempString(1 To examples.Length)

  For i = 1 To examples.Length
    tempString(i) = examples.Item(i - 1).childNodes(0).nodeTypedValue
  Next i

  GetWordExamples = tempString

End Function

Sample usage

Sub TestGetExamples()

Dim apiKey As String
Dim word As String
Dim tempString() As String
Dim i As Long

  apiKey = "your API key here"
  word = "blather"

  tempString = GetWordExamples(word, apiKey)
  For i = LBound(tempString) To UBound(tempString)
    Debug.Print tempString(i)
  Next i

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The AutoCompleteWord Function

A useful function indeed; this one takes a word fragment and returns a list of possible matches. Like the GetWordFrequency function, this one has extra information which needs to be carefully sidestepped to extract what we're really looking for.

Function AutoCompleteWord(wordFragment As String, apiKey As String, _
     Optional numberOfResults As Long = 5, Optional startAt As Long = 1) As String()

Dim xml As Object
Dim result As String
Dim tempFile As String
Dim i As Long
Dim tempString() As String
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim matches As Object ' MSXML2.IXMLDOMNodeList

  tempFile = environ("temp") & "\" & wordFragment & "_autocomplete.xml"

  ' requery website if file doesn't exist
  If Len(Dir(tempFile)) = 0 Then

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/suggest.xml/" & wordFragment, False
    xml.setRequestHeader "api_key", apiKey
    xml.setRequestHeader "limit", numberOfResults
    xml.setRequestHeader "skip", startAt
    xml.Send

    result = xml.responsetext

    ' create XML file from result
    Call CreateFile(tempFile, result)

  End If

  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set matches = GetChildNodes(xmlDocRoot)

  ' resize array
  ReDim tempString(1 To matches.Length)

  ' loop
  For i = 1 To matches.Length

    ' there are two different types of nodes!
    If matches.Item(i - 1).nodeName = "match" Then
      tempString(i) = matches.Item(i - 1).childNodes(1).nodeTypedValue
    Else
      tempString(i) = matches.Item(i - 1).nodeTypedValue
    End If

  Next i

  AutoCompleteWord = tempString

End Function

Sample usage

Sub TestAutoCompleteWord()

Dim apiKey As String
Dim word As String
Dim tempString() As String
Dim i As Long

  apiKey = "your API key here"
  word = "bla"

  tempString = AutoCompleteWord(word, apiKey)
  For i = LBound(tempString) To UBound(tempString)
    Debug.Print tempString(i)
  Next i

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The GetWordOfTheDay Function

This function returns Wordnik's word of the day. If the function has already been run on a given day, the same word is returned (because the existing XML file will be used instead of re-querying the API, and, uh, also because the word of the day is the same for the whole day!). The API also returns sample sentences and definitions for the word.

Function GetWordOfTheDay(apiKey As String) As String()

Dim xml As Object
Dim result As String
Dim tempFile As String
Dim tempWord() As String
Dim i As Long
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim lowerNodes As Object ' MSXML2.IXMLDOMNodeList

  tempFile = environ("temp") & "\wordnik_wordoftheday" & Format(Date, "mmddyyyy") & ".xml"

  ' if XML file exists, don't requery website
  If Len(Dir(tempFile)) = 0 Then

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/wordoftheday.xml", False
    xml.setRequestHeader "api_key", apiKey
    xml.Send

    result = xml.responsetext

    ' create XML file from result
    Call CreateFile(tempFile, result)

  End If

  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set lowerNodes = GetChildNodes(xmlDocRoot)

  ' size array
  ReDim tempWord(1 To lowerNodes.Length)

  ' insert values into array
  For i = 1 To lowerNodes.Length
    tempWord(i) = lowerNodes.Item(i - 1).nodeTypedValue
  Next i

  GetWordOfTheDay = tempWord

End Function

Sample usage

Sub TestWordOfTheDay()

Dim apiKey As String
Dim word As String
Dim tempString() As String
Dim i As Long

  apiKey = "your API key here"
  word = "blather"

  tempString = GetWordOfTheDay(apiKey)
  For i = LBound(tempString) To UBound(tempString)
    Debug.Print tempString(i)
  Next i

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The GetRandomWord Function

The GetRandomWord function returns a randomly selected word from the Wordnik dictionary. It has three parameters:

  • the API key,
  • the length of the word (from 6 to 15 characters
  • return only results that have a dictionary definition (true/false), and

In response to a request from Tim Falk in the Wordnik Google Group (as well as new information from Wordnik themselves), the GetRandomWord has been amended to not cache queries.

Function GetRandomWord(apiKey As String, Optional wordLength As Long, _
    Optional hasDictionaryDef As Boolean = True) As String

Dim xml As Object
Dim result As String
Dim tempFile As String
Dim xmlDoc As Object ' MSXML2.DOMDocument
Dim xmlDocRoot As Object ' MSXML2.IXMLDOMNode
Dim word As Object ' MSXML2.IXMLDOMNodeList
Dim wordstring As Object ' MSXML2.IXMLDOMNode

  ' length must be between 6 and 15 chars
  If wordLength > 0 Then
    If wordLength < 6 Or wordLength > 15 Then
      MsgBox "length must be between 6 and 15 characters."
      Exit Function
    End If
  End If

  tempFile = Environ("temp") & "\random_wordnik_word.xml"

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/words.xml/randomWord?length=" & wordLength, False
    xml.setRequestHeader "api_key", apiKey
    xml.setRequestHeader "hasDictionaryDef", hasDictionaryDef

    xml.send

    result = xml.responseText

    ' create XML file from result
   Call CreateFile(tempFile, result)

  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
 If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
 Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
 Set word = GetChildNodes(xmlDocRoot)

  GetRandomWord = word.Item(1).nodeTypedValue

End Function

By default, only words with dictionary definitions are returned.

We could also rewrite the function to get a new word if the previous word is more than X hours old (or any interval we want), by writing the date into the filename (as we did with the GetWordOfTheDay function) then parsing the filename for the date/time and comparing it with the current date/time.

Sample usage

Sub TestRandomWord()

Dim apiKey As String

  apiKey = "your API key here"

  Debug.Print GetRandomWord(apiKey)

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

The Get Bi-Gram Function

Bigrams are two-word phrases that commonly appear together in English. This function will return the common bigrams for a given word.

Function GetBiGrams(apiKey As String, word As String, Optional numberOfResults As Long = 5) As String()

Dim xml As MSXML2.XMLHTTP
Dim result As String
Dim tempFile As String
Dim tempString() As String
Dim xmlDoc As MSXML2.DOMDocument
Dim xmlDocRoot As MSXML2.IXMLDOMNode
Dim bigrams As MSXML2.IXMLDOMNodeList
Dim bigram As MSXML2.IXMLDOMNode
Dim i As Long, j As Long

  tempFile = environ("temp") & "\bigram" & word & ".xml"

  ' requery website if file doesn't exist
  If Len(Dir(tempFile)) = 0 Then

    Set xml = GetMSXML

    xml.Open "GET", "http://api.wordnik.com/api/word.xml/" & word & "/phrases", False
    xml.setRequestHeader "api_key", apiKey
    xml.setRequestHeader "limit", numberOfResults
    xml.Send

    result = xml.responseText

    ' create XML file from result
    Call CreateFile(tempFile, result)
  End If

  ' open XML file
  Set xmlDoc = GetDomDoc

  With xmlDoc
    .async = False
    .validateOnParse = False
    .Load tempFile
  End With

  ' check that the XML doc loaded
  If LoadError(xmlDoc) Then
    Exit Function
  End If

  ' get root node
  Set xmlDocRoot = GetRootNode(xmlDoc)
  ' get first level child nodes
  Set bigrams = GetChildNodes(xmlDocRoot)

  ' resize array, bi-grams have two fields (hence the prefix)
  ReDim tempString(1 To bigrams.Length, 1 To 2)

  For i = 1 To bigrams.Length
    Set bigram = bigrams.item(i - 1)

    For j = 1 To UBound(tempString, 2)
      tempString(i, j) = bigram.childNodes(j + 1).nodeTypedValue
    Next j
  Next i

  GetBiGrams = tempString

End Function

Sample usage

Sub TestGetBiGrams()

Dim apiKey As String
Dim word As String
Dim tempString() As String
Dim i As Long, j As Long

  apiKey = "your API key here"
  word = "blather"

  tempString = GetBiGrams(apiKey, word)
  For i = 1 To UBound(tempString)
    For j = 1 To UBound(tempString, 2)
      Debug.Print tempString(i, j)
    Next j
  Next i

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.number & " - " & Err.Description
  Resume ProgramExit
End Sub

GetPartOfSpeech and GetWordType are used to translate the Enum sections into Strings. Use the ClearCache function to delete the accumulated XML files from the local temp directory as needed.

Function GetPartOfSpeech(partOfSpeech As partOfSpeech) As String

  Select Case partOfSpeech
    Case 0
      GetPartOfSpeech = "noun"
    Case 1
      GetPartOfSpeech = "verb"
    Case 2
      GetPartOfSpeech = "adjective"
    Case 3
      GetPartOfSpeech = "adverb"
    Case 4
      GetPartOfSpeech = "idiom"
    Case 5
      GetPartOfSpeech = "article"
    Case 6
      GetPartOfSpeech = "abbreviation"
    Case 7
      GetPartOfSpeech = "preposition"
    Case 8
      GetPartOfSpeech = "prefix"
    Case 9
      GetPartOfSpeech = "interjection"
    Case 10
      GetPartOfSpeech = "suffix"
  End Select
End Function

Function GetWordType(wordType As wordType) As String

  Select Case wordType
    Case 0
      GetWordType = "synonym"
    Case 1
      GetWordType = "antonym"
    Case 2
      GetWordType = "form"
    Case 3
      GetWordType = "equivalent"
    Case 4
      GetWordType = "hyponym"
    Case 5
      GetWordType = "variant"
  End Select
End Function

Sample Application Using Wordnik API

The following Word 2003 application uses the Wordnik API to produce definitions for highlighted words. It was my first Word add-in effort. Note that it was written in Word 2003 and won't work in 2007 or 2010.

Once placed into your Startup folder, it adds a submenu to your Tools menu with several options. To find out where your Startup folder is, go to Tools » Options » File Locations tab and look for the "Startup" entry.

Wordnik Menu

If at any time you do not select a word before choosing any of the menu options, you get the following error message:

nothing selected

The Options

Check Current Word

This option merely verifies that the highlighted word is in the Wordnik dictionary.

check current word

Get Wordnik Definition

Choosing this option will prompt you for the part of speech you want:

part of speech

When you select one, you are shown the appropriate definition.

word definition

Get Related Words

This menu option will return any synonyms of the currently selected word. If there are none (according to Wordnik), you will see this dialog:

related words

Insert Definition

You can insert definitions directly into the active Document. After highlighting a word, choose "Insert Definition" and you'll be prompted as follows:

insert definition

Click "Insert Definition" and you'll see the part of speech dialog box. After choosing the part of speech you want to return, the highlighted word is replaced with its definition (if it exists for that part of speech).

The next two menu options should be obvious: "Clear Word Cache" deletes all the .xml files in the local temp folder, and "About Wordnik For Word" is a typical About dialog.

Download Wordnik For Word

How It Works

I had to learn how to code a Word add-in from scratch. There isn't much help available online. Almost everything I found was about writing COM add-ins using VSTO or some other platform.

After extensive browsing, I learned that the Word equivalent for Outlook's Application_Startup and Excel's Workbook_Open are AutoExec and AutoOpen. They both run at different times, but this MS KB article explains how to tell when each one fires.

The strange thing about both procedures is that they don't go into a class module. They can be placed anywhere in a standard module.

I simply duplicated the startup code in both AutoExec and AutoOpen because under usual circumstances (based on the KB article), only one will fire, so I wasn't concerned about duplicate code.

The AutoOpen/AutoExec macros

Using a global constant called APP_NAME, we first check if the Wordnik menu exists on the Tools menu.

Dim wordnikMenu As Office.CommandBarPopup
Set mcb = Application.CommandBars.FindControl(ID:=30007)
On Error Resume Next
Set wordnikMenu = mcb.Controls(JPWDvbp.APP_NAME)
wordnikMenu.Delete
On Error GoTo 0

I use a constant for the app name, so we can easily change the name of the app (for example, to add a version number) and the change will cascade throughout the application.

To add the submenu to the Tools menu, I use the following code:

Set wordnikMenu = mcb.Controls.Add(msoControlPopup)
wordnikMenu.Caption = "Wordnik &For Word"

Now we add each menu item in the order we want them to appear on the Wordnik menu.

Dim newButton As Office.CommandBarControl
Set newButton = wordnikMenu.Controls.Add(Type:=msoControlButton)

With newButton
  .Caption = "Check &Current Word"
  .OnAction = "GetCurrentWord"
  .FaceId = 837
End With

The Caption Property sets the display text for the control. Use the ampersand (&) to indicate the accelerator key.

The OnAction Property indicates the procedure to be run when the menu item is clicked.

To get the Face ID number, I use the excellent JMT Excel Utilities add-in for Excel.

To put a line between Insert Definition and Clear Word Cache, use ".BeginGroup = True".

The AutoClose procedure in Word is used for shutdown code. In this case, it deletes the Wordnik menu entry. All of the submenu items (Get Current Word, etc) disappear on their own.

Dim mcb As Office.CommandBarControl
Dim wordnikMenu As Office.CommandBarControl

' delete menu entry
Set mcb = Application.CommandBars.FindControl(ID:=30007)
On Error Resume Next
Set wordnikMenu = mcb.Controls(JPWDvbp.APP_NAME)
wordnikMenu.Delete
On Error GoTo 0

Get Current Word

The function GetWord returns the current word. All we need to do is call the function and check the return value. The function does all the work for us.

For all you aspiring Word VBA programmers, ActiveDocument.ActiveWindow.Selection.Text returns the currently highlighted text.

Sub GetCurrentWord()

  On Error GoTo ErrorHandler

  Dim currentSelection As String

  If Documents.Count > 0 Then
    currentSelection = Trim(Replace(ActiveDocument.ActiveWindow.Selection.Text, Chr(13), ""))

    If Len(currentSelection) < 2 Then
      MsgBox NOTHING_SELECTED, , JPWDvbp.APP_NAME
      Exit Sub
    End If

    If GetWord(currentSelection, apiKey) = currentSelection Then
      MsgBox "'" & currentSelection & "' is found in the Wordnik dictionary.", vbInformation, JPWDvbp.APP_NAME
    Else
      MsgBox "Cannot locate this word in the Wordnik dictionary", vbCritical, JPWDvbp.APP_NAME
    End If

  End If

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox "Sorry, an error occurred. Please try again."
  Resume ProgramExit
End Sub

The error handler merely displays a simple error message, rather than an error code. To is to avoid confusing the end user.

Get Current Word Definition

This procedure simply calls the GetDefinition function and parses the result. First it opens a userform that requests the part of speech. I chose to only return three possible parts of speech — nouns, verbs and adjectives.

In order to check the return value of the userform, we instantiate it as an object. Depending on which option button is selected, we set our partOfSpeech variable to the appropriate value and then pass it to the GetDefinition function so it knows which part of speech to pull from the web.

Sub GetWordDefinition()

  On Error GoTo ErrorHandler

  Dim currentSelection As String
  Dim frm As JPWDvbp.WordnikPartOfSpeech
  Dim pos As partOfSpeech
  Dim definition As String

  If Documents.Count > 0 Then
    currentSelection = Trim(Replace(ActiveDocument.ActiveWindow.Selection.Text, Chr(13), ""))

    If Len(currentSelection) < 2 Then
      MsgBox NOTHING_SELECTED, , JPWDvbp.APP_NAME
      Exit Sub
    End If

    Set frm = New JPWDvbp.WordnikPartOfSpeech

    frm.Show

    Select Case True
    Case frm.optAdjective
      pos = adjective
    Case frm.optNoun
      pos = noun
    Case frm.optVerb
      pos = Verb
    Case Else
      MsgBox "You must choose a part of speech to return the appropriate definition.", vbInformation, JPWDvbp.APP_NAME
      Exit Sub
    End Select

    ' return correct definition
    definition = GetDefinition(currentSelection, apiKey, pos)

    If Len(definition) > 0 Then
      MsgBox definition, vbInformation, JPWDvbp.APP_NAME & " - Definition"
    Else
      MsgBox "No definition found for this word.", , JPWDvbp.APP_NAME
    End If

  End If

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox "Sorry, an error occurred. Please try again."
  Resume ProgramExit
End Sub

The other functions work similarly. They call the appropriate function and parse the result or compare it with an empty string.

The Part of Speech Form

part of speech

All of the option buttons on the part of speech form have one line of code:

Unload Me

The form is called by two procedures (outside the class module), and each one instantiates the form as an Object. In this way, we can check the value of each option button after the form closes!

The Insert Definition Form

insert definition

This form does a couple of things:

  • Automatically picks up currently highlighted word in current document
  • Checks the spelling of current word

To pick up the currently selected word, we use the following code:

currentSelection = Trim(Replace(ActiveDocument.ActiveWindow.Selection.Text, Chr(13), ""))

Set frm = New JPWDvbp.WordnikForm

With frm
  .WordTextBox.Value = currentSelection
  .Show
End With

The About Form

This form has two labels that are formatted to look like hyperlinks. They work like hyperlinks too. When you double click them, they open up the default web browser and visit either this site, or Wordnik's website. The DblClick Event handles this for us.

Private Sub WORDNIK_DblClick(ByVal Cancel As MSForms.ReturnBoolean)

  On Error GoTo ErrorHandler

  System.Cursor = wdCursorWait
  ThisDocument.FollowHyperlink "http://www.wordnik.com/"
  System.Cursor = wdCursorNormal

ProgramExit:
  Exit Sub
ErrorHandler:
  MsgBox Err.Number & " - " & Err.Description
  Resume ProgramExit
End Sub

Download Wordnik For Word

Site last updated: August 20, 2014

Excel School