A question on the Bentley MDL discussion group 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 source code.
The code generator's data components are these files …
.xml).xsl)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 …
.h).mc)This ZIP archive contains the XML, XSLT, and batch files used to generate the example header and code files.
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...2008 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>
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.
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;
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 …
int xmlStruct_getIntValue (const char* xpath);long xmlStruct_getLongValue (const char* xpath);ULong xmlStruct_getULongValue (const char* xpath);double xmlStruct_getDoubleValue (const char* xpath);int xmlStruct_getStringValue (char* buffer, size_t size, const char* xpath);void xmlStruct_putIntValue (in value, const char* xpath);void xmlStruct_putLongValue (long value, const char* xpath);void xmlStruct_putULongValue (ULong value, const char* xpath);void xmlStruct_putDoubleValue (double value, const char* xpath);void xmlStruct_putStringValue (const char* value, const char* xpath);
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.
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
"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.
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;
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 DOMchar* 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 DOMchar* 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); }
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.
If this article is useful, let us know: here's a response form.
Return to MDL articles index.