james mckay dot net

because there are few things that are less logical than business logic
29
Apr

XsltArgumentList violates the Single Responsibility Principle

Can somebody please enlighten me as to why XsltMessageEncountered and AddExtensionObject are members of System.Xml.Xsl.XsltArgumentList?

It seems like a violation of the Single Responsibility Principle to me — neither of them are anything to do with the list of arguments that you pass into your transform. It would make more sense if they were members of XslCompiledTransform instead.

23
Feb

Sometimes, sweeteners can be a bit sour

There’s a gotcha with including scripts in your XSL stylesheets using the <msxsl:script> tag that I blogged about a couple of months ago.

Whenever you load in a stylesheet into an XslCompiledTransform object, it compiles the scripts into a new in-memory assembly using System.CodeDom. Every time you re-load the stylesheet, another assembly is created, and as assemblies are not garbage collected and can only be unloaded by unloading your entire application domain, this is a potentially pretty serious memory leak that can quickly bring your application to its knees.

According to Microsoft, this only affects ASP.NET 1.0, but people have reported problems with ASP.NET 1.1 and 2.0 through 3.5 as well.

There are two solutions to this. One is to use XSL extension objects instead of scripts. The other is to use a singleton design pattern, only creating your XslCompiledTransform once and caching it for future use. In fact, you should be doing this for performance reasons anyway, as parsing and compiling an XSL stylesheet takes time.

A class such as this should prove useful. It caches your XSL stylesheets so that each one will only be parsed into an XslCompiledTransform the first time:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Xml;
using System.Xml.Xsl;

public static class XslCache
{
    private static Dictionary<string, XslCompiledTransform> cache
        = new Dictionary<string, XslCompiledTransform>();

    [MethodImpl(MethodImplOptions.Synchronized)]
    public static XslCompiledTransform GetTransform(string xslFile)
    {
        // Normalise the path to the XSL file
        xslFile = new FileInfo(xslFile).FullName;
        
        // Return the cached transform if it exists.
        if (cache.ContainsKey(xslFile)) {
            return cache[xslFile];
        }
        
        // Otherwise, load the XSL stylesheet.
        var transform = new XslCompiledTransform();
        transform.Load(xslFile,
            new XsltSettings(true, true),
            new XmlUrlResolver()
        );
        
        cache.Add(xslFile, transform);
        return transform;
    }
}

XslCompiledTransform objects are threadsafe once they have been loaded.

24
Nov

Another XSLT sweetener

Update: There is a memory leak issue with using <msxsl:script> 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 <msxsl:script> 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.

17
Nov

An XSLT sweetener

If you’ve ever done anything with XSLT, you’ll no doubt be wondering why anyone would want to use such a verbose programming language, given that you need to churn out code such as this all over the place:

<xsl:element name="foo">
  <xsl:attribute name="bar">
    <xsl:value-of select="/some/xpath/@expression" />
  </xsl:attribute>
</xsl:element>

However, did you know that you can do this instead? Much cleaner and easier to read:

<foo bar="{/some/xpath/@expression}" />

(Hat tip: Ned Batchelder)