///Reusable XSL Stylesheets and Templates

Reusable XSL Stylesheets and Templates

Introduction

When producing software it is not considered good practice to write the same code over and over again. Not only is this inefficient when writing the code in the first place, it is also inefficient when the time comes to make changes as the same change has to be made to every copy of that code. That assumes that you can actually locate every copy. Instead the code should be written once and in a format that makes it reusable. In that way when you want to execute the code you simply call the central version and pass it whatever parameters or arguments it needs.

This technique has been practised for many years with traditional programming languages, but can it be applied to a relatively recent language such as XSL, and if so, then how?

In my dynamic web application all my HTML output is produced using XSL transformations. The PHP code for each page extracts the data from the database, puts it into an XML file, then transforms it using the contents of a static XSL file. Each page may contain several different “zones”, and the contents of each zone can be built using a different XSL template. Where the same zone appears in more than one page and therefore requires the same XSL template the code for that template need not be duplicated in each XSL stylesheet – it can be maintained in a single external file and incorporated into the XSL stylesheet at runtime by means of an

statement. Take the following screen shots as examples:

Figure 1 – a LIST screen

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 or as parameters to the transformation process, therefore common templates can be used in the XSL file without any modification.

Figure 2 – a DETAIL screen

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, title, navigation bar and action bar – which can be dealt with by common templates and thus do not require separate copies of the same code.

The structure of an XML file

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"?>

<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>
<nat_ins_no size="10">PA</nat_ins_no>
<first_name size="20">Pamela</first_name>
<last_name size="30">Anderson</last_name>
<star_sign control="dropdown"
optionlist="star_sign">Virgo</star_sign>
<pers_type_desc noedit="y">Dolly Bird</pers_type_desc>
</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>
<actbar>
<button id="reset">RESET</button>
<button id="finish">FINISH</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>
</navbar>
<menubar>
<button id="person_list.php" active="y">Person</button>
<button id="pers_type_list.php">Person Type</button>
</menubar>
</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.

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"?>

<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>
<nat_ins_no size="10">PA</nat_ins_no>
<first_name size="20">Pamela</first_name>
<last_name size="30">Anderson</last_name>
<initials size="6">pa</initials>
<star_sign size="3"
control="dropdown"
optionlist="star_sign">VIR</star_sign>
<email_addr size="50">pam@hollywood.com</email_addr>
<value1 size="6">36</value1>
<value2 size="12">12.34</value1>
<start_date size="12">01 Jul 1980</start_date>
<end_date size="12"></end_date>
</person>
<lookup>
<star_sign>
<star_sign.option id=""></star_sign.option>
<star_sign.option id="ARI">Aries</star_sign.option>
<star_sign.option id="AQU">Aquarius</star_sign.option>
......
<star_sign.option id="TAU">Taurus</star_sign.option>
<star_sign.option id="VIR">Virgo</star_sign.option>
</star_sign>
<pers_type_id>
<pers_type_id.option id=" "></pers_type_id.option>
<pers_type_id.option id="ACTOR">Actor/Artiste</pers_type_id.option>
<pers_type_id.option id="ANON">Anne Oni Mouse</pers_type_id.option>
......
<pers_type_id.option id="PP">Party Pooper</pers_type_id.option>
<pers_type_id.option id="QP">Of Questionable Parentage</pers_type_id.option>
</pers_type_id>
</lookup>
<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>
</menubar>
<navbar/>
<scrolling>
<scroll id="person" curitem="1" lastitem="10"/>
</scrolling>
<message/>
</root>

This file can be broken down into the following component parts:

<?xml version="1.0"?>

<root>
......
</root>

The first line contains the XML declaration. 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”, but it can be anything.

Here is some data from one of my database tables:

<person>

<person_id size="8" pkey="y">PA</person_id>
.....
<end_date size="12"></end_date>
</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”.

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

<lookup>

<star_sign>
<star_sign.option id="ARI">Aries</star_sign.option>
......
</star_sign>
<pers_type_id>
<pers_type_id.option id="ACTOR">Actor/Artiste</pers_type_id.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 last section contains other miscellaneous sets of data:

<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>
</menubar>
<navbar/>
<scrolling>
<scroll id="person" curitem="1" lastitem="10"/>
</scrolling>
<message/>

• “actbar” contains a series of buttons for the action bar.

• “menubar” contains a series of buttons for the menu bar.

• “navbar” contains a series of buttons for the navigation bar, although in this example there are none.

• “scrolling” contains values for a scroll bar belonging to the “person” entity.

The structure of an XSL file

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 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method='html'/>

<!-- param values may be changed during the XSL Transformation -->
<xsl:param name="script">script</xsl:param>
<xsl:param name="mode">read</xsl:param>

<!-- include common templates -->
<xsl:include href="std.actionbar.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.message.xsl"/>
<xsl:include href="std.menubar.xsl"/>
<xsl:include href="std.navbar.xsl"/>
<xsl:include href="std.pagination.xsl"/>

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

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

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<xsl:call-template name="head" />
<body>

<form method="post" action="{$script}">

<center><div class="universe">

<!-- create help button -->
<xsl:call-template name="help" />

<div class="body">

<!-- create menu buttons -->
<xsl:call-template name="menubar" />

<h1><xsl:value-of select="$title"/></h1>

<!-- create navigation buttons -->
<xsl:call-template name="navbar" />

<!-- this is the actual data -->
<table class="main">

<!-- 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>

<!-- look for optional messages -->
<xsl:call-template name="message"/>

<!-- insert the page navigation links -->
<xsl:call-template name="pagination" />

<!-- create standard action buttons -->
<xsl:call-template name="actbar"/>

</div>

</div></center>

</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 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method='html'/>

<!-- param values may be changed during the XSL Transformation -->
<xsl:param name="script">script</xsl:param>
<xsl:param name="mode">update</xsl:param>

<!-- include common templates -->
<xsl:include href="std.actionbar.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.message.xsl"/>
<xsl:include href="std.menubar.xsl"/>
<xsl:include href="std.navbar.xsl"/>
<xsl:include href="std.scrolling.xsl"/>

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

<xsl:template match="/">


<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<xsl:call-template name="head" />
<body>

<form method="post" action="{$script}">

<center>
<div class="universe">

<!-- create help button -->
<xsl:call-template name="help" />

<div class="body">

<!-- create menu buttons -->
<xsl:call-template name="menubar" />

<h1><xsl:value-of select="$title"/></h1>

<!-- create navigation buttons -->
<xsl:call-template name="navbar_detail" />

<!-- table contains a row for each database field -->
<table class="main">

<!-- set up column widths -->
<xsl:call-template name="column_group">
<xsl:with-param name="table" select="'main'"/>
</xsl:call-template>

<!-- 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>

<!-- 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>
</center>

</form>
</body>
</html>

</xsl:template>

</xsl:stylesheet>

These XSL stylesheets can be broken down into the following component parts:

<?xml version='1.0'?>

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

<xsl:output method='html'/>

......

</xsl:stylesheet>

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

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

<!-- param values may be changed during the XSL Transformation -->

<xsl:param name="script">script</xsl:param>
<xsl:param name="mode">update</xsl:param>

<!-- include common templates -->
<xsl:include href="std.actionbar.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.message.xsl"/>
<xsl:include href="std.menubar.xsl"/>
<xsl:include href="std.navbar.xsl"/>
<xsl:include href="std.scrolling.xsl"/>

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

The

<xsl:param>

statements identify values that are expected to be passed to the transformation process as arguments or parameters. These can then be referred to using the names

$script

and

$mode

. If a named parameter is not supplied at runtime then a default value can be used instead.

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>

statement extracts a value from the current XML file. This can then be referred to using the name $main.

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. These are 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 those sample XSL stylesheets there are references to numerous templates. Most of these have already been described in another document and can be viewed using the following links: std.actionbar.xsl, std.column_hdg.xsl, std.data_field.xsl, std.head.xsl, std.message.xsl, std.navbar.xsl, std.pagination.xsl and std.scrolling.xsl.

Levels of Reusability

4.1 A library of standard templates

Using the techniques described above has allowed me to achieve a significant amount of reusability with my XSL code. By maintaining the contents of standard templates in separate files which can be referenced by the

<xsl:include>

statement I have effectively created a library of standard subroutines.

4.2 A single “detail” stylesheet for multiple modes

Another area of reusability I have incorporated into my design revolves around the use of form families where a typical database table requires 6 forms to handle its maintenance – a LIST/BROWSE form, a SEARCH form, an INSERT form, an UPDATE form, an ENQUIRY form and a DELETE form. The LIST/BROWSE form deals with multiple rows displayed horizontally whereas all the others deal with a single row which is displayed vertically. Because this last set of forms all use the same layout I am able to satisfy them all with a single “detail” stylesheet. By passing a $mode parameter at runtime (containing the value “search”, “insert” “update”, “read” or “delete”) I am able to determine at runtime whether the fields are to be amendable or read-only and therefore create the relevant HTML code.

4.3 Stylesheets do not specify HTML controls for fields

In an early version of my code when a stylesheet referred to a fieldname it had to specify which HTML control (eg. text box, dropdown list, radio group) was to be used for that field. This worked OK until I came across a situation where the same field needed to be handled in different ways depending on circumstances which were determined at runtime within my PHP code. In my first resolution of this problem I got my PHP code to specify the required control type as an attribute to the field in the XML file, and I changed my stylesheet to call the relevant template depending on this attribute. This worked OK, but when I looked at the code I realised that instead of getting my stylesheet to call the relevant template depending on the control attribute I could create an intermediate template which would do the job for me. Consequently all my stylesheets now call a standard datafield template for each field, and it is this template which checks the value in the control attribute and calls the next template which deals with that particular control type. I currently make use of the following attributes in the XML file for each field:

size Determines the size of the text box.
pkey Identifies the field as part of the primary key.
noedit Makes the field read-only.
nodisplay Excludes the field from the HTML output
control Identifies the control type. The default is “text”, but can be “dropdown”, “boolean”, “radiogroup”, “popup” or “multiline”.
password Does not echo each character as it is typed in.
required Indicates that this is a required field.

4.4 Stylesheets do not contain table or field names

Until recently each physical screen in my web application required its own separate stylesheet as this had hard-coded into it the names of the database table(s) and the field(s) which were to be included in the HTML output. Although it was a relatively simple process to take a copy of the basic stylesheet and customise it as required I began to wonder if it were possible to use a generic stylesheet and pass it the table and field names at runtime within the XML file. It did not take much experimentation to discover that this was definitely a workable proposition.

4.4.1 Screen structure in XML format

The first step was to include in my XML file the necessary elements to identify which tables and fields were to be processed, and in what order. Here is a sample of the extra information which appears in the XML file for detail screens:

<structure>

<main id="person">
<columns>
<column width="150"/>
<column width="•"/>
</columns>
<fieldlist>
<field id="person_id" label="ID"/>
<field id="first_name" label="First Name"/>
<field id="last_name" label="Last Name"/>
<field id="initials" label="Initials"/>
<field id="nat_ins_no" label="Nat. Ins. No."/>
<field id="pers_type_id" label="Person Type"/>
<field id="star_sign" label="Star Sign"/>
<field id="email_addr" label="E-mail"/>
<field id="value1" label="Value 1"/>
<field id="value2" label="Value 2"/>
<field id="start_date" label="Start Date"/>
<field id="end_date" label="End Date"/>
<field id="selected" label="Selected"/>
</fieldlist>
</main>
</structure>

This structure identifies the following:

• It identifies the data for a single zone called “main”.

• This zone is to be filled with a record from the “person” table.

• It will require 2 columns of the specified widths.

• The fieldlist identifies which fields are to be painted on the screen and in what order.

• The fieldlist also supplies the label for each field.

4.4.2 Screen structure in PHP format

This information is supplied to my application in the form of a PHP “include” file, as shown in the following sample. I have a standard PHP function which reads this in and adds the details to the current XML file.

<?php

$structure['xsl_file'] = 'std.detail1.xsl';

$structure['tables']['main'] = 'person';

$structure['main']['columns'][] = array('width' => 150);
$structure['main']['columns'][] = array('width' => '•');

$structure['main']['fields'] = array('person_id' => 'ID',
'first_name' => 'First Name',
'last_name' => 'Last Name',
'initials' => 'Initials',
'nat_ins_no' => 'Nat. Ins. No.',
'pers_type_id' => 'Person Type',
'star_sign' => 'Star Sign',
'email_addr' => 'E-mail',
'value1' => 'Value 1',
'value2' => 'Value 2',
'start_date' => 'Start Date',
'end_date' => 'End Date',
'selected' => 'Selected');
?>

4.4.3 Process screen structure – verticallyIn detail screens the fields for a single record are displayed vertically, with the field label shown in front of each field value as shown in figure 2. The code to achieve this, which is used in the XSL file for a DETAIL screen, is explained below.

<xsl:variable name="main" select="//structure/main/@id"/>

This line of code creates a variable called $main and selects the contents of the “id” attribute of the “main” node which is a child of the “structure” node. In this example the resulting value is “person”.

<xsl:call-template name="column_group">

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

This calls the column-group template with a parameter called “table” which has the value “main”.

<xsl:template name="column_group">

<xsl:param name="table"/>

<xsl:for-each select="//structure/•[name()=$table]/columns/column">
<colgroup>
<xsl:if test="@width">
<xsl:attribute name="width"><xsl:value-of select="@width"/></xsl:attribute>
</xsl:if>
<xsl:if test="@class">
<xsl:attribute name="class"><xsl:value-of select="@class"/></xsl:attribute>
</xsl:if>
</colgroup>
</xsl:for-each>

</xsl:template>

This is the column-group template. The select portion of the

<xsl:for-each>

statement is the method whereby a string is converted into a node set. In this example it is equivalent to “//structure/main/columns/column” and steps through all the designated “column” nodes in the XML document. For each node it will create an HTML “<colgroup>” tag with either a “width” attribute or a “class” attribute.

<xsl:for-each select="//•[name()=$main][1]">

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

The select portion of the

<xsl:for-each>

statement in this example is equivalent to “//person[1]” and selects the first occurrence of the node with the name “person”. It then calls the display_vertical template to process this node and passes it the zone name of “main” (remember that this same template may be used for other zones in other stylesheets).

<xsl:template name="display_vertical">

<xsl:param name="zone"/> <!-- could be 'main', 'inner', 'outer', etc -->

This is the start of the display_vertical template. It is passed a single parameter called $zone

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

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

It creates two variables. In this example $table will contain “person” and $position will contain “1”.

<xsl:for-each select="//structure/•[name()=$zone]/fieldlist/field">

This line of code is equivalent to the XPath expression “//structure/main/fieldlist/field” and will loop through each field in the fieldlist of the main entity of the structure element. In this example it will start at “person_id” and finish at “selected”.

<xsl:variable name="fieldname" select="@id" />

<xsl:variable name="field"
select="//•[name()=$table][position()=$position]/•[name()=$fieldname]" />

The first line of code creates a variable called $fieldname using the “id” attribute of the current node. As this is inside a for-each loop it will start at “person_id” and finish at “selected”.

The second line of code extracts the value for $fieldname. As it progresses through the for-each loop it will be equivalent to the XPath expression “//person[1]/person_id” all the way down to “//person[1]/selected”.

<xsl:if test="$field">

This line will ignore the current $field if it is not defined within the XML file.

<xsl:if test="not($field/@nodisplay)">

This line will ignore the current $field if it is has the “nodisplay” attribute set.

<tr class="{$zone}">

<td>
<xsl:attribute name="class">
<xsl:value-of select="concat($zone,'label')"/>
</xsl:attribute>
<xsl:value-of select="@label"/>
</td>
<td>
<xsl:call-template name="datafield">
<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:call-template>
</td>
</tr>

This code will create a single row within an HTML table. The “<tr>” tag has its “class” attribute set to the value of $zone, which in this example is “main”.

The first table cell has its “class” attribute set to “mainlabel”, and its value is set to the “label” attribute of the current “field” node.

The second table cell is filled with the value for the current node. This is done by calling the datafield template and passing it parameters which contain the field value, the field name, the table name (“person”), and the table row (“1”). This will utilise the field attributes within the XML file to determine which HTML control to use for this field.

</xsl:if>

</xsl:if>
</xsl:for-each>
</xsl:template>

The remaining lines simply close the existing XSL statements.

4.4.4 Process screen structure – horizontally

In list screens there are multiple occurrences which are displayed horizontally. The first row in the HTML table contains all the field labels in the form of column headings, and this is followed by rows of field values, one line for each occurrence in the XML file. An example of this is shown in figure 1. The code to achieve this, which is used in the XSL file for a LIST screen, is explained below.

<thead>

<xsl:call-template name="column_headings">
<xsl:with-param name="table" select="'main'"/>
</xsl:call-template>
</thead>

This calls the column-headings template with a parameter called “table” which has the value “main”.

<xsl:template name="column_headings">

<xsl:param name="table"/>

<tr>
<xsl:for-each select="//structure/•[name()=$table]/fieldlist/field">
<th>
<xsl:call-template name="column_hdg">
<xsl:with-param name="item" select="@id"/>
<xsl:with-param name="label" select="@label"/>
</xsl:call-template>
</th>
</xsl:for-each>
</tr>

</xsl:template>

This is the column-headings template. The select portion of the <xsl:for-each> statement in this example is equivalent to “//structure/main/fieldlist/field” and steps through all the designated “field” nodes in the XML document. For each node it will call the column-hdg template.

<tbody>

<xsl:for-each select="//•[name()=$main][count(•)>0]">
<xsl:call-template name="display_horizontal">
<xsl:with-param name="zone" select="'main'"/>
</xsl:call-template>
</xsl:for-each>
</tbody>

The select portion of the

<xsl:for-each>

statement in this example is equivalent to “//person[count(•)>0]” and selects all non-empty occurrences of the node with the name “person”. It then calls the display_horizontal template to process each node and passes it the zone name of “main” (remember that this same template may be used for other zones in other stylesheets).

<xsl:template name="display_horizontal">

<xsl:param name="zone"/> <!-- could be 'main', 'inner', 'outer', etc -->

This is the start of the display_horizontal template. It is passed a single parameter called $zone.

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

It creates two variables. In this example $table will contain “person” and $position will start at “1” and end at the last row number in the XML file.

<tr>

<xsl:attribute name="class">
<xsl:choose>
<xsl:when test="position()mod 2">odd</xsl:when>
<xsl:otherwise>even</xsl:otherwise>
</xsl:choose>
</xsl:attribute>

This will set the “class” attribute of the “<tr>” tag to ‘odd’ or ‘even’ to determine the colour.

<xsl:for-each select="//structure/•[name()=$zone]/fieldlist/field">

This line of code is equivalent to the XPath expression “//structure/main/fieldlist/field” and will loop through each field in the fieldlist of the main entity of the structure element.

<xsl:variable name="fieldname" select="@id" />

<xsl:variable name="field"
select="//•[name()=$table][position()=$position]/•[name()=$fieldname]" />

The first line of code creates a variable called $fieldname using the “id” attribute of the current node. As this is inside a for-each loop the value will change for each iteration.

The second line of code extracts the value for the current field identified in $fieldname. Note that one loop controls the value for $position while another loop controls the value for $fieldname.

<td>

<xsl:call-template name="datafield">
<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:call-template>
</td>
</xsl:for-each>
</tr>

</xsl:template>

This calls the datafield template and passes it parameters which contain the field value, the field name, the table name (“person”), and the table row (which starts at “1” and increments with each iteration). This will utilise the field attributes within the XML file to determine which HTML control to use for this field.

Summary

I have heard some people complain that XSL is too verbose, and that it takes an enormous amount of code to do even the most simple things. These people obviously do not know how to structure their code into reusable modules. As I (hope) I have demonstrated in this article, the steps to creating reusable XSL code are not that much different from creating reusable code in any other programming language:

1. Divide the HTML page layout into separate zones.

2. Create a separate XSL template to process each zone.

3. Put the code for each template into a separate file. You may use a separate file for each template, or you may group several similar templates into a single file.

4. Incorporate the required templates into individual stylesheets by using the

<xsl:include>

statement. This makes the template available to the stylesheet at runtime, and has the same effect as calling a routine from an external library.

5. Use the same DETAIL stylesheet for multiple modes – search, input, update, enquiry and delete.

6. Use a common template to deal with all fields. In this way the XML file determines which HTML control is to be used, not the XSL stylesheet.

7. Get the XML file to identify the table and field names that need to be processed. In this way each screen can use a generic stylesheet instead of having its own customised version.

By using these techniques I have created a web application of over 300 screens using just 8 generic XSL stylesheets and a collection of standard XSL templates. This means that the speed at which I can create a new component which can use an existing stylesheet is greatly increased. Because each stylesheet is merely a collection of calls to standard templates then even the creation of a new stylesheet is now a minor matter. Just imagine how much extra effort would be required if each of those 300 screens had to be hand crafted!

Can you achieve the same level of reusability in YOUR application?

2010-05-26T11:31:12+00:00 April 20th, 2005|XML|0 Comments

About the Author:

I have been a software engineer, both designing and developing, since 1977. I have worked with a variety of 2nd, 3rd and 4th generation languages on a mixture of mainframes, mini- and micro-computers. I have worked with flat files, indexed files, hierarchical databases, network databases and relational databases. The user interfaces have included punched card, paper tape, teletype, block mode, CHUI, GUI and web. I have written code which has been procedural, model-driven, event-driven, component-based and object oriented. I have built software using the 1-tier, 2-tier, 3-tier and Model-View-Controller (MVC) architectures. After working with COBOL for 16 years I switched to UNIFACE in 1993, starting with version 5, then progressing through version 6 to version 7. In the middle of 2002 I decided to teach myself to develop web applications using PHP and MySQL.

Leave A Comment