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.