Another XSLT sweetener

This post is more than 15 years old.

Posted at 08:00 on 24 November 2008

Update: There is a memory leak issue with using tags in your stylesheets. For details on the problem and how to mitigate it, see here.

XSLT can be pretty perplexing to newbies. It gives you a small number of string manipulation and other functions, but the selection at your disposal often seems very limiting. It also doesn't have variables, so unless you're familiar with recursion and other concepts of pure functional programming, it can seem very much like a language that doesn't have nouns. It is easy to wonder how you could possibly do anything fancy with it.

Fortunately, the .NET framework allows you to extend XSLT, using the Microsoft-specific extension. So, for a simple example, here is a stylesheet that will give you a comma-separated list of the titles of all the items in an RSS feed, converted to uppercase:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:my="http://example.com/my-stuff"
  exclude-result-prefixes="msxsl">
 
  <msxsl:script language="c#" implements-prefix="my">
    <![CDATA[
      public string join(string separator, XPathNodeIterator nodes)
      {
        StringBuilder sb = new StringBuilder();
        foreach (XPathNavigator node in nodes) {
          if (sb.Length > 0) {
            sb.Append(separator);
          }
          sb.Append(node.Value.ToUpper());
        }
        return sb.ToString();
      }
    ]]>
  </msxsl:script>
 
  <xsl:template match="/">
    <xsl:value-of select="my:join(', ', rss/channel/item/title)"/>
  </xsl:template>
</xsl:stylesheet>

In order to use this, however, you need to specifically enable it when you are performing your transform. To do this, you need to pass a XsltSettings object in the call to your XmlCompiledTransform's Load method:

var transform = new XslCompiledTransform();
transform.Load("test.xsl", new XsltSettings(true, true),
    new XmlUrlResolver());
transform.Transform("test.rss", new XmlTextWriter(Console.Out));

An alternative, cleaner approach is to use an extension object, which has the added advantage that you can use Linq and other C# 3.0 goodies as well:

var transform = new XslCompiledTransform();
var transformArgs = new XsltArgumentList();
transformArgs.AddExtensionObject
    ("http://example.com/my-stuff", new MyExtensions());
transform.Load(@"test.xsl");
transform.Transform(@"test.rss",
    transformArgs, new XmlTextWriter(Console.Out));

where your extension object might look like this:

using System;
using System.Linq;
using System.Xml.XPath;
 
namespace ConsoleApplication1
{
  public class MyExtensions
  {
    public string join(string separator, XPathNodeIterator iterator)
    {
      return String.Join(separator,
        (from XPathNavigator i in iterator
         select i.Value.ToUpper()).ToArray()
      );
    }
  }
}

and your stylesheet might look like this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
    xmlns:my="http://example.com/my-stuff"
    exclude-result-prefixes="msxsl">
 
  <xsl:template match="/">
    <xsl:value-of select="my:join(', ', rss/channel/item/title)"/>
  </xsl:template>
</xsl:stylesheet>

Your code needs to be executing under full trust in order to use XSLT extension objects.