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 enum
s. 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
} 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 ) ;
}
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
} 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 ) ;
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
} 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:
- Adding a value to the
enum
, but not to the array - 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
="1.0"="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>"<xsl:value-of
select="name()"/>"
</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