9. Style Sheet Design Patterns

Push versus Pull Processing

Push processing lets the document decide which template should be instantiated. This is also called pattern-driven control or the rule-based design pattern.

A Non-Uniform XML Document

Push processing works best when the structure of the XML document is non-uniform or if the style sheet must process different forms of documents that contain the same elements.

<items>
   <contact>
      <name> Joe Smith </name>
      <phone> (213) 555-1234 </phone>
   </contact>
   <appointment>
      <date> June 21, 2003 </date>
      <time> 1:00 PM </time>
      <location> My office </location>
      <with> Joe Smith </with>
   </appointment>
   <contact>
      <name> Bill Jones </name>
      <phone> (408) 555-9050 </phone>
      <email> jones@cia.gov </email>
   </contact>
   <reminder>
      Bill's birthday is on June 26!
   </reminder>
   <appointment>
      <date> June 25, 2003 </date>
      <time> 1:30 PM </time>
      <location> My office </location>
      <with> Bill Jones </with>
   </appointment>
   <reminder>
      Don't forget to take your Prozac
   </reminder>
</items>

The HTML Result

In this example we want to extract only appointments and phone numbers:

A Style Sheet

Each template calls apply templates

<xsl:template match = "items">
   <html>
   <head><title> XSL Demo </title></head>
   <body>
   Appointments:
   <xsl:apply-templates select = "//appointment"/>
   </body>
   </html>
</xsl:template>

<xsl:template match = "appointment">
   <hr />
   date and time:
   <xsl:value-of select = "date"/>@
   <xsl:value-of select = "time"/>
   with: <br/>
   <xsl:variable name = "who" select = "with"/>
   <xsl:value-of select = "$who"/>
   <br/>
   <xsl:apply-templates select = "//contact">
      <xsl:with-param name = "who" select = "$who"/>
   </xsl:apply-templates>
   <br/>
   <hr />
</xsl:template>

<xsl:template match = "contact">
   <xsl:param name = "who"/>
   <xsl:if test = "contains(name, $who) or contains($who, name)">
      phone:
      <xsl:value-of select = "phone"/>
   </xsl:if>
</xsl:template>

Pull Variant Style Sheet

In the pull variant, also called control-driven or navigational, it's the style sheet's job to explicitly pull data from the XML document. This can lead to nested iterations and style sheets that need to be re-written if and when the format of the XML document changes.

<xsl:template match = "items">
   <html>
   <head><title> XSL Demo </title></head>
   <body>
   Appointments:
   <xsl:for-each select = "appointment">
      <hr />
      date and time:
      <xsl:value-of select = "date"/>@
      <xsl:value-of select = "time"/>
      with: <br/>
      <xsl:variable name = "who" select = "with"/>
      <xsl:value-of select = "$who"/>
      <xsl:for-each select =
         "/items/contact[contains(name, $who) or contains($who, name)]">
         <br/>phone:
         <xsl:value-of select = "phone"/>
      </xsl:for-each>
   </xsl:for-each>
   </body>
   </html>
</xsl:template>

Four Pattern Families

Fill-in-the-blanks

A Literal Result Element As Stylesheet or Simplified Style Sheet is a style sheet that does not have top level  xsl:stylesheet or xsl:template elements. This makes it possible to embed XSL elements in other XML documents. This means, among other things, that XSL elements can be used in HTML documents.

pearce.xml

<?xml version = "1.0"?>
<bio>
   <name> Jon Pearce </name>
   <education>
      <school>
         <name> UC Berkeley </name>
         <start> Fall 1967 </start>
         <end> Winter 1972 </end>
         <degree> BA </degree>
         <major> Math </major>
      </school>
   </education>
   <employment>
      <job>
         <employer> Stanford University Hospital</employer>
         <title> Orderly </title>
         <start> January 1968 </start>
         <end> June 1972 </end>
         <responsibilities>
            Assist the doctors and nurses in the emergency room.
         </responsibilities>
      </job>
      <job>
         <employer> Silma Inc.</employer>
         <title> Programmer </title>
         <start> September 1985 </start>
         <end> December 1990 </end>
         <responsibilities>
            Implement and maintain the development language for
            Silma's factory simulation software.
         </responsibilities>
      </job>
   </employment>
   <interests>
      <interest category = "travel">
         I like to immerse myself in foregin cultures. I have
         lived in Africa and Asia.
      </interest>
      <interest category = "philosophy">
         I am interested in Cognitive Psychology.
      </interest>
      <interest category = "photography">
         I have a digital camera, and I am learning
         to use Photo Shop.
      </interest>
   </interests>
</bio>

pearce.html

pearce1.xsl

<html
   xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
   xsl:version = "1.0"
>
<head><title> My Home Page </title></head>
<body bgcolor = "cyan">
   <h1 align = "center"> Welcome to Jon Pearce's Home Page </h1>
   <p align = "center">
      <image src="c:\pearce\spain\miro\DSCN0461.jpg"
         height="300" width="300"/> </p> <hr/>
   <p> Here are some of the places I have worked: <br/>
      <ul>
         <xsl:for-each select = "/bio/employment/job">
            <li>
               <xsl:value-of select = "title"/> at
               <xsl:value-of select = "employer"/>

            </li>
         </xsl:for-each>
      </ul> </p>
   <p> My interests include: <br />
      <ul>
         <xsl:for-each select = "/bio/interests/interest">
            <li><b><xsl:value-of select = "@category"/>: </b>
               <xsl:value-of select = "."/>
            </li>
         </xsl:for-each>
      </ul>
   </p> <hr/> </body> </html>

Navigational

If we want to add top level elements such as variables and additional templates to our style sheet, then we must have a root-level xsl:stylesheet element. In the next version of our style sheet, data from the source tree is extracted by explicitly navigating to the node where the data is declared. We might call this type of style sheet procedural, because it is close to traditional procedural programming. However, the order nodes in the source tree are extracted is still not under our control, hence Kay refers to this as the navigational style.

pearce.xsl

<?xml version = "1.0"?>
<xsl:stylesheet
   xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
   version = "1.0">

<xsl:template match = "/">
   <html>
   <head><title> My Home Page </title></head>
   <body bgcolor = "cyan">
      <h1 align = "center"> Welcome to Jon Pearce's Home Page </h1>
      <p align = "center">
         <image src="c:\pearce\spain\miro\DSCN0461.jpg"
            height="300" width="300"/> </p> <hr/>
      <xsl:call-template name = "jobs"/>
      <xsl:call-template name = "interests"/>
      <hr/> </body> </html>
</xsl:template>

<xsl:template name = "jobs">
   <p> Here are some of the places I have worked: <br/>
   <ul> <xsl:for-each select = "/bio/employment/job">
      <li>
         <xsl:value-of select = "title"/> at
         <xsl:value-of select = "employer"/>
      </li>
   </xsl:for-each> </ul> </p>
</xsl:template>

<xsl:template name = "interests">
   <p> My interests include: <br />
   <ul> <xsl:for-each select = "/bio/interests/interest">
      <li><b><xsl:value-of select = "@category"/>: </b>
         <xsl:value-of select = "."/>
      </li>
   </xsl:for-each> </ul> </p>
</xsl:template>
</xsl:stylesheet>

Rule-based

A rule-based style sheet is a collection of templates, one matching each type of element in our source tree. If a template needs to invoke other templates, it does so by calling:

<xsl:apply-templates/>

Control is handed over to the processor and the structure of the source tree to decide which templates to instantiate. This type of style sheet can work with many different types of source trees, as long as they contain the same elements. It doesn't matter where the elements are located.

pearce3.xsl

<?xml version = "1.0"?>
<xsl:stylesheet
   xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
   version = "1.0">

<xsl:template match = "bio">
   <html>
   <head><title> My Home Page </title></head>
   <body bgcolor = "cyan">
      <h1 align = "center"> Welcome to Jon Pearce's Home Page </h1>
      <p align = "center">
      <image src="c:\pearce\spain\miro\DSCN0461.jpg"
         height="300" width="300"/>
      </p> <hr/>
      <xsl:apply-templates/>
      <hr/> </body> </html>
</xsl:template>

<xsl:template match = "employment">
   <p> Here are some of the places I have worked: <br/> <ul>
      <xsl:apply-templates/>
   </ul> </p>
</xsl:template>

<xsl:template match = "interests">
   <p> My interests include: <br /> <ul>
      <xsl:apply-templates/>
   </ul> </p>
</xsl:template>

<xsl:template match = "job">
   <li>
      <xsl:value-of select = "title"/> at
      <xsl:value-of select = "employer"/>
   </li>
 </xsl:template>

<xsl:template match = "interest">
   <li><b><xsl:value-of select = "@category"/>: </b>
      <xsl:value-of select = "."/>
   </li>
</xsl:template>

<xsl:template match = "name | education">
   <!-- do nothing at this point -->
</xsl:template>

</xsl:stylesheet>

Computational

Computational style sheets need to output nodes that aren't explicitly in the source tree. This includes computing values that aren't explicitly in the source tree.

As such, this isn't really a design pattern. Instead, it's a collection of techniques such as using recursion to preserve state.

mt1.xml

<?xml version = "1.0"?>
<!-- scores from midterm #1 -->
<scores>
  <score> 92 </score>
  <score> 35 </score>
  <score> 95 </score>
  <score> 75 </score>
  <score> 76 </score>
  <score> 75 </score>
  <score> 67 </score>
  <score> 69 </score>
  <score> 35 </score>
  <score> 75 </score>
  <score> 92 </score>
  <score> 65 </score>
  <score> 95 </score>
  <score> 75 </score>
  <score> 76 </score>
</scores>

stats.html

stats.xsl

We need to compute the min, max, and average of a list of scores. This would be done with a single iteration in traditional languages, but in XSLT, we need to compute these values separately, and we need to do it using recursion, because we can't maintain the current minimum or maximum in variables that can be updated.

<xsl:template match = "/">
   <html>
   <head><title> Midterm 1 </title></head>
   <body>
   <h1 align = "center"> Results from Midterm 1 </h1>
  
   <p>
   The maximum score was
   <xsl:call-template name = "getmax">
      <xsl:with-param name = "nums" select = "//score"/>
      <xsl:with-param name = "result" select = "0"/>
   </xsl:call-template>
   </p>
  
   <p>
   The minimum score was
   <xsl:call-template name = "getmin">
      <xsl:with-param name = "nums" select = "//score"/>
      <xsl:with-param name = "result" select = "100"/>
   </xsl:call-template>
   </p>
  
   <p>
   The average score was
      <xsl:value-of select = "sum(//score) div count(//score)"/>
   </p>
  
   <xsl:variable name = "scores2"
      select = "/scores/score[not(.=preceding-sibling::score)]"/>
  
   <p> Here's the distribution: <br/>
  
   <xsl:for-each select = "$scores2">
      <xsl:sort order = "descending"/>
      <xsl:value-of select = "."/>
      <xsl:call-template name = "getcount">
         <xsl:with-param name = "nums" select = "//score"/>
         <xsl:with-param name = "result" select = "''"/>
         <xsl:with-param name = "target" select = "."/>
      </xsl:call-template> <br/>
   </xsl:for-each>
   </p> </body> </html>
</xsl:template>

The getmax template demonstrates recursion and conditional initialization of the temp variable.

<xsl:template name = "getmax">
   <xsl:param name = "nums"/>
   <xsl:param name = "result"/>
  
   <xsl:choose>
      <xsl:when test = "$nums">
         <xsl:variable name = "temp">
            <xsl:choose>
               <xsl:when test = "$nums[1] &gt; $result">
                  <xsl:value-of select = "$nums[1]"/>
               </xsl:when>
               <xsl:otherwise>
                  <xsl:value-of select = "$result"/>
               </xsl:otherwise>
            </xsl:choose>
         </xsl:variable>
         <xsl:call-template name = "getmax">
            <xsl:with-param name = "nums"
               select = "$nums[position() != 1]"/>
            <xsl:with-param name = "result" select = "$temp"/>
         </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
         <xsl:value-of select = "$result"/>
      </xsl:otherwise>
   </xsl:choose>
</xsl:template>

The getmin and getcount templates are nearly identical to the getmax template and are left as exercises. Is there some way the same template could be used for all three calculations?