Outputting Code - Supporting Stylesheets
(Page 9 of 19 )
Supporting stylesheets allow you to isolate generic templates that you can reuse in different XSLT stylesheets. For example, the SimpleDataContainer.xslt stylesheet uses the NewLine template in the Support.xslt file. Using this template doesn’t save much typing, but it keeps you (and later programmers) from having to remember the hexadecimal values of the carriage-return and line-feed characters:
<xsl:template name="NewLine">
<xsl:text> </xsl:text>
</xsl:template>
As another example, you’ll frequently need to control commas in lists. This is an example of when you might need to output this:
{0, 1, 2}
You can control the comma at the beginning or the end of the list, but the CommaIfNotFirst template is theoretically more efficient than CommaIfNotLast:
<xsl:template name="CommaIfNotFirst">
<xsl:if test="position()!=1">, </xsl:if>
</xsl:template>
<xsl:template name="CommaIfNotLast">
<xsl:if test="position()!=last()">, </xsl:if>
</xsl:template>
NOTE: XPath has a number of useful functions. If you have trouble finding references to them in the .NET Help, look up the “xslt, reference” section in the index. This will get you into the fairly good XSLT documentation. Appendix A also discusses XPath functions. |
The SimpleDataContainer.xslt stylesheet uses the FileOpen template in Support.xslt to supply a consistent header at the top of the output files, which you can adjust to your liking. When you make a change, the change will appear in all stylesheets importing or including Support.xslt. This template needs to run as part of a stylesheet that has the fileName stylesheet parameter or variable. If not run with this available, you’ll get XSLT errors. If you aren’t sure this parameter will be available, explicitly pass it as a template parameter because not passing a template parameter isn’t considered an error. Accessing a variable that doesn’t exist, however, is an XSLT error.
The FileOpen template outputs standard Option Strict, Option Explicit, and Imports System statements into your output file’s header. The imports parameter passes additional imports as a comma-delimited string. The named template RecursiveImports breaks this string down and outputs the Imports statement. The template then outputs a comment block containing the filename:
<xsl:template name="FileOpen">
<xsl:param name="imports" />
Option Strict On
Option Explicit On
Imports System
<xsl:call-template name="RecursiveImports">
<xsl:with-param name="imports" select="normalize-space($imports)"/>
</xsl:call-template>
#Region "Description"
' <xsl:call-template name="StripPath">
<xsl:with-param name="fname" select="$fileName" />
</xsl:call-template>
#End Region
</xsl:template>
TIP: You could include the generation date and time, but that would cause source control to always see the file as new and unnecessarily mark the file as changed. Because of this complication, I suggest you don’t include the date and time in the header. |
Using Recursive Templates
How do you break down a comma-delimited string if you can’t reassign the values of variables? You use a recursive template that creates a new variable every time you call it. This is an advanced aspect of XSLT that can be handy at times. String manipulation such as the previous two samples is a convenient place to use recursive templates. The RecursiveImports template outputs an Import statement for each namespace in a comma-delimited list:
<xsl:template name="RecursiveImports">
<xsl:param name="imports"/>
<xsl:variable name="remaining" select="substring-after($imports,',')"/>
<xsl:choose>
<xsl:when test="string-length($remaining) > 0">
Imports <xsl:value-of select="normalize-space(substring-before($imports,','))"/>
<xsl:call-template name="RecursiveImports">
<xsl:with-param name="imports" select="$remaining"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
Imports <xsl:value-of select="normalize-space($imports)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
TIP: I included this example so you have a reference of when you need recursion. Don’t worry if you get a little lost in it—you don’t need to use recursion often. |
The imports parameter contains the comma-delimited string. The template creates a new variable named remaining that contains everything after the first comma. An xsl:choose directive tests whether the remaining variable contains anything. The remaining variable would be empty if the imports parameter ended with a comma or didn’t contain a comma. If the remaining variable isn’t zero length, the template outputs an Imports statement with the contents before the comma. The normalize-space XPath function is basically the same as the .NET String class’s Trim method. After outputting the Imports statement, the template calls itself, passing the contents of the $remaining variable as the imports parameter. This effectively calls the RecursiveImports for every item in the comma-delimited string, with the imports parameter chopped off at the first comma each time. The last time it’s called, the imports parameter contains no comma, the xsl:otherwise directive is processed, and the template simply outputs the Imports statement.
The trick to creating recursive templates (or using recursion in any language) is to provide a clear end to the recursion. In this case, you chop off string elements until there’s no comma left. If you fail to provide this clear end point, the recursion is endless, or unbounded.
TIP: When processing XSLT in .NET, if you encounter endless recursion, your application will appear to freeze up (the action will take significantly too long). If the debugger is running, you can break using Ctrl+Break and end the process. |
You can use the same approach to strip a path off a full filename, resulting in just the filename. In the file header, the name of the file is useful. The directory where you generate it may not be its permanent location, so it isn’t included. The StripPath template is otherwise similar to the RecursiveImports template:
<xsl:template name="StripPath">
<xsl:param name="fname"/>
<xsl:variable name="remaining" select="substring-after($fname,'\')"/>
<xsl:choose>
<xsl:when test="string-length($remaining) > 0">
<xsl:call-template name="StripPath">
<xsl:with-param name="fname" select="$remaining"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$fname"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
NOTE: \ is the escape sequence for the backslash character. You have to escape the backslash in at least some cases. |
Capping Off XSLT Code Generation
The previous sections took XSLT code generation out of the theoretical to show how you can use it in the real world with advanced XSLT techniques. To make XSLT code generation easy, you’ll want to use the following:
- A single entry-level template accessing the root
- A single high-level processing template called in the context you’re processing
- A named template (xsl:call-template) for each region in the output after you organize your sample code into regions
- Additional nested named and match templates (xsl:apply-templates) as regions become longer and more complex
- xsl:value-of for inserting values
- xsl:if when a conditional block has only one option and xsl:choose when there are multiple conditional blocks
- Match templates for any looping elements unless the output is nearly trivial, in which case you should use an xsl:for-each
- A separate supporting stylesheet to contain reusable utility templates
You’ll see more samples of XSLT code generation throughout the book. I use it for examples because it’s more concise than the other code generation mechanisms.
This chapter is from Code Generation in Microsoft .NET by Kathleen Dollard (Apress, 2004, ISBN: 1590591372). Check it out at your favorite bookstore today.
Buy this book now. |
Next: Exploring Details of Brute-Force Generation >>
More .NET Articles
More By Apress Publishing