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

XSL Code Generator

4.75/5 (7 votes)
4 Oct 2011CPOL5 min read 64.9K   1.4K  
An XML/XSLT based solution for auto-generating API and code.

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:

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

C#
public interface IRemoteControl
{
  void Play();
  void Stop();
}

Now, let's look at the XSL translator - that's where the magic happens:

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

XML
<!-- MAIN -->
<xsl:template match="/">
    <xsl:apply-templates select="class" mode="csharp"/>
    <xsl:apply-templates select="class" mode="cpp"/>
</xsl:template>

<!-- C# -->
<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>

<!-- C++ -->
<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
MC++
// C#
public interface IRemoteControl
{
    void Play();
    void Stop();
}

// C++
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:

C#
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
  1. The syntax is: CodeGen.exe <XML filename> <XSL filename> <output filename>.
  2. The code breakdown would simply be: load the XML and XSL files, transform them using XslCompiledTransform, and write the result to an output file.
  3. I encourage you to look into XsltArgumentList, which may be used in order to pass parameters to XslCompiledTransform.
  4. 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:

XML
<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):

XML
<!-- MAIN -->
<xsl:template match="/">
    <xsl:apply-templates select="class" mode="csharp"/>
    <xsl:apply-templates select="class" mode="cpp"/>
</xsl:template>

<!-- C# -->
<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>

<!-- C++ -->
<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>

<!-- Common -->
<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
C#
//////////////////////////////////////////
// C# section
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(); }
}

//////////////////////////////////////////
// C++ section
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.

License

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