Introduction
In some stage of development (professional and/or mental), you might find yourself seeking sanctuary in the realms of auto-generated code.
The goal of this article is to supply you with the tools to create a custom, generic, home-made code-generator. One that will produce interfaces,
data-objects (and even concrete code if needed) in one or more languages of choice, on any platform that supports XML/XSL.
Target audience (On the technical side)
The article is targeted at developers who have sufficient knowledge in XML and XSLT/XPATH. A short program is provided in C# and
the output of the below examples will be in C# and C++.
How it works
The suggested solution makes use of XML as the API input, along with a corresponding XSL translator. Combining the two together using a third component produces
the desired output. This allows us to have complete control over the generated products.
Sleeves up
Before we take a dive, I'll just mention that the examples given here are partial. The demo project
contains the full code along with a batch-file that will help build the output files.
Let's get this show on the road.
Hands on
In the following examples, we'll define a simple RemoteControl
API.
Example #1
Let's start with the Play
and Stop
abilities.
The input XML would be:
<class name="RemoteControl">
<method name="Play"/>
<method name="Stop"/>
</class>
And if we look at the output of the C# code-generator, we'd like to see something like this:
public interface IRemoteControl
{
void Play();
void Stop();
}
Now, let's look at the XSL translator - that's where the magic happens:
<xsl:template match="class">
public interface I<xsl:value-of select="@name"/>
{
<xsl:for-each select="method">
void <xsl:value-of select="@name"/>();
</xsl:for-each>
}
</xsl:template>
Breakdown
This is pretty straightforward, I guess. For each class
node in the input XML, output the class, iterate all its methods, and surround them
with a hard-coded void
on one side and ();
on the other.
Example #2
Using the same XML input, let's rewrite our XSL translator to produce both C# and C++ interfaces:
<xsl:template match="/">
<xsl:apply-templates select="class" mode="csharp"/>
<xsl:apply-templates select="class" mode="cpp"/>
</xsl:template>
<xsl:template match="class" mode="csharp">
// C#
public interface I<xsl:value-of select="@name"/>
{
<xsl:for-each select="method">
void <xsl:value-of select="@name"/>();
</xsl:for-each>
}
</xsl:template>
<xsl:template match="class" mode="cpp">
// C++
class I<xsl:value-of select="@name"/>
{
public:
<xsl:for-each select="method">
virtual void <xsl:value-of select="@name"/>() = 0;
</xsl:for-each>
};
</xsl:template>
Breakdown
We start at the root of the XML (match="/"
) and for each class
node, we produce its C# representation, followed by its C++ representation.
In this example, the mode
attribute was used as the language-type. It is, of course, possible to pass the language-type
as a parameter to the xsl:template
, or work with a global XSL value, or even get it as a command-line argument, etc.
The result output
public interface IRemoteControl
{
void Play();
void Stop();
}
class IRemoteControl
{
public:
virtual void Play() = 0;
virtual void Stop() = 0;
};
Speaking of the code generator, let's see how the examples are produced.
The output code-generator
So we have the XML and XSL input files, but we'll need something that will combine them together and produce the output file.
Here's a simple, short C# program that will do the job:
class CodeGen
{
static void Main( string[] args )
{
XPathDocument xmlDoc = new XPathDocument( args[0] );
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load( args[1] );
XmlTextWriter writer = new XmlTextWriter( args[2], null );
xslt.Transform( xmlDoc, writer );
}
}
Notes
- The syntax is: CodeGen.exe <XML filename> <XSL filename> <output filename>.
- The code breakdown would simply be: load the XML and XSL files, transform them using
XslCompiledTransform
, and write the result to an output file. - I encourage you to look into
XsltArgumentList
,
which may be used in order to pass parameters to XslCompiledTransform
. - Obvious, but worth mentioning, this tiny program does not contain any form of error handling. Feel free to decorate it with your own.
Example #3
I assume the general idea is already clear. This third and final example is a bit more complex / comprehensive. Its main goal is to give
a wider angle at what can be achieved using this paradigm.
So, let's enrich our XML input with return values, some more methods, and the occasional parameters:
<class name="RemoteControl">
<method name="Play" retval="void" />
<method name="Stop" retval="void" />
<method name="GetTrackNumber" retval="int"/>
<method name="GetTrackLength" retval="double">
<param name="deviceNumber" type="int" />
<param name="trackNumber" type="int" />
</method>
</class>
Now let's update our XSL translator.
We would like our output to, again, generate C# and C++ results. This time, for each language, we will generate both an interface and a stub class (e.g., for unit testing):
<xsl:template match="/">
<xsl:apply-templates select="class" mode="csharp"/>
<xsl:apply-templates select="class" mode="cpp"/>
</xsl:template>
<xsl:template match="class" mode="csharp">
//////////////////////////////////////////
// C# section
public interface I<xsl:value-of select="@name"/>
{
<xsl:for-each select="method">
<xsl:apply-templates select="."/>;
</xsl:for-each>
}
public class <xsl:value-of select="@name"/>Stub : I<xsl:value-of select="@name"/>
{
<xsl:for-each select="method">
public <xsl:apply-templates select="."/> { throw new NotImplementedException(); }
</xsl:for-each>
}
</xsl:template>
<xsl:template match="class" mode="cpp">
//////////////////////////////////////////
// C++ section
class I<xsl:value-of select="@name"/>
{
public:
<xsl:for-each select="method">
virtual <xsl:apply-templates select="."/> = 0;
</xsl:for-each>
};
class <xsl:value-of select="@name"/>Stub :
public I<xsl:value-of select="@name"/>
{
public:
class NotImplementedException {};
<xsl:for-each select="method">
virtual <xsl:apply-templates select="."/> { throw new NotImplementedException(); }
</xsl:for-each>
};
</xsl:template>
<xsl:template match="method"><xsl:value-of select="@retval"/>
<xsl:value-of select="@name"/>(<xsl:apply-templates select="param"/>)
</xsl:template>
<xsl:template match="param"><xsl:if test="position() > 1">,
</xsl:if><xsl:value-of select="@type"/>
<xsl:value-of select="@name"/></xsl:template>
Breakdown
The MAIN
section takes care of iterating through all class
nodes (under the root of the input XML file).
For each node, it will generate the C# representation, followed by the C++ one.
The C#
and C++
sections both produce an interface and a stub class for a given XML node.
Note that both languages make use of the COMMON
section. That's because the core of the method-signature in both languages is identical (well.. not in real life,
but in this example, it is). A method may optionally have parameters. Those will also be processed in the same way for both languages.
The result output
public interface IRemoteControl
{
void Play();
void Stop();
int GetTrackNumber();
double GetTrackLength(int deviceNumber, int trackNumber);
}
public class RemoteControlStub : IRemoteControl
{
public void Play() { throw new NotImplementedException(); }
public void Stop() { throw new NotImplementedException(); }
public int GetTrackNumber() { throw new NotImplementedException(); }
public double GetTrackLength(int deviceNumber,
int trackNumber) { throw new NotImplementedException(); }
}
class IRemoteControl
{
public:
virtual void Play() = 0;
virtual void Stop() = 0;
virtual int GetTrackNumber() = 0;
virtual double GetTrackLength(int deviceNumber, int trackNumber) = 0;
};
class RemoteControlStub : public IRemoteControl
{
public:
class NotImplementedException {};
virtual void Play() { throw new NotImplementedException(); }
virtual void Stop() { throw new NotImplementedException(); }
virtual int GetTrackNumber() { throw new NotImplementedException(); }
virtual double GetTrackLength(int deviceNumber,
int trackNumber) { throw new NotImplementedException(); }
};
What's next?
The examples here demonstrate the basic concept of developing a proprietary code-generator.
These concepts may be enriched with one or more of the following:
- Auto-generated data structures. Ones that may also be used by the auto-generated interfaces.
- A .NET project might use .xsd files along with xsd.exe for this task.
- Automate the creation of a set of input/output files.
- You might want to auto-generate a project file (e.g., .csproj) that will contain all the auto-generated source-code files. You may then build
this project as part of a final validation phase.
- These build results may be used as the final compiled binaries.
- As a by-product, they may also serve as error detectors in case there are compilation errors.
- Static arguments to XSL (e.g., the current time), or even ones that are passed from command-line.
- Dynamic code objects that may be passed to XSL (take a closer look at
XsltArgumentList
, namely: XsltArgumentList.AddExtensionObject
). - Dynamic scripts that include code which is complied and executed on the fly (see XSLT Stylesheet Scripting using <msxsl:script>).
- ... and so on and so forth (subject to your wildest imagination).
It is now left to your skills and creativity to tailor a solution that will fit your specific needs.
I think it's plain to see - The Sky's The Limit!.
History
- v1.0 (02-Oct-2011).
- v1.1 (04-Oct-2011) - Added a link to the demo project.