perfectxml.com
 Basic Search  Advanced Search   
Topics Resources Free Library Software XML News About Us
You are here: home »» Free Library »» Wrox Press » Professional ASP.NET 1.0 XML with C# Saturday, 13 October 2007

Page: 1   |   2   |   3   |   4   |   5   |   6        

Understanding XSLT Functions, Variables, and Parameters

The XSLT language supports a number of functions to provide, among other things, extended support for node-set operations. We will not be able to cover every XSLT function in this section, so we will instead highlight just those functions that can be very useful in a web environment.

 

Uniquely Identifying Nodes with generate-id()

We often need a way to uniquely identify nodes in the output document. We may need a unique value for an ID attribute, or to differentiate two or more nodes with the same name and value. The generate-id() function gives us this ability.

 

Save transform-three.xslt as transform-four.xslt in our Transforms web application. In transform-four.xslt, we'll add another column to our table, which will contain a button to display the generated ID for each <item> node.

 

In the <tr> tag holding table heading information, add a column named ID:

 

<tr style="background-color:#000000;color:#FFFFFF">

   <th>Line Number</th>

   <th>ID</th>

   <th>Item Number</th>

   <th>Description</th>

   <th>Quantity</th>

   <th>List Price</th>

   <th>Notes</th>

 </tr>

 

Then, in the <for-each> element, add the code highlighted below:

 

<xsl:for-each select="//item">

   <xsl:sort  select="list-price"

     data-type="number"

     order="descending"

   />

   <xsl:element name="tr">

     <xsl:attribute name="style">

       <xsl:choose>

         <xsl:when test="position() mod 2">

           background-color:#DDDDFF;

         </xsl:when>

         <xsl:otherwise>

           background-color:#BBBBBB;

         </xsl:otherwise>

       </xsl:choose>

     </xsl:attribute>

     <td><xsl:value-of select="position()" /></td>

     <td>

       <input  type="button"

           id="{generate-id()}"

           onclick="alert(this.id);"

           value="Click!" />

     </td>

     <td><xsl:value-of select="@id" /></td>

     <td><xsl:value-of select="description" /></td>

     <td align="right"><xsl:value-of select="quantity" /></td>

     <td align="right"><xsl:value-of select="list-price" /></td>

     <td>

       <xsl:if test="@productLine='2'" >

         Call for details!

       </xsl:if>

     </td>

   </xsl:element>

 </xsl:for-each>

 

Here, we have added an HTML <input> tag of type button. Notice the syntax for the id attribute. enclosing the function in curly braces ({}) produces the same effect as calling an <xsl:value-of> statement right inside the quotes. The function returns a string that uniquely identifies the context node, which in this case is the current <item> element. The onclick attribute calls the Javascript alert function to open a message box with the value of the unique ID.

 

Save the changes to transform-four.xslt, and specify it in the query string for the TransformXML.aspx page. The results look like this:

 

 

If we click the first button, we get a message box something like this:

 

 

Each button will of course return a different value. The method used to generate the ID is dependent on the implementation, and cannot be relied upon to always produce the same value for the same document, even with the same transform engine.

 

We will use this function again in the next example to compare nodes with each other.

 

Accessing Nodes using <key> and key()

A common situation I have faced when using XSLT style sheets is the need to transform data items according to the categories they are classed under in that particular XML dialect. The real challenge is to do so dynamically, without hard-coding template rules for each category. By using the XSLT <key> element and key() function, we can retrieve such "category" nodes in a document prior to processing the template rules, and then use the acquired node-set for access later.

 

Understanding the <key> Element

The <key> element is a top-level element, which means it must be a child of the <stylesheet> element. There are three attributes of <key>:

 

§          name – Used as an identifier in the XSLT style sheet. Typically referenced by the key() function.

 

§          match – An XPath expression that identifies the nodes in the source XML document to be gathered into the node-set. This search is done throughout the document, so it is not necessary to use the // operator to find all nodes.

 

§          use – A relative location path identifying how to access the matched node. To access the node by its own value, supply the "." self expression. To access the matched node by a relative node (such as an attribute of the element), provide the respective location path. This is also referenced by the key() function. This will become clearer once we've looked at an example.

 

Understanding the key() Function

The key() function allows us to access the matched nodes identified by the XSLT <key> element. The function accepts two arguments:

 

§          name – This should match the name attribute of the corresponding <key> element

 

§          key – The value by which to access the node-set. This corresponds to the use attribute of the <key> element.

 

To demonstrate, we'll use the following XML document, distinct.xml:

 

<?xml version="1.0"?>

 <doc>

   <parent category="E">

     <child>1</child>

   </parent>

   <parent category="D">

     <child>2</child>

   </parent>

   <parent category="E">

     <child>3</child>

   </parent>

   <parent category="A">

     <child>4</child>

   </parent>

   <parent category="B">

     <child>5</child>

   </parent>

   <parent category="B">

     <child>6</child>

   </parent>

   <parent category="C">

     <child>7</child>

   </parent>

   <parent category="A">

     <child>8</child>

   </parent>

   <parent category="A">

     <child>9</child>

   </parent>

   <parent category="B">

     <child>10</child>

   </parent>

 </doc>

 

Now, say that we want to display the value of each child listed according to its parent's category. If we know all the possible category values, we could create a separate template rule for each one. But there is a more dynamic and robust way which uses the XSLT <key> element and key() function.

 

Create a new XSLT style sheet in our Transforms project, and name it transform-five.xslt. This style sheet will be used to transform the distinct.xml document listed above.

 

Begin the style sheet with the following markup:

 

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

 version="1.0">

   <xsl:key name="category_key" match="parent" use="@category" />

 

We have defined an XSLT <key> element, and named it category_key. The match attribute contains the single value of parent. By supplying this value, the XSLT processor will search the entire source document node by node for all occurrences of <parent> elements, and build a node-set accordingly. The use attribute informs the processor that the node-set is to be accessed by providing the value of the category attribute.

 

We now create a template match for the root node:

 

<xsl:template match="/">

   <html>

     <head>

       <title>Transformation Example</title>

     </head>

     <body>

       <xsl:apply-templates select="doc"/>

     </body>

   </html>

 </xsl:template>

 

The above template formats the output as an HTML document. The content of the <body> tag is provided by the template matching the <doc> document element.

 

Now it gets interesting. This is the template matching <doc>:

 

<xsl:template match="doc">

   <xsl:for-each select="parent[generate-id(.)=

    generate-id(key('category_key', @category)[1])]">

     <xsl:sort select="@category" />

     <div style="font-weight:bold">

       Category=<xsl:value-of select="@category" />

     </div>

     <xsl:for-each select="key('category_key', @category)">

       <xsl:sort select="child"

                 order="descending"

                 data-type="number" />

       Child=<xsl:value-of select="child" /><br />

     </xsl:for-each><br />

   </xsl:for-each>

 </xsl:template>

 

The magic really happens in the first <for-each> element. The XPath expression looks a little tricky, and I'll explain it as best I can. The first location step searches for <parent> elements that are children of the <doc> element (as this is the context node in this template). The predicate then selects only the <parent> element whose uniquely generated ID matches the first node returned from the category_key node-set containing the same generated ID.

 

The result is that this <for-each> neatly collects together <parent> nodes with distinct values in their category attribute.

 

The inner <for-each> statement then iterates through each child of each <parent> element, listing them in descending order.

 

The last thing to do is close the style sheet:

 

</xsl:stylesheet>

 

Save the transform-five.xslt sheet, and feed it and the distinct.xml file into the TransformXML.aspx page in your browser. The results are shown in the following screenshot:

 

 

In this way, we have dynamically categorized our output. Experiment by adding or removing categories in the distinct.xml document – the style sheet will react accordingly.

 

Assigning Values with <variable>

We can assign values to use in XPath expressions with the XSLT <variable> element. An XSLT variable's scope is limited to the element that it is a child of, so if declared as a top-level element, it can be accessed throughout the style sheet. The element has just two attributes:

 

§          name – Supplies the identifier for the variable, within the scope of its declaration

 

§          select – An optional XPath expression returning the value to be assigned to the variable

 

Alternatively, we can specify the value of the variable by placing content directly in the element. If select is not provided, and the element is empty, the variable equals an empty string.

 

Here is a simple demonstration of how to use a variable. In the transform-five.xslt style sheet, locate the template match for <doc>, and make the changes highlighted below:

 

<xsl:template match="doc">

   <xsl:for-each select="parent[generate-id(.)=

    generate-id(key('category_key', @category)[1])]">

     <xsl:sort select="@category" />

     <xsl:variable name="CategoryName" select="@category" />

     <div style="font-weight:bold">

       Category=<xsl:value-of select="$CategoryName" />

     </div>

     <xsl:for-each select="key('category_key', @category)">

       <xsl:sort   select="child"

             order="descending"

             data-type="number" />

       Child=<xsl:value-of select="child" /><br />

     </xsl:for-each>

     <br />

   </xsl:for-each>

 </xsl:template>

 

In this example, we have assigned the category attribute to the CategoryName variable. For each iteration, the variable value changes. Note the syntax to access the variable in the line that follows shortly after the variable declaration:

 

Category=<xsl:value-of select="$CategoryName" />

 

The $ character denotes a variable identifier.

 

This example does not change the behavior of our transformation, and may seem a little pointless. Variables can really save us a lot of trouble when used to hold complex XPath expressions that are employed several times in the same template. In following examples though, we will see another practical use of this feature: to hold a complete XML document.

Page: 1   |   2   |   3   |   4   |   5   |   6        

Next Page >>>        

  Contact Us |  | Site Guide | About PerfectXML | Advertise ©2004 perfectxml.com. All rights reserved. | Privacy