Processing XML configuration files Professional
This section discusses XML file usage from the application's point of view. It describes the technology of the Alaska Software XML parser, how to use it und how to process configuration data for an Xbase++ application defined in XML files.
The Alaska Software XML (ASXML) implementation provides an extremly fast lightweight XML processor. To give you an idea of its performance: it can process approximately 250.000 XML tags per second on a Dell Inspiron Laptop with a 433 MHz CPU.
The major design goals for the XML processor have been: simple usage, speed and a callback processing architecture. Callback processing simplifies the development of XML readers tremendously. Unlike Microsoft's XML implementation, where the parser creates a complete tree structure representing an entire XML document, a callback parser allows you to register own functions to process specific tags in a specific hierarchy.
For example, with a hierarchical parser you have to "walk through" the tree structure until you reach the node/tag of interest. The following pseudo code shows you the steps necessary if you would want to extract the contents of the <load> tag from an XML file using a hierarchical parser:
With a hierarchical parser you would extract the start, or root, tag and iterate through the tree until the desired tag is reached. In contrast, a callback processing parser uses a different approach. It assumes the user to register a function for a specific group of tags, or nodes, and passes the tags of interest along with associated data to that function. This means, the user associates an action with a tag and the parser starts traversing the tree and would execute all callback functions registered for specific tags. The following example demonstrates this technique and shows how easy it is to process the <load> tag using a callback processing approach.
The program code for the callback processing approach is not only much easier to read, it is also easier to maintain if the XML tag definition changes. We just have to register a function to be called for a specific node in the XML document structure ("//DatabaseEngines/load") and then we implement this the callback function to process the tag we are interested in. If the structure of a XML configuration file changes, there is no need to adapt PRG source code to reflect the changes, because the code is executed on a "per tag" basis and is independent of the physical structure of the XML document.
Now that we had a little look into the architecture of the ASXML library let us use it to process our XML configuration file.
The ASXML library provides a callback processing parser for XML documents. A generic usage pattern for the parser can be split into the following steps:
That's all about it. There are only four functions required from the ASXML library to process our document.
Function | Purpose |
---|---|
XMLDocOpenFile( cFileName ) | Load a XML document from file |
XMLDocSetAction( nHandle, cNode, bCallback) | Sets the action per node |
XMLDocProcess() | Process the document and execute all callback functions |
XMLDocClose() | Close the document an release the parser |
The source code below implements the XML document loader and processing part. For illustrational purposes we have left out error handling. In our configuration file, we have specified two primary tags, <load> and <build>. Both tags must be located between the opening and closing <DatabaseEngines> tag, i.e. they are child tags. That means we have to register our callback functions at "//DatabaseEngines/load" and "//DatabaseEngines/build" to process these tags. The first slash in this node-string is a place holder for all tags preceding the <DatabaseEngines> tag in the XML file structure. The following slashes delimit the names of the tags we are interested in. The implementation how to process each tag is programmed in our callback functions handleLoad() and handleBuild().
As you can see, writing the processor for our own XML document format is a relatively simple task. The most important part is the design of the XML document, and the specification of the nodes in the tree we are interested in.
To process each tag, we have to implement the callback functions handleLoad() and handleBuild(). We start with the handleLoad() function which is easy since the name of the DBE is the content of the tag. Therefore, we simply retrieve the tag from its handle and use the tag's content (the string between the opening and closing tag) as the name for the DBE to be loaded. The source code of this task is outlined next.
The XMLGetTag() functions retrieves an array of values for a specific tag determined by its unique handle. The array is comprised of the following attributes.
#define | Content |
---|---|
XMLTAG_NAME | Name of the tag |
XMLTAG_CONTENT | The string between the opening and closing tag |
XMLTAG_CHILD | Array of handles for the child tags |
XMLTAG_ACTION | Codeblock if action was registered |
XMLTAG_ATTRIB | Array of attributes for tag |
The next sample-code illustrates the implementation of our <build> tag processing, which is more complex. Remember, we have used an attribute for the tag to specify the name of the DBE to be built, and we have added a couple of child tags to specify the Data and/or Order component of the DBE. Therefore, we have to obtain the attributes of an XML tag and we have to access specific child tags.
Our callback function handleBuild() is executed each time the parser finds a node "//DatabaseEngine/build" in our XML document. To retrieve an attribute of a tag by name, we can use the XMLGetAttribute() function (line #8), which returns the value of the attribute. This is the name of the DBE to be built.
Now we must access the child tags <data> and <order> and retrieve their contents which is a little bit more complex. First we have to retrieve the child tag by its tag name. This is done using the XMLGetChild() function (line #10) which accepts the numeric handle of the <build> tag currently being processed (the parent tag). This function returns a numeric handle for the requested child tag. The retrieval of the contents of the <order> and <data> tags is then accomplished by the XMLGetTag() function, just as we have done it already with the <load> tag.
That's all! To process XML documents, we have implemented the document loader and processor and we have registered two callback functions to process two specific tags. Then we have implemented both callback functions to process each tag. With this code you have a full working reader and processor for the example configuration file to handle loading and building of DatabaseEngines.
For your convenience, we have added the full source code and a sample XML file to this document's appendix. Try it out and adapt it to your specific needs. We are sure that XML will now find its place in your code-library of ready-to-use solutions.
We have explored how to design and process a XML document which fits a specific purpose - here a configuration file for our mission critical database application. We have left out error handling and how to ensure that our XML document conforms with the invented syntax and structure. We have also left out XML usage scenarios related to database export/ import functionalities. This and more will be a part of one of our next Alaska Software TechNote articles. So please stay tuned for the next TechNote from Alaska Software.
If you see anything in the documentation that is not correct, does not match your experience with the particular feature or requires further clarification, please use this form to report a documentation issue.