XML

XSL Document Elements

The value-of and template elements

The value-of element selects a node and returns the value of the node as text. The syntax for the value-of element is as follows:

  <xsl:value-of select="pattern">

The value-of element will find the pattern within the current context. If the select attribute is not included, select will be set equal to "/", which selects the element and its children. This is what we saw in the previous XSL example document. The value-of element may be a child of the following XSL elements: attribute, comment, copy, element, for-each, if, otherwise, pi, template, and when.

The template element defines a template for the output of a particular type of node. It has the following syntax:

  <xsl:template language="language-name" match="Node-context">

The language attribute specifies the scripting language used within the template. The Node-context in the match attribute is a pattern that is also used with XPath. The default pattern is node()|/|@*, which includes all the nodes in the document. The template element can be a child of only the stylesheet and apply-templates elements.

Using the template element, you can set the context for the elements contained within the template element. Using patterns, you can set the context to any element, and its child elements, in the XML document. You can also use a template element to create templates that define how an element should be transformed.

The template element in the previous XSL example document defines an element that we want to match to-in this case, the root element (/ returns the root). Once we have the element, the value-of element will return all the elements specified in a pattern as HTML. Since a pattern was not specified, the default will be used. The default will return everything included in the match, which is the root and all its children, as text. If you try to display the NorthwindPO.xml in a browser, the browser will ignore the XSL elements because they are not HTML elements and will output the content of the elements. The output will appear as shown in Figure 12-1.

Figure 12-1. The NorthwindPO.xml document transformed into XHTML.

Now we need to tell Internet Explorer 5 how we want the content displayed in the browser by adding XHTML elements and using styles. Let's look more closely at the elements that can be used in an XSL file.

We could rewrite the XSL document example to create a template that defines how elements should look after transformation as follows:

  <?xml version="1.0"?>
  <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
     <xsl:template match="/">
        <html>
           <style type="text/css">
              body {font-family:Arial;
                    font-size:12pt; font-weight:normal;
                    color:blue;
                    line-height:150%}
           </style>
           <body>
              <xsl:value-of select="//body" />
           </body>
        </html>
     </xsl:template>
  </xsl:stylesheet>

This document will now look as shown in Figure 12-2.

Figure 12-2. The transformed NorthwindPO.xml document with styles.

We have now created an XSL template that will take any XML document that is valid according to the schema located at http://schemas.biztalk.org/BizTalk/zi0124pf.xml and convert it to XHTML according to the styles and transformations defined in the XSL document.

Recall that a schema or DTD defines an entire class of documents. An XSL document will be able to transform any document that is included in that class.

In the previous code, you inserted some HTML into the template, defined a style for the body element, and placed the transformed XML into the body element. In addition, you used the select attribute to select only the text within the body element and its child elements. Unfortunately, the document still appears as one large blob of text. To break the text apart, you would need to rewrite the template as follows:

  <?xml version="1.0"?>
  <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
  <xsl:template match="/">
        <html>
           <style TYPE="text/css">
              body {font-family:Arial;
                    font-size:12pt; font-weight:normal;
                    color:blue;
                    line-height:55%}
           </style>
           <body>
              <p>
              <strong>Message ID: </strong>
                 <xsl:value-of select="//messageID" /><br></br>
              <strong>Sent: </strong>
                 <xsl:value-of select="//sent" /><br></br>
              <strong>Subject: </strong>
                 <xsl:value-of select="//subject" /><br></br>
              <strong>To: </strong>
                 <xsl:value-of select="//to/address" /><br></br>
              <strong>From: </strong>
                 <xsl:value-of select="//from/address" /><br></br>
              <strong>PO Number: </strong>
                 <xsl:value-of select="//body//poNumber" />
                 <br></br>
              <strong>Customer ID: </strong>
                 <xsl:value-of select="//body//custID" /><br></br>
              <strong>Description: </strong>
                 <xsl:value-of select="//body//description" />
                 <br></br>
              <strong>Contact: </strong>
                 <xsl:value-of select="//body//contactName" />
                 <br></br>
                 <xsl:value-of select="//body//contactEmail" />
                 <br></br>
                 <xsl:value-of select="//body//contactPhone" />
                 <br></br>
              <strong>POShipTo: </strong>
                 <xsl:value-of select="//body//attn" /><br></br>
                 <xsl:value-of select="//body//street" />
                 <br></br>
                 <xsl:value-of select="//body//city" />
                 <xsl:value-of select="//body//stateProvince" />
                 <xsl:value-of select="//body//postalCode" />
                 <xsl:value-of select="//body//country" /><br></br>
              <strong>count: </strong>
                 <xsl:value-of select="//body//count" /><br></br>
              <strong>Total Amount: </strong>
                 <xsl:value-of select="//body//totalAmount" />
                 <br></br>
              <strong>Item:<br></br>Line Number: </strong>
                 <xsl:value-of select="//body//line" /><br></br>
              <strong>Part Quantity: </strong>
                 <xsl:value-of select="//body//qty" /><br></br>
              <strong>Part Unit Of Measurement: </strong>
                 <xsl:value-of select="//body//uom" /><br></br>
              <strong>Part Unit Price: </strong>
                 <xsl:value-of select="//body//unitPrice" />
                 <br></br>
              <strong>Part Discount: </strong>
                 <xsl:value-of select="//body//discount" />
                 <br></br>
              <strong>Part Total Amount: </strong>
                 <xsl:value-of select="//body//totalAmount" />
                 <br></br>
              </p>
           </body>
        </html>
     </xsl:template>
  </xsl:stylesheet>

This XML document will now appear as shown in Figure 12-3.

Figure 12-3. The formatted NorthwindPO.xml document.

As you can see, this document is now approaching a form that can be easily read. There are still a few problems, such as only one Item element being shown. We will solve this problem with the apply-templates element that will be discussed later in this chapter.

The copy element

The copy element copies the current node into the output without any changes. The node's child nodes and attributes will not be copied automatically. Essentially, this element doesn't convert the XML into XHTML and could be useful if you are already using XHTML.

For example, you could use the copy element as follows:

  <xsl:template match="//body">
     <xsl:copy />
  </xsl:template>

This code will output all the body as text without any changes. Thus you can use this element to transform identical data.

The for-each and apply-templates elements

The for-each element has the following syntax:

  <xsl:for-each order-by="sort-criteria-list" select="pattern">

The for-each element will iterate over a set of nodes determined by the pattern value of the select attribute. The default value of the select attribute is node()-that is, all nodes that are currently in context. The order-by criteria can be a semicolon-separated list. If the first sort order results in two items that are identical, the second sort criterion, if listed, will be used to determine the order. This pattern will continue throughout the number of criteria listed. You can place a + or - sign before the sort criterion to indicate ascending (the default) or descending order. The for-each element can be a child of the following XSL elements: attribute, comment, copy, element, for-each, if, otherwise, pi, template, and when.

The apply-templates element has the following syntax:

  <xsl:apply-templates order-by="sort-criteria-list" select="pattern">

The pattern can be the same as the pattern used in XPath, or you can reference a template element that defines a transformation of an element in the XML document. The order-by element allows you to order the results by a pattern. The apply-templates element will tell the processor to search for and apply any templates that match the select attribute. The search will begin within the XSL document. If a template element is defined that matches the value in the select attribute's pattern, this template element will be used first. Using the template elements, you can create your own templates. We can now place all the Item child elements into a table as follows:

  <?xml version="1.0"?>
  <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
     <xsl:template match="/">
        <html>
           <style TYPE="text/css">
              body {font-family:Arial;
                    font-size:12pt; font-weight:normal;
                    color:blue;
                    line-height:55%}
           </style>
           <body>
              <p>
              <strong>Message ID: </strong>
                 <xsl:value-of select="//messageID" /><br></br>
              <strong>Sent: </strong>
                 <xsl:value-of select="//sent" /><br></br>
              <strong>Subject: </strong>
                 <xsl:value-of select="//subject" /><br></br>
              <strong>To: </strong>
                 <xsl:value-of select="//to/address" /><br></br>
              <strong>From: </strong>
                 <xsl:value-of select="//from/address" />
                 <br></br>
              <strong>PO Number: </strong>
                 <xsl:value-of select="//body//poNumber" />
                 <br></br>
              <strong>Customer ID: </strong>
                 <xsl:value-of select="//body//custID" />
                 <br></br>
              <strong>Description: </strong>
                 <xsl:value-of select="//body//description" />
                 <br></br>
              <strong>Contact: </strong>
                 <xsl:value-of select="//body//contactName" />
                 <br></br>
                 <xsl:value-of select="//body//contactEmail" />
                 <br></br>
                 <xsl:value-of select="//body//contactPhone" />
                 <br></br>
              <strong>POShipTo: </strong>
                 <xsl:value-of select="//body//attn" /><br></br>
                 <xsl:value-of select="//body//street"/>
                 <br></br>
                 <xsl:value-of select="//body//city" />
                 <xsl:value-of select="//body//stateProvince" />
                 <xsl:value-of select="//body//postalCode" />
                 <xsl:value-of select= "//body//country" />
                 <br></br>
              <strong>count: </strong>
                 <xsl:value-of select="//body//count" />
                 <br></br>
              <strong>Total Amount: </strong>
                 <xsl:value-of select="//body//totalAmount" />
                 <br></br>
              <strong>Item</strong>
              <table>
              <td><strong>Line Number: </strong></td>
              <td><strong>Part Number: </strong></td>
              <td><strong>Part Quantity: </strong></td>
              <td><strong>Part Unit Of Measurement: </strong></td>
              <td><strong>Part Unit Price: </strong></td>
              <td><strong>Part Discount: </strong></td>
              <td><strong>Part Total Amount: </strong></td>
              <!--Above, a template element was used to define
                 the context. This template element used the
                 backslash as its match attribute. Thus, the
                 context for the for-each element is the root
                 document-that is, the for-each element
                 can iterate through any element in the
                 document. In this case, it is iterating
                 through the Item elements-->
              <xsl:for-each select="//Item">
              <tr>
             <!-- uses default line template-->
                 <xsl:apply-templates select="line" />
             <!-- uses default partno template-->
                 <xsl:apply-templates select="partno" />
             <!-- uses default qty template-->
                 <xsl:apply-templates select="qty" />
             <!-- uses default uom template-->
                 <xsl:apply-templates select="uom" />
             <!-- uses default unitPrice template-->
                 <xsl:apply-templates select="unitPrice" />
             <!-- uses default discount template-->
                 <xsl:apply-templates select="discount" />
             <!-- uses default totalAmount template-->
                 <xsl:apply-templates select="totalAmount"/>
              </tr>
              </xsl:for-each>
              </table>
              </p>
           </body>
        </html>
     </xsl:template>
     <!--Below are the templates for the table cells.-->
     <!--The line template uses the default text template.-->
     <xsl:template match="//body//line">
        <td><xsl:apply-templates /></td>
     </xsl:template>
     <!--The partno template uses the default text template.-->
     <xsl:template match="partno">
        <td><xsl:apply-templates /></td>
     </xsl:template>
     <!--The qty template uses the default text template.-->
     <xsl:template match="qty">
        <td><xsl:apply-templates /></td>
     </xsl:template>
     <!-The uom template, uses default text template.-->
     <xsl:template match="//body//uom">
        <td><xsl:apply-templates /></td>
     </xsl:template>
     <!-- The unitPrice template uses the default text template.-->
     <xsl:template match="//body//unitPrice">
        <td><xsl:apply-templates /></td>
     </xsl:template>
     <!--The discount template uses the default text template.-->
     <xsl:template match="//body//discount">
        <td><xsl:apply-templates /></td>
     </xsl:template>
     <!--The totalAmount template uses the default text template.-->
     <xsl:template match="//body//totalAmount">
        <td><xsl:apply-templates /></td>
     </xsl:template>
     <!--The default text template.-->
     <xsl:template match="text()"><xsl:value-of /></xsl:template>
  </xsl:stylesheet>

We have made several changes to the code to allow the multiple Item elements to be presented in a table. Figure 12-4 shows what the bottom of this XML document will look like in the browser. As you can see, two items are listed at the bottom of the figure.

Figure 12-4. Presenting the NorthwindPO.xml document items in a table.

Look at the bottom of the previous code listing; you can see the following line of code:

  <xsl:template match="text()"><xsl:value-of /></xsl:template>

This line of code defines a template that handles the text nodes. The XSL draft considers this template one of two built-in templates. The other built-in template is:

  <xsl:template match="/|*"><xsl:apply-templates/></xsl:template>

Internet Explorer 5 does not have these templates built in, but you can add them to your XSL documents. These built-in templates enable you to create style sheets for irregular data by passing text from the source document to the output automatically.

The xsl:template match="text()" template is used quite often in the XSL documents when an apply-templates element does not have a select attribute, such as in the following declaration:

  <td><xsl:apply-templates /></td>

Let's look at the following template from the previous code listing, which we will call the totalAmount template, and see what it does:

  <xsl:template match="//body//totalAmount">
        <td><xsl:apply-templates /></td>
  </xsl:template>

The <xsl:template match="//body//totalAmount"> line of code will create a new totalAmount template. Any apply-templates element that has its select attribute set to totalAmount will be instructing the parser to select this totalAmount template. Because match is set to //body//totalAmount, the content of the totalAmount template will be able to use XSL to transform the totalAmount element in the XML document. The value of match also defines the content of this template element. The content of the totalAmount template includes the start HTML td tag for the table, followed by an XSL apply-templates element and the end td tag. The apply-templates element will use the text template to return the current content as text. Since the current content is the totalAmount element and its attributes and content, the apply-templates element will return the text contents of the totalAmount element. Thus, the totalAmount template will return the start td tag, the text content of the totalAmount element, and the end td tag.

Using this combination of apply-templates elements with template elements, you can create XSL documents that can do virtually anything you require. The template element can contain far more than just HTML elements and the XSL apply-templates element; they can also contain other XSL elements such as choose, when, otherwise, if, script, and eval. Let's look at some of these other elements.

The choose, when, and otherwise elements

The choose, when, and otherwise elements work together to create something similar to what is commonly called a select case statement in other programming languages. The choose element has the following syntax:

  <xsl:choose>

Either a when or otherwise element will follow the choose element that will allow for the choice between different options. The choose element can be a child element of the following elements: attribute, comment, copy, element, for-each, if, otherwise, pi, template, and when.

The when element provides the testing conditions for the choose element. If the discount element was not already defined in the previous example, we could replace <xsl:apply-templates select="discount" /> with this code:

  <td>
     <xsl:choose>
        <xsl:when test="unitPrice[. $lt$ 500]">
        10
     </xsl:when>
     <xsl:when test="unitPrice[. $ge$ 500]">
        5
     </xsl:when>
     <xsl:otherwise>
        15
     </xsl:otherwise>
     </xsl:choose>
  </td>
We could not place this code in a template element in our example XSL documents because the template element would have to refer to the discount element, which would define the content as being only the discount element. Since the unitPrice element is not included within the context of a discount element, a template would not work.

Let's look at how to use the when element in a template. In our example XSL document, you can give a different color to an item depending on its unit price by changing:

  <xsl:template match="//body//unitPrice">
     <td><xsl:apply-templates /></td>
  </xsl:template>

to this code:

  <xsl:template match="//body//unitPrice">
     <xsl:choose>
        <xsl:when test=".[. $lt$ 500.00]">
        <td style="color:green"><xsl:apply-templates /></td>
        </xsl:when>
        <xsl:otherwise>
        <td style="color:red"><xsl:apply-templates /></td>
        </xsl:otherwise>
     </xsl:choose>
  </xsl:template>

In this example, we used the test attribute with the when element. This example will make the unit price green for items priced under 500 and red for all other items.

The if element

You can use the if element to selectively choose output as shown here:

  <xsl:if test=".[. $lt$ 500.00]">do something if true</xsl:if>

In the above line of code, test is the condition in the source data that will be tested for being either true or false. In this case, we are testing whether the current element is less than 500. The content of the if statement is a template that will be applied if the test is true. If the expression in this content identifies at least one node in the current source document, the content of xsl:if is placed in the output.

The eval element

The eval element allows you to evaluate a script expression. This element can be used only by a style sheet with the xsl namespace, http://www.w3.org/TR/WD-xsl. You cannot use this element in a style sheet that uses the XSLT namespace, http://www.w3.org/1999/XSL/Transform. We'll examine the XSLT namespace in detail later in this chapter. The eval element can be a child element of the following elements: attribute, comment, copy, element, for-each, if, otherwise, pi, template, and when. You can find the code that illustrates how to use this element in the section "Using the XML DOM to Work with XSLT and XSL" later in this chapter.

The XSL elements for creating XML nodes

You can also use XSL elements to create new nodes that will result in an updated XML document. The elements that are used for creating nodes are: attribute, cdata, comment, element, entity-ref, and pi. If we wanted to create an HTML link attribute for the Item element whose itemID is 0001, we could do the following:

  <xsl:template match="//Item[@itemID=0001]/a">
     <xsl:attribute name="href">
        http://www.northwindtraders.com/item0001.gif
     </xsl:attribute>
  </xsl:template>

The other node elements will work in a similar manner.