Use T4 Templates For XSLT Transformation

Suppose you have XSLT style sheets that produce some code from XML files. Now suppose you want to integrate these style sheets into your Visual Studio solution to generate code artefacts. You could call command line tools from the pre-build event to process the XSLT files. Alternatively, you could somehow bring the XSLT markup into a T4 template and let the regular Visual Studio integration initiate execution of the template. That is actually not that difficult, but there is one major obstacle.

So at first you might ask “why would I want to do this”? Applying the XSLT transformation with a command line tool from the pre-build event has some drawbacks:

  1. The XSLT transformation will be performed at each build, even if the input file has not changed.
  2. The command line tool must be accessible whenever a build is started. This includes build servers. If the command line tool is not accessible, the build will fail.
  3. If the build event is not used, the transformation must be initiated manually. In large solutions with many XSLT transformations, manually starting the transformations can be very annoying – or prone to forgetting some XSLT files.

Using T4 for these transformations has the following advantages:

  1. The T4 transformation will only be performed when the T4 file is saved or Text Templating File Generator is manually called.
  2. The T4 transformation will only occur at developement time, i.e. not necessarily on a build server. Even if some required components are not available on the build server, the build can still succeed, since the generated code artefacts have already been created.
  3. Visual Studio has a button called “Transform All T4 Templates” (at least VS2012 has). Using this button, all T4 templates can be processed at once. This is quite convenient.

In my opinion, performing code generation and transformation using T4 templates is easier and more convenient than directly using XSLT. A T4 template that for XSLT transformation could look like this:

<#@ template hostspecific="true" inherits="MunirHusseini.T4Xslt" language="C#" #>
<#@ output extension="html" #>
<#@ assembly name="MunirHusseini.T4Xslt.dll" #>
<?xml version="1.0" encoding="UTF-8"?>
<!-- input: $(ProjectDir)\todo-list.xml -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="/">
		<HTML>
			<BODY STYLE="font-family:Arial, helvetica, sans-serif; font-size:12pt; background-color:#EEEEEE">
				<xsl:for-each select="todo-list/item">
					<DIV STYLE="background-color:orange; color:white; padding:4px">
						<xsl:value-of select="due"/> - 
						<SPAN STYLE="font-weight:bold; color:black">
							<xsl:value-of select="name"/>
						</SPAN>
					</DIV>
					<DIV STYLE="padding:0.4em 0 0.4em 2em; font-size:10pt">
						<xsl:value-of select="description"/>
						<SPAN STYLE="font-style:italic">(effort: <xsl:value-of select="effort"/>)</SPAN>
					</DIV>
				</xsl:for-each>
			</BODY>
		</HTML>
	</xsl:template>
</xsl:stylesheet>

Now the question is “how do we do that”? The idea is simple: put the XSLT markup into the T4 file “as is”, that means as if the T4 template should output the XSLT markup itself. Now we somehow need to intercept the transformation of the T4 template and use the output of the T4 transformation as input for the XSLT transformation. The result of the XSLT transformation will then be returned as the final output of the transformation. To illustrate this, let’s think of in terms of class inheritance. Assume that the regular T4 transformation is done by a class called T4Base and our XSLT transformation is done by a child class called T4Xslt:

class T4Base
{	
	protected StringBuilder Buffer{ get; private set; }
	// Omitted: all members (e.g. Write() and WriteLine()) needed 
	// for inheriting classes to write code into the buffer.

    public virtual string TransformText()
    {
        return Buffer.ToString(); // return regular T4 output.
    }
	
    public virtual void Initialize()
	{
	}	
}

class T4Xslt: T4Base
{
    public override string TransformText()
    {
        // The output of the base class is the XSLT markup.
        var xslt = base.TransformText();

        // The location of the XML file will later be defined in the template text.
        var xml = File.OpenText("mydata.xml");

        // Apply XSLT transformation with standard .NET classes.
        var result = TransformXslt(xslt, xml);

        return result;
    }
	
    public override void Initialize()
	{
		base.Initialize();
	}
}

Simple enough, isn’t it? Well, it isn’t. Consider this: the Text Templating File Generator generates a C# class that already overrides the TransformText method from above and the generated TransformText does not call the base class so we could hook up somewhere. Thus, we won’t be able to directly use the code from above. Fortunately, the generated C# class does call its parent’s Initialize method. In the parent’s Initialize, we can start the code generation of the child by calling TransformText, executing the XSLT transformation from above, save the results to the buffer and then turn off any further writing to the buffer. When the Text Templating File Generator later calls the child’s TransformText, it will return the buffer, which is filled with the result of the XSLT transformation.

class T4Base
{	
	internal bool DisableWriting { get; set; }
	
	private StringBuilder Buffer;
	// Omitted: all members (e.g. Write() and WriteLine()) needed 
	// for inheriting classes to write code into the buffer.

    public virtual string TransformText()
    {
        return Buffer.ToString(); // return regular T4 output.
    }
	
    public virtual void Initialize()
	{
	}	
	
	public void Write(string textToAppend)
	{
		if (DisableWriting) return;
		
		// ... append text to buffer
	}
}

class T4Xslt: T4Base
{	
    public override void Initialize()
	{
		var stylesheet = TransformText();
		var result = TransformXslt(stylesheet);

		Buffer.Clear();
		Buffer.Append(result);

		DisableWriting = true;
	}
}

Using this T4Xslt class as a base class for T4 templates allows us to directly place XSLT markup into the T4 file and process it. The name of the input XML file can be placed anywhere in the T4 template as well and must be parsed before the XSLT transformation. In the example above, you can see the Visual Studio macro $(ProjectDir). This post describes how to resolve such macros.

The code from above and some more or a pre-compiled version can be downloaded from the T4 XSLT project at SourceForge. Suite yourselves.

Freelance full-stack .NET and JS developer and architect. Located near Cologne, Germany.

Leave a Reply

Your email address will not be published. Required fields are marked *