Reusable XSL Stylesheets and Templates

Tony Marston - 31st March 2004
Amended 3rd June 2006


1. Introduction

Some people complain than XML and XSL are too verbose, that it takes too may keystrokes to achieve the simplest of things. To me this is irrelevant - it is much more important that software be readable and understandable to other humans than be quick to type. I would rather take a minute to type something that can be understood in a second than take a second to type something that takes a minute to understand.

Another way to overcome the verbosity of any language, not just XSL, is not to find yourself writing the same code over and over again. Instead you should find a way to put that code into a reusable library so that when you want that code you call a library routine instead of writing it all again in long hand. XSL has the ability to create reusable routines in the form of XSL templates which can be called from any number of different places. There is also the ability to maintain this template code in a single location and to make the contents of this file available in any XSL stylesheet by means of the <xsl:include> statement. Having identified that XSL contains the tools to build reusable code the only remaining difficulty is to decide what code can be put into reusable template. The purpose of this article is to explain the steps that I have taken which make my library of XSL stylesheets and templates much more reusable than most.

When constructing an XSL stylesheet it is usual to start with minimum reusability and to work up in stages to maximum reusability, such as in the following:

  1. Here all field names hard-coded, and for each of those fields the HTML control is constructed manually, as in:
      <tr>
        <td class="label">User Name</td>
        <td>
          <input type="text" name="user_name" size="//person/user_name/@size">
            <xsl:attribute name="value">
              <xsl:value-of select="//person/user_name"/>
            </xsl:attribute>
          </input>
        </td>
      </tr>
    
    Notice here that the field's size is taken from the size attribute in the XML file and not hard-coded in the XSL stylesheet. It is good practice to have data passed in as XML elements, but to have meta-data (which is "data about data") passed in as XML attributes. An element can have only one value but any number of attributes.
  2. Instead of building each HTML control manually the next step is put all that code into a template which can be called, as in:
      <tr>
        <td class="label">User Name</td>
        <td>
          <xsl:call-template name="textbox">
            <xsl:with-param name="field" select="//person/user_name"/>
          </xsl:call-template>
          </input>
        </td>
      </tr>
    
  3. But what if the HTML control for a field is not known until run-time? The answer is to have the control type passed in as another attribute in the XML file, and to call a different template:
      <tr>
        <td class="label">User Name</td>
        <td>
          <xsl:call-template name="datafield">
            <xsl:with-param name="field" select="//person/user_name"/>
          </xsl:call-template>
        </td>
      </tr>
    
    This "datafield" template will examine the control attribute and then call the relevant sub-template for that HTML control, as in:
    <xsl:template name="datafield">
      <xsl:param name="field"/>
      
        <xsl:choose>
          
          <xsl:when test="$field/@control='dropdown'">
            <xsl:call-template name="dropdown">
              <xsl:with-param name="field" select="$field"/>
            </xsl:call-template>
          </xsl:when>
          
          <xsl:when test="$field/@control='radiogroup'">
            <xsl:call-template name="radiogroup">
              <xsl:with-param name="field" select="$field"/>
            </xsl:call-template>
          </xsl:when>
          
          <xsl:otherwise> <!-- this is the default control type -->
            <xsl:call-template name="textbox">
              <xsl:with-param name="field" select="$field"/>
            </xsl:call-template>
          </xsl:otherwise>
          
        </xsl:choose>
    
    </xsl:template>
    
  4. A lot of people may reach step (2), a few people may reach step (3), but most people fail to realise that there is another step that will eliminate the need to have a separate XSL stylesheet for each screen in which all the field names are hard-coded. If you think about it, the list of field names to be displayed is just a simple list, and it is possible to have this list passed in as data within the XML file, as in the following:
      <structure>
        <main id="person">
          <row>
            <cell label="Id"/>
            <cell field="user_id"/>
          </row>
          <row>
            <cell label="Name"/>
            <cell field="user_name"/>
          </row>
      </structure>
    

    The main element is an internal stylesheet (zone) name while the id attribute identifies the database table name within the XML file. All subordinate row and cell elements belong to this table. The following XSL statement will extract all the cell elements which have a label attribute:

      <xsl:for-each select="//structure/main/row/cell[@label]">
    

    If a stylesheet contains more than one zone then the zone name can be passed in as a parameter and processed with the following statement:

      <xsl:for-each select="//structure/*[name()=$zone]/row/cell[@label]">
    

    Similarly all the field names and their corresponding values can be extracted with the following statements:

      <xsl:for-each select="//structure/*[name()=$zone]/row/cell[@field]">
        
        <xsl:variable name="fieldname"  select="@field" />
        <xsl:variable name="fieldvalue" select="//*[name()=$table]/*[name()=$fieldname]"/>
    
      </xsl:for-each>
    

I reached as far as step (3) in 2003 as documented in Generating dynamic web pages using XSL and XML. Since that time I have been able to implement step (4) which means that instead of having a customised XSL stylesheet for each "detail" screen I can now use a generic XSL stylesheet instead. This means that the time I need to spend in building individual screens is far less than it used to be, and this boost in productivity produces significant savings which I can pass on to my customers.

You may think that it is impossible to build an entire application from 1 or 2 XSL stylesheets, and you would be right. The same stylesheet can only be used in transactions which have the same structure, and my long experience has allowed me to identify just a dozen or so structures that can be used in an application of over 500 components. I have organised these different screen structures into what I call transaction patterns which are a combination of structure and behaviour. Each individual transaction is an implementation of a particular pattern (structure and behaviour) with content (application data). It is possible for the same structure to be combined with different behaviours to form different patterns. This can be demonstrated with the INSERT, UPDATE, DELETE, ENQUIRE and SEARCH forms which all share the same DETAIL stylesheet.

My entire development framework is based around the Model-View-Controller design pattern which blends in very easily with my transaction patterns with their mixture of Structure-Behaviour-Content, as indicated in the following:

Each controller communicates with one or more models in order to generate a response to a request, then the data is extracted from each model and written to an XML document by the view. The contents of a screen structure file are then appended to the XML document before the XSL transformation process which creates the HTML output.

The following screen shots show sample screens with the different areas (zones) which are built into each pattern. Each of these zones is constructed from a central library of common XSL templates which means that it is a simple process to construct a new XSL stylesheet to deal with a different screen structure.

Figure 1 - a LIST screen

reusable-xsl-001 (15K)

This layout can be used for any number of database tables - all that changes is the title, the column headings and the data area. The contents of the pagination area, menu, navigation and action bars are supplied in the XML file, therefore common templates can be used in the XSL file without any modification.

Figure 2 - a DETAIL screen

reusable-xsl-002 (13K)

This layout can be used for any number of database tables - all that changes is the title and the data area. The contents of the scrolling area, menu, navigation and action bars are supplied in the XML file, therefore common templates can be used in the XSL file without any modification.

Within these two different layouts there are common zones - the menu bar, breadcrumb area, title, navigation bar, message area and action bar - which can be dealt with by common templates and thus do not require separate copies of the same code.


2. The structure of an XML document

Please note that these XML documents are constructed automatically by the framework and do not require any intervention from the developer.

2.1 XML file for a LIST screen

The following is a sample of an XML file which was used to create a list screen as shown in figure 1.

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <person>
    <person_id size="8" pkey="y">PA</person_id>
    <pers_type_id size="6" 
                  control="dropdown" 
                  optionlist="pers_type_id">DOLLY</pers_type_id>
    <first_name size="20">Pamela</first_name>
    <last_name size="30">Anderson</last_name>
    <initials size="6">PA</initials>
    <star_sign control="dropdown"
               optionlist="star_sign">Virgo</star_sign>
    <selected noedit="y">1</selected>
  </person>
  <person>
    <person_id size="8" pkey="y">KB</person_id>
    ......
  </person>
  <person>
    <person_id size="8" pkey="y">FB</person_id>
    ......
  </person>
  <person>
    <person_id size="8" pkey="y">BB</person_id>
    ......
  </person>
  <person>
    <person_id size="8" pkey="y">CC</person_id>
    ......
  </person>
  <person>
    <person_id size="8" pkey="y">WC</person_id>
    ......
  </person>
  <person>
    <person_id size="8" pkey="y">DD</person_id>
    ......
  </person>
  <person>
    <person_id size="8" pkey="y">EE</person_id>
    ......
  </person>
  <person>
    <person_id size="8" pkey="y">SF</person_id>
    ......
  </person>
  <person>
    <person_id size="8" pkey="y">BG</person_id>
    ......
  </person>
  <lookup>
    <star_sign>
      <option id="ARI">Aries</option>
      ......
      <option id="VIR">Virgo</option>
    </star_sign>
    <pers_type_id>
      <option id="ACTOR">Actor/Artiste</option>
      ......
      <option id="QP">Of Questionable Parentage>/option>
    </pers_type_id>
  </lookup>
  <cssfiles>
    <filename>HTTP://localhost/radicore/style_default.css</filename> 
    <filename>HTTP://localhost/radicore/xample/style_custom.css</filename> 
  </cssfiles>
  <actbar>
    <button id="reset">RESET</button>
  </actbar>
  <navbar>
    <button id="task#person_add.php" 
            context_preselect="N">New</button>
    <button id="task#person_upd.php" 
            context_preselect="Y">Update</button>
    <button id="task#person_enq.php" 
            context_preselect="Y">Read</button>
    <button id="task#person_del.php" 
            context_preselect="Y">Delete</button>
    <button id="task#person_search.php" 
            context_preselect="N">Search</button>
    <button id="task#pers_opt_xref_link(a).php" 
            context_preselect="Y">Maintain Options</button>
    <button id="task#pers_opt_xref_list(a).php" 
            context_preselect="Y">List Options</button>
  </navbar>
  <menubar>
    <button id="person_list.php" active="y">Person</button>
    <button id="pers_type_list.php">Person Type</button>
    <stack id="/sample/person_list.php" active="y">Person</stack>
  </menubar>
  <pagination>
    <page id="main" numrows="32" curpage="1" lastpage="4"/>
  </pagination>
  <structure>
    <main id="person">
      <columns>
        <column width="5"/>
        <column width="70"/>
        <column width="100"/>
        <column width="100"/>
        <column width="100"/>
        <column width="*"/>
      </columns>
      <row>
        <cell label="Select"/>
        <cell field="selectbox"/>
      </row>
      <row>
        <cell label="Id"/>
        <cell field="person_id"/>
      </row>
      <row>
        <cell label="First Name"/>
        <cell field="first_name"/>
      </row>
      <row>
        <cell label="Last Name"/>
        <cell field="last_name"/>
      </row>
      <row>
        <cell label="Star Sign"/>
        <cell field="star_sign"/>
      </row>
      <row>
        <cell label="Person Type"/>
        <cell field="pers_type_desc"/>
      </row>
    </main>
  </structure>
  <params>
    <script>HTTP://localhost/sample/person_list.php</script>
    <session_name>sample</session_name>
    <http_host>HTTP://localhost</http_host>
    <doc_root>HTTP://localhost/sample</doc_root>
    <title>List PERSON</title>
    <language>en</language>
    <text>
      <page>Page</page>
      <item>Item</item>
      <of>of</of>
      <first>FIRST</first>
      <last>LAST</last>
      <prev>PREV</prev>
      <next>NEXT</next>
      <show>show</show>
      <select-all>select all</select-all>
      <unselect-all>unselect all</unselect-all>
      <help>help</help>
      <page-created>page created in</page-created>
      <seconds>seconds</seconds>
    </text>
    <mode>read</mode>
    <taskid>person_list.php</taskid>
    <help_root>HTTP://localhost/sample</help_root>
    <script_time>0.22325</script_time>
  </params>
</root>

2.2 XML file for a DETAIL screen

The following is a sample of an XML file which was used to create a detail screen as shown in figure 2.

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <person>
    <person_id size="8" pkey="y">BB</person_id>
    <pers_type_id size="6" 
                  control="dropdown" 
                  optionlist="pers_type_id">DOLLY</pers_type_id>
    <node_id size="5" 
             control="popup" 
             foreign_field="node_desc" 
             task_id="task#tree_structure_popup.php">53</node_id>
    <nat_ins_no size="10">BB</nat_ins_no>
    <first_name size="20">Billy</first_name>
    <last_name size="30">Bunter</last_name>
    <initials size="6">bb</initials>
    <star_sign size="3" 
               control="dropdown" 
               optionlist="star_sign">CAN</star_sign>
    <email_addr size="50">bb@fatman.com</email_addr>
    <value1 size="6"></value1>
    <value2 size="12"></value1>
    <start_date size="12">01 Dec 2002</start_date>
    <end_date size="12"></end_date>
    <picture size="40" 
             control="filepicker" 
             task_id="task#filepicker.php" 
             image="y" imagewidth="75" imageheight="95"></picture>
    <created_date size="21" noedit="y">01 Jan 2003 12:00:00</created_date>
    <created_user size="16" noedit="y">AJM</created_user>
    <revised_date size="21" noedit="y">30 Aug 2004 16:13:12</revised_date>
    <revised_user size="16" noedit="y">AJM</revised_user>
    <pers_type_desc noedit="y">Cartoon Character</pers_type_desc>
    <node_desc noedit="y">AJM Business Solutions (BS) Ltd</node_desc>
  </person>
  <lookup>
    <star_sign>
      <option id=""> </option>
      <option id="ARI">Aries</option>
      ......
      <option id="VIR">Virgo</option>
    </star_sign>
    <pers_type_id>
      <option id=""> </option>
      <option id="ACTOR">Actor/Artiste</option>
      ......
      <option id="QP">Of Questionable Parentage>/option>
    </pers_type_id>
  </lookup>
  <cssfiles>
    <filename>HTTP://localhost/radicore/style_default.css</filename> 
    <filename>HTTP://localhost/radicore/xample/style_custom.css</filename> 
  </cssfiles>
  <actbar>
    <button id="submit">SUBMIT</button>
    <button id="finish">CANCEL</button>
  </actbar>
  <menubar>
    <button id="person_list.php" active="y">Person</button>
    <button id="pers_type_list.php">Person Type</button>
    <button id="option_list.php">Option</button>
    <button id="tree_type_list.php">Tree Type</button>
    <stack id="/sample/person_list.php" active="y">Person</stack>
    <stack id="/sample/person_upd.php">Update</stack>
  </menubar>
  <navbar/>
  <scrolling>
    <scroll id="person" curitem="1" lastitem="10"/>
  </scrolling>
  <message/>
  <structure>
    <main id="person">
      <columns>
        <column width="185"/>
        <column width="180"/>
        <column width="115"/>
        <column width="180"/>
        <column width="45"/>
        <column width="45"/>
      </columns>
      <row>
        <cell label="Id"/>
        <cell field="person_id" colspan="5"/>
      </row>
      <row>
        <cell label="First Name"/>
        <cell field="first_name" size="15"/>
        <cell label="Last Name"/>
        <cell field="last_name" size="15"/>
        <cell label="Initials"/>
        <cell field="initials"/>
      </row>
      <row>
        <cell label="Picture"/>
        <cell field="picture" colspan="5"/>
      </row>
      <row>
        <cell label="Nat. Ins. No."/>
        <cell field="nat_ins_no" colspan="5"/>
      </row>
      <row>
        <cell label="Person Type"/>
        <cell field="pers_type_id" colspan="5"/>
      </row>
      <row>
        <cell label="Star Sign"/>
        <cell field="star_sign" colspan="5"/>
      </row>
      <row>
        <cell label="Organisation"/>
        <cell field="node_id" colspan="5"/>
      </row>
      <row>
        <cell label="E-mail"/>
        <cell field="email_addr" colspan="5"/>
      </row>
      <row>
        <cell label="Value 1"/>
        <cell field="value1" colspan="5"/>
      </row>
      <row>
        <cell label="Value 2"/>
        <cell field="value2" colspan="5"/>
      </row>
      <row>
        <cell label="Start Date"/>
        <cell field="start_date"/>
        <cell label="End Date"/>
        <cell field="end_date" colspan="3"/>
      </row>
      <row>
        <cell label="Created Date"/>
        <cell field="created_date" colspan="5"/>
      </row>
      <row>
        <cell label="Created By"/>
        <cell field="created_user" colspan="5"/>
      </row>
      <row>
        <cell label="Revised Date"/>
        <cell field="revised_date" colspan="5"/>
      </row>
      <row>
        <cell label="Revised By"/>
        <cell field="revised_user" colspan="5"/>
      </row>
    </main>
  </structure>
  <params>
    <script>HTTP://localhost/sample/person_upd.php</script>
    <session_name>sample</session_name>
    <http_host>HTTP://localhost</http_host>
    <doc_root>HTTP://localhost/sample</doc_root>
    <title>Update PERSON</title>
    <language>en</language>
    <text>
      <page>Page</page>
      <item>Item</item>
      <of>of</of>
      <first>FIRST</first>
      <last>LAST</last>
      <prev>PREV</prev>
      <next>NEXT</next>
      <show>show</show>
      <select-all>select all</select-all>
      <unselect-all>unselect all</unselect-all>
      <help>help</help>
      <page-created>page created in</page-created>
      <seconds>seconds</seconds>
    </text>
    <mode>update</mode>
    <taskid>person_upd.php</taskid>
    <help_root>HTTP://localhost/sample</help_root>
    <script_time>0.22231</script_time>
  </params>
</root>

2.3 XML file constituent parts

The XML document can be broken down into the following constituent parts:

<?xml version="1.0" encoding="UTF-8"?>
<root>
......
</root>

The first line contains the XML declaration and the encoding. The second and last lines identify the root node within the document. Every XML document must contain a root node to encompass all the other nodes. In this example the name of the root node is "root" (how original!), but it could be anything.


Here is some data from a database table:

  <person>
    <person_id size="8" pkey="y">PA</person_id>
    .....
    <end_date size="12"></end_date>
  </person>
  <person>
    .....
    <person_addr>
      .....
    </person_addr>
  </person>

Everything between <person>...</person> belongs to the same occurrence (row) from the "person" table. Each element in between belongs to a different field (column) of that table. Note that an element can contain a value and any number of attributes each of which can have its own value. For example, the "person_id" element contains the value "PA" but also attributes for "size" and "pkey".

It is possible for an XML file to contain multiple occurrences, either from the same table or even from different tables. To indicate a relationship between two tables it is also possible for the occurrences of a child table to be held within the related occurrence of the parent table.


The next section identifies the options for dropdown lists or radio groups. These are all contained within the node called "lookup".

  <lookup>
    <star_sign>
      <option id=""> </option>
      <option id="ARI">Aries</option>
      ......
      <option id="VIR">Virgo</option>
    </star_sign>
    <pers_type_id>
      <option id=""> </option>
      <option id="ACTOR">Actor/Artiste</option>
      ......
      <option id="QP">Of Questionable Parentage>/option>
    </pers_type_id>
  </lookup>

Here there are lists for two fields, "star_sign" and "pers_type_id". Each fields has a number of options which are broken down into ID (supplied as an attribute) and VALUE.


The next section contains other miscellaneous sets of data:

  <cssfiles>
    <filename>HTTP://localhost/radicore/style_default.css</filename> 
    <filename>HTTP://localhost/radicore/xample/style_custom.css</filename> 
  </cssfiles>
  <actbar>
    <button id="copy">Copy</button>
    ......
  </actbar>
  <menubar>
    <button id="person_list.php" active="y">Person</button>
    <button id="pers_type_list.php">Person Type</button>
    <stack id="/sample/person_list.php" active="y">Person</stack>
    <stack id="/sample/person_upd.php">Update</stack>
  </menubar>
  <navbar>
    <button id="task#person_add.php" context_preselect="N">New</button>
    <button id="task#person_upd.php" context_preselect="Y">Update</button>
  </navbar>
  <pagination>
    <page id="main" numrows="32" curpage="1" lastpage="4"/>
  </pagination>
  <scrolling>
   <scroll id="person" curitem="1" lastitem="10"/>
  </scrolling>
  <message/>

The next section tells the XSL stylesheet which data belongs in which zone, which fields are to be displayed, and in what order.

  <structure>
    <main id="person"> <!-- zone id = 'main', but associated table name = 'person'-->
      <columns>
        <column width="185"/>
        ......
      </columns>
      <row>
        <cell label="Id"/>
        <cell field="person_id" colspan="5"/>
      </row>
      <row>
        <cell label="First Name"/>
        <cell field="first_name" size="15"/>
        <cell label="Last Name"/>
        <cell field="last_name" size="15"/>
        <cell label="Initials"/>
        <cell field="initials"/>
      </row>
      ......
    </main>
  </structure>

Here there is a single zone called "main" which will be populated using entries from the "person" table. This contains a group of column specifications which is then followed by specifications for each HTML table row and cell which is to appear in that zone. Note that each cell may contain either a field label (which is supplied as a literal string) or the name of the field in the XML document which will supply the value.

It is possible for an XSL stylesheet to contain multiple data zones, so each zone will have its own specifications in the XML document.

For LIST screens (with a horizontal layout) all "label" entries will appear as a single line of column headings at the top of the display while the "field" entries will be grouped into a single data line, one line for each database occurrence.

For DETAIL screens (with a vertical layout) each row will be an actual HTML table row, with the "label" entry on the left and the contents of the "field" entry on the right. Note that it is also possible to have more than one label/field combination appearing in the same row. The "colspan" attribute will allow an entry to span multiple columns, and the "rowspan" attribute will allow an entry to span multiple rows.

The optional "size" attribute is used to reduce the size of the field to something which is smaller than its actual size in the database.


The last area holds values that were originally passed in as parameters during the XSL transformation process which was performed on the server. These were later switched to being ordinary entries with the XML document so that they could be made available for client-side transformations.

  <params>
    <script>HTTP://localhost/sample/person_list.php</script>
    ......
    <language>en</language>
    <text>
      <page>Page</page>
      ......
      <seconds>seconds</seconds>
    </text>
  </params>

These entries are arranged in two levels:


3. The structure of an XSL stylesheet

3.1 XSL file for a LIST screen

The following is a sample of an XSL file used to create a list screen as shown in figure 1.

XSL file 1 - std.list1.xsl

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

<xsl:output method="xml"
            indent="yes"
            omit-xml-declaration="yes"
            doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"
            doctype-system = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
/>

<!-- include common templates -->
<xsl:include href="std.buttons.xsl"/>
<xsl:include href="std.column_hdg.xsl"/>
<xsl:include href="std.data_field.xsl"/>
<xsl:include href="std.head.xsl"/>
<xsl:include href="std.pagination.xsl"/>

<!-- get the name of the MAIN table -->
<xsl:variable name="main" select="//structure/main/@id"/>
<xsl:variable name="numrows" select="//pagination/page[@id='main']/@numrows"/>

<xsl:template match="/"> <!-- standard match to include all child elements -->

  <html xml:lang="{/root/params/language}" lang="{/root/params/language}">
    <xsl:call-template name="head" />
  <body>
  
  <form method="post" action="{$script}">
  
    <div class="universe">
      
      <!-- create help button -->
      <xsl:call-template name="help" />
      
      <!-- create menu buttons -->
      <xsl:call-template name="menubar" />
      
      <div class="body">
        
        <h1><xsl:value-of select="$title"/></h1>
        
        <!-- create navigation buttons -->
        <xsl:call-template name="navbar">
          <xsl:with-param name="noshow"   select="//params/noshow"/>
          <xsl:with-param name="noselect" select="//params/noselect"/>
        </xsl:call-template>
        
        <div class="main">
        
          <!-- this is the actual data -->
          <table>
          
            <!-- set up column widths -->
            <xsl:call-template name="column_group">
              <xsl:with-param name="table" select="'main'"/>
            </xsl:call-template>
            
            <thead>
              <!-- set up column headings -->
              <xsl:call-template name="column_headings">
                <xsl:with-param name="table" select="'main'"/>
              </xsl:call-template>
            </thead>
            
            <tbody>
              <!-- process each non-empty row in the MAIN table of the XML file -->
              <xsl:for-each select="//*[name()=$main][count(*)>0]">
              
                <!-- display all the fields in the current row -->
                <xsl:call-template name="display_horizontal">
                  <xsl:with-param name="zone" select="'main'"/>
                </xsl:call-template>
                
              </xsl:for-each>
            </tbody>
            
          </table>
          
        </div>
        
        <!-- look for optional messages -->
        <xsl:call-template name="message"/>
        
        <!-- insert the page navigation links -->
        <xsl:call-template name="pagination">
          <xsl:with-param name="object" select="'main'"/>
        </xsl:call-template>
        
        <!-- create standard action buttons -->
        <xsl:call-template name="actbar"/>
        
      </div>
      
    </div>
  
  </form>
  </body>
  </html>

</xsl:template>

</xsl:stylesheet>

3.2 XSL file for a DETAIL screen

The following is a sample of an XSL file used to create a detail screen as shown in figure 2.

XSL file 2 - std.detail1.xsl

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

<xsl:output method="xml"
            indent="yes"
            omit-xml-declaration="yes"
            doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"
            doctype-system = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
/>

<!-- include common templates -->
<xsl:include href="std.buttons.xsl"/>
<xsl:include href="std.column_hdg.xsl"/>
<xsl:include href="std.data_field.xsl"/>
<xsl:include href="std.head.xsl"/>
<xsl:include href="std.pagination.xsl"/>

<!-- get the name of the MAIN table -->
<xsl:variable name="main" select="//structure/main/@id"/>
<xsl:variable name="numrows">1</xsl:variable>

<xsl:template match="/">

  <html xml:lang="{/root/params/language}" lang="{/root/params/language}">
    <xsl:call-template name="head" />
  <body>
  
  <form method="post" action="{$script}">
  
    <div class="universe">
    
      <!-- create help button -->
      <xsl:call-template name="help" />
      
      <!-- create menu buttons -->
      <xsl:call-template name="menubar" />
      
      <div class="body">
        
        <h1><xsl:value-of select="$title"/></h1>
        
        <!-- create navigation buttons -->
        <xsl:call-template name="navbar_detail" />
        
        <div class="main">
          <!-- table contains a row for each database field -->
          <table>
        
            <!-- process the first row in the MAIN table of the XML file -->
            <xsl:for-each select="//*[name()=$main][1]">
            
              <!-- display all the fields in the current row -->
              <xsl:call-template name="display_vertical">
                <xsl:with-param name="zone" select="'main'"/>
              </xsl:call-template>
              
            </xsl:for-each>
            
          </table>
        </div>
        
        <!-- look for optional messages -->
        <xsl:call-template name="message"/>
        
        <!-- insert the scrolling links for MAIN table -->
        <xsl:call-template name="scrolling" >
          <xsl:with-param name="object" select="$main"/>
        </xsl:call-template>
        
        <!-- create standard action buttons -->
        <xsl:call-template name="actbar"/>
        
      </div>
      
    </div>
    
  </form>
  </body>
  </html>

</xsl:template>

</xsl:stylesheet>

3.3 XSL file constituent parts

The XSL stylesheet can be broken down into the following constituent parts:

<?xml version='1.0'?>
<xsl:stylesheet ......>

<xsl:output ....../>

......

</xsl:stylesheet>

The first line contains the XML declaration. The second line and last lines identify the contents as an XSL stylesheet. The <xsl:output> line specifies the output format required by the transformation process.


The next set of lines perform various functions before the main template is executed.

<!-- include common templates -->
<xsl:include href="......"/>

<!-- get the name of the MAIN table -->
<xsl:variable name="main" select="//structure/main/@id"/>
<xsl:variable name="numrows" select="//pagination/page[@id='main']/@numrows"/>

The <xsl:include> statements make the contents of those files available to the transformation process. Each of these files may contain any number of XSL templates.

The <xsl:variable> statements extract values from the current XML file.


The remainder of the code performs the actual transformation.

<xsl:template match="/">

  <html>
  <body>
  
  <form method="post" action="{$script}">
  
  ......
    
  </form>
  </body>
  </html>

</xsl:template>

Note here that every XSL transformation requires a template which matches "/" - this path expression includes everything which is subordinate to the root node of the XML document. The lines within this template are then scanned and processed sequentially. Anything beginning with <xsl: is treated as an XSL instruction. Everything else, such as ordinary HTML tags, is output as is.

Individual parts of the XML document are then processed using different named XSL templates, as shown in the following section.

3.4 Common XSL templates

All programming languages allow common code to be defined once in a subroutine which can then be referenced from multiple places instead of having to be duplicated in each of those places. In XSL these "subroutines" are called "templates". They can either be hard-coded into individual stylesheets or held in central files which can be incorporated into any number of stylesheets by means of the <xsl:include> statement.

A template can be called using code similar to the following if no parameters are required:

<xsl:call-template name="head"/>

If parameter values are to be passed they must be specified by name, as in the following example:

<xsl:call-template name="display_vertical">
  <xsl:with-param name="zone" select="'main'"/>
</xsl:call-template>

Each template definition must specify any parameter it needs by name, as in the following example:

<xsl:template name="display_vertical">
  <xsl:param name="zone"/>
  <xsl:param name="noedit"/>
  
  ......
  
</xsl:template>  

Note that the order in which the parameters are specified is irrelevant as they are all matched by name. If any parameter is not supplied it is treated as having a null value.

In the sample XSL stylesheets for LIST and DETAIL screens there are references to numerous templates which provide the following functionality:

Name Description
actbar This is responsible for creating the action bar in the HTML document.
column_group This is responsible for creating the <colgroup> element of the HTML document for that data zone.
column_headings This is responsible for creating the column headings in the HTML document.
display_horizontal This is responsible for outputting the data for multiple database occurrences, one line per occurrence, in a horizontal arrangement.
display_vertical This is responsible for outputting the data for a single database occurrence in a vertical arrangement.
head This is responsible for creating the <head> element of the HTML document. It contains the form title and links to CSS files.
help This is responsible for creating the top row of the menu bar in the HTML document.
menubar This is responsible for creating the bottom two rows of the menu bar in the HTML document.
message This is responsible for creating the message area in the HTML document.
navbar This is responsible for creating the navigation bar in the HTML document.
pagination This is responsible for creating the pagination area in the HTML document.
scrolling This is responsible for creating the scrolling area(s) in the HTML document.

The following global variables are constructed for use within any template:

<xsl:variable name="client-side"   select="/root/params/client-side"/>
<xsl:variable name="doc_root"      select="/root/params/doc_root"/>
<xsl:variable name="help_root"     select="/root/params/help_root"/>
<xsl:variable name="mode"          select="/root/params/mode"/>
<xsl:variable name="orderby"       select="/root/params/orderby"/>
<xsl:variable name="order"         select="/root/params/order"/>
<xsl:variable name="print-preview" select="/root/params/print-preview"/>
<xsl:variable name="script"        select="/root/params/script"/>
<xsl:variable name="script_time"   select="/root/params/script_time"/>
<xsl:variable name="title"         select="/root/params/title"/>
<xsl:variable name="select_one"    select="/root/params/select_one"/>
<xsl:variable name="session_name"  select="/root/params/session_name"/>
<xsl:variable name="session"       select="concat('session_name=',$session_name)" />
<xsl:variable name="taskid"        select="/root/params/taskid"/>

<!-- extract pieces of text in the user's language -->
<xsl:variable name="show"          select="/root/params/text/show"/>
<xsl:variable name="select-all"    select="/root/params/text/select-all"/>
<xsl:variable name="unselect-all"  select="/root/params/text/unselect-all"/>
<xsl:variable name="page-created"  select="/root/params/text/page-created"/>
<xsl:variable name="seconds"       select="/root/params/text/seconds"/>
<xsl:variable name="page"          select="/root/params/text/page"/>
<xsl:variable name="item"          select="/root/params/text/item"/>
<xsl:variable name="of"            select="/root/params/text/of"/>
<xsl:variable name="first"         select="/root/params/text/first"/>
<xsl:variable name="last"          select="/root/params/text/last"/>
<xsl:variable name="prev"          select="/root/params/text/prev"/>
<xsl:variable name="next"          select="/root/params/text/next"/>

<xsl:template name="actbar">

This is responsible for creating the action bar in the HTML document.

<xsl:template name="actbar"> (1)

  <xsl:if test="not($print-preview)"> (2)
  
    <div class="actionbar">
  
      <div class="left">
        <xsl:text> </xsl:text>  <!-- insert a space to prevent an empty element -->
        <xsl:for-each select="//actbar/button[starts-with(@id,'submit') 
                              or @id='choose']"> (3)
          <!-- create a button on the left for each element within actionbar -->
          <input class="submit" type="submit" name="{@id}" value="{node()}" /> (4)
          <xsl:text> </xsl:text>
        </xsl:for-each>
      </div>
  
      <div class="right">
        <xsl:text> </xsl:text>  <!-- insert a space to prevent an empty element -->
        <xsl:for-each select="//actbar/button[not(starts-with(@id,'submit')) 
                              and not(@id='choose')]"> (5)
          <!-- create a button on the right for each element within actionbar -->
          <input class="submit" type="submit" name="{@id}" value="{node()}" /> (4)
          <xsl:text> </xsl:text>
        </xsl:for-each>
      </div>
  
    </div>
    
  </xsl:if>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="column_group">

This is responsible for creating the <colgroup> element of the HTML document for that data zone.

<xsl:template name="column_group"> (1)
  <xsl:param name="zone"/> <!-- zone name (eg: main, inner, outer) -->
  <xsl:param name="count"/> <!-- column count -->

  <colgroup>

    <xsl:for-each select="//structure/*[name()=$zone]/columns/column"> (2)
      <col>
        <xsl:if test="@width"> (3)
          <xsl:attribute name="width" ><xsl:value-of select="@width" /></xsl:attribute>
        </xsl:if>
        <xsl:if test="@class"> (4)
          <xsl:attribute name="class" ><xsl:value-of select="@class" /></xsl:attribute>
        </xsl:if>
      </col>
    </xsl:for-each>
  
    <xsl:if test="$count > 1"> (5)
      <!-- repeat until $count is reduced to 1 -->
      <xsl:call-template name="column_group">
        <xsl:with-param name="zone" select="$zone"/>
        <xsl:with-param name="count" select="$count -1"/>
      </xsl:call-template>
    </xsl:if>

  </colgroup>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="column_headings">

This is responsible for creating the column headings in the HTML document.

<xsl:template name="column_headings">
  <xsl:param name="zone"/> <!-- zone name (eg: main, inner, outer) -->
  <xsl:param name="count"/> <!-- column count -->

  <tr> <!-- these are all within a single row -->
    <xsl:call-template name="column_heading">
      <xsl:with-param name="zone" select="$zone"/>
      <xsl:with-param name="count" select="$count"/>
    </xsl:call-template>
  </tr>

</xsl:template>

This calls the column_heading template inside a <tr> (table row) tag.

<xsl:template name="column_heading">

<xsl:template name="column_heading">
  <xsl:param name="zone"/> <!-- zone name (eg: main, inner, outer) -->
  <xsl:param name="count"/> <!-- column count -->

  <xsl:for-each select="//structure/*[name()=$zone]/row/cell[@label]"> (1)
    <th>
      <xsl:if test="string-length(@label)"> (2)

        <xsl:call-template name="column_hdg"> (3)
          <!-- get fieldname from the FIELD attribute of the following sibling -->
          <xsl:with-param name="fieldname" 
                        select="string(following-sibling::*[@field]/@field)" />
          <xsl:with-param name="label" select="@label"/>
          <xsl:with-param name="nosort" select="@nosort"/>
        </xsl:call-template>
        
      </xsl:if>
    </th>
  </xsl:for-each>

  <xsl:if test="$count > 1"> (4)
    <!-- repeat until $count is reduced to 1 -->
    <xsl:call-template name="column_heading">
      <xsl:with-param name="zone" select="$zone"/>
      <xsl:with-param name="count" select="$count -1"/>
    </xsl:call-template>
  </xsl:if>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="column_hdg">

<xsl:template name="column_hdg">
  <xsl:param name="fieldname"/>
  <xsl:param name="label"/>
  <xsl:param name="nosort"/>
  <xsl:param name="count"/>

  <xsl:choose>

    <xsl:when test="$fieldname='selectbox'"> (1)
      <!-- text only, no hyperlink -->
      <xsl:value-of select="$label"/>
    </xsl:when>

    <xsl:when test="$numrows > 0 and not($nosort)"> (2)
      <!-- $numrows is one of the XSL parameters -->
      <!-- note that if 'nosort' is set there are no hyperlinks for sorting -->

      <!-- create hyperlink to sort on this field -->
      <a href="{$script}?{$session}&orderby={$fieldname}"> (3)
        <!-- this is the visible text for the hyperlink -->
        <xsl:value-of select="$label"/>
      </a>
      <!-- if sorted by this field insert ascending or descending image -->
      <xsl:if test="$orderby=$fieldname"> (4)
        <img src="images/order_{$order}.gif" height="16" width="16" alt="order_{$order}.gif" />
      </xsl:if>

    </xsl:when>

    <xsl:otherwise>
      <!-- no sorting allowed, so don't bother with the hyperlink -->
      <xsl:value-of select="$label"/> (5)
    </xsl:otherwise>

  </xsl:choose>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="datafield">

This template is used to create the HTML control for each field. The type of control to be used is passed as an attribute in the XML file, and this value is then used to activate the relevant sub-template. This allows the HTML control type to be decided at runtime instead of being hard-coded within the component stylesheet. Any other values required by individual templates for their control types must be supplied as additional attributes within the XML file.

This template can be called from either the display_horizontal or display_vertical templates.

<xsl:template name="datafield"> 
  <xsl:param name="item"/>       <!-- the item value -->
  <xsl:param name="itemname"/>   <!-- the item name -->
  <xsl:param name="multiple"/>   <!-- set this for more than one occurrence -->
  <xsl:param name="path"/>       <!-- the entity name -->
  <xsl:param name="position"/>   <!-- the row number -->
  <xsl:param name="noedit"/>     <!-- no edit, display only -->
  <xsl:param name="str-size"/>   <!-- size value from the screen structure file -->

  <xsl:choose>

    <xsl:when test="$itemname='selectbox'"> (1)
      <!-- insert a checkbox to make selections -->
      <xsl:call-template name="selectbox">
        <xsl:with-param name="path" select="$path"/>
        <xsl:with-param name="position" select="$position"/>
      </xsl:call-template>
    </xsl:when>

    <xsl:otherwise>

      <!-- do nothing unless the item is present -->
      <xsl:if test="$item"> (2)

        <xsl:choose>

          <xsl:when test="$item/@nodisplay"> (3)
            <!-- 'nodisplay' attribute set, so display nothing -->
            <xsl:text> </xsl:text>
          </xsl:when>

          <xsl:when test="$item/@control='checkbox'"> (4)
            <xsl:choose>
              <xsl:when test="$mode='search'">
                <!-- make this a radio group to give 3 options - ON,OFF,UNDEFINED -->
                <xsl:call-template name="radiogroup">
                  <xsl:with-param name="item" select="$item"/>
                  <xsl:with-param name="multiple" select="$multiple"/>
                  <xsl:with-param name="noedit" select="$noedit"/>
                  <xsl:with-param name="position" select="$position"/>
                </xsl:call-template>
              </xsl:when>
              <xsl:otherwise>
                <xsl:call-template name="checkbox">
                  <xsl:with-param name="item" select="$item"/>
                  <xsl:with-param name="multiple" select="$multiple"/>
                  <xsl:with-param name="noedit" select="$noedit"/>
                  <xsl:with-param name="position" select="$position"/>
                </xsl:call-template>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:when>

          <xsl:when test="$item/@control='dropdown'"> (4)
            <xsl:call-template name="dropdown">
              <xsl:with-param name="item" select="$item"/>
              <xsl:with-param name="multiple" select="$multiple"/>
              <xsl:with-param name="noedit" select="$noedit"/>
              <xsl:with-param name="position" select="$position"/>
            </xsl:call-template>
          </xsl:when>

          <xsl:when test="$item/@control='filepicker'"> (4)
            <xsl:call-template name="filepicker">
              <xsl:with-param name="item" select="$item"/>
              <xsl:with-param name="multiple" select="$multiple"/>
              <xsl:with-param name="noedit" select="$noedit"/>
            </xsl:call-template>
          </xsl:when>

          <xsl:when test="$item/@control='multiline'"> (4)
            <xsl:call-template name="multiline">
              <xsl:with-param name="item" select="$item"/>
              <xsl:with-param name="multiple" select="$multiple"/>
              <xsl:with-param name="noedit" select="$noedit"/>
            </xsl:call-template>
          </xsl:when>

          <xsl:when test="$item/@control='popup'"> (4)
            <xsl:call-template name="popup">
              <xsl:with-param name="item" select="$item"/>
              <xsl:with-param name="multiple" select="$multiple"/>
              <xsl:with-param name="noedit" select="$noedit"/>
              <xsl:with-param name="path" select="$path"/>
              <xsl:with-param name="position" select="$position"/>
            </xsl:call-template>
          </xsl:when>

          <xsl:when test="$item/@control='radiogroup'"> (4)
            <xsl:call-template name="radiogroup">
              <xsl:with-param name="item" select="$item"/>
              <xsl:with-param name="multiple" select="$multiple"/>
              <xsl:with-param name="noedit" select="$noedit"/>
              <xsl:with-param name="position" select="$position"/>
            </xsl:call-template>
          </xsl:when>

          <xsl:when test="$item/@control='hyperlink'"> (4)
            <xsl:choose>
              <xsl:when test="$mode='insert' or $mode='update' or $mode='search'">
                <!-- change this into a modifiable text field -->
                <xsl:call-template name="textfield">
                  <xsl:with-param name="item" select="$item"/>
                  <xsl:with-param name="multiple" select="$multiple"/>
                  <xsl:with-param name="noedit" select="$noedit"/>
                  <xsl:with-param name="position" select="$position"/>
                  <xsl:with-param name="str-size" select="$str-size"/>
                </xsl:call-template>
              </xsl:when>
              <xsl:otherwise>
                <!-- display this as a hyperlink -->
                <xsl:call-template name="hyperlink">
                  <xsl:with-param name="item" select="$item"/>
                </xsl:call-template>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:when>

          <xsl:otherwise> <!-- this is the default control type -->
            <xsl:call-template name="textfield"> (5)
              <xsl:with-param name="item" select="$item"/>
              <xsl:with-param name="multiple" select="$multiple"/>
              <xsl:with-param name="noedit" select="$noedit"/>
              <xsl:with-param name="position" select="$position"/>
              <xsl:with-param name="str-size" select="$str-size"/>
            </xsl:call-template>
          </xsl:otherwise>

        </xsl:choose>

      </xsl:if>

    </xsl:otherwise>

  </xsl:choose>

  <!-- check if field has error attribute set -->
  <xsl:if test="$item/@error"> (6)
    <br/><span class="error"><xsl:value-of select="$item/@error"/></span>
  </xsl:if>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="display_horizontal">

This is responsible for outputting the data for multiple database occurrences, one line per occurrence, in a horizontal arrangement, as can be found on LIST screens.

<xsl:template name="display_horizontal">
  <xsl:param name="zone"/>      <!-- could be 'main', 'inner', 'outer', etc -->
  <xsl:param name="multiple"/>  <!-- set this for more than one occurrence -->

  <xsl:variable name="table" select="name()"/> (1)      <!-- current table name -->
  <xsl:variable name="position" select="position()"/>   <!-- current row within table -->

  <tr>
    <!-- set the row class to 'odd' or 'even' to determine the colour -->
    <xsl:attribute name="class"> (2)
      <xsl:choose>
        <xsl:when test="position()mod 2">odd</xsl:when>
        <xsl:otherwise>even</xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>

    <!-- step through the fields defined in the STRUCTURE element -->
    <xsl:for-each select="//structure/*[name()=$zone]/row/cell[@field]"> (3)

      <!-- get fieldname from the FIELD attribute -->
      <xsl:variable name="fieldname" select="@field" /> (4)

      <!-- select the field (identified in STRUCTURE) from the current row 
           of the specified table -->
      <xsl:variable name="field" (5)
                  select="//*[name()=$table][position()=$position]/*[name()=$fieldname]" /> 

      <td>
        <!-- process the named field from the current row -->
        <xsl:call-template name="datafield"> (6)
          <xsl:with-param name="item"     select="$field"/>
          <xsl:with-param name="itemname" select="$fieldname"/>
          <xsl:with-param name="path"     select="$table"/>
          <xsl:with-param name="position" select="$position"/>
          <xsl:with-param name="multiple" select="$multiple"/>
        </xsl:call-template>
      </td>

    </xsl:for-each>
  </tr>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="display_vertical">

This is responsible for outputting the data for a single database occurrence in a vertical arrangement, as can be found on DETAIL screens. Note that this allows a row to contain more than one field, and for a field to span more than one row.

<xsl:template name="display_vertical">
  <xsl:param name="zone"/>    <!-- could be 'main', 'inner', 'outer', etc -->
  <xsl:param name="noedit"/>  <!-- y = no edit, display only -->

  <xsl:variable name="table" select="name()"/> (1)      <!-- current table name -->
  <xsl:variable name="table_row" select="position()"/>  <!-- current row within table -->

  <!-- output column settings -->
  <xsl:call-template name="column_group"> (2)
    <xsl:with-param name="zone" select="$zone"/>
  </xsl:call-template>

  <!-- step through each row/item defined in the STRUCTURE element -->
  <xsl:for-each select="//structure/*[name()=$zone]/row"> (3)

    <xsl:variable name="struct_row" select="position()"/> <!-- current row within structure -->

    <!-- build a node-set of field names to be processed for this row -->
    <xsl:variable name="fieldnames" select="cell[@field]/@field"/> (4)

    <!-- build a node-set of field names which actually exist as data elements -->
    <xsl:variable name="fieldsfound" (5)
                select="//*[name()=$table][position()=$table_row]/*[name()=$fieldnames]"/>

    <!-- build a node-set of fields which have the NODISPLAY attribute set -->
    <xsl:variable name="nodisplay" select="$fieldsfound[@nodisplay]"/> (6)

    <!-- build a node-set of fields which have the DISPLAY-EMPTY attribute set -->
    <xsl:variable name="display-empty" select="cell[@display-empty]/@field"/> (7)

    <xsl:choose>
      <xsl:when test="count($fieldsfound) = count($nodisplay) and not($display-empty)"> (8)
        <!-- all the fields in this row have the NODISPLAY attribute set,
             so do not output anything -->
      </xsl:when>

      <xsl:otherwise>
        
        <xsl:call-template name="display_vertical_row"> (9)
          <xsl:with-param name="zone"       select="$zone"/>
          <xsl:with-param name="table"      select="$table"/>
          <xsl:with-param name="table_row"  select="$table_row"/>
          <xsl:with-param name="struct_row" select="$struct_row"/>
          <xsl:with-param name="noedit"     select="$noedit"/>
        </xsl:call-template>

      </xsl:otherwise>

    </xsl:choose>

  </xsl:for-each>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="display_vertical_row"> for further processing.

<xsl:template name="display_vertical_row">
  <xsl:param name="zone"/>        <!-- could be 'main', 'inner', 'outer', etc -->
  <xsl:param name="table"/>       <!-- name of data table -->
  <xsl:param name="table_row"/>   <!-- position of this data element -->
  <xsl:param name="struct_row"/>  <!-- position of this structure element -->
  <xsl:param name="noedit"/>      <!-- y = no edit, display only -->

  <tr>
  
    <!-- step through the cells defined in the STRUCTURE element for the current ROW -->
    <xsl:for-each select="//structure/*[name()=$zone]/row[position()=$struct_row]/cell"> (1)
  
      <td>
  
        <xsl:if test="@colspan"> (2)
          <xsl:attribute name="colspan"><xsl:value-of select="@colspan" /></xsl:attribute>
        </xsl:if>
        <xsl:if test="@rowspan"> (3)
          <xsl:attribute name="rowspan"><xsl:value-of select="@rowspan" /></xsl:attribute>
        </xsl:if>
  
        <xsl:choose>
          <xsl:when test="@label"> (4)
            <!-- get fieldname from the FIELD attribute of the following sibling -->
            <xsl:variable name="fieldname" (5)
                        select="string(following-sibling::*[@field]/@field)" />
  
            <!-- obtain the value for this field from the current row of the specified table -->
            <xsl:variable name="fieldvalue" (6)
                        select="//*[name()=$table][position()=$table_row]/*[name()=$fieldname]" />
  
            <xsl:choose>
              <!-- do nothing unless the field is actually present in the XML file -->
              <!-- and it does not have the @nodisplay attribute set -->
              <xsl:when test="$fieldvalue and not($fieldvalue/@nodisplay)"> (7)
  
                <!-- set classname for this label cell -->
                <xsl:attribute name="class">label</xsl:attribute> (8)
  
                <xsl:choose>
                  <!-- insert indicator if field is marked as 'required' -->
                  <xsl:when test="$mode='insert' (9)
                            and ($fieldvalue/@pkey or $fieldvalue/@required)">
                    <span class="required">* </span>
                  </xsl:when>
                  <xsl:when test="$mode='update' (9)
                            and $fieldvalue/@required 
                            and not($fieldvalue/@pkey) and not($noedit)">
                    <span class="required">* </span>
                  </xsl:when>
                </xsl:choose>
  
                <!-- output the value for the label -->
                <xsl:value-of select="@label"/> (10)
  
              </xsl:when>
  
              <xsl:otherwise> (11)
                <xsl:text>&#160;</xsl:text> <!-- insert non-breaking space -->
              </xsl:otherwise>
  
            </xsl:choose>
  
          </xsl:when>
  
          <xsl:otherwise> (12)
            <!-- get fieldname from the FIELD attribute -->
            <xsl:variable name="fieldname" select="@field" /> (13)
  
            <!-- obtain the value for this field from the current row of the specified table -->
            <xsl:variable name="fieldvalue" (14)
                        select="//*[name()=$table][position()=$table_row]/*[name()=$fieldname]" />
  
            <xsl:choose>
              <!-- do nothing unless the field is actually present in the XML file -->
              <!-- and it does not have the @nodisplay attribute set -->
              <xsl:when test="$fieldvalue and not($fieldvalue/@nodisplay)"> (15)
  
                <!-- process the named field from the current row -->
                <xsl:call-template name="datafield"> (16)
                  <xsl:with-param name="item"     select="$fieldvalue"/>
                  <xsl:with-param name="itemname" select="$fieldname"/>
                  <xsl:with-param name="path"     select="$table"/>
                  <xsl:with-param name="position" select="$table_row"/>
                  <xsl:with-param name="noedit"   select="$noedit"/>
                  <xsl:with-param name="str-size" select="@size"/>
                </xsl:call-template>
  
              </xsl:when>
  
              <xsl:otherwise> (17)
                <xsl:text>&#160;</xsl:text> <!-- insert non-breaking space -->
              </xsl:otherwise>
  
            </xsl:choose>
  
          </xsl:otherwise>
        </xsl:choose>

      </td>

    </xsl:for-each>

  </tr>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="head">

This is responsible for creating the <head> element of the HTML document. It contains the form title and links to CSS files.

<xsl:template name="head">

  <!-- output standard <HEAD> element into HTML document -->
  <head>
    <title><xsl:value-of select="$title"/></title> (1)

    <xsl:if test="/root/params/screen_refresh"> (2)
      <!-- cause the browser to refresh this screen every N seconds -->
      <meta http-equiv="refresh">
        <xsl:attribute name="content">
          <xsl:value-of select="/root/params/screen_refresh" />
          <xsl:text>;</xsl:text>
          <xsl:value-of select="$script"/>
        </xsl:attribute>
      </meta>
    </xsl:if>

    <xsl:for-each select="/root/cssfiles/filename"> (3)
        <link rel="stylesheet" type="text/css" href="{node()}" />
    </xsl:for-each>

  </head>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="help">

This is responsible for creating the top row of the menu bar in the HTML document.

<xsl:template name="help">

  <div class="loggedinas"> (1)
    <xsl:if test="not($mode='logon') and not ($mode='recover')">
      <!-- do not include this in the logon screen -->
      <xsl:value-of select="/root/params/text/logged-in-as"/>
      <xsl:text> </xsl:text>
      <xsl:value-of select="/root/params/logged-in-as"/>
    </xsl:if>
  </div>

  <div class="help"> (2)
    <p>
      <xsl:if test="not($mode='logon') and not ($mode='recover')">
        <!-- create a logout link -->
        <a href="{$script}?action=logout&{$session}" >
          <xsl:value-of select="/root/params/text/logout"/>
        </a>
        <xsl:text> | </xsl:text>
        <!-- create a logout (all) link -->
        <a href="{$script}?action=logout_all&{$session}" >
          <xsl:value-of select="/root/params/text/logout-all"/>
        </a>
        <xsl:text> | </xsl:text>
        <!-- create a link to start a new session -->
        <!-- (this creates a new session name with a new session id) -->
        <a href="{$script}?action=newsession&{$session}" >
          <xsl:value-of select="/root/params/text/new-session"/>
        </a>
        <xsl:text> | </xsl:text>

        <xsl:choose>
          <xsl:when test="$print-preview"> (3)
            <!-- create a link to turn off print-preview mode -->
            <a href="{$script}?action=noprint&{$session}" >
              <xsl:value-of select="/root/params/text/noprint"/>
            </a>
          </xsl:when>
          <xsl:otherwise>
            <!-- create a link to redisplay the page in print mode -->
            <a href="{$script}?action=print&{$session}" >
              <xsl:value-of select="/root/params/text/print"/>
            </a>
          </xsl:otherwise>
        </xsl:choose>

        <xsl:text> | </xsl:text>
      </xsl:if>

      <xsl:if test="$mode='logon'"> (4)
          <!-- create a password recovery link -->
          <a href="{$doc_root}/menu/mnu_user_pswd.php?{$session}" >
            <xsl:value-of select="/root/params/text/recover-pswd"/>
          </a>
          <xsl:text> | </xsl:text>
      </xsl:if>

      <!-- create a HELP link --> (5)
      <a href="{$help_root}/help.php?taskid={$taskid}">
        <xsl:value-of select="/root/params/text/help"/>
      </a>
    </p>
  </div>

</xsl:template>

Here is the description of the numbered items:

Note that all text is obtained from /root/params/text/??? elements in the XML document as it may be displayed in different languages.

<xsl:template name="menubar">

This is responsible for creating the bottom two rows of the menu bar in the HTML document.

<xsl:template name="menubar">

  <xsl:if test="not($print-preview)"> (1)

    <div id="menubar">
      <!-- produce a list of menu items, with the one which is active being highlighted -->
      <ul>
        <xsl:for-each select="//menubar/button"> (2)
          <li>
            <xsl:attribute name="class"> (3)
              <xsl:choose>
                <xsl:when test="@active">active</xsl:when>
                <xsl:otherwise>inactive</xsl:otherwise>
              </xsl:choose>
            </xsl:attribute>

            <!-- create a link for each element within menubar -->
            <a href="{$script}?selection={@id}&{$session}"> (4)
              <xsl:value-of select="node()"/>
            </a>

          </li>
        </xsl:for-each>
      </ul>
    </div>

    <div id="menustack-outer">
      <div id="menustack">

        <!-- this area is the same colour as the active item in the previous line -->
        <!-- it also contains entries for each page in the current hierarchy -->
        <!-- (aka 'breadcrumbs') -->
        <ul>

          <xsl:for-each select="//menubar/stack"> (5)
           <li>
              <xsl:choose>
                <xsl:when test="position()=last() or @active">
                  <!-- last/active entry is not a hyperlink, just plain text -->
                  <xsl:value-of select="node()"/> (6)
                </xsl:when>
                <xsl:otherwise>
                  <!-- insert hyperlink -->
                  <a href="{$script}?selection={@id}&{$session}">
                    <xsl:value-of select="node()"/> (7)
                  </a>
                </xsl:otherwise>
              </xsl:choose>

              <xsl:if test="not(position()=last())">
                <!-- not last entry, so insert a '>>' separator before the next one -->
                <xsl:text>&#187;</xsl:text> (8)
              </xsl:if>
            </li>
          </xsl:for-each>

        </ul>

      </div>
    </div>

  </xsl:if>

</xsl:template>

Here is the description of the numbered items:

<xsl:template name="message">

This is responsible for creating the message area in the HTML document.

<xsl:template name="message">

  <xsl:if test="//infomsg/*"> (1)
    <div class="infomsg">
      <xsl:for-each select="//infomsg/line">
        <p><xsl:value-of select="node()"