DekGenius.com
[ Team LiB ] Previous Section Next Section

7.3 Using XSLT

System.Xml.Xsl is an extremely small namespace. Although it consists of just two exceptions, three types, and two interfaces, it still manages to provide full support for version 1.0 of the W3C XSLT specification, as well as providing additional support for embedded scripting in C#, Visual Basic .NET, and JScript.

7.3.1 Transforming an XML Document

Example 7-5 shows one of the simplest possible XSLT-related programs in C#. Given a source filename, a stylesheet filename, and a destination filename, it transforms the source into the destination using the stylesheet. It will work with any XML source file and any XSLT stylesheet.

Example 7-5. One of the simplest possible XSLT programs
using System.Xml.Xsl;

public class Transform {
  public static void Main(string [ ] args) {
    string source = args[0];
    string stylesheet = args[1];
    string destination = args[2];
    XslTransform transform = new XslTransform( );
    transform.Load(stylesheet);
    transform.Transform(source, destination);
  }
}

I won't explain in excruciating detail how this program works, but I will point out a few important facts about the XslTransform type. It only has one property, XmlResolver, and two methods, Load( ) and Transform( ), but it is still one of the most versatile pitchers in the .NET XML bullpen.

First, the Load( ) method has eight overloads. All of them load the specified XSLT stylesheet, but each of them takes the stylesheet in a different parameter type. The stylesheet may be specified as an IXPathNavigable (such as XmlDocument), URL, XmlReader, or XPathNavigator; together, these types cover every possible way to read an XML document. For each Load( ) method that takes one of these parameters, there is another one taking an XmlResolver as the second parameter. This XmlResolver is used to resolve any stylesheets referenced in xsl:import and xsl:include elements.

xsl:import and xsl:include perform similar, but not identical, functions, Both allow you to place common templates in a separate stylesheet for easy reuse. However, xsl:import can only appear at the beginning of a stylesheet, and any imported templates have a lower priority than the template in the importing stylesheet. In contrast, xsl:include can appear anywhere in a stylesheet, and any included templates have the same priority as those in the including stylesheet.


If an overload of Load( ) with no resolver parameter is used, the default XmlResolver, XmlUrlResolver, is used; if a null instance is passed in, external namespaces are not resolved. The XmlResolver passed in is used only for purposes of loading the specified stylesheet, and is not cached for use in processing the XSL transform.

In version 1.1 of the .NET Framework, an additional parameter of type System.Security.Policy.Evidence is added to several overloads of the Load( ) method. The Evidence type provides information used to authorize assembly access and code generation for any scripts included in the stylesheet. I'll discuss scripting towards the end of this chapter.

Second, the Transform( ) method has a total of nine overloads. One takes two strings, an input URI and an output URI. The others take various combinations of IXPathNavigable or XPathNavigator as the first parameter, an XsltArgumentList as the second parameter, and nothing, a Stream, an XmlWriter, or a TextWriter as the third parameter. Two other overloads take only two parameters and return the result tree as an XmlReader, which can be navigated as you've already seen in Chapter 2.

Finally, in version 1.0 of the .NET Framework, the XmlResolver property contains the XmlResolver used when invoking the XSLT document( ) function. The document( ) function lets you process multiple source documents in a single stylesheet. This XmlResolver may be the same as the one passed in to the Load( ) method, but does not need to be. In version 1.1 of the .NET Framework, this XmlResolver instance is passed as a parameter to the Transform( ) method.

You may never need to do any more than this with XSLT but if you do, you should be aware that there is a lot more you can do with .NET and XSLT.

7.3.2 Associating a Stylesheet with an XML Document

Although it's not part of the actual XSLT specification, there is a way to associate a stylesheet with an XML document. The XML Stylesheet recommendation, Version 1.0, suggests that the xml-stylesheet processing instruction be used to link an XML document to its stylesheets:

<?xml-stylesheet href="stylesheet.xsl" type="text/xsl"?>

Although you're free to use the xml-stylesheet processing instruction in your source XML document, there is no guarantee that any given XSLT processor will do anything special with it; .NET's XslTransform, for example, does not.

You can make use of the xml-stylesheet processing instruction even though XslTransform does not use it automatically. Let's construct a program that examines the source document for an xml-stylesheet processing instruction, and transforms it according to the href contained within it. I'll just call it Transform.cs.

These familiar lines create an instance of XmlDocument and load it from the first command-line argument:

public class Transform {
  public static void Main(string [ ] args) {

    string source = args[0];
    string destination = args[1];

    XmlDocument document = new XmlDocument( );
    document.Load(source);

The next line will search the loaded XmlDocument for the XPath expression //processing-instruction('xml-stylesheet'). As you will recall, this expression will match any processing instruction with the target xml-stylesheet. If none is found, SelectSingleNode( ) will return a null instance:

XmlProcessingInstruction stylesheetPI = 
  (XmlProcessingInstruction)document.SelectSingleNode(
    "//processing-instruction('xml-stylesheet')");

After determining that the xml-stylesheet processing instruction is present, these lines declare and initialize a couple of variables that will be used in a moment:

if (stylesheetPI != null) {
  char [ ] splitChars = new char [ ] { ' ', '=' };
  char [ ] quoteChars = new char [ ] { '"', '\'' };
  string stylesheet = null;

The XmlProcessingInstruction has a Data property, the contents of which are everything after the target. In this case, the data is href="stylesheet.xsl" type="text/xsl". Here, the string.Split( ) method splits the string on every space and equals sign, returning an array containing four elements: href, "stylesheet.xsl", type, and "text/xsl":

String [ ] stylesheetParts = stylesheetPI.Data.Split(splitChars);

The content of the xml-stylesheet processing instruction may look like two attributes, but it's not. It just happens to look that way for this particular processing instruction. In general, a processing instruction's data format is entirely dependent on the expectations of the processor that is consuming it. In .NET, you have to use XmlProcessingInstruction.Data to retrieve the pseudoattributes, and then do the work of parsing them into name/value pairs yourself.


Before proceeding, you need to ensure that the xml-stylesheet processing instruction you've located is one that links the document to an XSLT stylesheet. Since the xml-stylesheet processing instruction can also be used for CSS stylesheets, for example, this line checks the processing instruction's Data property to ensure that it contains the string text/xsl:

if (stylesheetPI.Data.IndexOf("text/xsl") != -1) {

Once you have verified that the processing instruction does specify an XSLT stylesheet, you then need to find the name of the stylesheet referenced. You know that the array should contain four items, and the item following href should be the name of the stylesheet. These lines of code pluck that item from the array, trimming off any quote characters at the start and end:

int indexOfHref = Array.IndexOf(stylesheetParts,"href");
if (indexOfHref != -1) {
  stylesheet = stylesheetParts[indexOfHref+1].Trim(quoteChar);
}

If, at the end of all this processing, the stylesheet is still not an empty string, you're ready to use it to transform the source document much as it was done in the first example:

if (stylesheet != null) {
  XslTransform transform = new XslTransform( );
  transform.Load(stylesheet);
  transform.Transform(source, destination);
}

Example 7-6 shows the complete source code listing for Transform.cs.

Example 7-6. Using the xml-stylesheet processing instruction
using System.Xml;
using System.Xml.Xsl;

public class Transform {
  public static void Main(string [ ] args) {

    string source = args[0];
    string destination = args[1];

    XmlDocument document = new XmlDocument( );
    document.Load(source);

    XmlProcessingInstruction stylesheetPI =    
     (XmlProcessingInstruction)document.SelectSingleNode(
        "//processing-instruction('xml-stylesheet')");

      if (stylesheetPI != null) {
      char [ ] splitChars = new char [ ] { ' ', '=' };
      char [ ] quoteChars = new char [ ] { '"', '\'' };
      string stylesheet = null;

      string [ ] stylesheetParts = stylesheetPI.Data.Split(splitChars);

      if (stylesheetPI.Data.IndexOf("text/xsl") != -1) {
        int indexOfHref = Array.IndexOf(stylesheetParts,"href");

        if (indexOfHref != -1) {
          stylesheet = stylesheetParts[indexOfHref+1].Trim(quoteChar);
        }
      }

      if (stylesheet != null) {
        XslTransform transform = new XslTransform( );
        transform.Load(stylesheet);
        transform.Transform(source, destination);
      }
    }
  }
}

The xml-stylesheet processing instruction can also specify the medium to which it applies with the media pseudoattribute; this allows you to transform the source document differently, depending on who's asking. Given what you've seen in Example 7-5, it should be fairly easy to figure out the additional steps to do that. Remember that more than one node may match the //processing-instruction('xml-stylesheet') XPath expression.

I've rewritten the Main( ) method in Example 7-7, with the changes highlighted. Simply put, it executes the transformation for every xsl-stylesheet processing instruction with media pseudoattribute equal to "printer" or no media pseudoattribute. Note that if there is more than one matching processing instruction, it will overwrite the destination file each time.

Example 7-7. Using the xml-stylesheet processing instruction with different media types
public static void Main(string [ ] args) {

  string source = args[0];
  string destination = args[1];

  XmlDocument document = new XmlDocument( );
  document.Load(source);

  XmlNodeList nodeList = 
    document.SelectNodes("//processing-instruction('xml-stylesheet')");

  foreach (XmlProcessingInstruction stylesheetPI in nodeList) {      
    char [ ] splitChars = new char [ ] { ' ', '=' };
    char [ ] quoteChars = new char [ ] { '"', '\'' };
    string stylesheet = null;

    string[ ] stylesheetParts = stylesheetPI.Data.Split(splitChars);

    if (stylesheetPI.Data.IndexOf("text/xsl") != -1) {
      int indexOfHref = Array.IndexOf(stylesheetParts,"href");
      if (indexOfHref != -1) {
        int indexOfMedia = Array.IndexOf(stylesheetParts,"media");
        string media = null;
        if (indexOfMedia != -1) {
          media = stylesheetParts[indexOfMedia+1].Trim(quoteChars);
        }
        if (media == null || media == "printer") {
          stylesheet = stylesheetParts[indexOfHref+1].Trim(quoteChars);
        }
      }

      if (stylesheet != null) {
        XslTransform transform = new XslTransform( );
        transform.Load(stylesheet);
        transform.Transform(source, destination);
      }
    }
  }
}

7.3.3 Working with a Stylesheet Programmatically

The real magic of XSLT in .NET comes from the fact that an XSLT stylesheet is just an XML document; therefore, you can create and manipulate the stylesheet just like any other XML document, using the tools you're already familiar with.

7.3.3.1 Creating a stylesheet

Example 7-8 shows a program that creates the stylesheet in Example 7-1 using XmlWriter. It's fairly straightforward, so I'll let the code speak for itself.

Example 7-8. Creating a stylesheet programmatically
using System;
using System.IO;
using System.Xml;
using System.Xml.Xsl;

public class CreateStylesheet {
  private const string ns = "http://www.w3.org/1999/XSL/Transform";

  public static void Main(string [ ] args) {
    XmlTextWriter writer = new XmlTextWriter(Console.Out);
    writer.Formatting = Formatting.Indented;

    writer.WriteStartDocument( );

    writer.WriteStartElement("xsl","stylesheet",ns);
    writer.WriteAttributeString("version","1.0");

    writer.WriteStartElement("xsl:output");
    writer.WriteAttributeString("method","html");
    writer.WriteEndElement( );

    CreateRootTemplate(writer);
    CreateInventoryTemplate(writer);
    CreateDateTemplate(writer);
    CreateItemsTemplate(writer);
    CreateItemTemplate(writer);

    writer.WriteEndElement( ); // xsl:stylesheet
    writer.WriteEndDocument( );
  }

  private static void CreateRootTemplate(XmlWriter writer) {

    writer.WriteStartElement("xsl:template");
    writer.WriteAttributeString("match","/");

    writer.WriteStartElement("html");

    writer.WriteStartElement("head");

    writer.WriteStartElement("title");
    writer.WriteString("Angus Hardware | Online Catalog");
    writer.WriteEndElement( ); // title

    writer.WriteEndElement( ); // head

    writer.WriteStartElement("xsl:apply-templates");

    writer.WriteEndElement( ); // xsl:apply-templates
    writer.WriteEndElement( ); // html
    writer.WriteEndElement( ); // xsl:template
  }

  private static void CreateInventoryTemplate(XmlWriter writer) {
    writer.WriteStartElement("xsl:template");
    writer.WriteAttributeString("match","inventory");

    writer.WriteStartElement("body");
    writer.WriteAttributeString("bgcolor","#FFFFFF");

    writer.WriteStartElement("h1");
    writer.WriteString("Angus Hardware");
    writer.WriteEndElement( ); // h1

    writer.WriteStartElement("h2");
    writer.WriteString("Online Catalog");
    writer.WriteEndElement( ); // h2

    writer.WriteStartElement("xsl:apply-templates");
    writer.WriteEndElement( );

    writer.WriteEndElement( ); // body
    writer.WriteEndElement( ); // xsl:template
  }

  private static void CreateDateTemplate(XmlWriter writer) {
    writer.WriteStartElement("xsl:template");
    writer.WriteAttributeString("match","date");

    writer.WriteStartElement("p");

    writer.WriteString("Current as of ");
    
    writer.WriteStartElement("xsl:value-of");
    writer.WriteAttributeString("select", "@month");
    writer.WriteEndElement( ); // xsl:value-of

    writer.WriteString("/");

    writer.WriteStartElement("xsl:value-of");
    writer.WriteAttributeString("select", "@day");
    writer.WriteEndElement( ); // xsl:value-of

    writer.WriteString("/");

    writer.WriteStartElement("xsl:value-of");
    writer.WriteAttributeString("select", "@year");
    writer.WriteEndElement( ); // xsl:value-of

    writer.WriteEndElement( ); // p
    writer.WriteEndElement( ); // xsl-template
  }

  private static void CreateItemsTemplate(XmlWriter writer) {
    writer.WriteStartElement("xsl:template");
    writer.WriteAttributeString("match","items");

    writer.WriteStartElement("p");
    writer.WriteString("Currently available items:");
    writer.WriteEndElement( ); // p

    writer.WriteStartElement("table");
    writer.WriteAttributeString("border","1");

    writer.WriteStartElement("tr");

    writer.WriteStartElement("th");
    writer.WriteString("Product Code");
    writer.WriteEndElement( ); // th

    writer.WriteStartElement("th");
    writer.WriteString("Description");
    writer.WriteEndElement( ); // th

    writer.WriteStartElement("th");
    writer.WriteString("Unit Price");
    writer.WriteEndElement( ); // th

    writer.WriteStartElement("th");
    writer.WriteString("Number in Stock");
    writer.WriteEndElement( ); // th

    writer.WriteStartElement("xsl:apply-templates");
    writer.WriteEndElement( ); // xsl:apply-templates

    writer.WriteEndElement( ); // tr
    writer.WriteEndElement( ); // table
    writer.WriteEndElement( ); // xsl:template
  }

  private static void CreateItemTemplate(XmlWriter writer) { 
    writer.WriteStartElement("xsl:template");
    writer.WriteAttributeString("match","item");

    writer.WriteStartElement("tr");

    writer.WriteStartElement("td");
    writer.WriteStartElement("xsl:value-of");
    writer.WriteAttributeString("select","@productCode");
    writer.WriteEndElement( ); // xsl:value-of
    writer.WriteEndElement( ); // td

    writer.WriteStartElement("td");
    writer.WriteStartElement("xsl:value-of");
    writer.WriteAttributeString("select","@description");
    writer.WriteEndElement( ); // xsl:value-of
    writer.WriteEndElement( ); // td

    writer.WriteStartElement("td");
    writer.WriteStartElement("xsl:value-of");
    writer.WriteAttributeString("select","@unitCost");
    writer.WriteEndElement( ); // xsl:value-of
    writer.WriteEndElement( ); // td

    writer.WriteStartElement("td");
    writer.WriteStartElement("xsl:value-of");
    writer.WriteAttributeString("select","@quantity");
    writer.WriteEndElement( ); // xsl:value-of
    writer.WriteEndElement( ); // td

    writer.WriteEndElement( ); // xsl:template

    writer.WriteEndElement( ); // xsl:
  }
}

This example is just one of many ways to create a stylesheet programmatically. In this case, I've used XmlTextWriter, which I introduced in Chapter 4, to create the document and write it to a file. I also could have created an XmlTextWriter from any other sort of Stream or TextWriter; for example, I could have written the XML to a MemoryStream, and then turned around and used that MemoryStream in the constructor of an XmlTextReader, which can be passed to XsltTransform's Load( ) method.

Assuming you had something like the code in Example 7-8 in a private method called WriteStylesheet( ), the following snippet would write the stylesheet to a memory buffer, then cause it to be loaded and used to transform an XML document from an input tree to an output tree:

MemoryStream stream = new MemoryStream( );
XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8);
this.WriteStylesheet(writer);

stream.Seek(SeekOrigin.begin, 0);

XslTransform transform = new XslTransform( );
transform.Load(new XmlTextReader(stream));

transform.Transform(input, output);
7.3.3.2 Manipulating an existing stylesheet

Just like any XML document, an XSLT stylesheet can be loaded into an XmlDocument and manipulated via the DOM, or navigated with XPath. For example, if you have a stylesheet on disk but wish to change the order of some nodes, you could load it into an XmlDocument, navigate to the desired node with SelectSingleNode( ), and then detach that node from its current location and place it into its new location.

7.3.4 Scripting with XslTransform

Creating and manipulating stylesheets is all well and good if all you need to do is use XSLT's built-in abilities. But .NET's XSLT tools can do a lot more. They allow you to execute arbitrary C#, Visual Basic, or JScript code embedded in the stylesheet, pass arguments to the XSLT processor, and call back to methods in your own C# code from expressions in the XSLT code.

7.3.4.1 Embedded scripts

.NET has the ability to interpret script code embedded within the XSLT stylesheet. Example 7-9 shows the same catalog.xsl stylesheet shown previously, but with a few minor changes. I'll explain the highlighted changes.

Example 7-9. Catalog stylesheet with embedded scripting
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:angus="http://angushardware.com/"
  version="1.0">

  <msxsl:script implements-prefix="angus" language="C#">
    <![CDATA[
      decimal CalculateInventoryValue(int number, decimal unitCost) {
          return number * unitCost;
      }
    ]]>
  </msxsl:script>
    
  <xsl:output method="html"/>
    
  <xsl:template match="/">
    <html>
      <head>
        <title>Angus Hardware | Online Catalog</title>
      </head>
      <xsl:apply-templates/>
    </html>
  </xsl:template>

  <xsl:template match="inventory">
    <body bgcolor="#FFFFFF">
      <h1>Angus Hardware</h1>
      <h2>Online Catalog</h2>
      <xsl:apply-templates/>
    </body>
  </xsl:template>
    
  <xsl:template match="date">
    <p>Current as of <xsl:value-of select="@month" />/<xsl:
value-of select="@day" />/<xsl:value-of select="@year" />
    </p>
  </xsl:template>
  
  <xsl:template match="items">
    <p>Currently available items:</p>
    <table border="1">
      <tr>
        <th>Product Code</th>
        <th>Description</th>
        <th>Unit Price</th>
        <th>Number in Stock</th>
        <th>Value of Inventory</th>
      </tr>
      <xsl:apply-templates />
    </table>
  </xsl:template>

  <xsl:template match="item">
    <tr>
      <td><xsl:value-of select="@productCode" /></td>
      <td><xsl:value-of select="@description" /></td>
      <td><xsl:value-of select="@unitCost" /></td>
      <td><xsl:value-of select="@quantity" /></td>
      <td><xsl:value-of select="angus:CalculateInventoryValue(@quantity,@unitCost)"/></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

Each new section of code is explained below:

xmlns:msxsl="urn:schemas-microsoft-com:xslt"

To use the scripting capabilities of XsltTransform, the stylesheet must include the namespace urn:schemas-microsoft-com:xslt. By convention, this namespace is mapped to the msxsl prefix:

xmlns:angus="http://angushardware.com/"

Each piece of script code in the stylesheet must belong to a namespace as well. This can be thought of as equivalent to a .NET assembly namespace. In this case, I've defined the namespace http://angushardware.com/ with the prefix angus:

<msxsl:script implements-prefix="angus" language="C#">
  <![CDATA[
    decimal CalculateInventoryValue(int number, decimal unitCost) {
        return number * unitCost;
    }
  ]]>
</msxsl:script>

Now the CalculateInventoryValue( ) method is defined, to return the total value of the stock on hand for each product type. The msxsl:script element indicates that this is a block of code. The implements-prefix attribute indicates that the code is associated with the angus prefix, and the language attribute indicates that this block of code is written in C#.

Although the permissible values of the msxsl:script element's language attribute are C#, CSharp, VisualBasic, VB, JScript, and JavaScript, all code with the same implements-prefix attribute value must use the same language. The default language is JScript. The language attribute is case insensitive, so VisualBasic and visualbasic are equivalent; and C# is an alias for CSharp, just as VisualBasic is for VB and JavaScript is for JScript.


The code within this msxsl:script element is enclosed within a CDATA section; while not strictly necessary, this is strongly recommended to avoid XML parsing problems.

The CalculateInventoryValue( ) method itself should require no further explanation:

<td><xsl:value-of select="angus:CalculateInventoryValue(@quantity,@unitCost)"/></td>

In this portion of the stylesheet, the CalculateInventoryValue( ) method defined previously is actually invoked. The xsl:value-of element converts the expression in the select attribute into a text node; in this case, the expression is the call to CalculateInventoryValue( ), with the angus prefix. The parameters passed into the method are themselves XPath expressions, and they are coerced into the appropriate types (int and decimal, respectively) by XSLT.

The result of transforming the inventory.xml document with this stylesheet appears in Example 7-10, with changes highlighted.

Example 7-10. New HTML catalog output
<html xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:angus="http://angushardware.com/">
  <head>
    <META http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Angus Hardware | Online Catalog</title>
  </head>
  <body bgcolor="#FFFFFF">
    <h1>Angus Hardware</h1>
    <h2>Online Catalog</h2>
    <p>Current as of 6/22/2002</p>
    <p>Currently available items:</p>
    <table border="1">
      <tr>
        <th>Product Code</th>
        <th>Description</th>
        <th>Unit Price</th>
        <th>Number in Stock</th>
        <th>Value of Inventory</th>
      </tr>
      <tr>
        <td>R-273</td>
        <td>14.4 Volt Cordless Drill</td>
        <td>189.95</td>
        <td>15</td>
        <td>2849.25</td>
      </tr>
      <tr>
        <td>1632S</td>
        <td>12 Piece Drill Bit Set</td>
        <td>14.95</td>
        <td>23</td>
        <td>343.85</td>
      </tr>
    </table>
  </body>
</html>

The obvious difference is the new fifth column on the HTML table in the output. In addition, the msxsl and angus namespaces are included in the html element, even though they are not used in the output document.

This technique of embedding code in the stylesheet can be quite convenient, because changes to the embedded code do not require you to recompile any C# code. However, embedded code is not portable to XSLT processors that do not support the languages in question or the urn:schemas-microsoft-com:xslt namespace. For that reason, passing additional arguments to the XSLT processor becomes an interesting solution.

7.3.4.2 Adding parameters with XsltArgumentList

XsltArgumentList is a type which may be passed as a parameter to most of the XslTransform.Transform( ) overloads. It allows additional parameters and extensions to be passed to the XSLT processor.

Compared to embedded scripting, XsltArgumentList provides for better encapsulation and code reuse, enables you to keep stylesheets smaller and thus more easily maintainable, supports use of classes in other namespaces besides those supported by XslTransform, and allows node fragments to be passed to the stylesheet using XPathNavigator.

There are two ways of using XsltArgumentList. The first allows you to pass a parameter into the stylesheet, to be replaced in the output tree whenever the xsl:value-of element with that name is evaluated.

The following code would set a parameter named greeting to a value of Hello!:

XslTransform transform = new XslTransform( );
transform.Load(stylesheet);

XsltArgumentList argList = new XsltArgumentList( );
argList.AddParam("greeting","","Hello!");

transform.Transform(new XPathDocument(source), argList, new
  StreamWriter(destination));

In the stylesheet, you would first need to declare the parameter with the following element:

<xsl:param name="greeting" />

The xsl:param element can appear either at the top-level element or within an xsl:template element. The parameter thus declared will then be scoped at the level of xsl:param's parent. The name attribute is required, and is used to map the name passed in to its value.

xsl:param has an optional attribute, select, which can be used to define the parameter's value, although this defeats the purpose of XsltArgumentList.


To refer to the parameter within that scope, you can think of it as another XPath expression. The stylesheet uses the name of the parameter prefixed with a dollar sign ($) in the select attribute of an xsl:value-of element:

<xsl:value-of select="$greeting" />
7.3.4.3 Adding extensions with XsltArgumentList

In addition to direct parameter replacement, XsltArgumentList enables you to associate your own C# code with an xsl:value-of element. For example, you may wish to create a type that has a property to output today's date:

public class Utilities {
  public string Today {
    get {
      return DateTime.Now.ToString( );
    }
  }
}

The Utilities type simply has one property, Today, which returns a string representation of the current date and time. You could add other properties and methods, including methods that take parameters.

XSLT extensions only work with the methods of extension types, not their properties. However, if you know a simple trick, you can access properties just like methods. The trick to remember is that the C# compiler creates a method named get_XXX( ) for each property named XXX. So you can access a property named Today by using util:get_Today( ) in the select attribute of xsl:value-of.

Now, it's simply a matter of instantiating a Utilities object, and adding it to the XsltArgumentList. The changed lines of the source code are highlighted:

XslTransform transform = new XslTransform( );
transform.Load(stylesheet);

XsltArgumentList argList = new XsltArgumentList( );
argList.AddParam("greeting","","Hello!");
argList.AddExtensionObject("urn:Utilities", new Utilities( ));

transform.Transform(new XPathDocument(source), argList, new 
  StreamWriter(destination));

The XsltArgumentList.AddExtensionObject( ) method allows you to associate a type with a qualified name in the stylesheet. In this example, urn:Utilities is the qualified name.

In order to use Utilities.Today( ), you must make some small changes in catalog.xsl:

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:util="urn:Utilities"
version="1.0">
...
  <xsl:template match="inventory">
    <body bgcolor="#FFFFFF">
      <h1>Angus Hardware</h1>
      <h2>Online Catalog</h2>
      <p><xsl:value-of select="$greeting" /></p>
      <p><xsl:value-of select="util:get_Today( )" /></p>
      <xsl:apply-templates/>
    </body>
</xsl:template>

In these lines, the util prefix is associated with the urn:Utilities namespace, and util:get_Today( ) is invoked in the select expression of an xsl:value-of element.

The relevant section of the HTML output is shown below:

<h1>Angus Hardware</h1>
<h2>Online Catalog</h2>
<p>Hello!</p>
<p>7/27/2002 7:14:57 PM</p>
<p>Current as of 6/22/2002</p>

An extension need not be a custom type. For example, to insert the XSLT process name and process ID into the output tree, you might add a System.Diagnostics.Process instance to the XsltArgumentList:

argList.AddExtensionObject("urn:Process",Process.GetCurrentProcess( ));

And then you could call the urn:Process extension from your XSLT stylesheet:

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:util="urn:Utilities"
  xmlns:proc="urn:Process"
  version="1.0">
...
<p>Generated by <xsl:value-of select="proc:get_ProcessName( )" />,
  process ID <xsl:value-of select="proc:get_Id( )" /></p>

Be careful when making a non-custom extension available to XSLT. Instead of proc:get_ProcessName( ), you could easily have called proc:Kill( ), which would terminate the XSLT processor—and any other threads in the same process, such as a web server—in mid-stream. When you make a type available as an extension, you make all its public methods available.


    [ Team LiB ] Previous Section Next Section