Click here to Skip to main content
Click here to Skip to main content

Using XML/XSLT with the "C Preprocessor"

, 7 May 2007
Rate this:
Please Sign up or sign in to vote.
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

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

# 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

# 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

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

# 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:

<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 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

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)

Share

About the Author

PIEBALDconsult
Software Developer (Senior)
United States United States
BSCS 1992 Wentworth Institute of Technology
 
Originally from the Boston (MA) area. Lived in SoCal for a while. Now in the Phoenix (AZ) area.
 
OpenVMS enthusiast, ISO 8601 evangelist, photographer, opinionated SOB
 
---------------
 
"If you need help knowing what to think, let me know and I'll tell you." -- Jeffrey Snover [MSFT]
 
"Typing is no substitute for thinking." -- R.W. Hamming
 
"I find it appalling that you can become a programmer with less training than it takes to become a plumber." -- Bjarne Stroustrup
 
ZagNut’s Law: Arrogance is inversely proportional to ability.
 
"Well blow me sideways with a plastic marionette. I've just learned something new - and if I could award you a 100 for that post I would. Way to go you keyboard lovegod you." -- Pete O'Hanlon
 
"linq'ish" sounds like "inept" in German -- Andreas Gieriet
 
"Things would be different if I ran the zoo." -- Dr. Seuss
 
"Wrong is evil, and it must be defeated." – Jeff Ello
 
"A good designer must rely on experience, on precise, logical thinking, and on pedantic exactness." -- Nigel Shaw
 
“It’s always easier to do it the hard way.” -- Blackhart

“If Unix wasn’t so bad that you can’t give it away, Bill Gates would never have succeeded in selling Windows.” -- Blackhart

"Omit needless local variables." -- Strunk... had he taught programming
 

 
"We learn more from our mistakes than we do from getting it right the first time."
 
My first rule of debugging: "If you get a different error message, you're making progress."
 
My golden rule of database management: "Do not unto others' databases as you would not have done unto yours."
 
My general rule of software development: "Design should be top-down, but implementation should be bottom-up."

Comments and Discussions

 
GeneralHummmm PinmemberMiguel Barros30-Aug-09 14:19 
GeneralRe: Hummmm PinmemberPIEBALDconsult31-Aug-09 6:21 
GeneralInformation PinmemberMike DiRenzo7-May-07 9:30 
GeneralRe: Information PinmemberPIEBALDconsult7-May-07 15:44 
GeneralUsing XML/XSLT with the (what?) Pinmemberednrgc7-May-07 6:15 
GeneralRe: Using XML/XSLT with the (what?) PinmemberPIEBALDconsult7-May-07 15:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140814.1 | Last Updated 7 May 2007
Article Copyright 2007 by PIEBALDconsult
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid