XML 2002 logo

Developing Enterprise XSL Stylesheets - Best Practices and Lessons Learned

Abstract

This session will outline the approaches taken and lessons learned in developing Extensible Stylesheet Language, Transformations (XSLT), and Extensible Stylesheet Language, Formatting Objects (XSL-FO) stylesheets for a large international organization[1] Some of the requirements for this project were that the output support seven languages for publication, that all data be presented identically regardless of the language of the document instance, that document instances from several document types could be presented as either 'stand-alone' or embedded into a 'master' document with identical formatting, and that the stylesheets be 'pure' Extensible Stylesheet Language (XSL) (i.e., no extension functions) to allow partners freedom in their choice of XSL processor.

Faced with these requirements, a component-based architecture was chosen, using structural templates to define the framework of the rendered document and <xsl:include>s to call the required components. Commonly-used functions, such as those for converting text to upper- or lower-case and formatting of data (e.g., dates) were implemented by passing parameters to named templates. Boilerplate text for each language was stored in XML documents referenced by elements in the stylesheet templates. By using this architecture the stylesheet for each language contains identical formatting, regardless of the language of the boilerplate text; additionally, adding translations of the boilerplate text to the XML file could easily produce stylesheets for new publication languages.

The stylesheets were compiled into distributable stand-alone versions by processing the component stylesheets with assembler stylesheets; the result, that one hundred component files were assembled into seven HTML rendering and seven rendering stylesheets for each language.

Keywords


Table of Contents

1. Introduction
2. Overview: The Business Problem
3. Best Practices
3.1. Driver Files
3.2. Structural Presentational Logic
3.3. Formatting Presentational Logic
3.4. Boilerplate Text
3.5. Conditional Text Templates
3.6. Commonly-used Functions
4. Assembly
5. Lessons Learned
6. Summary
Glossary
Biography

1. Introduction

This paper will outline the approaches taken and lessons learned in developing XSLT and XSL-FO stylesheets for a large international organization. It should be clear that development in an emerging technology is not accomplished in a vacuum, but as part of a collaborative practice. To that end, I would like to thank the users of the XSL-List http://www.mulberrytech.com/xsl/xsl-list/ and its host, Mulberry Technologies, as well as Dave Pawson, who has compiled the unofficial XSLT FAQ http://www.dpawson.co.uk/ from conversations that have taken place on the list. These are invaluable resources for XSL developers.

2. Overview: The Business Problem

The techniques that will be discussed have been used in creating XSL stylesheets for a large international organization that had a number of specific business requirements:

  • Appearance of the rendered documents must be identical to legacy document formats

  • This organization charges fees based on the number of printed pages; thus the rendered output must exactly match the legacy output (although this archaic 'per-page' fee schedule will eventually evolve into one more compatible with electronic data)

  • Multi-language support (seven official languages of publication)

  • Conformity of presentation (that is, the same data must have the same appearance regardless of the language the text was rendered in)

Additionally, there were other requirements that had to be met:

  • Several document types could either be rendered as 'stand-alone' or embedded as part of a larger document

  • The stylesheets must be distributed with a minimum amount of effort; for example, no dependencies on external files

  • No extension functions were to be used; the stylesheets must conform to XSL 1.0 and execute under any conforming XSL processor

  • The rendering must not rely on one vendor's implementations of standards

3. Best Practices

Architecture

A general best practice in software design is to develop reusable components; among the benefits that can be realized by using this approach are:

  • Shortened development schedules, reduced risk, and lower costs

  • Accelerated development through reuse of existing software components

  • Higher quality products assembled from proven and tested components

Although this has been hotly debated, XSL can be viewed as a programming language, and as such, a modular, component-based approach was adopted to achieve the benefits outlined above.

In developing these stylesheets each template was examined for its purpose and its level of reusability (for example, does this element appear in several document types? Is it formatted in the same way in each of these document types?). Templates that were specific to one document type were stored in stylesheet files particular to that document type, while templates that could be reused across a variety of document types were stored as separate files. Finally, stylesheets containing all required templates were assembled using XSLT to produce compiled, stand-alone stylesheets for distribution.

Similar to the design of modular Extensible Hypertext Markup Language (XHTML), component files were divided into the following groups, according to their functionality:

  1. Section 3.1'Driver' files to include necessary modules

  2. Section 3.2Structural presentational logic

  3. Section 3.3Formatting presentational logic

  4. Section 3.4Boilerplate text for each language

  5. Section 3.5Conditional text templates

  6. Section 3.6Commonly-used functions

3.1. Driver Files

Stylesheets for each document type use a 'driver' file, an XSL template that is processed by XSL 'assembler' stylesheets. The stylesheet driver file accomplishes the following tasks:

  1. ???Determines the language of the document for subsequent processing

  2. ???Defines page size

  3. ???Defines formatting characteristics for that page size (e.g., size, margins)

  4. ???Sets headers and/or footers

  5. ???Calls the named template that will process the document, and

  6. ???Includes required component templates via <xsl:include> statements. Note that in this example, the actual processing of the document is passed on to the included 'fee_info' template. This same named template is included in the stylesheets for multiple document types, as the fees may be a 'stand-alone' document or embedded as part of larger documents.

<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' 
                xmlns:fo='http://www.w3.org/1999/XSL/Format' version='1.0'>  
   <xsl:output method='xml' version='1.0' indent='no' encoding='UTF-8'/>
   <xsl:strip-space elements='*'/>
   <!--   PARAMETER 'lang': determines language of application and formats    
          stylesheet-generated text accordingly    -->
<-- [1] -->
 <xsl:variable name='lang' select="translate(//fee-sheet/@lang,    
                                     'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                                     'abcdefghijklmnopqrstuvwxyz')"/>
<-- [2] -->
<!--   default page size = A4    -->
   <xsl:param name='page_size'/>
   <xsl:variable name='page_height'>
      <xsl:choose>  
         <xsl:when test="$page_size='A4' or $page_size='a4'">297</xsl:when>
         <xsl:when test="$page_size='Letter' 
                      or $page_size='letter'">279</xsl:when>
         <xsl:otherwise>297</xsl:otherwise>
      </xsl:choose>
   </xsl:variable>
   <xsl:variable name='page_width'>  
      <xsl:choose>  
         <xsl:when test="$page_size='A4' or $page_size='a4'">210</xsl:when>
         <xsl:when test="$page_size='Letter' 
                      or $page_size='letter'">215</xsl:when>
         <xsl:otherwise>210</xsl:otherwise>
      </xsl:choose>
   </xsl:variable>
<!-- parameter for path and filename of document instance if embedded -->
   <xsl:param name='fee_doc'/>
   <xsl:template match='/'>  
      <fo:root>  
         <fo:layout-master-set>  
<-- [3] -->  
         <fo:simple-page-master master-name='page'
                                   page-width='{$page_width}mm' 
                                   page-height='{$page_height}mm' 
                                   margin-top='1.5cm' 
                                   margin-bottom='2cm' 
                                   margin-left='2cm' 
                                   margin-right='2.3cm'>  
               <fo:region-body margin-bottom='0mm' margin-right='0mm' 
                               margin-left='0mm' margin-top='15mm'/>
               <fo:region-before extent='2.5cm'/>
            </fo:simple-page-master>
            <fo:page-sequence-master master-name='fee-sheet'>  
               <fo:repeatable-page-master-reference master-reference='page'/>
            </fo:page-sequence-master>
         </fo:layout-master-set>
         <xsl:variable name='avail_width'>  
            <xsl:value-of select='$page_width - 50'/>
         </xsl:variable>
         <fo:page-sequence master-reference='fee-sheet'>  
            <fo:static-content flow-name='xsl-region-before'>  
               <fo:block width='{$avail_width}mm' font-family='Helvetica'>  
                  <fo:table table-layout='fixed'>  
                     <fo:table-column column-width='55mm'/>
                     <fo:table-column column-width='55mm'/>
                     <fo:table-column column-width='55mm'/>
                     <fo:table-body>  
                        <fo:table-row>  
                           <fo:table-cell><fo:block/></fo:table-cell>
                           <fo:table-cell>  
                              <fo:block text-align='center' font-size='10pt' 
                                           background-color='white'>  
                              <fo:page-number/>/
                              <fo:page-number-citation ref-id='endofdoc'/>
                             </fo:block>
                           </fo:table-cell>
                           <fo:table-cell>
                             <fo:block/>
                           </fo:table-cell>
                        </fo:table-row>
                        <fo:table-row>  
<-- [4] -->    
                         <fo:table-cell number-columns-spanned='2'>                  
                              <fo:block text-align='left' font-size='10pt' 
                                    color='black' background-color='white'>
                                 <fo:inline font-weight='bold'>  
                                    <include file='commonText.xml' 
                                               toTranslate='feeAnnex_Text'/>
                                     </fo:inline>
                                   </fo:block>
                                </fo:table-cell>
                           <fo:table-cell><fo:block/></fo:table-cell>
                           <fo:table-cell>  
                              <fo:block text-align='right' font-size='8pt' 
                                        background-color='white'>  
                                 <xsl:value-of select='./file-reference-id'/>
                              </fo:block>
                          </fo:table-cell>
                        </fo:table-row>
                        <fo:table-row>  
                           <fo:table-cell number-columns-spanned='3'>
                              <fo:block text-align='center' font-size='8pt' 
                                        background-color='white'>  
                                 <include file='commonText.xml' 
                                            toTranslate='original_Text'/>
                                 <fo:inline font-weight='bold'>  
                                    <include file='commonText.xml' 
                                               toTranslate='SUBMISSION_Text'/>
                                     </fo:inline>)</fo:block>
                            </fo:table-cell>
                        </fo:table-row>
                        <fo:table-row>  
                           <fo:table-cell number-columns-spanned='3' 
                                          text-align='center' 
                                          font-size='8pt' 
                                          vertical-align='top'>  
                              <fo:block background-color='white'>  
                                 <include file='commonText.xml' 
                                            toTranslate='thisSheet_Text'/>
                                    </fo:block>
                                </fo:table-cell>
                             </fo:table-row>
                          </fo:table-body>
                       </fo:table>
                    </fo:block>
                 </fo:static-content>
            <fo:flow flow-name='xsl-region-body'>  
               <fo:block xsl:use-attribute-sets="hyphenation">  
                   <xsl:attribute name="font-family">Helvetica</xsl:attribute>
<-- [5] -->   
                <xsl:call-template name="fee_info" />
                </fo:block>  
                <fo:block id="endofdoc" />   
            </fo:flow>  
      </fo:page-sequence>  
 </fo:root>  
 </xsl:template>  
  
<-- [6] --> 
<!--  INCLUDE COMPONENT TEMPLATES   -->
 <xsl:include href="./../templates/functions/uppercaser.xsl" />   
 <xsl:include href="./../templates/functions/format_date.xsl" />   
 <xsl:include href="./../templates/functions/format_name.xsl" />   
 <xsl:include href="./templates/officeName.xsl" />   
 <xsl:include href="./templates/attribute-sets.xfo" />   
 <xsl:include href="./templates/fee_info.xfo" />   
 <xsl:include href="./templates/doc-page.xfo" />   
 <!-- process the document -->
    <xsl:template match="fee-sheet">  
      <xsl:call-template name="fee_info">  
         <xsl:with-param name="lang" select="$lang" />   
      </xsl:call-template>  
    </xsl:template>  
 </xsl:stylesheet>                              
				

3.2. Structural Presentational Logic

As one of the commonly touted benefits of XML is separating content from style, different XSL templates can be used to separate structure from style.

Separate templates define structural frameworks; in most cases, this consists of a table with columns for displaying codes for internal processing, boilerplate text, and data from the XML document being rendered. Formatting is applied to the structure via included formatting templates. By defining and reusing structural templates, development time was greatly reduced, as (in the example below) each table cell was assigned a set of formatting attributes based on its position (tdL, tdC, tdR).

In the code example below, a table is constructed for displaying information relating to a type of person. Formatting specifics are not included in this template, but through including named attribute sets.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:fo="http://www.w3.org/1999/XSL/Format">
 <!--   *** types of person ***    -->   
 <xsl:template name="person_data">  
 <xsl:for-each select=".//butcher
                    | .//baker
                    | .//candlestick-maker">              
 <xsl:variable name="seq" select="position()" />   
 <fo:table table-layout="fixed" border-collapse="separate" 
           keep-together.within-page="always">  
    <fo:table-column column-width="12mm" />   
    <fo:table-column column-width="53mm" />   
    <fo:table-column column-width="105mm" />   
      <fo:table-body>  
         <fo:table-row>  
           <fo:table-cell xsl:use-attribute-sets="tdTL">
             <fo:block>III-<xsl:value-of select="$seq" /></fo:block>  
           </fo:table-cell>  
           <fo:table-cell xsl:use-attribute-sets="tdTC">  
             <fo:block>
               <include file="commonText.xml" toTranslate="address_Text" />  
             </fo:block>
           </fo:table-cell>  
          <fo:table-cell xsl:use-attribute-sets="tdTR">  
             <fo:block />   
          </fo:table-cell>  
     </fo:table-row>
				

3.3. Formatting Presentational Logic

The presentational qualities for all templates are stored in one file that is included into all compiled stylesheets. Styling is accomplished by defining common <xsl:attribute-set>s containing formatting properties (e.g., border-width, font family and size, padding, etc.) for XSL-FO, and by creating Cascading Style Sheets (CSS) classes for XSLT.

Example: formatting logic component for XSLT

<?xml version="1.0" encoding=""?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
   <!-- Inserts a <style> element containing a list of CSS styles used by      
        stylesheets into the <head> section of the resulting HTML document.    -->
     
   <xsl:template name='css_styles'>  
      <style type='text/css'>
       /* table data top left*/ .tdTL { border-top: 1px; 
                                        border-top-color: black; 
                                        border-top-style: solid; 
                                        border-left: 1px; 
                                        border-left-color: black; 
                                        border-left-style: solid; 
                                        font-size: smaller; 
                                        vertical-align: top;} 
       /* table data top center */ .tdTC {border-top: 1px; 
                                          border-top-color: black; 
                                          border-top-style: solid; 
                                          border-left: 1px; 
                                          border-left-color: black; 
                                          border-left-style: solid; 
                                          border-right: 1px; 
                                          border-right-color: black; 
                                          border-right-style: solid; 
                                          font-size: smaller; 
                                          vertical-align: top;} 
       /* table data top right */ .tdTR {border-top: 1px; 
                                         border-top-color: black; 
                                         border-top-style: solid; 
                                         border-right: 1px; 
                                         border-right-color: black; 
                                         border-right-style: solid; 
                                         font-size: smaller; 
                                         vertical-align: top;}            
      </style>
   </xsl:template>
</xsl:stylesheet>
				

Example: formatting logic component for XSL-FO

<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' 
                xmlns:fo='http://www.w3.org/1999/XSL/Format' version='1.0'>  
   <xsl:attribute-set name='tdTL'>
      <!--   table, top left column    -->
      <xsl:attribute name='border-top'>solid black 0.5pt</xsl:attribute>
      <xsl:attribute name='font-size'>8pt</xsl:attribute>
      <xsl:attribute name='vertical-align'>top</xsl:attribute>
      <xsl:attribute name='padding'>1pt</xsl:attribute>
      <xsl:attribute name='text-align'>left</xsl:attribute>
   </xsl:attribute-set>
   <xsl:attribute-set name='tdTC'> 
      <!--   table, top center column    -->
      <xsl:attribute name='border-top'>solid black 0.5pt</xsl:attribute>
      <xsl:attribute name='border-left'>solid black 0.5pt</xsl:attribute>
      <xsl:attribute name='border-right'>solid black 0.5pt</xsl:attribute>
      <xsl:attribute name='font-size'>8pt</xsl:attribute>
      <xsl:attribute name='vertical-align'>top</xsl:attribute>
      <xsl:attribute name='padding'>1pt</xsl:attribute>
   </xsl:attribute-set>
   <xsl:attribute-set name='tdTR'> 
      <!--   table, top right column, with data    -->
      <xsl:attribute name='border-top'>solid black 0.5pt</xsl:attribute>
      <xsl:attribute name='font-size'>12pt</xsl:attribute>
      <xsl:attribute name='font-family'>Courier</xsl:attribute>
      <xsl:attribute name='padding'>1pt</xsl:attribute>
   </xsl:attribute-set>
</xsl:stylesheet>
				

Defining styles once and then importing them assured a consistency of output across document types, the automatic promulgation of changes to all stylesheets, and allowed faster creation of templates.

3.4. Boilerplate Text

The rendered output contained much text not found in the document instance. Rather than create separate templates to be used for each language of publication, elements were added to the stylesheet templates that were placeholders for text to be inserted into when the stylesheets were compiled:

 <fo:block>
     <fo:inline font-weight="bold">
        <include file="commonText.xml"  toTranslate="address_Text" />                   
      </fo:inline>
 </fo:block>
				

XML files were used to store generated text for all languages. These files contained not only legal text, but also translations for the names of countries and languages taken from International Standards Organization (ISO) standards.

<term name="address_Text">
     <text xml:lang="en">Address:</text> 
     <text xml:lang="de">Anschrift:</text> 
     <text xml:lang="es">Dirección:</text> 
     <text xml:lang="fr">Adresse:</text> 
     <text xml:lang="ja">あて名</text> 
     <text xml:lang="ru">Адрес</text> 
     <text xml:lang="zh">地址: </text> 
  </term>
				

This enabled each template to be used for all languages; additionally, it became easier to manage the boilerplate text -- since it was now contained in one file, it was easy to determine what text needed to be translated or process via XSLT to see if a translation was missed. Further, if the stylesheets are needed in new languages in the future, the XML document can be updated with the proper translations and a new suite of stylesheets can be automatically produced. Finally, the application itself could use these XML files to generate the text for portions of the user interface. An added benefit was that I could still use my favorite text editor to develop in, instead of a Unicode editor (although this still had to be used for the XML document containing the boilerplate text).

3.5. Conditional Text Templates

There are many occasions when a template must store an index of values that may be output according to data present in the XML file being processed. In this example, a lookup table for associating country names with ISO country codes was created by importing all language-appropriate data into the compiled stylesheet.

Example: calling named template from compiled stylesheet

<xsl:when test="name(.)='country'">
          <xsl:call-template name="country_ChoiceText">
          <xsl:with-param name="country" select="."/>
          </xsl:call-template>
</xsl:when>
				

Example: component named template

  <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">  
     <xsl:template name="country_ChoiceText">  
         <xsl:param name="country" />   
         <xsl:choose>  
              <xsl:when test="$country='AE'"> 
                     <include file="countryText.xml" toTranslate="AE" />   
             </xsl:when>
      . . .
				

Example: XML text file

<term name="AE">
          <text xml:lang="en">United Arab Emirates </text>
          <text xml:lang="de">Vereinigte Arabische Emirate </text>
          <text xml:lang="es">Emiratos Arabes Unidos </text>
          <text xml:lang="fr">Émirats arabes unis </text>
          <text xml:lang="ja">アラブ首長国連邦 </text>
          <text xml:lang="ru">Объединённые Арабские Эмираты </text>
          <text xml:lang="zh">阿拉伯联合酋长国 </text>
<term>
				

Resulting in language-appropriate country names in the compiled version of the stylesheets:

 

            <!-- Excerpt from English version compiled stylesheet -->
<xsl:template name="country_ChoiceText">  
    <xsl:param name="country" />   
      <xsl:choose>  
          <xsl:when test="$country='AE'">United Arab Emirates</xsl:when>   
          <xsl:when test="$country='AL'">Albania</xsl:when>   
          <xsl:when test="$country='AM'">Armenia</xsl:when> 
<!--  Excerpt from Japanese version compiled stylesheet -->
<xsl:template name="country_ChoiceText">  
    <xsl:param name="country" />   
     <xsl:choose>
        <xsl:when test="$country='AE'">アラブ首長国連邦</xsl:when>   
        <xsl:when test="$country='AL'">アルバニア</xsl:when>   
        <xsl:when test="$country='AM'">アルメニア</xsl:when>  
				

3.6. Commonly-used Functions

These templates display data according to a specified format in many stylesheets, for example formatting names as last, first in bold or displaying a date with the value of '20020921' in English as '21 September 2002'. By calling named templates and supplying parameters, XSL can be an effective programming language:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                version="1.0">
<!-- 
  *********************************************************************
  * FORMAT DATES:  changes numerical dates to text month names        *
  *********************************************************************
  * parameters:                                                       * 
  * $lang       = the language of the text to be output
  * $date       = the incoming <date> to be formatted                 *
  * $text       = '1' use the text name of the month,                 *
  *                   otherwise, format as YYYY-MM-DD                 *
  * $nDate      = the normalized date, with any common formatting     *
  *               characters stripped                                 *
  * $year_only  = '1' to return only the four-digit year              *
  * $show_date  = '1' to include the 'dd' portion in the return text  *
  * $add_comma  = '1' to add comma after the YYYY in return text      * 
  * $date_too   = '1' to show '(dd.mm.yyyy)' after text               *
  *********************************************************************
 -->
   <xsl:template name="format_date">
      <xsl:param name="lang"/>
      <xsl:param name="date"/>
      <xsl:param name="text"/>
      <xsl:param name="year_only"/>
      <xsl:param name="show_date"/>
      <xsl:param name="add_comma"/>
      <xsl:param name="date_too"/>
      <xsl:param name="nDate">
         <xsl:value-of select="normalize-space(translate($date,'/\.,-',''))"/>
      </xsl:param>
      <xsl:param name="y" select="substring($nDate, 1,4)"/>
      <xsl:param name="m" select="substring($nDate,5,2)"/>
      <xsl:param name="d" select="substring($nDate,7,2)"/>
      <xsl:choose>        
         <xsl:when test="$year_only='1'">
            <xsl:value-of select="$y"/>
         </xsl:when>
         <xsl:when test="$text != '1'">
            <xsl:value-of select="$y"/>-
            <xsl:value-of select="$m"/>-
            <xsl:value-of select="$d"/>
         </xsl:when>
         <xsl:otherwise>
            <xsl:if test="$show_date='1'">
               <xsl:text> </xsl:text>
               <xsl:value-of select="$d"/>
               <xsl:text> </xsl:text>
            </xsl:if>
            <xsl:choose>
               <xsl:when test="$m='01'">
                 <include file="commonText.xml" toTranslate="jan_Text" />
               </xsl:when>
               <xsl:when test="$m='02'">
                 <include file="commonText.xml" toTranslate="feb_Text" />
               </xsl:when>
               <xsl:when test="$m='03'">
                 <include file="commonText.xml" toTranslate="mar_Text" />
               </xsl:when>
               <xsl:when test="$m='04'">
                <include file="commonText.xml" toTranslate="apr_Text" />
               </xsl:when>
               <xsl:when test="$m='05'">
                <include file="commonText.xml" toTranslate="may_Text" />
               </xsl:when>
               <xsl:when test="$m = '06'">
                <include file="commonText.xml" toTranslate="jun_Text" />
               </xsl:when>
               <xsl:when test="$m='07'">
                <include file="commonText.xml" toTranslate="jul_Text" />
               </xsl:when>
               <xsl:when test="$m='08'">
                <include file="commonText.xml" toTranslate="aug_Text" />
               </xsl:when>
               <xsl:when test="$m='09'">
                <include file="commonText.xml" toTranslate="sep_Text" />
               </xsl:when>
               <xsl:when test="$m='10'">
                <include file="commonText.xml" toTranslate="oct_Text" />
               </xsl:when>
               <xsl:when test="$m='11'">
                <include file="commonText.xml" toTranslate="nov_Text" />
               </xsl:when>
               <xsl:when test="$m='12'">
                <include file="commonText.xml" toTranslate="dec_Text" />
               </xsl:when>
                    <!-- trap error (e.g., 1-digit month) -->
               <xsl:otherwise>
                  <xsl:value-of select="$m"/> 
                  <xsl:value-of select="$d"/>
               </xsl:otherwise>
            </xsl:choose>
            <xsl:value-of select="$y"/>
            <xsl:if test="$add_comma='1'">
               <xsl:text>, </xsl:text>
            </xsl:if>
                <xsl:if test="$date_too = '1'">
                     <xsl:text>  </xsl:text>
                    <xsl:value-of select="concat('(',$d,'.',$m,'.',$y,')')"/>
                    <xsl:text>  </xsl:text>
                </xsl:if>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:template>
</xsl:stylesheet>
               
				

4. Assembly

Stylesheets were compiled into distributable stand-alone versions by running batch files to process 'assembler' XSL templates in a two stages. The first stage used a simple identity transform to copy all referenced component templates into one file. The second stage replaced the <include> instructions with language-appropriate text.

<-- Example of replacing <include> instructions in second stage of assembly -->
<!--  param to be fed from command line for language of text   -->   
<xsl:param name="language" /> 
<!--  param to be fed from command line for directory containing text XML files --> 
<xsl:param name="path" />   
<xsl:template match="include">  
    <xsl:param name="trans" select="@toTranslate" />   
    <xsl:value-of select="document(concat($path,@file))//term[@name=$trans]
                                                     /text[@xml:lang=$language]" />   
</xsl:template>
			

The end result is that one hundred individual template components were assembled into seven XSL-FO and seven XSLT stylesheets to be distributed for each language. All are identical in terms of structure and formatting; the only difference is the language of the boilerplate text.

5. Lessons Learned

Many lessons were learned over the course of developing these stylesheets; some of these are things to beware of, some are work-arounds, and some are just common sense, but bear repeating:

Learn and use XSL functions.

Many of the most common problems can be solved by using one (or several) of the XSL/XML Path Language (XPath) functions.

For example, when performing comparisons on data, remember that <foo>bar</foo> and<foo> bar </foo>are not the same;

<xsl:if test=". = preceding-sibling::foo">

will return false. The normalize-space() function will return a string with leading and trailing spaces removed;

<xsl:if test="normalize-space(.) = normalize-space(preceding-sibling::foo)">

will return true.

Remember that functions can nest within each other.

For example, this instruction uses three nested XPath functions:

<xsl:value-of select="concat(translate(normalize-space(.)
                                  ,'abdefghijklmnopqrstuvwxyz'
                                  ,'ABDEFGHIJKLMNOPQRSTUVWXYZ'),': ')"/>
			

Use proper grouping

For example,

<xsl:when test="(document($test)//$condition1
                    or document($test)//$condition2)
                  and (document($test)//$test2
                    or document($test)//test1)">
			

and

   
<xsl:when test="document($test)//$condition1
           or document($test)//$condition2
         and (document($test)//$test2
           or document($test)//test1)">
			

are not equivalent because of the grouping; the first test will match the conditions while the second will fail.

Operators.

If you are familiar with other programming languages, remember that in XSLT the '|' operator expresses a Union, not Logical Or. The Union operator returns the union of two or more node sets.

Learn and use the key() function.

This can greatly reduce the size of your files and increase processing performance. For example, to determine the amount of currencies used to pay fees, one could get the count of all elements with a given fee currency, then count how many different fees were used:

<xsl:variable name="USDCount">
   <xsl:choose>
      <xsl:when test="count(.//*[@currency='USD']) > 0">1</xsl:when> 
      <xsl:otherwise>0</xsl:otherwise> 
   </xsl:choose>
  </xsl:variable>
 <xsl:variable name="RURCount">
    <xsl:choose>
      <xsl:when test="count(.//*[@currency='RUR']) > 0">1</xsl:when> 
      <xsl:otherwise>0</xsl:otherwise> 
    </xsl:choose>
  </xsl:variable>
  <xsl:variable name="CNYCount">
    <xsl:choose>
      <xsl:when test="count(.//*[@currency='CNY']) > 0">1</xsl:when> 
      <xsl:otherwise>0</xsl:otherwise> 
    </xsl:choose>
  </xsl:variable>
  <xsl:variable name="CHFCount">
    <xsl:choose>
      <xsl:when test="count(.//*[@currency='CHF']) > 0">1</xsl:when> 
      <xsl:otherwise>0</xsl:otherwise> 
    </xsl:choose>
  </xsl:variable>
  <xsl:variable name="JPYCount">
    <xsl:choose>
      <xsl:when test="count(.//*[@currency='JPY']) > 0">1</xsl:when> 
      <xsl:otherwise>0</xsl:otherwise> 
    </xsl:choose>
  </xsl:variable>
  <xsl:variable name="EURCount">
    <xsl:choose>
      <xsl:when test="count(.//*[@currency='EUR']) > 0">1</xsl:when> 
      <xsl:otherwise>0</xsl:otherwise> 
    </xsl:choose>
  </xsl:variable>
  <xsl:variable name="curCount">
      <xsl:value-of select="$USDCount + $RURCount + $CNYCount 
                                      + $CHFCount + $JPYCount + $EURCount" /> 
  </xsl:variable>

The same can be accomplished using keys, resulting in a much more concise statement that is easier to read:

  <xsl:key name="allCur" match="//*/@currency" use="." /> 
  <xsl:variable name="curCount" 
    select="count(//*/@currency[generate-id(.)=generate-id(key('allCur',.))])" /> 

Using Lookup Tables.

As discussed previously, XSLT can be used to create lookup tables to perform certain tasks; in the example shown previously, a lookup table exchanged a country name with a country code value. However, lookup tables can also be used for validation purposes (e.g., inserting "Please provide your address" if there is no <address> element or if its string-length is 0), or for as a reverse lookup table for generating items not found in the document.

Test for conditionals in the template match.

When appropriate, use conditional expressions within the template match attribute, as opposed to using xsl:if or xsl:choose: (for example, <xsl:template match="p[not(parent::section)]">)

Zero is not a number.

Be aware that to XSLT, 0 is not a number. For example,

<foo>0</foo>
<xsl:if test="number(foo)"> 

will return false.

Remember your context.

For example, xsl:for-each changes the current context to the node-set being evaluated If the document

<foo><bar></foo>

is being processed with the following template

<xsl:for-each select=".//foo">
	<xsl:if test=".//foo/bar">

the if test will fail, as there are no nested <foo> elements and the context is already in the <foo> element node list. Losing scope of your context can negatively affect processing speed or cause node tests to fail.

Calling named templates.

Remember that although you can pass parameters with <xsl:call-template>, you do not always have to. The XSL processor will maintain the context from the calling to the called template

Operator syntax.

Remember to use correct syntax when using the 'and' and 'or' operators. For example

<xsl:when test="$var='a'or 'b'"> is incorrect; <xsl:when test="$var='a' or $var='b'"> is correct.

6. Summary

This document has outlined the business requirements for XSL renderings, and demonstrated how the architecture for creating and distributing XSL stylesheets from reusable components met those requirements. By employing the techniques discussed, the advantages of rapid development of quality products assembled from proven components can be realized.

Glossary

CSS

Cascading Style Sheets

HTML

Hypertext Markup Language

ISO

International Standards Organization

XHTML

Extensible Hypertext Markup Language

XPath

XML Path Language

XSL

Extensible Stylesheet Language

XSL-FO

Extensible Stylesheet Language, Formatting Objects

XSLT

Extensible Stylesheet Language, Transformations

Biography

Chief Technologist, XML Technologies

John Dunning has been working with XML since its inception, primarily in the intellectual property arena, where he has developed solutions for large national and international intellectual property offices.



[1] Throughout this document the term 'XSLT' shall be used to refer to an XML to HTML transformation for purposes of rendering; XSL-FO shall be used to refer to XSL Formatting Objects.