Introduction: If you look at a large number of client-side scripts that manipulate hierarchical documents what you find is that many of them are doing the same thing -- transforming nodes. Having made this observation it immediately becomes apparent that some of the difficulties arise because we are using a procedural scripting languages. Instead, it would be better to use a declarative language that allows us to write down rules describing the transformations.

Many such languages exist, and some have been around for many years (e.g., awk). The one we will consider here is XSL.

A Brief History of XSL: XSL is an acronym for the eXtensible Stylesheet Language. The developers of XSL were interested in creating a method of defining the formatting/presentation of XML documents.

The original XSL proposal was submitted to the W3C in August of 1997. The first working draft was published about a year later. In early 1999 it became apparent that it would be better to think of XSL as two distinct components, XSL Transformations (XSLT) and XSL Formatting Objects (XSL-FO). XSL-FO is similar in spirit to other types of stylesheets (including CSS and DSSSL). XSLT, on the other hand, is a declarative language for selecting and processing the nodes in a document. Since this is a programming course, our concern is with XSLT.

In November of 1999, XSLT (and the closely related XPath, which is used to identify the parts of a document) became W3C recommendations.

Overview of XSLT: XSLT is a declarative language. That is, it contains a set of template rules, each of which describes how a particular element type should be processed. In other words, an XSLT program is a set of rules each of which describe what output should be produced when a particular "pattern" is encountered.

The rules in an XSLT program are described using XML. All XSL tags are in the xsl namespace. Hence, the are all prefixed by xsl:.

XSLT rules can appear in any order. It is, by design, free of side-effects.

An XSLT program can transform an XML (input) document into output of any kind. On the "client side" the most common output format is HTML. Some client-side applications transform XML documents into other XML documents or into ASCII text.

A Simple XSLT Example: Consider the following simple XML document:
    <?xml version="1.0" ?>
    <?xml-stylesheet type="text/xsl" href="simple.xsl" ?>
    <text>I Love CS685!</text>
    

What we would like to do is load this document into a browser, process it, and display the contents of the text tag in the browser. Obviously, we could write a procedural script to accomplish this. However, let's instead think about a set of rules that would allow us to accomplish the same thing.

Rather than worry about formatting details, we will focus on transforming an XML document into an HTML document (with or without an associated CSS).

First, we need a rule for processing the root element. When the root element is encountered, we want to build the skeleton of the HTML document. This can be accomplished in XSLT as follows:

    <xsl:template match="/">
      <HTML>
        <BODY>
          <xsl:apply-templates />
        </BODY>
      </HTML>
    </xsl:template>
    

This rule says to look for an element that matches "/" (the root element). If one is encountered, insert opening HTML and BODY tags, look for other matches (and process them), and insert closing BODY and HTML tags.

Second, we need a rule for processing text elements. When a text elements is encountered, we want to insert a P element into the HTML skeleton, and insert the contents of the text element in the P. This can be accomplished in XSLT as follows:

    <xsl:template match="text">
      <P>
        <xsl:value-of select="." />
      </P>
    </xsl:template>
    

This rule says to look for an element that matches "text". When one is encountered, insert the P element and the contents of the text note.

Putting it all together we have the following:

    <?xml version="1.0" ?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"
                    result-ns="html">


    <xsl:template match="/">
      <HTML>
        <BODY>
          <xsl:apply-templates />
        </BODY>
      </HTML>
    </xsl:template>
    


    <xsl:template match="text">
      <P>
        <xsl:value-of select="." />
      </P>
    </xsl:template>



    </xsl:stylesheet>
    

Note that this XSLT program is, itself, an XML document.

Basic XSLT Elements: There are a relatively small number of "basic" XSLT elements, including


xsl:stylesheet
The root element of all XSLT programs is the xsl:stylesheet element. It has several attributes, including:


xsl:template
The xsl:template element defines a template (or rule) for producing output. It has the following attributes:


xsl:apply-templates
The xsl:apply-templates element indicates that the processor should search for and apply matching templates. It has the following attributes:


xsl:value-of
The xsl:value-of element returns an XML node. It has the following attributes:
    <?xml version="1.0" ?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"
                      result-ns="html">
    
    <!-- reference.xsl v1.0 -->
    
    
    
    <!-- root element -->
    <xsl:template match="/">
      <HTML>
        <BODY>
          <xsl:apply-templates />
        </BODY>
      </HTML>
    </xsl:template>
    
    
    
    
    <!-- author -->
    <xsl:template match="author">
      <xsl:apply-templates select="firstname"/>
      <xsl:apply-templates select="lastname"/>,
    </xsl:template>
    
    
    
    <!-- bibliography -->
    <xsl:template match="bibliography">
      <xsl:apply-templates />
    </xsl:template>
    
    
    
    
    <!-- book -->
    <!-- Note the use of the built-in text() function -->
    <!-- that prevents the children from being selected -->
    <xsl:template match="book">
      <EM><xsl:value-of select="text()" /></EM>,
      <xsl:apply-templates select="editor"/>
      <xsl:apply-templates select="publisher"/>
      <xsl:apply-templates select="pages"/>.
    </xsl:template>
    
    
    
    <!-- editor -->
    <xsl:template match="editor">
      <xsl:apply-templates select="firstname"/>
      <xsl:apply-templates select="lastname"/>,
    </xsl:template>
    
    
    
    
    <!-- firstname -->
    <xsl:template match="firstname">
      <xsl:value-of select="." />
    </xsl:template>
    
    
    
    
    
    
    <!-- journal -->
    <!-- Note the use of the built-in text() function -->
    <!-- that prevents the children from being selected -->
    <xsl:template match="journal">
      <EM><xsl:value-of select="text()" /></EM>,
      <xsl:apply-templates select="volume"/>
      <xsl:apply-templates select="pages"/>.
    </xsl:template>
    
    
    
    
    <!-- lastname -->
    <xsl:template match="lastname">
      <xsl:value-of select="." />
    </xsl:template>
    
    
    
    
    
    <!-- pages -->
    <xsl:template match="pages">
      pp. <xsl:value-of select="." />
    </xsl:template>
    


    <!-- publisher -->
    <xsl:template match="publisher">
      <xsl:value-of select="." />,
    </xsl:template>
    
    
    
    
    <!-- reference -->
    <xsl:template match="reference">
      <P>
        <xsl:apply-templates select="author"/>
        <xsl:apply-templates select="year"/>
        <xsl:apply-templates select="title"/>
        <xsl:apply-templates select="journal"/>
        <xsl:apply-templates select="book"/>
      </P>
    </xsl:template>

    



    <!-- title -->
    <xsl:template match="title">
      "<xsl:value-of select="." />",
    </xsl:template>





    <!-- volume -->
    <xsl:template match="volume">
      Vol. <xsl:value-of select="." />,
    </xsl:template>




    <!-- year -->
    <xsl:template match="year">
      (<xsl:value-of select="." />)
    </xsl:template>
    



    </xsl:stylesheet>    
    
Path Expressions: A path expression describes a sequence of "branches" in a document tree. In other words, it describes a way of "finding" a node in the document tree.

Path expressions use the following operators:

    <?xml version="1.0"?>

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"
                    result-ns="html"
                    language="JScript">
    
    
      <!-- Root -->
      <xsl:template match="/">
        <HTML>
          <HEAD>
          </HEAD>
    
          <BODY BGCOLOR="#FFFFFF">
            <xsl:apply-templates select="timetable"/>
          </BODY>
        </HTML>
      </xsl:template>
    
    
    
      <!-- Station -->
      <xsl:template match="station">
        <B>
          <xsl:value-of select="text()" />
        </B>
      </xsl:template>
    
    
    
      <!-- Stop -->
      <xsl:template match="stop">
        <TD>
          <xsl:apply-templates select="station" />
        </TD>
      </xsl:template>
    
    
    
    
      <!-- Time -->
      <xsl:template match="time">
        <TD>
          <xsl:value-of select="text()" />
        </TD>
      </xsl:template>
    
    
    
      <!-- Timetable -->
      <!-- Note the use of the attribute operator -->
      <xsl:template match="timetable">
        <H2>
          <xsl:value-of select="@title"/>
        </H2>
        <H3>
          <xsl:value-of select="@subtitle"/>
        </H3>
        <TABLE>
          <xsl:apply-templates match="train"/>
        </TABLE>
      </xsl:template>
    
    
    
    
      <!-- Train -->
      <!-- Note the use of the attribute and child operators -->
      <xsl:template match="train">
          <TR>
            <TD><xsl:value-of select="@number" /></TD>
            <TD><xsl:value-of select="@normaldays" /></TD>
            <xsl:apply-templates select="stop/time" />
          </TR>
      </xsl:template>
    
      <!-- Note the use of the index, attribute, and child operators -->
      <xsl:template match="train[0]">
          <TR>
            <TD><B>Train Number</B></TD>
            <TD><B>Normal Days</B></TD>
            <xsl:apply-templates select="stop" />
          </TR>
          <TR>
            <TD><xsl:value-of select="@number" /></TD>
            <TD><xsl:value-of select="@normaldays" /></TD>
            <xsl:apply-templates select="stop/time" />
          </TR>
      </xsl:template>
    
    
    
    
    
    </xsl:stylesheet>
    
Filter Operators: Elements can also be selected using filters. Filters allow elements to be selected based on:

An XSLT filter has the following syntax:

where operator denotes a filter operator and pattern denotes a filter pattern.

The set of operators includes:

Decision Structures: It is sometimes necessary to generate different output depending on the value of a test condition. In XSLT there are two different elements that allow this kind of conditional processing, xsl:if and xsl:choose.


xsl:if
The xsl:if element has the following attributes:

For example, the following sippet:

    <xsl:if test="pages">
      ,<xsl:apply-templates select="pages"/>
    </xsl:if>
    

will output a comma and the output of the pages rule only if there is a pages element.


xsl:choose
The xsl:choose element contains one or more xsl:when elements and at most one csl:otherwise element, each of which behaves like an xsl:if element.
    <?xml version="1.0" ?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"
                      result-ns="html">
    
    <!-- reference.xsl v2.0 -->
    
    
    
    <!-- root element -->
    <xsl:template match="/">
      <HTML>
        <BODY>
          <xsl:apply-templates />
        </BODY>
      </HTML>
    </xsl:template>
    
    
    
    
    <!-- author -->
    <!-- v2.0 Fixes first author and last author -->
    <xsl:template match="author">
      <xsl:choose>
        <xsl:when match="author[0]">
          <xsl:apply-templates select="lastname"/>,
          <xsl:apply-templates select="firstname"/>
        </xsl:when>
        <xsl:when match="author[end()]">
          and
          <xsl:apply-templates select="firstname"/>
          <xsl:apply-templates select="lastname"/>
        </xsl:when>
        <xsl:otherwise>
          ,
          <xsl:apply-templates select="firstname"/>
          <xsl:apply-templates select="lastname"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
    
    
    
    <!-- bibliography -->
    <xsl:template match="bibliography">
      <xsl:apply-templates />
    </xsl:template>
    
    
    
    
    <!-- book -->
    <!-- Note the use of the built-in text() function -->
    <!-- that prevents the children from being selected -->
    <xsl:template match="book">
      <EM><xsl:value-of select="text()" /></EM>
      , Edited by
      <xsl:apply-templates select="editor"/>
      <xsl:apply-templates select="publisher"/>
      <xsl:apply-templates select="pages"/>.
    </xsl:template>
    
    
    
    <!-- editor -->
    <!-- v2.0 Fixes last editor and last editor -->
    <xsl:template match="editor">
      <xsl:choose>
        <xsl:when match="editor[0]">
          <xsl:apply-templates select="firstname"/>
          <xsl:apply-templates select="lastname"/>
        </xsl:when>
        <xsl:when match="editor[end()]">
          and
          <xsl:apply-templates select="firstname"/>
          <xsl:apply-templates select="lastname"/>
        </xsl:when>
        <xsl:otherwise>
          ,
          <xsl:apply-templates select="firstname"/>
          <xsl:apply-templates select="lastname"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
    
    
    
    
    <!-- firstname -->
    <xsl:template match="firstname">
      <xsl:value-of select="." />
    </xsl:template>
    
    
    
    
    
    
    <!-- journal -->
    <!-- Note the use of the built-in text() function -->
    <!-- that prevents the children from being selected -->
    <!-- -->
    <!-- v2.0 fixed problem with missing elements -->
    <xsl:template match="journal">
      <EM><xsl:value-of select="text()" /></EM>
      <xsl:if test="volume">
        ,<xsl:apply-templates select="volume"/>
      </xsl:if>
      <xsl:if test="pages">
        ,<xsl:apply-templates select="pages"/>
      </xsl:if>
      .
    </xsl:template>
        



    <!-- lastname -->
    <xsl:template match="lastname">
      <xsl:value-of select="." />
    </xsl:template>
    
    
    
    
    
    <!-- pages -->
    <xsl:template match="pages">
      pp. <xsl:value-of select="." />
    </xsl:template>
    
    
    
    <!-- publisher -->
    <xsl:template match="publisher">
      <xsl:value-of select="." />,
    </xsl:template>
    
    
    
    
    <!-- reference -->
    <xsl:template match="reference">
      <P>
        <xsl:apply-templates select="author"/>
        <xsl:apply-templates select="year"/>
        <xsl:apply-templates select="title"/>
        <xsl:apply-templates select="journal"/>
        <xsl:apply-templates select="book"/>
      </P>
    </xsl:template>
    
    



    <!-- title -->
    <xsl:template match="title">
      "<xsl:value-of select="." />",
    </xsl:template>
    
    
    
    
    
    <!-- volume -->
    <xsl:template match="volume">
      Vol. <xsl:value-of select="." />
    </xsl:template>




    <!-- year -->
    <xsl:template match="year">
      (<xsl:value-of select="." />)
    </xsl:template>




    </xsl:stylesheet>
    
User-Defined Functions: It is sometimes useful (within a rule) to be able to evaluate a function of some kind (e.g., to perform a calculation). This can be accomplished with the xsl:script and xsl:eval elements. The xsl:script element is comparable to the SCRIPT element in HTML. That is, it contains the source code. The xsl:eval element, on the other hand, is used to execute a function.
    <?xml version="1.0" ?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"
                      result-ns="html">

    <!-- manifest.xsl -->



    <!-- root element -->
    <xsl:template match="/">
      <HTML>
        <BODY>
          <xsl:apply-templates />
        </BODY>
      </HTML>
    </xsl:template>



    <!-- box -->
    <xsl:template match="box">
      <TR>
        <TD>
          <xsl:apply-templates select="ship-to" />
        </TD>
        <TD>
          <xsl:apply-templates select="description" />
        </TD>
        <TD>
          <xsl:apply-templates select="weight" />
        </TD>
      </TR>
      <xsl:if match="box[end()]">
      <TR> 
        <TD>
        </TD>
        <TD>
        </TD>
        <TD>
          <STRONG>
            <xsl:eval>totalWeight.pounds</xsl:eval>lbs
            <xsl:eval>totalWeight.ounces</xsl:eval>oz
          </STRONG>
        </TD>
      </TR>
      </xsl:if>
    </xsl:template>






    <!-- description -->
    <xsl:template match="description">
      <EM><xsl:value-of select="text()" /></EM>
    </xsl:template>



    <!-- manifest -->
    <xsl:template match="manifest">
      <TABLE>
        <xsl:apply-templates select="box" />
      </TABLE>
    </xsl:template>



    <!-- ship-to -->
    <xsl:template match="ship-to">
      <xsl:value-of select="text()" />
    </xsl:template>
    


    <!-- weight -->
    <xsl:template match="weight">
      <xsl:value-of select="@pounds" />lbs
      <xsl:value-of select="@ounces" />oz
      <xsl:eval>
        var lbs, oz, w;

        lbs = parseInt(this.attributes.getNamedItem("pounds").nodeValue);
        oz = parseInt(this.attributes.getNamedItem("ounces").nodeValue);
        w = new Weight(lbs,oz);
        totalWeight.increase(w);
      </xsl:eval>
    </xsl:template>




    <!-- Scripts -->
    <xsl:script language="JavaScript">
    <![CDATA[
    
    var totalWeight = new Weight(0,0);
    
    
    /**
     * A Weight class
     *
     * Author:  CS685
     *          Department of Computer Science
     *          James Madison University
     *
     * Date: 26 August 2000
     */
    
    
    /**
     * Construct a new Weight
     *
     * Arguments: lbs - The number of pounds
     *            oz  - The number of ounces
     */
    function Weight(lbs, oz)
    {
      if ((lbs < 0) || (oz < 0)) {
    
        this.pounds = 0;
        this.ounces = 0;
    
      } else {
    
        this.pounds = lbs;
        this.ounces = oz;
    
        this.pounds += Math.floor(this.ounces/16);
        this.ounces = this.ounces % 16;
      }
    }
    
    
    /**
     * Increase this Weight
     *
     * Arguments: inc - The amount of the increase (which is a Weight)
     *
     * Side Effects: Increases this Weight
     */
    function _increase(inc)
    {
      this.pounds += inc.pounds;
      this.ounces += inc.ounces;
    
      this.pounds += Math.floor(this.ounces/16);
      this.ounces =  this.ounces % 16;
    }
    
    // Make _increase a method of the prototype
    //
    Weight.prototype.increase = _increase;


    ]]>
    </xsl:script>

    </xsl:stylesheet>