Select Parameters Utility: Explanation

The Select Parameters Utility helps you create dynamic web pages. You can use it to let users of your webpages select the parameters that they want to apply within the stylesheet.

This page explains how the Select Parameters Utility works by going step by step through the stylesheet. It is intended for those interested in learning more about how XSLT works with real problems rather than as instructions on how to use the utility.

Stylesheet parameters

The parameters that I define within the selectParameters.xsl stylesheet are those that are needed to pass information from one transformation to the next. These parameters won't be set the first time that the transformation occurs, so their values have to be derived that first time. After the first time, the parameters are passed into the stylesheet directly.

$selectParameters-xsl-file

$selectParameters-xsl-file contains the name of the XSLT stylesheet file that's being used to transform the source XML. The default value for this parameter is derived from the href pseudo-attribute on the xml-stylesheet processing instruction.

<xsl:param name="selectParameters-xsl-file">
  <xsl:variable name="pi" select="//processing-instruction()[name() = 'xml-stylesheet']" />
  <xsl:variable name="after" select="substring-after($pi, ' href=')" />
  <xsl:variable name="quote" select="substring($after, 1, 1)" />
  <xsl:value-of select="substring-before(substring-after($after, $quote), $quote)" />
</xsl:param>
$selectParameters-xml-file

$selectParameters-xml-file holds the file name for the XML file that is being processed. Since there is no automated way of getting hold of this file name, the selectParameters stylesheet uses the 'base' attribute on the document element to identify the name of the file.

<xsl:param name="selectParameters-xml-file" select="/*/@base" />
$stylesheetParams

$stylesheetParams holds a string giving the names and values of all the parameters that are passed from one run of the stylesheet to the next. These have to be kept in a string in this way because you cannot access the value of a parameter given its name as a string in XSLT1.0 (saxon:evaluate() would grant this functionality).

The $stylesheetParams string is made up of name=value pairs separated by the $param-separator (a unique string). To construct the default value, the parameter definitions (xsl:param elements) in the importing stylesheet are accessed and their 'select' attribute used to give the value for the parameter. The selectParameters stylesheet assumes that the parameters are given string values.

<xsl:param name="stylesheetParams">
  <xsl:for-each select="$params">
    <xsl:value-of select="@name" />=<xsl:value-of select="substring(@select, 2, string-length(@select) - 2)" /><xsl:value-of select="$param-separator" /><xsl:text />
  </xsl:for-each>
</xsl:param>

Stylesheet variables

There are two global variables defined within the selectParameters stylesheet.

$params

$params holds a node set containing all the parameter definitions (xsl:param elements) within the importing stylesheet (as specified within the source XML by an xml-stylesheet processing instruction).

<xsl:variable name="params" select="document($selectParameters-xsl-file, /)/*/xsl:param" />
    
$param-separator = '---uniqueParamSeparator---'

$param-separator is a unique string that is used to separate the name/value pairs within the $stylesheetParams parameter. This is a long unique string to ensure that the separator does not occur in the names or values of the parameters themselves.

<xsl:variable name="param-separator" select="'---uniqueParamSeparator---'" />
    

The 'param-doc' key

The 'param-doc' key is used to index into parameter documentation within the importing stylesheet, based on the name of the parameter. This allows the stylesheet to access documented information about a parameter, such as the options and default value, quickly and easily.

<xsl:key name="param-doc" match="/*/doc:param" use="@name" />

The 'insert-selectParameters-form' template

The 'insert-selectParameters-form' template is the main template in the stylesheet, which inserts into the output tree an HTML form that, when submitted, runs a stylesheet over an XML document with the parameters specified within the form. I use a named template because the identity of the current node isn't important.

<xsl:template name="insert-selectParameters-form">
  ...
</xsl:template>

The first thing is to identify the parameters and their default values. There are five parameters:

$xml-file [= $selectParameters-xml-file]
the XML file to be transformed: it's safest to pass this value in directly within the importing stylesheet - if it isn't, the $selectParameters-xml-file, derived from the 'base' attribute of the document element, will be used instead
$xsl-file [= $selectParameters-xsl-file]
the XSLT stylesheet to be used to transform the XML; again, it's safest to pass this value in to the template, and $selectParameters-xsl-file will be used if it isn't
$choose-xml-file [= false]
$choose-xsl-file [= false]
$lang [= 'en']
the language that should be used within the form
<xsl:param name="xml-file" select="$selectParameters-xml-file" />
<xsl:param name="xsl-file" select="$selectParameters-xsl-file" />
<xsl:param name="choose-xml-file" select="false()" />
<xsl:param name="choose-xsl-file" select="false()" />
<xsl:param name="lang" select="'en'" />

The next part of the stylesheet inserts some javascript into the output within a 'script' element:

<script type="text/javascript">
   ...
</script>

The javascript holds two functions. The first function is explainParseError, which takes an parseError generated while parsing an XML file and gives an explanation of the error and a pointer to the location in the file that the error occurs.

function explainParseError(error) {
   return error.reason + '[' + error.url + ': line ' + error.line + ', col ' + error.linepos + ']';
}

The second function is the function that is called when the form is submitted. The purpose of the function is to replace the current content of the window with the result of transforming the XML file with the XSLT stylesheet based on the parameters given within the form.

function formLoadXML() {
  ...
}

The first part of the function creates a DOM object to hold the result of parsing the XML file. If the user is allowed to choose the XML file to be processed, then the file that they specify is used; if not, the XML file that is passed as a parameter into the template is used instead. If this XML filename is blank (i.e. the user didn't enter a name or a filename wasn't passed in) then the document URL is used. Any errors in the parsing of the XML are reported in an alert box.

This piece of the stylesheet shows a mix between javascript that will be output into the resulting file and XSLT that governs what javascript will be output into the resulting file. For example, the xsl:choose element actually changes what javascript line is added to the script.

XMLDOM = new ActiveXObject('Msxml2.FreeThreadedDOMDocument');
XMLDOM.async = false;

<xsl:choose>
   <xsl:when test="$choose-xml-file">
      XMLfile = stylesheetParams.all('selectParameters-xml-file').value;
   </xsl:when>
   <xsl:otherwise>
      XMLfile = '<xsl:value-of select="$xml-file" />';
   </xsl:otherwise>
</xsl:choose>
if (XMLfile == '') {
   XMLfile = document.URL;
}
XMLDOM.load(XMLfile);

if (XMLDOM.parseError.errorCode != 0) {
   alert('Error parsing XML file:\n' + explainParseError(XMLDOM.parseError));
   return;
}

The next piece of the javascript function does the same for the XSLT stylesheet: sets up a DOM, finds out what file should be used, loads it into the DOM and alerts the user to any parse errors that occur.

XSLTDOM = new ActiveXObject('Msxml2.FreeThreadedDOMDocument');
XSLTDOM.async = false;

<xsl:choose>
   <xsl:when test="$choose-xsl-file">
      XSLfile = stylesheetParams.all('selectParameters-xsl-file').value;
   </xsl:when>
   <xsl:otherwise>
      XSLfile = '<xsl:value-of select="$xsl-file" />';
   </xsl:otherwise>
</xsl:choose>

if (XSLfile == '') {
   XSLfile = '<xsl:value-of select="$xsl-file" />';
}
XSLTDOM.load(XSLfile);

if (XSLTDOM.parseError.errorCode != 0) {
   alert('Error parsing stylesheet:\n' + explainParseError(XSLTDOM.parseError));
   return;
}

The final part of the script sets up the XSLT processor to be used to transform the XML. For each of the parameters in the importing stylesheet (identified within the $params variable), the value for that parameter given in the relevant field within the form is set for the XSLT processor. While cycling through these, the javascript is also created to construct the $stylesheetParams string to be passed into the next run of the stylesheet. This, and the names of the XML and XSLT files are also set as parameters, to be used within the selectParameters stylesheet. The result of the transformation is written to the document.

XSLStylesheet = new ActiveXObject('Msxml2.XSLTemplate');
XSLStylesheet.stylesheet = XSLTDOM;
XSLTProcessor = XSLStylesheet.createProcessor();

XSLTProcessor.input = XMLDOM;
stylesheetParamString = '';
<xsl:for-each select="$params">
   XSLTProcessor.addParameter('<xsl:value-of select="@name" />', stylesheetParams.all('<xsl:value-of select="@name" />').value);
   stylesheetParamString = stylesheetParamString + '<xsl:value-of select="@name" />=' + stylesheetParams.all('<xsl:value-of select="@name" />').value + '<xsl:value-of select="$param-separator" />';
</xsl:for-each>

XSLTProcessor.addParameter('selectParameters-xml-file', XMLfile);
XSLTProcessor.addParameter('selectParameters-xsl-file', XSLfile);

if (stylesheetParamString != '') {
   XSLTProcessor.addParameter('stylesheetParams', stylesheetParamString);
}
XSLTProcessor.transform();

document.open();
document.write(XSLTProcessor.output);
document.close();

The next part of the template deals with adding the form with which the user can select the values for the parameters to be used during processing.

 
<form name="stylesheetParams" action="javascript:formLoadXML()">
   ...
</form>

The first thing to do is add any fields that are needed to store/set the XML and XSLT file names. This is done in a big ugly choose statement because the combination of which of the files can be selected determines how a 'table' should be wrapped around them.

<xsl:choose>
   <xsl:when test="$choose-xml-file and $choose-xsl-file">
      <table>
         <xsl:call-template name="insert-selectParameters-xml-file">
            <xsl:with-param name="xml-file" select="$xml-file" />
            <xsl:with-param name="lang" select="$lang" />
         </xsl:call-template>
         <xsl:call-template name="insert-selectParameters-xsl-file">
            <xsl:with-param name="xsl-file" select="$xsl-file" />
            <xsl:with-param name="lang" select="$lang" />
         </xsl:call-template>
      </table>
   </xsl:when>
   <xsl:when test="$choose-xml-file">
      <table>
         <xsl:call-template name="insert-selectParameters-xml-file">
            <xsl:with-param name="xml-file" select="$xml-file" />
            <xsl:with-param name="lang" select="$lang" />
         </xsl:call-template>
      </table>
      <xsl:call-template name="insert-selectParameters-xsl-file">
         <xsl:with-param name="xsl-file" select="$xsl-file" />
         <xsl:with-param name="choose" select="false()" />
         <xsl:with-param name="lang" select="$lang" />
      </xsl:call-template>        
   </xsl:when>
   <xsl:when test="$choose-xsl-file">
      <table>
         <xsl:call-template name="insert-selectParameters-xsl-file">
            <xsl:with-param name="xsl-file" select="$xsl-file" />
            <xsl:with-param name="lang" select="$lang" />
         </xsl:call-template>
      </table>
      <xsl:call-template name="insert-selectParameters-xml-file">
            <xsl:with-param name="xml-file" select="$xml-file" />
            <xsl:with-param name="choose" select="false()" />
            <xsl:with-param name="lang" select="$lang" />
      </xsl:call-template>        
   </xsl:when>
   <xsl:otherwise>
      <xsl:call-template name="insert-selectParameters-xml-file">
         <xsl:with-param name="xml-file" select="$xml-file" />
         <xsl:with-param name="choose" select="false()" />
         <xsl:with-param name="lang" select="$lang" />
      </xsl:call-template>
      <xsl:call-template name="insert-selectParameters-xsl-file">
         <xsl:with-param name="xsl-file" select="$xsl-file" />
         <xsl:with-param name="choose" select="false()" />
         <xsl:with-param name="lang" select="$lang" />
      </xsl:call-template>        
   </xsl:otherwise>
</xsl:choose>

Finally, I call two templates the 'insert-selectParameters-entries' template that inserts the main fields of the form with which the user can edit the values of the parameters to be passed into the next transformation; and the 'insert-selectParameters-button' template that inserts a button that the user can press to submit the form and enact the transform.

    <xsl:call-template name="insert-selectParameters-entries" />
    <xsl:call-template name="insert-selectParameters-button" />

The 'insert-selectParmaeters-xml-file' and 'insert-selectParameters-xsl-file' template

The two named templates 'insert-selectParameters-xml-file' and 'insert-selectParameters-xsl-file' are very similar. They are both used to insert either a field into the form that allows the user to give the names of the files to be used or a hidden field that holds the same value to be carried through to the next run.

The templates are passed three parameters:

$xml-file/xsl-file
the name of the file (the default value for the field)
$choose [= true]
$lang [= 'en']
the language to be used in the naming of the file

If $choose is true, then a table row is constructed in which the first cell holds the label for the field (i.e. 'XML File:' or 'XSL File:') and the second cell holds a text input field with the default value being the value of the $xml-file/$xsl-file parameter. If $choose is false, then a hidden input field is given instead.

<xsl:template name="insert-selectParameters-xml-file">
  <xsl:param name="xml-file" />
  <xsl:param name="choose" select="true()" />
  <xsl:param name="lang" select="'en'" />
  <xsl:choose>
    <xsl:when test="$choose">
      <tr>
        <th><label for="stylesheetParamselectParameters-xml-file">XML File:</label></th>
        <td>
          <input id="stylesheetParamselectParameters-xml-file"
                 name="selectParameters-xml-file"
                 value="{$xml-file}"
                 type="text" />
        </td>
      </tr>
    </xsl:when>
    <xsl:otherwise>
      <input id="stylesheetParamselectParameters-xml-file"
             name="selectParameters-xml-file"
             value="{$xml-file}"
             type="hidden" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="insert-selectParameters-xsl-file">
  <xsl:param name="xsl-file" />
  <xsl:param name="choose" select="true()" />
  <xsl:param name="lang" select="'en'" />
  <xsl:choose>
    <xsl:when test="$choose">
      <tr>
        <th><label for="stylesheetParamselectParameters-xsl-file">XSL File:</label></th>
        <td>
          <input id="stylesheetParamselectParameters-xsl-file"
                 name="selectParameters-xsl-file"
                 value="{$xsl-file}"
                 type="text" />
        </td>
      </tr>
    </xsl:when>
    <xsl:otherwise>
      <input id="stylesheetParamselectParameters-xsl-file"
             name="selectParameters-xsl-file"
             value="{$xsl-file}"
             type="hidden" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

The 'insert-selectParameters-entries' template

The 'insert-selectParameters-entries' template takes one parameter:

$lang [= 'en']
the language to be used in the form fields

It creates a table and then applies templates to the parameters definitions (xsl:param elements) in the importing stylesheet in 'entry' mode, passing the $lang parameter along. This template, along with the entry template could be overridden within your importing stylesheet to give a different layout to the form, or to select only some parameters as selectable within it.

<xsl:template name="insert-selectParameters-entries">
   <xsl:param name="lang" select="'en'" />
   <table>
      <xsl:apply-templates select="$params" mode="entry">
         <xsl:with-param name="lang" select="$lang" />
      </xsl:apply-templates>
   </table>
</xsl:template>

The entry template

The entry template is a template that matches a parameter definition (xsl:param element) in 'entry' mode. It's applied by the 'insert-selectParameters-entries' template. This template adds the entries for a particular parameter. It takes one parameter:

$lang [= 'en']
the language to be used in the form fields

This template creates a table row that contains two cells: a label cell that contains the result of applying templates to the parameter definition in 'label' mode; and an input cell that contains the result of applying templates to the parameter definition in 'input' mode.

This template, along with the 'insert-selectParameters-entries' template could be overridden in your importing stylesheet if you want a different way of laying out each of the parameters, such as not using a table for them, for example.

<xsl:template match="xsl:param" mode="entry">
   <xsl:param name="lang" select="'en'" />
   <tr>
      <th>
         <xsl:apply-templates select="." mode="label">
            <xsl:with-param name="lang" select="$lang" />
         </xsl:apply-templates>
      </th>
      <td>
         <xsl:apply-templates select="." mode="input">
            <xsl:with-param name="lang" select="$lang" />
         </xsl:apply-templates>
      </td>
   </tr>
</xsl:template>

The label template

The label template generates a label for the input field for adding a value for the parameter. It takes one parameter:

$lang [= 'en']
the language to be used in the form fields

The template creates an HTML label for the input field associated with the parameter definition. It idenfies the documentation associated with the particular parameter using the 'param-doc' key. It then constructs a label that is, in order of preference:

  1. the doc:label given within parameter documentation that is in the language given by $lang
  2. the first doc:label given within the parameter documentation
  3. the name of the parameter

This template could be overridden within your importing stylesheet if you wanted to give a specific label to a particular parameter, or wanted to use another method of identifying what the text of the label should be.

<xsl:template match="xsl:param" mode="label">
   <xsl:param name="lang" select="'en'" />
   <label for="stylesheetParam{@name}">
      <xsl:variable name="label" select="key('param-doc', @name)/doc:label" />
      <xsl:choose>
         <xsl:when test="$label[lang($lang)]"><xsl:value-of select="$label[lang($lang)]" /></xsl:when>
         <xsl:when test="$label"><xsl:value-of select="$label" /></xsl:when>
         <xsl:otherwise><xsl:value-of select="@name" /></xsl:otherwise>
      </xsl:choose>
   </label>
</xsl:template>

The input template

The input template generates an input field based on a parameter definition and its documentation. It takes one parameter:

$lang [= 'en']
the language to be used in the form fields
<xsl:template match="xsl:param" mode="input">
  <xsl:param name="lang" select="'en'" />
  ...
</xsl:template>

The input template can be overridden within the importing stylesheet to create different types of input fields according to other sources of information about the parameters. For example, if you wanted to have a parameter called 'text' be given a value using an HTML textarea, then you could create a template that matched that parameter and created a textarea for the user to complete:

<xsl:template match="xsl:param[@name = 'text']" mode="input">
   <xsl:variable name="default" select="substring-before(substring-after(concat($param-separator, $stylesheetParams), concat($param-separator, @name, '=')), $param-separator)" />
   <textarea id="stylesheetParamtext" name="text">
      <xsl:value-of select="$default" />
   </textarea>
</xsl:template>

The first thing the input template does is to set up a number of variables that contain information about the parameter:

$default
the current value that the parameter is set to, retrieved from the $stylesheetParams global parameter
<xsl:variable name="default" select="substring-before(substring-after(concat($param-separator, $stylesheetParams), concat($param-separator, @name, '=')), $param-separator)" />
$param-doc
the documentation within the importing stylesheet that is associated with the parameter
<xsl:variable name="param-doc" select="key('param-doc', @name)" />
$desc
the description of the parameter given within the parameter documentation
<xsl:variable name="desc" select="$param-doc/doc:desc" />
$choices
the (mutually exclusive) values that the parameter can take, as defined within the parameter documentation
<xsl:variable name="choices" select="$param-doc/doc:choice" />
$option
the multiple values that the parameter can take, as defined within the parameter documentation
<xsl:variable name="options" select="$param-doc/doc:option" />
$min
the minimum value for the (numerical) parameter
<xsl:variable name="min" select="$param-doc/doc:min" />
$max
the maximum value for the (numerical) parameter
<xsl:variable name="max" select="$param-doc/doc:max" />

The template then has a big choose statement the generates different input fields depending on the type of parameter value that should be given. The three types are:

<xsl:choose>
   <xsl:when test="$choices">
      ...
   </xsl:when>
   <xsl:when test="$options">
      ...
   </xsl:when>
   <xsl:otherwise>
      ...
   </xsl:otherwise>
</xsl:choose>

When a number of mutually exclusive choices are specified, these are given within a drop-down list: an HTML 'select' element with a number of 'option' elements giving each option. Each option has a value and a label - again these are specified by the parameter documentation.

<select id="stylesheetParam{@name}" name="{@name}">
   <xsl:for-each select="$choices">
      <xsl:variable name="label">
         <xsl:choose>
            <xsl:when test="doc:label[lang($lang)]">
               <xsl:value-of select="doc:label[lang($lang)]" />
            </xsl:when>
            <xsl:otherwise>
               <xsl:value-of select="doc:label" />
            </xsl:otherwise>
         </xsl:choose>
      </xsl:variable>
      <option value="{doc:value}">
         <xsl:if test="string($label)">
            <xsl:attribute name="label"><xsl:value-of select="$label" /></xsl:attribute>
         </xsl:if>
         <xsl:if test="doc:value = $default">
            <xsl:attribute name="selected">selected</xsl:attribute>
         </xsl:if>
         <xsl:choose>
            <xsl:when test="string($label)">
               <xsl:value-of select="$label" />
            </xsl:when>
            <xsl:otherwise>
               <xsl:value-of select="doc:value" />
            </xsl:otherwise>
         </xsl:choose>
      </option>
   </xsl:for-each>
</select>

When a number of options can be specified, these are given as a number of labelled checkboxes, each of which can be checked and unchecked independently. When a parameter takes this kind of value, the various checked options are combined using '::'s to give the parameter value that is passed to the stylesheet.

<xsl:variable name="name" select="@name" />
<xsl:for-each select="$options">
   <input type="checkbox" name="{$name}" value="{doc:value}" id="stylesheetParam{$name}{doc:value}">
      <xsl:if test="contains(concat('::', $default, '::'), concat('::', doc:value, '::'))">
         <xsl:attribute name="checked">checked</xsl:attribute>
      </xsl:if>
   </input>
   <label for="stylesheetParam{$name}{doc:value}">
      <xsl:choose>
         <xsl:when test="doc:label[lang($lang)]">
            <xsl:value-of select="doc:label[lang($lang)]" />
         </xsl:when>
         <xsl:when test="doc:label">
            <xsl:value-of select="doc:label" />
         </xsl:when>
         <xsl:otherwise>
            <xsl:value-of select="doc:value" />
         </xsl:otherwise>
      </xsl:choose>
   </label>
</xsl:for-each>

Finally, other types of parameters can be specified through a text input field. If a minimum/maximum for the value is given, then javascript is added to check that the entered value fulfils the conditions placed on it.

<input id="stylesheetParam{@name}" name="{@name}">
   <xsl:if test="$default">
      <xsl:attribute name="value"><xsl:value-of select="$default" /></xsl:attribute>
   </xsl:if>
   <xsl:if test="$min or $max">
      <xsl:attribute name="onchange">
         <xsl:choose>
            <xsl:when test="$min and $max">
               javascript:
                  if (this.value &lt; <xsl:value-of select="$min" /> | this.value > <xsl:value-of select="$max" />) {
                     alert("<xsl:value-of select="@name" /> must be between <xsl:value-of select="$min" /> and <xsl:value-of select="$max" />")
                  }
            </xsl:when>
            <xsl:when test="$min">
               javascript:
                  if (this.value &lt; <xsl:value-of select="$min" />) {
                     alert("<xsl:value-of select="@name" /> must be greater than <xsl:value-of select="$min" />")
                  }
            </xsl:when>
            <xsl:otherwise>
               javascript:
                  if (this.value > <xsl:value-of select="$max" />) {
                     alert("<xsl:value-of select="@name" /> must be less than <xsl:value-of select="$max" />")
                  }
            </xsl:otherwise>
         </xsl:choose>
      </xsl:attribute>
   </xsl:if>
</input>

The 'insert-selectParameters-button' template

The 'insert-selectParameters-button' template inserts a button into the output tree that is used to submit the form. It takes one parameter:

$lang [= 'en']
the language to be used in the form fields

The template simply inserts an HTML button element with the text 'change'. The template can be overridden within the importing stylesheet to give a different button for making the change to the form, such as an icon or a button with different text, a different look and feel, or other options.

<xsl:template name="insert-selectParameters-button">
   <xsl:param name="lang" select="'en'" />
   <button type="submit">change</button>
</xsl:template>

More Information


/xslt/utilities/selectParameters-explanation.xml by Jeni Tennison; generated using SAXON 6.5 from Michael Kay