Internet Technologies:xml

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.

Alaska XML parser technology

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:

01: oRoot   := getRootTag() 
02: oParent := oRoot:getChild( "DatabaseEngines" ) 
03; aChild  := oParent:getChildren( "load" ) 
04: FOR i:=1 TO Len( aChild ) 
05:   oChild := aChilds[i] 
06:   // do whatever you want to to with the <load> tag 
07: NEXT i 

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.

01: registerFunction( "//DatabaseEngines/load", "myFunction" ) 
02: processDocument() 
03: 
04: FUNCTION myFunction(oTag) 
05:   // do whatever you want to do with the <load> tag 
06: RETURN(.T.) 

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.

XML function overview

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:

Open the XML document with the XMLDocOpenFile() function.
Register functions to process the tags of interest, i.e. we have to define the action the parser should take for a node in the XML structure. This is done using the XMLDocSetAction() function, which allows us to associate a code block with a particular XML tag. We can set as many actions for a document as we want, as long as there is only one action per node.
Start the callback processor using the XMLDocProcess() function.
Close the document using the XMLDocClose() function.

That's all about it. There are only four functions required from the ASXML library to process our document.

XML functions used to process a XML 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().

01:/* 
02: * Document Processor cFilename must be a XML document 
03: * according to our configuration file syntax sample. 
04: */ 
05:FUNCTION processConfig(cFileName) 
06:  LOCAL nXMLDoc,nActions 
07: 
08:  // load XML document 
09:  nXMLDoc := XMLDocOpenFile(cFileName) 
10: 
11:  // register functions by setting actions. 
12:  nActions := XMLDocSetAction(nXMLDoc, "//DatabaseEngines/load",; 
13:                              {|n,c,a,ch|handleLoad(n,c,a,ch)}) 
14: 
15:  nActions := XMLDocSetAction(nXMLDoc, "//DatabaseEngines/build",; 
16:                              {|n,c,a,ch|handleBuild(n,c,a,ch)}) 
17: 
18:  // If there is something to process, we start the processor 
19:  // to call our callback functions handleLoad() and handleBuild() 
21:  IF nActions != 0 
22:    XMLDocProcess(nXMLDoc) 
23:    XMLDocResetAction(nXMLDoc) 
24:  ELSE 
25:    RETURN(.F.) 
26:  ENDIF 
27:  XMLDocClose(nXMLDoc) 
28:RETURN(.T.) 

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.

01:/* Callback function to handle the DbeLoad action 
02: * 
03: * Tag: <load>#PCDATA</load> 
04: */ 
05:FUNCTION handleLoad(cTag,cContent,aMember,nHandle) 
06:  LOCAL aCH 
07:  XMLGetTag(nHandle,@aCH) 
08:  DbeLoad(aCH[XMLTAG_CONTENT],.F.) 
09:RETURN (XML_PROCESS_CONTINUE) 

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 constants for the TAG attribute array
#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.

01:/* Callback function to handle DbeBuild action 
02: * 
03: */ 
04:FUNCTION handleBuild(cTag,cContent,aMember,nHandle) 
05:  LOCAL aCH :={} 
06:  LOCAL nHChild,cName,cOrder,cData 
07: 
08:  cName := XMLGetAttribute(nHandle,"name") 
09: 
10:  nHChild := XMLGetChild(nHandle,"data") 
11:  XMLGetTag(nHChild,@aCH) 
12:  cData := aCH[XMLTAG_CONTENT] 
13: 
14:  nHChild := XMLGetChild(nHandle,"order") 
15:  XMLGetTag(nHChild,@aCH) 
16:  cOrder:= aCH[XMLTAG_CONTENT] 
17: 
18:  DbeBuild(cName,cData,cOrder) 
19:RETURN (XML_PROCESS_CONTINUE) 

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.

A final word

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.

Feedback

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.