dba key graphicdatabase advisors graphic

Using XML to Store Configuration Data

By Jim DeMarco view Bio

Demo File: ConfigReaderDemo.mdb in Access 97 download sample files   Print this Page

 

Many times we need to store values outside of our database, and configuration data is a perfect example. In a recent application we displayed lookup values in a grid control, pulling those values from a CSV file. We configured the grid according to the shape of the file (in this case, nine columns and four rows of data). In the first version of the app we hard-coded these numbers into the application startup routines. Everything was fine until the agency providing the data changed the format of the file (two rows were consolidated so we now only had three rows of data). We decided we’d to store the configuration data in an external file. Doing so gives our application more flexibility; if the shape of the grid and its corresponding text file change, replacing the text file and the configuration file are all that’s necessary to keep the application running (eliminating re-coding).

            A natural choice for this job is XML. Specifically, the XML DOM (Document Object Model) allows us to navigate a properly formatted document’s hierarchy and set/retrieve values at a given location. The following is an example of an XML file containing grid dimensions:

 

<?xml version="1.0" encoding="iso-8859-1" ?>

<config> 
  <GridNames>
    <Name>AEligibility</Name>
    <Name>BEligibility</Name>
    <Name>CEligibility</Name>
  </GridNames>

  <AEligibilityRowCol>
    <NumFixedRows>1</NumFixedRows>
    <NumFixedCols>1</NumFixedCols>
    <NumRows>5</NumRows>
    <NumCols>3</NumCols>
  </AEligibilityRowCol>

  <AEligibilityRowHead>
    <Head1>Row 1</Head1>
    <Head2>Row 2</Head2>
    <Head3>Row 3</Head3>
    <Head4>Row 4</Head4>
  </AEligibilityRowHead>

  <AEligibilityColHead>
    <Head0>Row Nums</Head0>
    <Head1>Col 1</Head1>
    <Head2>Col 2</Head2>
  </AEligibilityColHead>
</config>

 

Under the <?xml> declaration we’ve created a <config> element that contains child elements that contain the names of the grids and the dimensions for each along with row and column header information.  We could expand on this theme by including row and column heights and widths but for the purpose of this example we will not. You might consider this type of file the XML version of an INI file. The <config> element has children (<GridNames>, <AEligibilityRowCol>, etc) that correspond to section headings in an INI file, and each contains children that would be the individual related items within a section in the INI file.

 

The cConfigReader Class

The cConfigReader class is an easy to use class module that provides the functionality for reading and writing to XML configuration files. It can handle files containing data stored by section (as in the example above) or a simpler format that just includes data elements without sections. Sections are useful for grouping related items but they do not have to be used if the element structure is simple. The following is an example of configuration file without sections:

 

<?xml version="1.0" encoding="iso-8859-1" ?>

<config>
 <LastFormOpened>frmMain</LastFormOpened>
 <MaxTries>3</MaxTries>
 <UserName>John Doe</UserName>
</config>

 

Creating the cConfigReader Class Module

The cConfigReader class module wraps XML DOM functions so there’s no need to get into the dirty details of navigating XML nodes. To create the class open a class module and set a reference to MS XML 3.0 (Tools | References). Then, copy the following code into the module:

 

'---------------------------------------------------------------------------------------
' Module    : cConfigReader
' DateTime  : 1/16/02 12:55
' Author    : Jim DeMarco
' Purpose   : Read and write configuration data to/from an xml file
' Dependency: MS XML 3.0 (msxml3.dll)
' Rev       : JD 031202 Added KeyCount and ListKeyValues methods

'Usage:
'RETRIEVE A KEY WITH NO SECTIONS
'oconfig.LoadXML(filename)
'retrieve a key - LONG FORM - CLASS PROPERTIES
'oconfig.KeyName = "mykey"
'myvar = oconfig.KeyValue
'oconfig.KeyName = "anotherkey"
'othervar = oconfig.KeyValue

'or SHORT FORM - GETKEYVALUE METHOD
'oconfig.LoadXML(filename)
'myvar = oconfig.GetKeyValue("mykey")
'othervar = oconfig.GetKeyValue("anotherkey")

'RETRIEVE A KEY WITH SECTION KEY - set section name property once
'LONG FORM - CLASS PROPERTIES
'oconfig.LoadXML(filename)
'oconfig.SectionName = "Paths"
'oconfig.KeyName = "mykey"
'myvar = oconfig.KeyValue
'or LONG FORM GETKEYVALUE METHOD
'oconfig.LoadXML(filename)
'oconfig.SectionName = "Paths"
'myothervar = oconfig.GetKeyValue("myotherkey")

' or use SHORT FORM - GETKEYVALUE METHOD WITH OPTIONAL SECTION ARGUMENT
'oconfig.LoadXML(filename)
'myvar = oconfig.GetKeyValue("mykey", "mysection")
'othervar = oconfig.GetKeyValue("anotherkey") 'only need to set section once _
    but you could set it here anyway

'SET A KEY WITH NO SECTIONS
'oconfig.LoadXML(filename)
'set a key - LONG FORM - CLASS PROPERTIES
'oconfig.KeyName = "mykey"
'oconfig.KeyValue = 10
'oconfig.KeyName = "anotherkey"
'oconfig.KeyValue = "stringvalue"

'or SHORT FORM - SETKEYVALUE METHOD
'oconfig.LoadXML(filename)
'oconfig.SetKeyValue("mykey", 10)
'oconfig.SetKeyValue("anotherkey", "stringvalue")

'if using SECTION KEY - set section name property once
'LONG FORM - CLASS PROPERTIES
'oconfig.LoadXML(filename)
'oconfig.SectionName = "Paths"
'oconfig.KeyName = "mykey"
'oconfig.KeyValue = 10
'OR
'oconfig.LoadXML(filename)
'oconfig.SectionName = "Paths"
'oconfig.SetKeyValue("myotherkey", "stringvalue")

' or use SHORT FORM - SETKEYVALUE METHOD WITH OPTIONAL SECTION ARGUMENT
'oconfig.LoadXML(filename)
'oconfig.SetKeyValue("mykey", 10, "mysection")
'oconfig.SetKeyValue("anotherkey", "stringvalue") 'only need to set section once _
    but you could set it here anyway for consistency

'---------------------------------------------------------------------------------------
Option Compare Database
Option Explicit

Private m_sFileName As String
Private m_sSectionName As String
Private m_sKeyName As String
Private m_vKeyValue As Variant
Private m_oXML As DOMDocument30
'
Public Property Get SectionName() As String
    SectionName = m_sSectionName
End Property
'
Public Property Let SectionName(sSectionName As String)
    m_sSectionName = sSectionName
End Property

Public Property Get KeyName() As String
    KeyName = m_sKeyName
End Property

Public Property Let KeyName(sKeyName As String)
    m_sKeyName = sKeyName
End Property

Public Property Get KeyValue() As Variant
    m_vKeyValue = GetConfigValue 
    KeyValue = m_vKeyValue
End Property

Public Property Let KeyValue(vKeyValue As Variant)
    If vKeyValue = m_vKeyValue Then Exit Property 'no change is being made
    SetConfigValue vKeyValue
    m_vKeyValue = vKeyValue
End Property

Public Function GetKeyValue(Key As String, Optional Section) As Variant
'provides a shorter syntax for getting a key value
'if config file contains sections:
'SectionName can be set via SectionName property or optional Section argument
    If Not IsMissing(Section) Then
        m_sSectionName = Section
    End If

    m_sKeyName = Key
    GetKeyValue = GetConfigValue
End Function

Public Function SetKeyValue(Key As String, Value As Variant, Optional Section)
'provides a shorter syntax for getting a key value
'if config file contains sections:
'SectionName can be set via SectionName property or optional Section argument
    If Not IsMissing(Section) Then
        m_sSectionName = Section
    End If

    m_sKeyName = Key
    SetConfigValue Value
    'GetKeyValue = GetConfigValue
End Function

Public Function KeyCount(Section As String) As Integer
'Section - the name of the section to count items in
'returns the number of items in Section
Dim nodeMaster As IXMLDOMElement
Dim nodeSection As IXMLDOMNode
Dim iResult As Integer

    With m_oXML
        Set nodeMaster = .documentElement
        Set nodeSection = nodeMaster.selectSingleNode(Section)
        iResult = nodeSection.childNodes.Length
    End With

    KeyCount = iResult

    Set nodeSection = Nothing
    Set nodeMaster = Nothing
End Function

Public Function ListKeyValues(Section As String, Optional Delimiter As String = ",") As String
'Section - the name of the section to get items from
'Delimiter - delimiter character to use in returned string
'returns a delimited list containing the items in Section
Dim nodeMaster As IXMLDOMElement
Dim nodeSection As IXMLDOMNode
Dim iNumKeys As Integer
Dim i As Integer
Dim sResult As String

    With m_oXML
        Set nodeMaster = .documentElement
        Set nodeSection = nodeMaster.selectSingleNode(Section)
        iNumKeys = nodeSection.childNodes.Length
        For i = 0 To iNumKeys - 1
            sResult = sResult & nodeSection.childNodes(i).Text & Delimiter
        Next i
    End With

    ListKeyValues = Left$(sResult, Len(sResult) - 1)
    Set nodeSection = Nothing
    Set nodeMaster = Nothing
End Function

Public Function LoadConfig(FileName As String) As Boolean
'filename must include path
Const FILE_NOT_FOUND_ERROR = 53

    If Len(Dir(FileName)) = 0 Then
        Err.Raise FILE_NOT_FOUND_ERROR, "cConfigReader: LoadConfig", Err.Description
    End If

    m_oXML.Load FileName
    If m_oXML.parseError.errorCode <> 0 Then
        Err.Raise m_oXML.parseError.errorCode, "cConfigReader:LoadXML", _
                    m_oXML.parseError.reason
    End If

    m_sFileName = FileName
    LoadConfig = True
End Function

Private Function GetConfigValue() As Variant
Dim sSectionName As String

    If Len(m_sSectionName) Then
        sSectionName = m_sSectionName  & "/"
    End If

    If Len(KeyName & "") = 0 Then Exit Function '
    GetConfigValue = m_oXML.selectSingleNode("//" & sSectionName & KeyName).Text

End Function

Private Function SetConfigValue(NewValue As Variant)
Dim oNode As IXMLDOMNode
Dim sSectionName As String

    If Len(m_sSectionName) Then
        sSectionName = m_sSectionName & "/"
    End If

    If Len(KeyName & "") = 0 Then Exit Function '
    Set oNode = m_oXML.selectSingleNode("//" & sSectionName & KeyName)
    oNode.Text = NewValue
    m_oXML.Save m_sFileName
    Set oNode = Nothing

End Function

Private Sub Class_Initialize()
    Set m_oXML = New DOMDocument30
    m_oXML.async = False 'give it time to load!
End Sub

Private Sub Class_Terminate()
    Set m_oXML = Nothing
End Sub

 

The Class Properties and Methods

To view the available properties and methods, open any code module, and press F2 to launch the Object Browser. Right-click in the right pane and choose Group Members to order the class by its Properties, then Methods. The class contains the following properties and methods:

 

·         KeyName – The name of the data element whose value is being retrieved or set.

·         KeyValue – The value being read or set from KeyName.

·         SectionName – The name of the section containing KeyName.

·         LoadConfig(FileName As String) – Loads the configuration file (FileName) into memory.

·         GetKeyValue(Key As String, Optional Section) As Variant – Key is the name of the key, Section is the name of the section. Retrieves the current value of Key (KeyName). If Section is included Key is retrieved from Section.

·         SetKeyValue(Key As String, Value As Variant, Optional Section) – Key is the name of the key, Value is the value to set Key to, and Section is the name of the section. Sets the value of Key to Value. If Section is included Key is set in Section.

·         KeyCount(Section As String) As Integer – Section is the name of the section. Returns an integer value containing the number of elements (keys) within Section.

·         ListKeyValues(Section As String, Optional Delimiter As String = “,”) As String – Section is the name of the section, Delimiter is the delimiter for the returned string. Returns a delimited string containing all of the key values in Section.

 

The cConfigReader class has two interfaces for getting and setting key values. The values can be accessed via its properties using the following code:

 

‘not using section names here
Dim sUser As String
Dim oConfig As cConfigReader
  Set oConfig = New cConfigReader
  oConfig.LoadConfig “c:\myconfig.con”
  ‘retrieve a value
  oConfig.KeyName = “UserName”
  SUser = oConfig.KeyValue
  ‘set a value
  oConfig.KeyName = “MaxTries”
  oConfig.Value = 4

 

Or, use the following methods in this code:

‘not using section names here
Dim sUser As String
Dim oConfig As cConfigReader
  Set oConfig = new cConfigReader
  OConfig.LoadConfig “c:\myconfig.con”
  ‘retrieve a value
  SUser = oConfig.GetKeyValue(“UserName”)
  ‘set a value
  oConfig.SetKeyValue(“LastFormOpened”, “frmProducts”)

 

The Example Files

The example file is in Access 97 and shows how to use a configuration file to store log in information and set the dimensions of an MS FlexGrid control.  Here’s how to use the example file:

 

1.      Copy ConfigReaderDemo.mdb and sample.con (the XML file) to a folder then open ConfigReaderDemo.mdb.

2.      Enter your name in the text box (replacing the prompt).  This stores the value entered into the XML configuration file.

3.      Click the Continue button to open the grid form.

4.      Click the Shape Grid command button to see the grid shape itself according to the configuration file and then display the row and column headings stored in the configuration file. Try modifying the dimensions and heading text in the configuration file and then click the Shape Grid button again to see the immediate change (don’t forget to save the XML file first).

5.      Open the sample.con file in Internet Explorer to see how Explorer displays a treeview version of the configuration file (an easy way to do this is to drag the file from Windows Explorer and drop it on the Internet Explorer window). 

 

Please contact the author at Jdemarco@hshhp.org to ask questions or report errors.
May be distributed as long as the copyright remains.