Introduction

A question on the Be Communities MicroStation Programming Forum prompted this project. The question was "How can I persist data in a C struct to XML?" My reponse was to turn the question around and demonstrate how, using XML to specify a data structure, an XSLT file can transform the specification into resource source code and function prototypes.

The code generator's data components are these files …

The code generator's work is performed by a transformation engine that uses the XSLT files to generate header and code files from the XML specification …

The code generator creates these files …

Example Files

This ZIP archive contains the XML, XSLT, and batch files used to generate the example header and code files.

XML Specification

The XML specification is straightforward. An outer element <structures> contains one or more <structure> elements. Each <structure> element contains one or more <member> elements. Each <member> has name and dataType attributes. A member intended to store a string has a char dataType and has an additional size attribute that indicates the length of the string buffer.

<?xml version="1.0" ?>
<!-- Example structure definition    -->
<!-- Copyright (C) 2005...2018 LA Solutions -->
<structures>
  <structure name="Example1" >
    <member name="int1" dataType="int" />
    <member name="long1" dataType="long" />
    <member name="int2" dataType="int" />
    <member name="name" dataType="char" size="32" />
  </structure>
</structures>

XSLT Rules

There are two transformation rule files: one to generate a C header (.h) file and one to generate an MDL code (.mc) file. Actually, there's nothing particular to MDL about the code file: it could also be a real C or C++ file.

XSLT Header Rules

The rules to generate a header file (xml2struct.xsl) from the XML specification are simple. The most complex part is the <xsl:choose> element, which works like a switch statement. It writes the corresponding C data type according the the member's dataType attribute.

I've also made an assumption that you like to see a struct with a lower-case tag and an upper-case name. For example:

  typedef struct tag_mystruct
  {
     …
  } MYSTRUCT, *LP_MYSTRUCT;

The generator takes the specified struct name (in this example, mystruct) and applies a prefix tag_ to the name. The typedef is assigned the upper-case name, and I've added a pointer name as well, so you can refer to the struct by value or by reference …

  MYSTRUCT   myStruct;	  //	By value
  myStruct.int1 = 1;

  LP_MYSTRUCT  pData	  //	By reference
  pData->int1 = 1;

XSLT Code Rules

The rules to generate a code file (xml2mc.xsl) from the XML specification are more complex. We want skeleton functions that transfer data between a structure and the document object model  (DOM) that can be persisted as an XML file. Therefore, we want both load and store functions that operate on structure members. This is implemented by an xsl:template with a mode attribute that may be load or store. When the mode is load, we copy data from the DOM into our structure. When the mode is store, we copy data from the structure into the DOM.

The generator presupposes functions already exist that can load and store various data types. The implementation of these functions is independent of the code generator and also independent of the schema of your XML resource file. They have these prototypes …

In each case, the xpath argument passes a DOM search path. This will be dependent on the structure of your XML resource file (i.e.  the file where you persist your data, not the XML files discussed here). For example, xpath might be something like data/settings/lights/value1, where value1 corresponds to one of the structure members created by the generator. Each of the functions above will include code to search the DOM using the xpath to locate the element that holds data for a particular structure member.

Transformation Engine

The transformation is implemented by a third-party executable that accepts the XML specification and transforms it first to the header file and then to the code file. I've chosen to use Microsoft's XSLT transformation engine, but you can use any one you like: another popular engine is Xalan. You can find more about Microsoft's XML technology (MSXML) and download their libraries here.

The transformation engine is invoked through a batch file containing two commands. Obviously, you should edit this batch file to reflect the actual location of your installation of msxsl.exe …

"C:\Program Files\MSXML 4.0\bin\msxsl.exe" example.xml xml2struct.xsl -o example.h "C:\Program Files\MSXML 4.0\bin\msxsl.exe" example.xml xml2mc.xsl include-file=example.h -o example.mc

Both transformations use the same data source, in this case example.xml. The first invocation generates the C header file, using the xml2struct style sheet. The second invocation generates the C source code, using the xml2mc style sheet. There is an additional command-line parameter named include-file with value example.h, which is provided so that the style sheet can add the appropriate #include statement.

Header File

The generator writes a simple header file using the rules in xml2struct.xsl. It creates a C typedef struct for each <structure> element found in the XML specification file …

  //	Structure Example1
  typedef struct tag_example1
  {
    int    int1;
    long   long1;
    int    int2;
    char   name [32];

  } EXAMPLE1, *LP_EXAMPLE1;

Code File

The generator writes a code (.mc) file that provides functions named xmlStruct_loadXxx and xmlStruct_storeXxx, where the structure name is substituted for Xxx.

The first line is an #include statement for the file specified by the include-file parameter to the transform.

In this example, we find a load function …

void          xmlStruct_loadEXAMPLE1
(
LP_EXAMPLE1   pData,       //    <= Load struct from DOM
char*         path         //    => Path to parent node in XML resource file
)
{
    char xpath [128];

    sprintf (xpath, "%s/%s", path, "int1");
    pData->int1 = xmlStruct_getIntValue (xpath);

    sprintf (xpath, "%s/%s", path, "long1");
    pData->long1 = xmlStruct_getLongValue (xpath);

    sprintf (xpath, "%s/%s", path, "int2");
    pData->int2 = xmlStruct_getIntValue (xpath);

    sprintf (xpath, "%s/%s", path, "name");
    xmlStruct_getStringValue (pData->name, xpath);
}

And a store function …

void          xmlStruct_storeEXAMPLE1
(
LP_EXAMPLE1   pData,       //    => Store struct in DOM
char*         path         //    => Path to parent node in XML resource file
)
{
    char xpath [128];

    sprintf (xpath, "%s/%s", path, "int1");
    xmlStruct_putIntValue (pData->int1, xpath);

    sprintf (xpath, "%s/%s", path, "long1");
    xmlStruct_putLongValue (pData->long1, xpath);

    sprintf (xpath, "%s/%s", path, "int2");
    xmlStruct_putIntValue (pData->int2, xpath);

    sprintf (xpath, "%s/%s", path, "name");
    xmlStruct_putStringValue (pData->name, xpath);
}

Conclusion

XSLT can transform XML to many things. Writing this article, and developing the XSLT rules to transform the specification to source code, has been an interesting diversion. The current code generator is pretty raw: I'm sure that you can think of all sorts of bells, whistles, and other embellishments. There's a ZIP archive you can download. It contains the XML example and the XSLT rules, as well as example.h and example.mc files produced by the generator.

Here's a related article that discusses C++ class for settings persistence in an XML file XML Settings at The Code Project.

e-mail If this article is useful, let us know: here's a response form.

Return to MicroStation CONNECT Articles index.