Recipe6.6.Emulating Object-Oriented Reuse and Design Patterns


Recipe 6.6. Emulating Object-Oriented Reuse and Design Patterns

Problem

You would like to graduate from cut and paste XSLT reuse to creating libraries of reusable XSLT code.

Solution

Clearly XSLT 2.0 is not an object-oriented programming language. However, object orientation is as much about how to engineer generic reusable code as it is about the creation of classes, objects, inheritance, encapsulation, and the like.

There are two features of XSLT 2.0 that facilitate an object-oriented style. The first is the instruction xsl:next-match and the second is tunnel parameters. The xsl:next-match instruction is a generalization of XSLT 1.0's xsl:apply-imports. Recall that in XSLT 1.0, you use xsl:apply-imports to invoke templates of lower import precedence. The xsl:next-match instruction generalizes this behavior by allowing you to invoke matching templates of lower priority within the same stylesheet and importing stylesheets. This is akin to calling a base class method in OO programming:

<xsl:template match="author | title | subtitle | deck" priority="1">   <a name="{generate-id( )}">     <span >       <xsl:apply-templates/>     </span>   </a> </xsl:template>   <xsl:template match="author" priority="2">   <div>     <span >By </span>     <xsl:next-match/>   </div> </xsl:template>   <xsl:template match="title" priority="2">   <h1 ><xsl:next-match/></h1> </xsl:template>   <xsl:template match="deck" priority="2">   <h2 ><xsl:next-match/></h2> </xsl:template>   <xsl:template match="subtitle" priority="2">   <h2 ><xsl:next-match/></h2> </xsl:template>

A further enhancement in 2.0 is the ability to pass parameters to both xsl:next-match and xsl:apply-imports:

<xsl:next-match>   <xsl:with-param name="indent" select="2"/> </xsl:next-match>

A further capability in XSLT 2.0 templates is tunnel parameters , a form of dynamic scoping that is popular in functional programming. Tunnel parameters allow calls to xsl:apply-templates to pass parameters that are not necessarily known to the immediately matching templates. However, these templates are transparently carried over from call to call until they arrive at a template that contains such parameters. Note that the attribute tunnel="yes" must be used both at the point of call and the point where the parameter is accepted:

<xsl:stylesheet version="2.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!--Standard processing rules for doc --> <xsl:import href="doc.xslt"/> <!-- A custom parameter not envisioned by the author of doc.xslt --> <xsl:param name="customParam"/> <xsl:template match="/">  <!--Invoke templates from doc.xslt that have no       knowledge of customParam -->   <xsl:apply-templates>       <xsl:with-param name="customParam"                       select="$customParam" tunnel="yes"/>    </xsl:apply-templates>  </xsl:template> <xsl:template match="heading1">     <!-- Do something special with heading1 elements           based on customParam -->      <xsl:param name="customParam" tunnel="yes"/>      <!-- ...                   --> </xsl:template> </xsl:stylesheet>

This is an extremely important enhancement to XSLT, because it allows you to decouple application-specific templates from generic ones without introducing global parameters or variables.

Discussion

In object-oriented development, the notion of design patterns is quite popular. These are tried and true techniques that have broad application in a variety of problems. The patterns facilitate communication between developers by providing semi-standard names for the techniques described by the pattern, as well as the applicable context.

The facilities described in this recipe and Recipe 6.3 can be used to implement some of the standard patterns from an XSLT perspective. Next we adapt some standard patterns to the domain of XSLT.

Template method

Define the skeleton of a stylesheet in an operation, deferring some steps to templates implemented by importing stylesheets. Template Method lets others redefine certain steps of a transformation without changing the transformation's structure.

Consider a stylesheet that defines the standard by which your company renders an XML document to the web:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0"                  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"                  xmlns:xs="http://www.w3.org/2001/XMLSchema" >        <xsl:output method="xhtml" indent="yes"/>   <xsl:param name="titlePrefix" select=" '' " as="xs:string"/>        <xsl:template match="/">     <html>       <head>         <title><xsl:value-of                      select="concat($titlePrefix, /*/title)"/></title>       </head>       <body>         <xsl:call-template name="standard-processing-sequence"/>       </body>     </html>     </xsl:template>   <xsl:template name="standard-processing-sequence">     <xsl:apply-templates mode="front-matter">       <xsl:with-param name="mode" select=" 'front-matter' "                        tunnel="yes" as="xs:string"/>     </xsl:apply-templates>              <xsl:apply-templates mode="toc">       <xsl:with-param name="mode" select=" 'toc' "                        tunnel="yes" as="xs:string"/>     </xsl:apply-templates>              <xsl:apply-templates mode="body">         <xsl:with-param name="mode" select=" 'body' "                        tunnel="yes" as="xs:string"/>     </xsl:apply-templates>              <xsl:apply-templates mode="appendicies">       <xsl:with-param name="mode" select=" 'appendicies' "                        tunnel="yes" as="xs:string"/>     </xsl:apply-templates>   </xsl:template>        <xsl:template match="/*" mode="#all">     <xsl:param name="mode"  tunnel="yes" as="xs:string"/>     <div >       <xsl:apply-templates mode="#current"/>     </div>   </xsl:template>   <!-- Default templates for various modes go here -         these can be overridden in importing stylesheets -->        </xsl:stylesheet>

Here you use modes to identify each major stage of processing. However, you also pass the current mode's name in a tunnel parameter. This has two benefits. First, it is useful for debugging templates that match in multiple modes. Second, it allows similar multi-mode templates whose behavior varies by a small amount to implement this variation as a function of the mode parameter, without necessarily having knowledge of the specific modes. For example, if the template attaches CSS styles to the output elements, those styles can be prefixed with the mode name or use some other general mapping (e.g., table lookup) to go from mode to CSS style.

Chain of responsibility

Avoid coupling the initiator of a transformation to its templates that handle it by giving more than one template a chance to handle the request. Rely on template matching rather than conditional logic to determine the appropriate template.

Priorities are key to making this pattern portable because some XSLT processor may not handle templates with ambiguous template precedence. This example is adapted from a dynamic web site project I worked on that also used Cocoon. This project used templatized HTML where the class attributes dictated how dynamic content from an XML database would be rendered into the templated HTML. I omit the details of each template because they are less important than the overall structure. In this example, only one template will match in any given xhtm:td node, but in the general case, you can use xsl:next-match to combine the effects of multiple matching templates:

<xsl:template match="xhtm:td[matches(@class, '^keep-(\w+)')]"               mode="template" priority="2.1"> </xsl:template> <xsl:template match="xhtm:td[matches(@class, '^(flow|list)-(\w+)')]"               mode="template" priority="2.2"> </xsl:template> <xsl:template match="xhtm:td[matches(@class, '^repeat-(\w+)')]"               mode="template" priority="2.3">   </xsl:template> <xsl:template match="xhtm:td[matches(@class, '^download-(\w+)')]"               mode="template" priority="2.4"> </xsl:template>  <xsl:template match="xhtm:td[matches(@class, '^keep-(\w+)')]"               mode="template" priority="2.1"> <xsl:template>

Decorator

Add behavior to lower-priority templates by matching nodes using a higher priority of higher import precedence templates. Invoke the core behavior using xsl:next-match.

This is the classic use of the next match we discussed in the solution section, so we omit an example here.




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