XSLT Extension Objects

Let’s complete our examination of transformations by analyzing the XSLT extension objects. As mentioned, the XsltArgumentList class can contain both parameters and extension objects. Parameters are simply value types, whereas extension objects are instances of .NET classes. When passed to the Transform method, both parameters and extension objects can be invoked from style sheets.

The behavior of a style sheet can be extended in various ways. For example, you can use the <xsl:eval> instruction to run VBScript or JScript interpreted code. Before the advent of the .NET Framework, this was the only option available. With the .NET Framework, given the other characteristics of the XSLT processor, the <xsl:eval> instruction is by far the less interesting alternative.

In addition, in the .NET Framework, the <xsl:eval> instruction has been superseded by the <msxsl:script> element. This new instruction works in much the same way as <xsl:eval>, but it supports managed languages, thus providing access to the entire .NET Framework.

Processing Embedded Scripts

When the style sheet is loaded in the XslTransform class, all defined functions are wrapped in a class and compiled to the .NET Framework intermediate language (IL). They then become available to XPath expressions as native functions.

The .NET Framework XSLT processor accepts external scripts through the <msxsl:script> element. The script must use only XPath-compliant types even though, in most cases, type coercion is automatically provided by the processor. The type conformance is fundamental for input parameters and return values. Each script can internally use any .NET Framework type, paying some attention to the required namespaces. The following namespaces are imported by default: System, System.Text, System.Xml, System.Text.RegularExpressions, System.Xml.XPath, System.Xml.Xsl, System.Collections, and Microsoft.VisualBasic. Classes in other system namespaces can be used too, but their names must be fully qualified. For example, to use a DataSet object, you must call it System.Data.DataSet.

Important

An embedded script can’t call into a user-defined namespace. The XSLT subsystem knows nothing about dependent assemblies and so can’t reference them at compile time. To work around this issue, use extension objects.


The <msxsl:script> Instruction

The <msxsl:script> instruction has the following syntax:

<msxsl:script 
  language = "language" 
  implements-prefix = "prefix"> 
</msxsl:script>

Supported languages are C#, Visual Basic, and JScript. The language attribute is not mandatory and, if not specified, defaults to JScript. The implements-prefix attribute is mandatory, however. It declares a namespace and associates the user-defined code with it. The namespace must be defined somewhere in the style sheet. In addition, to make use of the <msxsl:script> instruction, the style sheet must include the following namespace:

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

Let’s see how to define a simple script. To start off, we’ll declare the extra namespaces in the the style sheet’s root node, as shown here:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
    xmlns:dino="urn:dino-scripts">

This declaration is necessary to be able to call the <msxsl:script> instruction. The namespace simply groups under a single roof some user-defined scripts. The prefix dino is now necessary to qualify any calls to any functions defined in a <msxsl:script> block. Script blocks can be defined as children of the <stylesheet> node, at the same level as templates.

The following script concatenates first and last names, separated by a comma:

  <msxsl:script implements-prefix="dino" language="C#">
  public string PrepareName(string last, string first)
  {  
      return last + ", " + first;
  }
  </msxsl:script>

In the body of the style sheet—typically in a template—you call the function, as follows:

  <xsl:template match="lastname">
    <TD style="border:1px solid black">
      <xsl:value-of select="dino:PrepareName(., ../firstname)" /> 
    </TD>
  </xsl:template>

If you enclose parameters in quotation marks, they will be treated as literals. To ensure that the function receives only node values, use the same expressions you would use with the select attribute of an <xsl:value-of> instruction. The preceding script runs from the context of a <lastname> node in the following schema:

<Employee>
  <lastname>...</lastname>
  <firstname>...<firstname>
</Employee>

The dot symbol (.) indicates the value of the current node, whereas ../firstname stands for the sibling of the current context node, named <firstname>.

When a function is declared, it is contained in a script block. Style sheets, however, can contain multiple blocks. All blocks are namespace-scoped and independent from each other. You can call a function defined in another block only when both functions share the same namespace and language.

Why should we use the same language to call into a function defined in another block? Isn’t the .NET Framework totally language-neutral? The explanation for this discrepancy is found under the hood of <msxsl:script>. The instruction works as a mere code runner. It groups all script blocks in one or more all-encompassing classes. Blocks with the same namespace flow in the same dynamically created class.

In light of this, calling into external blocks is only possible because both involved functions—the caller and the callee—are members of the same managed class. For the same reasons, you can’t use different languages. What the .NET Framework provides is the ability to invoke a compiled class irrespective of its source language. In no way does the .NET Framework provide you with the ability to write and compile a single class using different languages.

The CDATA Section

When an <msxsl:script> element is declared, you should enclose all of its code in a CDATA section. The main purpose of the CDATA delimitors is to protect the source code from the XML parser. A style sheet document is in fact still an XML document and as such gets parsed, as shown here:

<msxsl:script implements-prefix="dino" language="C#">
<![CDATA[
  code goes here
]]&gt; 
</msxsl:script>

Wrapped in a CDATA section, the user-defined code can contain any unescaped character that would otherwise confuse the parser. The most common example is <. If you omit the CDATA section and need to use < in a Boolean expression, you must use it in the escaped form &lt; or you’ll get an error.

Node Fragments in Transformations

As mentioned, you must always use XPath types when you pass arguments to <msxsl:script> blocks or return values from within a user-defined function. Let’s have a second look at the command we used to invoke our previously defined extension function:

<xsl:value-of select="dino:PrepareName(., ../firstname)" />

As you can see, the PrepareName function is actually passed a couple of XPathNodeIterator objects. Chapter 6 defined XPathNodeIterator objects as the .NET Framework implementation of XPath node-sets. What any function receives is always the .NET Framework type that represents the results of a particular XPath query. The XSLT processor attempts to coerce types whenever possible. In this example, the PrepareName function takes two string objects, and the processor coerces the results of the . and ../firstname expressions to string types.

When you need to process an entire node-set, declare your function to use an XPathNodeIterator argument, as shown here:

double CalculateSubTotal(XPathNodeIterator nodeset)
{
    double total = 0;

    while (nodeset.MoveNext())
        total += System.Convert.ToDouble(nodeset.Value);

    return total;
}

You call this function passing an XPath expression that evaluates to a node-set and then use the iterator’s methods to navigate the nodes.

Passing Managed Objects to the Style Sheet

Using the <msxsl:script> instruction lets you execute managed code, which is advantageous from at least two standpoints. First, you write extension code using high-level languages, thus accessing the true power of the .NET Framework. Second, you move some of the style sheet logic into functions, thus rendering it with more appropriate tools than XSLT instructions.

The <msxsl:script> instruction does not represent the optimal solution, however. The main problem is that you still have code defined in the body of the style sheet. In addition, this code is silently and automatically transformed into managed code through the intervention of a system tool— the <msxsl:script> instruction—whose activity is neither monitored nor controllable. For this reason, the XSLT processor allows you to define a second group of parameters—extension objects.

How Managed Extension Objects Work

The idea behind extension objects is simple. Instead of defining embedded scripts and leaving the <msxsl:script> instruction the task of grouping them into a dynamically created and compiled class, you just create and pass a managed class yourself!

Unlike embedded scripts, which are natively defined in the body of the style sheet, extension objects are external resources that must be plugged into the style sheet in some way. You can’t use the <xsl:param> mechanism, however, because XSLT parameters must be XPath types. On the other hand, conceptually speaking, an extension object is just an external argument you pass to the style sheet. For this reason, the XsltArgumentList class defines a parallel array of methods specifically to handle extension objects. (See the section “Passing and Retrieving Arguments,” on page 323.)

The XSLT processor maps the parameters in the argument list to the <xsl:param> instructions in the style sheet. The extension objects, on the other hand, are plugged in using the same internal mechanism that triggers when the <msxsl:script> code is gathered and then compiled. In abstract terms, using embedded scripts and using extension objects are somewhat equivalent. But using extension objects provides you with greater flexibility and improves the overall software design.

Script and Extension Object Trade-Offs

Using extension objects is preferable over using embedded scripts for at least three reasons. First, extension objects provide much better code encapsulation, not to mention the possibility of class reuse. Second, you end up with more compact, layered style sheets, with significant advantages also in terms of more seamless code maintenance.

Finally, using classes lets you exploit the true potential of the .NET Framework more easily. You no longer have to worry about CDATA sections. And you can cascade calls from one class to another, with each class compiled separately and written in any language. An additional pleasant side effect is that you can call methods in classes belonging to custom namespaces as well as system namespaces.

Extension Objects in Action

The following code demonstrates how to register extension objects for use with the XSLT processor:

// Create and configure the extension object 
ExtensionObject o = new ExtensionObject();
// *** set properties on the object if needed

// Register the object with the XSLT processor
XsltArgumentList args = new XsltArgumentList();
args.AddExtensionObject("urn:dino-objects", o);

XslTransform xslt = new XslTransform();
xslt.Transform(doc, args, writer);

The ExtensionObject class in this code snippet is any .NET class that is visible to the caller program. When you add a living instance of the object to the argument list, you must specify the namespace URI that will be used throughout the style sheet to qualify the object.

The style sheet must include the corresponding namespace declaration with its own style sheet–wide prefix, as in the following example:

<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:dino="urn:dino-objects">

Finally, you invoke the methods on the object’s interface using XPath expressions, as with embedded scripts. For example, if the ExtensionObject class has a DoSomething method, the following would be perfectly valid code:

  <xsl:template match="lastname">
    <TD style="border:1px solid black">
      <xsl:value-of select="dino:DoSomething(., ../firstname)" /> 
    </TD>
  </xsl:template>

As with embedded scripts, methods of extension objects must publicly handle .NET Framework types that can be converted to XPath types.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.136.18.48