Applying Transformations

The XSL style sheet and the XML source can be loaded from a variety of sources, including local disk files and remote URLs. You can’t load style sheets and source documents from a stream, but because you can easily obtain an XML reader from a stream, a workaround is quickly found. Whatever the input format, the content is transformed into an XPath navigator object immediately after reading.

In light of this, passing style sheet and XML source data directly as XPath documents or navigators is advantageous from two standpoints: you save conversion time, and you work with objects whose internal storage mechanism is lighter and more compact.

Choosing optimized forms of storage like XPath documents binds you to a read-only manipulation of the data. If you need to edit the document before a transformation is performed, load it into an XmlDocument object and apply all the changes. When you have finished, pass the XmlDocument object to the XslTransform class. As you’ll recall from Chapter 6, XmlDocument implements the IXPathNavigable interface and as such can be used with the Transform method.

The Load and Transform methods have several overloads each. In all this richness of call opportunities, not all possible combinations of input and output channels are always supported. For example, you can load the source document from a URL, but only if you output to another URL or disk file. Likewise, if you want to transform to a text writer, you can’t load the source from a file. Table 7-6 and Table 7-7 provide a quick-access view of the available overloads.

Table 7-6. Load Method Overloads
Return Type Style Sheet Source XML Resolver
void File or URL No
void XPath document No
void XPath navigator No
void XML reader No
void File or URL Yes
void XPath document Yes
void XPath navigator Yes
void XML reader Yes

The interface of the Load method is fairly regular. It always returns void, and it supports four reading media, with or without an XML resolver object.

Table 7-7. Transform Method Overloads
Return Type XML Source Argument List Output
void File or URL File or URL
void XPath document XsltArgumentList Stream
void XPath navigator XsltArgumentList Stream
void XPath document XsltArgumentList Text writer
void XPath navigator XsltArgumentList Text writer
void XPath document XsltArgumentList XML writer
void XPath navigator XsltArgumentList XML writer
XmlReader XPath document XsltArgumentList
XmlReader XPath navigator XsltArgumentList

The programming interface of the Transform method is much less regular. The overloads that return an XML reader work only on XPath documents or navigators. The overload that manages URLs or files is an exception, perhaps provided for the sake of simplicity. The remaining overloads are grouped by the type of the output media: stream, text, or XML writer. For each of them, you can have a source XML document read from an XPath document or an XPath navigator.

Design Considerations

The style sheet and the source XML document are two equally important arguments for the XSLT processor. The XslTransform programming interface requires that you indicate them in different steps, however. In doing so, the accent goes on a particular use—transforming multiple documents using the same style sheet.

Although optimized for a particular scenario, such a design doesn’t tax those programmers who use the style sheet for a single transformation. In this case, the only, and very minimal, drawback is that you have to write three lines of code instead of one! Look at the following class. It provides a static method for performing XSLT transformations. It doesn’t explicitly provide for style sheet reuse, but it does save you two lines of code!

public class QuickXslt 
{
    public static bool Transform(
        string source, string stylesheet, string output)
    {
        try 
        {
            XslTransform xslt = new XslTransform();
            xslt.Load(stylesheet);
            xslt.Transform(source, output);                
            return true;
        } 
        catch (Exception e)
        {
            return false;
        }
    }

The Transform method shown in the preceding code also catches any exceptions and flattens them into a Boolean value. Using this global method is as easy as writing the following code:

public static void Main(string[] args)
{
    bool b = QuickXslt.Transform(args[0], args[1], args[2]);    
    Console.WriteLine(b.ToString());
}

By design, the static Transform method accepts only disk files or URLs.

Tip

By passing an XML reader to the XslTransform class’s Load and Transform methods, you can load both the style sheet and the source document from an XML subtree. In this case, in fact, the XslTransform class will start reading from the reader’s current node and continue through the entire subtree.


Another interesting consideration that applies to XSLT concerns the process as a whole. The style sheet is always loaded synchronously. The transformation, on the other hand, can occur asynchronously—at least to some extent. Let’s see why.

Asynchronous Transformations

The Transform method has a couple of overloads that return an XML reader, as shown here:

public XmlReader Transform(XPathNavigator input, 
    XsltArgumentList args);
public XmlReader Transform(IXPathNavigable input, 
    XsltArgumentList args);

The signature, and the behavior, of these overloads is slightly different from the others. As you can see, the method does not accept any argument representing the output stream. The second argument can be an XsltArgumentList object, which serves other purposes that we’ll get into in the section “Creating a .NET Framework Argument List,” on page 324. The input document must be an XPath navigator or an XPath document referenced through the IXPathNavigable interface.

XSLT Output Records

The output of the transformation process is not written out to a stream but created in memory and returned to the user via an XML reader. The overall transformation process works by creating an intermediate data structure (referred to as the navigator input) in which the content of the style sheet is used as the underlying surface. Any XSLT tag found in the style sheet source is replaced with expanded text or any sequence of calls that results from embedded templates.

The final output looks like a compiled program in which direct statements are interspersed with calls to subroutines. In an XSLT program, these statements are called output records, while templates play the role of subroutines. Figure 7-8 shows how the XSLT processor generates its output.

Figure 7-8. An XML reader lets you access the output records one at a time.


When the Transform method gets an output stream to write to, the XSLT processor loops through all the records and accumulates the text into the specified buffer. If an XML reader has been requested, the processor creates an instance of an internal reader class and returns that to the caller. The exact name of the internal reader is System.Xml.Xsl.ReaderOutput. No transformation is performed until the caller explicitly asks to read the cached output records. Figure 7-9 shows how the XSLT processor returns its output.

Figure 7-9. The XSLT processor instantiates a reader object and returns. No transformation is performed until you “read” the internal data using the methods and the properties of the returned reader.


The XSLT Record Reader

The ReaderOutput class builds a virtual XML tree on top of the compiled style sheet, thus making it navigable using the standard XML reader interface. When the Transform method returns, the reader is in its initial state (and therefore it is not yet initialized for reading).

Each time you pop an element from the reader, a new output record is properly expanded and returned. In this way, you have total control over the transformation process and can plan and realize a number of fancy features. For example, you could provide feedback to the user, discard nodes based on run-time conditions and user roles, or cause the process to occur asynchronously on a secondary thread.

The reader interface exposes the XSLT records as XML nodes—the same XML nodes you will find by visiting the output document. The following code snippet demonstrates how to set up a user-controlled transformation:

// The XML source must be an XPath document or an XPath navigator
XPathDocument doc = new XPathDocument(source);

// No arg-list to provide in this case
XmlReader reader = xslt.Transform(doc, null);

// Perform the transformation, record by record
while (reader.Read())
{
    // Do something
}

Figure 7-10 shows the user interface of a sample application. It includes a list box control that is iteratively populated with information excerpted from the reader’s current node. Each row in the list box corresponds to an output record generated by the XSLT processor.

Figure 7-10. The HTML file generated by the transformation, rendered as a node tree, is received one row at a time.


In the reading loop, all nodes are analyzed and serialized to XML text, as shown in the following code. In this way, each row in the list box corresponds to the line of text that is sent to an output stream if you opt for a synchronous transformation.

void ReadOutputRecords(XmlReader reader)
{
    // Clear the list box
    OutputList.Items.Clear();

    // Read the records
    while(reader.Read())
    {
        string buf = "";
        switch(reader.NodeType)
        { 
            case XmlNodeType.Element:
                buf = String.Format("{0}<{1} {2}>", 
                    new String(‘ ’, 2*reader.Depth), 
                    reader.Name, 
                    GetNodeAttributes(reader));
                break;
            case XmlNodeType.EndElement:
                buf = String.Format("{0}</{1}>", 
                    new String(‘ ’, 2*reader.Depth),
                    reader.Name);
                break;
            case XmlNodeType.Text:
                buf = String.Format("{0}{1}", 
                    new String(‘ ’, 2*reader.Depth),
                    reader.Value);
                break;
        }
        OutputList.Items.Add(buf);
    }
}

The final text is indented using a padding string whose size depends on the reader’s Depth property. Node names and values are returned by the Name and Value properties. For element nodes, attributes are read using a piece of code that we examined in detail in Chapter 2:

string GetNodeAttributes(XmlReader reader)
{
    if (!reader.HasAttributes)
        return "";

    string buf = "";
    while(reader.MoveToNextAttribute())
        buf += String.Format("{0}="{1}" ", reader.Name, reader.Value);

    reader.MoveToElement(); 
    return buf;
}

Output Formats

An XSLT style sheet can declare the output format of the serialized text using the <xsl:output> statement. This statement features several attributes, the most important of which is method. The method attribute can be set with any of the following keywords: xml, html, or text. By default, the output format is XML unless the root tag of the results document equals <html>. In this case, the output is in HTML.

Differences between XML and HTML are minimal. If the output format is HTML, the XML well-formedness is sacrificed in the name of a greater programmer-friendliness. This means that, for example, empty tags will not have an end tag. In addition to method, other attributes of interest are indent, encoding, and omit-xml-declaration, which respectively indent the text, set the preferred character encoding, and omit the typical XML prolog.

If you add an <xsl:output> statement to the previously considered style sheets, the source code of the results document will be significantly different, but not its overall meaning. If you choose to output plain text, on the other hand, the XSLT processor will discard any markup text in the style sheet and output only text.

As a final note, consider that <xsl:output> is a discretionary behavior that not all XSLT processors provide and not all in the same way. In particular, when the Transform method is writing to a text writer or an XML writer, the .NET Framework XSLT processor ignores the encoding attribute in favor of the corresponding property on the object.

Passing and Retrieving Arguments

As mentioned, XSLT scripts can take arguments. You can declare arguments globally for the entire script or locally to a particular template. Arguments can have a default value that will make them always available as a variable in the scope. Aside from the default value, in XSLT there are no other differences between arguments and variables.

The following code shows a style sheet snippet in which a parameter named MaxNumOfRows is declared and initialized with a default value of 6:

 <xsl:template match="Employee">
  <xsl:param name="MaxNumOfRows" select="6" />
  <xsl:if test="$MaxNumOfRows > position()">
    <TR>
      <xsl:apply-templates select="employeeid" />
      <xsl:apply-templates select="lastname" />
      <xsl:apply-templates select="title" />
    </TR>
  </xsl:if>
</xsl:template>

The script retrieves the argument using its public name prefixed with a dollar sign ($). In particular, the conditional statement shown here applies to the template only if five employee nodes have not yet been processed:

<xsl:if test="$MaxNumOfRows > position()">
  <!-- Apply the template -->
</xsl:if>

Note that you can’t use the less than sign (<) in an XSLT expression because it could confuse the processor. Instead, use the escaped version of the character: &lt;. The greater than sign (>) can be safely used, however. If, like me, you don’t like escaped strings, you can invert the terms of the comparison.

Note

Parameters can be associated only with templates or with the global script. You can’t associate parameters with other XSLT instructions such as a <xsl:for-each>.


Calling Templates with Arguments

When you call a parameterized XSLT template, you give actual values to formal parameters using the <xsl:with-param> instruction. Here’s an example that calls the sample Employee template, giving the MaxNumOfRows argument a value of 7:

<xsl:apply-templates select="MyDataSet/NorthwindEmployees/Employee">
  <xsl:with-param name="MaxNumOfRows" select="7" />
</xsl:apply-templates>

If the called template has no such parameter, nothing happens, and the argument will be ignored. The <xsl:with-param> instruction can be associated with both <xsl:apply-templates> and <xsl:call-template> instructions.

Creating a .NET Framework Argument List

The Transform method lets you pass arguments to the style sheet using an instance of the XsltArgumentList class. When you pass arguments to an XSLT script in this way, you can’t specify what template call will actually use those arguments. You just pass arguments globally to the XSLT processor. The internal modules responsible for processing templates will then read and import those arguments as appropriate.

Creating an argument list is straightforward. You create an instance of the XsltArgumentList class and populate it with values, as shown here:

XsltArgumentList args = new XsltArgumentList();
args.AddParam("MaxNumOfRows", "", 7);

The AddParam method creates a new entry in the argument list. AddParam requires three parameters: the (qualified) name of the parameter, the namespace URI (if the name is qualified by a namespace prefix), and an object representing the actual value. Regardless of the .NET Framework type you use to pack the entry into the argument list, the parameter value must correspond to a valid XPath type: string, Boolean, number, node fragment, and node-set. The number type corresponds to a .NET Framework double type, whereas node fragments and node-sets are equivalent to XPath navigators and XPath node iterators. (See Chapter 6 for more information about these data types.)

The XsltArgumentList Class

Despite what its name suggests, XsltArgumentList is not a collection-based class. It does not derive from ArrayList or from a collection class, nor does it implement any of the typical list interfaces like IList or ICollection.

The XsltArgumentList class is built around a couple of child hash tables: one to hold XSLT parameters and one to gather the so-called extension objects. An extension object is simply a living instance of a .NET Framework object that you can pass as an argument to the style sheet. Of course, this feature is specific to the .NET XSLT processor. We’ll look at extension objects in more detail in the section “XSLT Extension Objects,” on page 336.

The programming interface of the XsltArgumentList class is described in Table 7-8. It provides only methods.

Table 7-8. Methods of the XsltArgumentList Class
Method Description
AddExtensionObject Adds a new managed object to the list. You can specify the namespace URI or use the default namespace by passing an empty string. If you pass null, an exception is thrown.
AddParam Adds a parameter value to the list. Must indicate the name of the argument and optionally the associated namespace URI.
Clear Removes all parameters and extension objects from the list.
GetExtensionObject Returns the object associated with the given namespace.
GetParam Gets the value of the parameter with the specified (qualified) name.
RemoveExtensionObject Removes the specified object from the list.
RemoveParam Removes the specified parameter from the list.

As with parameters, the style sheet identifies an extension object through its class name and an associated namespace prefix.

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

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