The Moose and Squirrel Files

August 24, 2008

Word Add-IN to Persist a Dictionary Object as XML

Filed under: Code — Tags: , — networknerd @ 5:50 am

A friend of my eldest child asked for some advice on creating a database. Seems that she was a keen fiction writer and needed to keep track of the meanings of the words she’d invented.

A Database would be overkill for the job and it seemed like a good opportunity to practice using XML and VBA.  Two scripting.dictionary objects could be used to hold the data in memory while creating the story. One for fiction to english translation and the other to perform reverse lookups. The data would be retrieved from the xml file when the add-in loaded and saved when it was unloaded.

It turned out to be a pretty simple job and the code is shown below. Four simple functions were required: add a word, delete a word, lookup and reverse lookup.

Dictionary Code Module


Option Explicit
Public objDict, objRDict As Variant
Public Const MYDOCUMENTS As Integer = 16
Public Const XML_ATTR_KEY As String = "Word"
Public Const XML_ATTR_VALUE As String = "Meaning"
Public Const XML_ELEMENT_OUTER As String = "Dictionary"
Public Const XML_ELEMENT_INNER As String = "Entry"
Public Const STR_INDENT As String = vbCrLf & "    "
Public Const XPATH As String = "/Dictionary/Entry"
Public Const STR_DICT_FILENAME As String = "EDictionary.xml"

Sub initDict()

Set objDict = CreateObject("Scripting.Dictionary")
Set objRDict = CreateObject("Scripting.Dictionary")

End Sub

Function GetMyDocuments() As String
Dim WSHShell As Object
  Set WSHShell = CreateObject("Wscript.Shell")
  GetMyDocuments = WSHShell.SpecialFolders(MYDOCUMENTS)
  Set WSHShell = Nothing
End Function

Sub addword()
Dim strFWord, strEWord As String
Dim frmTemp As Variant
Dim dlgResult As Integer

  If (Not Selection Is Nothing) Then
    strFWord = Trim(Selection.Words(1).Text)
  End If
  Set frmTemp = New frmLookup
  frmTemp.mkAddEntry (strFWord)
  Do
    frmTemp.Show
    If frmTemp.dlgResult = vbCancel Then
      Unload frmTemp
      Set frmTemp = Nothing
      Exit Sub
    End If
    strFWord = frmTemp.Fictionword()
    strEWord = frmTemp.Englishword()
    If (strFWord <> "" And _
        strEWord <> "" And _
        Not objDict.Exists(strFWord)) Then
      objDict.Add strFWord, strEWord
      objRDict.Add strEWord, strFWord
      Exit Do
    Else
      MsgBox "Word already exists in Dictionary or is blank.", vbExclamation
    End If
  Loop
End Sub

Sub delword()
Dim strFWord, strEWord As String
Dim frmTemp As Variant
Dim dlgResult As Integer

  If (Not Selection Is Nothing) Then
    strFWord = Trim(Selection.Words(1).Text)
  End If
  Set frmTemp = New frmLookup
  frmTemp.mkDelEntry (strFWord)
  Do
    frmTemp.Show
    If frmTemp.dlgResult = vbCancel Then
      Unload frmTemp
      Set frmTemp = Nothing
      Exit Sub
    End If
    strFWord = frmTemp.Fictionword()
    strEWord = frmTemp.Englishword()
    If (strFWord <> "" And objDict.Exists(strFWord)) Then
      strEWord = objDict.Item(strFWord)
      objDict.Remove strFWord
      If objRDict.Exists(strEWord) Then
        objRDict.Remove strEWord
      End If
      Exit Do
    Else
      MsgBox "Word not found in Dictionary.", vbExclamation
    End If
  Loop
End Sub

Sub rlookup()
Dim strFWord, strEWord, strMsg As String
Dim frmTemp As Variant
Dim dlgResult As Integer

  If (Not Selection Is Nothing) Then
    strEWord = Trim(Selection.Words(1).Text)
  End If
  Set frmTemp = New frmLookup
  frmTemp.mkRLookup (strEWord)
  Do
    frmTemp.Show
    If frmTemp.dlgResult = vbCancel Then
      Unload frmTemp
      Set frmTemp = Nothing
      Exit Sub
    End If
    strEWord = frmTemp.Englishword()
    If (strEWord <> "" And objRDict.Exists(strEWord)) Then
      strFWord = objRDict.Item(strEWord)
      frmTemp.FictionText = strFWord
    Else
      MsgBox "Word not found in Dictionary.", vbExclamation
    End If
  Loop
End Sub

Sub lookup()
Dim strFWord, strEWord, strMsg As String
Dim frmTemp As Variant
Dim dlgResult As Integer

  If (Not Selection Is Nothing) Then
    strFWord = Trim(Selection.Words(1).Text)
  End If
  Set frmTemp = New frmLookup
  frmTemp.mkLookup (strFWord)
  Do
    frmTemp.Show
    If frmTemp.dlgResult = vbCancel Then
      Unload frmTemp
      Set frmTemp = Nothing
      Exit Sub
    End If
    strFWord = frmTemp.Fictionword()
    If (strFWord <> "" And objDict.Exists(strFWord)) Then
      strEWord = objDict.Item(strFWord)
      frmTemp.EnglishText = strEWord
    Else
      MsgBox "Word not found in Dictionary.", vbExclamation
    End If
  Loop
End Sub

Sub dsave()
Dim strFileName As String

  strFileName = GetMyDocuments() & "\" & STR_DICT_FILENAME
  savedictionary (strFileName)
End Sub

Sub dload()
Dim strFileName As String

  strFileName = GetMyDocuments() & "\" & STR_DICT_FILENAME
  loaddictionary (strFileName)
End Sub

Sub savedictionary(strFileName)
Dim xmlDoc As DOMDocument
Dim key As Variant
Dim dict, entry, word, meaning, node As IXMLDOMNode

  Set xmlDoc = New DOMDocument
  Set node = xmlDoc.createNode(NODE_ELEMENT, XML_ELEMENT_OUTER, "")
  Set dict = xmlDoc.appendChild(node)
  For Each key In objDict.keys()
    Set node = xmlDoc.createTextNode(STR_INDENT)
    dict.appendChild node
    Set node = xmlDoc.createNode(NODE_ELEMENT, XML_ELEMENT_INNER, "")
    Set entry = dict.appendChild(node)
    Set word = xmlDoc.createAttribute(XML_ATTR_KEY)
    word.Text = key
    entry.Attributes.setNamedItem word
    Set meaning = xmlDoc.createAttribute(XML_ATTR_VALUE)
    meaning.Text = objDict.Item(key)
    entry.Attributes.setNamedItem meaning
  Next key
  Set node = xmlDoc.createTextNode(vbCrLf)
  dict.appendChild node
  xmlDoc.Save strFileName
End Sub

Sub loaddictionary(strFileName)
Dim xmlDoc As DOMDocument
Dim entries As IXMLDOMNodeList
Dim entry, word, meaning As IXMLDOMNode
Dim strWord, strMeaning As String

  initDict
  Set xmlDoc = New DOMDocument ' Create xmldoc and load the specified xml file
  xmlDoc.Load (strFileName)
  If xmlDoc.documentElement Is Nothing Then
    MsgBox "Error loading xml dictionary file: " _
      & vbCrLf & strFileName & vbCrLf _
      & "File not found or failed to parse." _
      , vbCritical
    Exit Sub
  End If
' Select all dictionary entries from the xml file
  Set entries = xmlDoc.selectNodes(XPATH)
' For each entry check for and extract the attributes
  For Each entry In entries
    Set word = entry.Attributes.getNamedItem(XML_ATTR_KEY)
    Set meaning = entry.Attributes.getNamedItem(XML_ATTR_VALUE)
    If (Not (word Is Nothing) And Not (meaning Is Nothing)) Then
      strWord = word.Text
      strMeaning = meaning.Text
      If (Not objDict.Exists(strWord)) Then
        objDict.Add strWord, strMeaning         'add to dictionary
        If (Not objRDict.Exists(strMeaning)) Then
          objRDict.Add strMeaning, strWord    'add to rev. dictionary
        End If
      End If
    End If
  Next entry
End Sub

Autoexec Module

Option Explicit
Sub main()
Dim cmdEbar As CommandBar
Dim ctlPopup As CommandBarPopup
Dim ctlButton As CommandBarButton
Dim i As Integer

  initDict ' initialise the in memory dictionary structures
  dload    ' load previously persisted dictionary xml file

  Set cmdEbar = Application.CommandBars.Add("E's Toolbar", , , True)
  With cmdEbar
    .Visible = True
    .Position = msoBarTop
  End With
  For i = 1 To 4
    Set ctlButton = cmdEbar.Controls.Add(Type:=msoControlButton)
    With ctlButton
      .Visible = True
      .Style = msoButtonIcon
      Select Case i
        Case 1
          .FaceId = 2006
          .OnAction = "addword"
          .TooltipText = "Add word to dictionary"
        Case 2
          .OnAction = "delword"
          .TooltipText = "Delete word from dictionary"
          .FaceId = 2005
        Case 3
          .OnAction = "lookup"
          .TooltipText = "Lookup Fiction -> English"
          .FaceId = 229
        Case 4
          .OnAction = "rlookup"
          .TooltipText = "Lookup English -> Fiction"
          .FaceId = 1820
        Case Else
      End Select
    End With
  Next i
End Sub

Sub main()
  Application.CommandBars("E's Toolbar").Delete ' lose the toolbar
  Call dsave  ' persist the dictionary to an XML file
End Sub

Form Code Module

Option Explicit
Private intResult As Integer  

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  If CloseMode = vbFormControlMenu Then
    Cancel = True           ' cancel this close event to prevent automation errors
    cmdCancel_Click     ' and start our approved shutdown sequence
  End If
End Sub

Property Get dlgResult() As Integer
  dlgResult = intResult
End Property

Private Sub cmdCancel_Click()
  intResult = vbCancel
  Me.Hide
End Sub

Private Sub cmdOk_Click()
  intResult = vbOK
  Me.Hide
End Sub

Public Sub mkLookup(strWord As String)
  txtEnglish.Enabled = False txtEnglish.BackColor = &H80000011
  txtFiction.Text = strWord
End Sub

Public Sub mkRLookup(strWord As String)
  txtFiction.Enabled = False
  txtFiction.BackColor = &H80000011
  Me.Caption = "Reverse " & Me.Caption
  txtEnglish.Text = strWord
End Sub

Public Sub mkAddEntry(strWord As String)
  Me.Caption = "Add Dictionary Entry"
  txtFiction.Text = strWord
End Sub

Public Sub mkDelEntry(strWord As String)
  Me.Caption = "Remove Dictionary Entry"
  txtEnglish.Enabled = False
  txtEnglish.BackColor = &H80000011
  txtFiction.Text = strWord
End Sub

Property Get Englishword() As String
Dim Englishwords As Variant
  Englishwords = Split(Trim(txtEnglish.Text)) ' remove leading spaces and get words
  If (UBound(Englishwords) <> -1) Then
    Englishword = LCase(Englishwords(0))
  Else
    Englishword = ""
  End If
End Property

Property Get Fictionword() As String
Dim Fictionwords As Variant
  Fictionwords = Split(Trim(txtFiction.Text)) ' remove leading spaces and get words
  Fictionword = Fictionwords(0)
  If (UBound(Fictionwords) <> -1) Then
    Fictionword = LCase(Fictionwords(0))
  Else
    Fictionword = ""
  End If
End Property

Property Let EnglishText(strWord)
  txtEnglish.Text = strWord
End Property

Property Let FictionText(strWord)
  txtFiction.Text = strWord
End Property

The Form

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: