Helping ordinary people create extraordinary websites!
HOME TUTORIALS SCRIPTS WEB HOSTING BLOG FORUM
Get Our Newsletter
Email:

Reusable XSL Stylesheets and Templates

By Tony Marston
2005-04-20


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:

sizeDetermines the size of the text box.
pkeyIdentifies the field as part of the primary key.
noeditMakes the field read-only.
nodisplayExcludes the field from the HTML output
controlIdentifies the control type. The default is "text", but can be "dropdown", "boolean", "radiogroup", "popup" or "multiline".
passwordDoes not echo each character as it is typed in.
requiredIndicates 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.

Tutorial Pages:
» Introduction
» The structure of an XML file
» The structure of an XSL file
» Levels of Reusability
» Summary


 | Bookmark
Related Tutorials:
» Starting with XML
» Performing Client-Side XSL Transformations
» Create a Google Sitemap for your Web Site
» XML and Scripting Languages
» Parsing Comma-Separated Values
» XML Security Suite: Increasing the Security of E-Business