Recipe16.1.Creating Polymorphic XSLT


Recipe 16.1. Creating Polymorphic XSLT

Problem

You want to create XSLT that performs the same function on disparate data.

Solution

There are two kinds of polymorphic behavior in XSLT. The first form is reminiscent of overloading, and the second is similar to overriding.

Some modern languages, notably C++, let you create overloaded functions: functions that have the same name but take different types as arguments. The compiler figures out which version of the function to call based on the type of data passed to it at the point of call. XSLT does not have this exact capability; however, consider the following stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">     <xsl:output method="html" />     <xsl:template match="/">   <html>     <head>       <title>Area of Shapes</title>     </head>     <body>       <h1>Area of Shapes</h1>       <table cellpadding="2" border="1">         <tbody>           <tr>             <th>Shape</th>             <th>Shape Id</th>             <th>Area</th>           </tr>           <xsl:apply-templates/>         </tbody>       </table>     </body>   </html> </xsl:template>     <xsl:template match="shape">   <tr>     <td><xsl:value-of select="@kind"/></td>     <td><xsl:value-of select="@id"/></td>     <xsl:variable name="area">       <xsl:apply-templates select="." mode="area"/>     </xsl:variable>      <td align="right"><xsl:value-of select="format-number($area,'#.000')"/></td>   </tr>   </xsl:template>       <xsl:template match="shape[@kind='triangle']" mode="area">   <xsl:value-of select="@base * @height"/> </xsl:template>     <xsl:template match="shape[@kind='square']" mode="area">   <xsl:value-of select="@side * @side"/>  </xsl:template>     <xsl:template match="shape[@kind='rectangle']" mode="area">   <xsl:value-of select="@width * @height"/> </xsl:template>     <xsl:template match="shape[@kind='circle']" mode="area">   <xsl:value-of select="3.1415 * @radius * @radius"/>  </xsl:template>     </xsl:stylesheet>

Notice that several templates have an area mode. These templates differ in what types of shapes they accept and compute a different function based on the particular shape. If you equate mode to function name and the match pattern to the data type, you will immediately see this technique as an instance of polymorphic overloading.

Overriding is the second form of polymorphism. You can see numerous examples of overriding in this book's examples. In XSLT, you use xsl:import to achieve this form of polymorphic behavior. The following example is a rewrite of the DocBook stylesheet from Recipe 10.1. It was engineered for extensibility in the following ways:

  • It uses variables to define primitive components of otherwise monolithic attribute content. The variables can be redefined when importing stylesheets.

  • It uses attribute sets, which can be augmented with additional attributes or have existing attributes overridden in importing stylesheets.

  • It uses simple templates for each section of the document that can be overridden in importing stylesheets.

  • It provides a hook via a call to the named template exTRa-head-meta-data whose default implementation does nothing.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output method="html"/>       <!-- Variables defining various style components -->   <xsl:variable name="standard-font-family" select=" 'font-family:    Times serif; font-weight' "/>       <xsl:variable name="chapter-label-font-size" select=" 'font-size : 48pt' "/>   <xsl:variable name="chapter-title-font-size" select=" 'font-size : 24pt' "/>   <xsl:variable name="epigraph-font-size" select=" 'font-size : 10pt' "/>   <xsl:variable name="sect1-font-size" select=" 'font-size : 18pt' "/>   <xsl:variable name="sect2-font-size" select=" 'font-size : 14pt' "/>   <xsl:variable name="normal-font-size" select=" 'font-size : 12pt' "/>       <xsl:variable name="normal-text-color" select=" 'color: black' "/>   <xsl:variable name="chapter-title-color" select=" 'color: red' "/>      <xsl:variable name="epigraph-padding" select=" 'padding-top:4;    padding-bottom:4' "/>      <xsl:variable name="epigraph-common-style" select="concat($standard-font- family,'; ', $epigraph-font-size, '; ', $epigraph-padding, '; ',$normal-text- color)"/>   <xsl:variable name="sect-common-style" select="concat($standard-font-family,';  font-weight: bold', '; ',$normal-text-color)"/>       <!-- Attribute sets -->   <xsl:attribute-set name="chapter-align">     <xsl:attribute name="align">right</xsl:attribute>    </xsl:attribute-set>       <xsl:attribute-set name="normal-align">   </xsl:attribute-set>      <xsl:attribute-set name="chapter-label" use-attribute-sets="chapter-align">     <xsl:attribute name="style">       <xsl:value-of select="$standard-font-family"/>;        <xsl:value-of select="$chapter-label-font-size"/>;        <xsl:value-of select="$chapter-title-color"/>       <xsl:text>; padding-bottom:10; font-weight: bold</xsl:text>     </xsl:attribute>    </xsl:attribute-set>         <xsl:attribute-set name="chapter-title" use-attribute-sets="chapter-align">     <xsl:attribute name="style">       <xsl:value-of select="$standard-font-family"/>;        <xsl:value-of select="$chapter-title-font-size"/>;        <xsl:value-of select="$chapter-title-color"/>       <xsl:text>; padding-bottom:150; font-weight: bold</xsl:text>     </xsl:attribute>    </xsl:attribute-set>         <xsl:attribute-set name="epigraph-para" use-attribute-sets="chapter-align">     <xsl:attribute name="style">       <xsl:value-of select="$epigraph-common-style"/><xsl:text>;        font-style: italic</xsl:text>     </xsl:attribute>    </xsl:attribute-set>       <xsl:attribute-set name="epigraph-attribution"    use-attribute-sets="chapter-align">     <xsl:attribute name="style">       <xsl:value-of select="$epigraph-common-style"/>     </xsl:attribute>    </xsl:attribute-set>       <xsl:attribute-set name="sect1">     <xsl:attribute name="align">left</xsl:attribute>      <xsl:attribute name="style">       <xsl:value-of select="$sect-common-style"/>;        <xsl:value-of select="$sect1-font-size"/>     </xsl:attribute>    </xsl:attribute-set>       <xsl:attribute-set name="sect2">     <xsl:attribute name="align">left</xsl:attribute>      <xsl:attribute name="style">       <xsl:value-of select="$sect-common-style"/>;        <xsl:value-of select="$sect2-font-size"/>     </xsl:attribute>    </xsl:attribute-set>       <xsl:attribute-set name="normal">     <xsl:attribute name="align">left</xsl:attribute>      <xsl:attribute name="style">       <xsl:value-of select="$standard-font-family"/>;        <xsl:value-of select="$normal-font-size"/>;        <xsl:value-of select="$normal-text-color"/>     </xsl:attribute>    </xsl:attribute-set>       <!-- Templates -->   <xsl:template match="/">     <html>       <head>         <xsl:apply-templates mode="head"/>         <xsl:call-template name="extra-head-meta-data"/>       </head>       <body style=       "margin-left:100;margin-right:100;margin-top:50;margin-bottom:50">         <xsl:apply-templates/>          <xsl:apply-templates select="chapter/chapterinfo/*" mode="copyright"/>        </body>     </html>      </xsl:template>       <!-- Head -->      <xsl:template match="chapter/title" mode="head">         <title><xsl:value-of select="."/></title>   </xsl:template>       <xsl:template match="author" mode="head">         <meta name="author" content="{concat(firstname,' ', surname)}"/>   </xsl:template>       <xsl:template match="copyright" mode="head">         <meta name="copyright" content="{concat(holder,' ',year)}"/>   </xsl:template>       <xsl:template match="text( )" mode="head"/>       <!--Override in importing stylesheet if necessary -->   <xsl:template name="extra-head-meta-data"/>    <!-- Body -->      <xsl:template match="chapter">     <div xsl:use-attribute-sets="chapter-label">       <xsl:value-of select="@label"/>     </div>     <xsl:apply-templates/>   </xsl:template>         <xsl:template match="chapter/title">     <div xsl:use-attribute-sets="chapter-title">       <xsl:value-of select="."/>     </div>   </xsl:template>       <xsl:template match="epigraph/para">     <div xsl:use-attribute-sets="epigraph-para">       <xsl:value-of select="."/>     </div>   </xsl:template>       <xsl:template match="epigraph/attribution">     <div xsl:use-attribute-sets="epigraph-attribution">       <xsl:value-of select="."/>     </div>   </xsl:template>         <xsl:template match="sect1">     <h1 xsl:use-attribute-sets="sect1">       <xsl:value-of select="title"/>     </h1>     <xsl:apply-templates/>   </xsl:template>       <xsl:template match="sect2">     <h2 xsl:use-attribute-sets="sect2">     <xsl:value-of select="title"/>     </h2>      <xsl:apply-templates/>   </xsl:template>      <xsl:template match="para">     <p xsl:use-attribute-sets="normal">       <xsl:value-of select="."/>     </p>   </xsl:template>       <xsl:template match="text( )"/>     <xsl:template match="copyright" mode="copyright">   <div style="font-size : 10pt; font-family: Times serif; padding-top : 100">     <xsl:text>Copyright </xsl:text>     <xsl:value-of select="holder"/>     <xsl:text> </xsl:text>     <xsl:value-of select="year"/>     <xsl:text>. All rights reserved.</xsl:text>   </div> </xsl:template>        <xsl:template match="*" mode="copyright"/>     </xsl:stylesheet>

Discussion

Calling XSLT an object-oriented language would stretch the truth. The behavior of xsl:import is only loosely similar to inheritance, and it operates at the level of the entire stylesheet. Furthermore, it has no notion of encapsulation or data abstraction. However, XSLT developers who already have an object-oriented mindset can often leverage that experience to the creation of more modular and reusable stylesheets.

Consider the example of overloading in the "Solution" section. Whenever you need to perform similar operations on disparate data, you will often want to structure your stylesheet in this manner. Of course, you could achieve the same result using conditional logic:

<xsl:template match="shape">   <tr>     <td><xsl:value-of select="@kind"/></td>     <td><xsl:value-of select="@id"/></td>     <xsl:variable name="area">       <xsl:call-template name="area"/>     </xsl:variable>      <td align="right"><xsl:value-of select="format-number($area,'#.000')"/></td>   </tr>   </xsl:template>       <xsl:template name="area">   <xsl:choose>        <xsl:when test="@kind='triangle' ">       <xsl:value-of select="@base * @height"/>     </xsl:when>          <xsl:when test="@kind='square' " >       <xsl:value-of select="@side * @side"/>      </xsl:when>          <xsl:when test="@kind='rectangle' ">       <xsl:value-of select="@width * @height"/>     </xsl:when>          <xsl:when test="@kind='circle' ">       <xsl:value-of select="3.1415 * @radius * @radius"/>      </xsl:when>        </xsl:choose>    </xsl:template>

In a simple case such as this, it is difficult to argue that the overloading method is superior to the conditional one. However, consider cases when someone needs to reuse the stylesheet, except that he must deal with triangles encoded in terms of sides and angles. If the stylesheet is written using separate templates for each shape, then importing the stylesheet and extending its behavior is simple. However, if the logic for each shape is written in a monolithic conditional manner, then the user must either copy the entire stylesheet and edit the parts that must change or extend the original and risk breaking it unintentionally.

Overriding is the key to creating modular and reusable XSLT. Nevertheless, reuse does not come for free; it requires planning. Specifically, you must think about three things:

  1. What clients of your stylesheet might want to alter

  2. What you may not want them to alter

  3. What they are unlikely to alter

Failing to think about Item 1 results in an inflexible stylesheet that can be reused only by the cut, paste, and edit mechanism. Failing to think about Item 2 could create pitfalls for the client of your stylesheet. Failing to think about Item 3 results in overly complicated stylesheets with potentially poor performance and low maintainability.

In the DocBook stylesheet, we made it convenient to override font and other text attributes individually. We made it less convenient to override alignment attributes in a way that would create inconsistent or poor alignment choices between text elements where you desire consistency or a particular alignment. For example, it is more difficult to change the alignment of section and normal text elements by specifying their alignment separately. Finally, we did not try to make it easy for particular HTML element names to be overridden because doing so would have complicated the stylesheet in a way that adds little value.

See Also

Chris Rathman has a more extensive example of polymorphic XSLT at http://www.angelfire.com/tx4/cus/shapes/xsl.html.




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net