Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XSLT

Using XML/XSLT with the C Preprocessor

3.52/5 (6 votes)
7 May 2007CPOL3 min read 1   75  
How to misuse XML/XSL to do what not even the

Introduction

This is likely going to be the least popular article here. It was prompted by a posting in the "Coding horrors" forum

(The example in this article is actually simpler than the one posted.)

Its only saving grace will be if anyone learns a little about XML and XSLT from it.

Background

Plain old C has a limitation that .NET (in particular, the Common Type System) doesn't have. In this respect, this article shouldn't be on Code Project at all, it doesn't really relate to .NET.

This particular limitation concerns enums. In .NET, getting the textual representation of an enumerated value is simple. In this C# example I define an enum, assign one of its values to a variable, and display it to the console. Note that I actually have to make an effort to get the numeric value of the enum member.

CSenum.cs

C#
namespace CSenum
{
    public partial class
    CSenum
    {
        public enum UserEnum
        {
            Abe
        ,
            Bob
        ,
            Cat
        ,
            Deb
        }

        public struct
        Message
        {
            public UserEnum From ;
            public string Text ;
        }

        [System.STAThreadAttribute]
        public static int
        Main
        (
            string[] args
        )
        {
            try
            {
                if ( args.Length > 0 )
                {
                    Message m ;

                    m.From = UserEnum.Bob ;
                    m.Text = args [ 0 ] ;

                    System.Console.WriteLine ( "{0} says \"{1}\"" , 
                        (int) m.From , m.Text ) ;
                    System.Console.WriteLine ( "{0} says \"{1}\"" , 
                        m.From , m.Text ) ;
                }
            }
            catch ( System.Exception err )
            {
                System.Console.Write ( err.Message ) ;
            }

            return ( 0 ) ;
        }
    }
}
Result:
C:\>CSenum Hello
1 says "Hello"
Bob says "Hello"

Now here's a C version that demonstrates the limitation. The enumerated value can't be converted to its textual representation. Cenum1.c

C
# include <stdio.h />

typedef enum
{
    Abe
,
    Bob
,
    Cat
,
    Deb
,
    MAX  /* Helps to bounds-check the value */
} UserEnum ;

typedef struct
{
    UserEnum From ;
    char*    Text ;
} Message ;

int
main
(
    int   argc
,
    char* argv[]
)
{
    int result = 0 ;

    if ( argc > 1 )
    {
        Message m ;

        m.From = Bob ;
        m.Text = argv [ 1 ] ;
       
        printf ( "%d says \"%s\"\n" , m.From , m.Text ) ;

        /* This line would cause an error */
//      printf ( "%s says \"%s\"\n" , m.From , m.Text ) ;
    }

    return ( result ) ;
}
</stdio.h />

Result:

C:\>Cenum1 Hello
1 says "Hello"

So, what we do is add an array of strings:

Cenum2.c

C++
# include <stdio.h>

typedef enum
{
    Abe
,
    Bob
,
    Cat
,
    Deb
,
    MAX  /* Helps to bounds-check the value */
} UserEnum ;

char *UserNames[] =
{
    "Abe"
,
    "Bob"
,
    "Cat"
,
    "Deb"
} ;

typedef struct
{
    UserEnum From ;
    char*    Text ;
} Message ;

int
main
(
    int   argc
,
    char* argv[]
)
{
    int result = 0 ;

    if ( argc > 1 )
    {
        Message m ;

        m.From = Bob ;
        m.Text = argv [ 1 ] ;
       
        printf ( "%d says \"%s\"\n" , m.From , m.Text ) ;

        /* This line would cause an error */
//      printf ( "%s says \"%s\"\n" , m.From , m.Text ) ;

        /* We use the numeric value as an index into the array of strings */
        printf ( "%s says \"%s\"\n" , UserNames [ m.From ] , m.Text ) ;
    }

    return ( result ) ;
}

Result:

C:\>Cenum2 Hello
1 says "Hello"
Bob says "Hello"

Ordinarily the definitions of the enum, the array of strings, and the struct would be in one or more header (h) files. Currently, I'll only split out the enum and the array of strings.

Cenum3.h

C++
typedef enum
{
    Abe
,
    Bob
,
    Cat
,
    Deb
,
    MAX  /* Helps to bounds-check the value */
} UserEnum ;

char *UserNames[] =
{
    "Abe"
,
    "Bob"
,
    "Cat"
,
    "Deb"
} ;

In the C file, after removing the above, add:

From Cenum3.c

C++
# include "Cenum3.h"

Breaking such definitions from the code that uses them allows for easier maintenance of C programs. But we're left with two definitions that are related and must be maintained together. Some potential problems that can arise are:

  1. Adding a value to the enum, but not to the array
  2. Adding a string to the array in the wrong place

Obviously, finding a way of having only one list to maintain is desirable. The code that is the topic of the Coding Horrors post is one such way, and it works, but it's rather horrific.

Using XML and XSLT to achieve the same result

WARNING: The technique I'm about to describe is only 'somewhat' less horrific. C purists shouldn't proceed.

I'll use XML to store the list. Such an XML document can pretty much be written any way you like (obeying the rules of well-formedness).

Well-formed XML documents include:

XML
<User>
  <Abe/>
  <Bob/>
  <Cat/>
  <Deb/>
</User>

<Enum>
  <Name>User</Name>
  <Value>Abe</Value>
  <Value>Bob</Value>
  <Value>Cat</Value>
  <Value>Deb</Value>
</Enum>

<Enum Name="User">
  <Value Name="Abe"/>
  <Value Name="Bob"/>
  <Value Name="Cat"/>
  <Value Name="Deb"/>
</Enum>

I prefer the first of these, if for no other reason than brevity. There also appears to be a general dislike of attributes as used in the third example. Such a file may be the output from a database query or other process.

Now we want to write an XSL document to transform the XML document into Cenum3.h (The exact format of the resultant header file won't be very important as long as the compiler understands it.)

Cenum3.xsl

XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
  <xsl:output omit-xml-declaration="yes" method="text"/>
  <xsl:template match="/">
    <xsl:for-each select="*">
      typedef enum {
      <xsl:for-each select="*">
    <xsl:value-of select="name()"/>,
        <xsl:if test="position()=last()">MAX</xsl:if>
      </xsl:for-each>} <xsl:value-of select="name()"/>Enum ;
      char *<xsl:value-of select="name()"/>Names[] = {
      <xsl:for-each select="*">
    <xsl:if test="position()!=1">,</xsl:if>&quot;<xsl:value-of 
      select="name()"/>&quot;
      </xsl:for-each>} ;
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

I won't go into detail as to what the XSL does; I'll just say that it iterates through the children of the User element twice. First it creates the definition of the enum, then it creates the definition of the string array.

Learn XSL here.

Resultant Cenum3.h

typedef enum {
Abe,
  Bob,
  Cat,
  Deb,
  MAX} UserEnum ;
char *UserNames[] = {
"Abe"
,"Bob"
,"Cat"
,"Deb"
} ;

Now you may ask yourself, "How did I get here?" By which I mean, "What utility exists to take the XML and the XSL and actually perform the transform?" Well, here's a very simple console program to do it:

SimpleXSLT.cs

C#
namespace SimpleXSLT
{
    public partial class SimpleXSLT
    {
        [System.STAThreadAttribute]
        public static int
        Main
        (
            string[] args
        )
        {
            int result = 0 ;

            try
            {
                System.Xml.Xsl.XslCompiledTransform xslt = new 
                    System.Xml.Xsl.XslCompiledTransform() ;

                if ( args.Length == 2 )
                {
                    xslt.Load ( args [ 1 ] ) ;

                    xslt.Transform
                    (
                        args [ 0 ]
                    ,
                        null
                    ,
                        System.Console.Out
                    ) ;
                }
                else
                {
                    System.Console.Write ( "Syntax: XSLT xmlfile xslfile" ) ;
                } 
            }
            catch ( System.Exception err )
            {
                System.Console.Write ( err.Message ) ;
            }

            return ( result ) ;
        }
    }
}

Result:

C:\>SimpleXSLT Cenum3.xml Cenum3.xsl

      typedef enum {
      Abe,
        Bob,
        Cat,
        Deb,
        MAX} UserEnum ;
      char *UserNames[] = {
      "Abe"
      ,"Bob"
      ,"Cat"
      ,"Deb"
      } ;

(And you may redirect the output to a file.)

C:\>SimpleXSLT Cenum3.xml Cenum3.xsl > Cenum3.h

History

2007-05-02 Original version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)