Click here to Skip to main content
Click here to Skip to main content

Recursive XSL Templates

, 22 Dec 2006
Rate this:
Please Sign up or sign in to vote.
This brief article will demonstrate the recursive nature of templates.

Recursive XSL Templates

Introduction

XSL is a declarative programming language. Variables that are once declared cannot be reassigned. It is often difficult for programmers coming from procedural languages background to do advanced tasks. Using XSL, we can solve complex problems, which at first glance often seem to be difficult or impossible. In this brief article, I will demonstrate the recursive nature of templates. In a typical case of a product catalog display, our requirement is to display the product details in each cell, and number of columns should be selectable by the user. The sample XML file is listed below:

<data>
 <product name="Heine HKL with trolleystand" 
          price="230.45" weight="34.4kg" />
 <product name="Universal clamp and Projector" 
          price="670.45" weight="10.64kg" />
 <product name="Examination Lamp, Universal Mount" 
          price="25.45" weight="1.08kg" />
 <product name="Provita Examination Lamp, Mobile Base" 
          price="215.45" weight="1.4kg" />
 <product name="35Watt Flexible Arm Light to fit Rail system" 
          price="130.45" weight="11.67kg" />
 .
 .
 .
 .
 .
 .
</data>

Assuming we are getting the above from a business component, each element row corresponds to a product, whose attributes comprises of product specific data. Each product along with its details will be rendered in a single cell. And the number of columns should be definable at runtime. Following is the brief XSL which does the rendering:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <xsl:param name="numCols" select="3"/>
     <table cellpadding="0" cellspacing="5" border="0">
            <xsl:call-template name="renderColumns">
                  <xsl:with-param name="listrows" select="//product"/>
                  <xsl:with-param name="startindex" select="1"/>
                  <xsl:with-param name="numofCols" select="$numCols"/>
            </xsl:call-template>
     </table>
 </xsl:template>

 <xsl:template name="renderColumns">
    <xsl:param name="listrows" />
    <xsl:param name="startindex"/>
    <xsl:param name="numofCols"/>
    <xsl:if test="count($listrows) > 0">
       <tr>
         <xsl:apply-templates 
           select="$listrows[position() >= 
           $startindex and position() < 
           ($startindex+$numofCols)]" mode="rows">
         </xsl:apply-templates>
       </tr>
         <xsl:call-template name="renderColumns">
            <xsl:with-param name="listrows" 
               select="$listrows[position() >= $startindex+$numofCols]"/>
            <xsl:with-param name="startindex" select="$startindex"/>
            <xsl:with-param name="numofCols" select="$numofCols"/>
         </xsl:call-template>
    </xsl:if>
 </xsl:template>

 <xsl:template match="node()" mode="rows">
  <td nowrap="true">
    <table style="width:100%;border-right: thin solid; 
                     border-top: thin solid; border-left: 
                     thin solid; border-bottom: thin solid;">
       <xsl:apply-templates select="@*"/>
    </table>
  </td>
 </xsl:template>

 <xsl:template match="@*">
   <tr>
    <td style="font-size: larger; text-transform: 
                  uppercase; background-color: gainsboro">
        <xsl:value-of select="name()"/>
    </td>
    <td>
        <xsl:value-of select="."/>
    </td>
   </tr>
 </xsl:template>
</xsl:stylesheet>

Explanation

<xsl:template match="/">
<xsl:param name="numCols" select="3"/>

As would a C or C++ programmer organize and reuse the code using functions or object methods, in XSL, we can organize code using templates. The above code is the root template which will be invoked by the XSLT processor. We are declaring a parameter using xsl:param, whose name is numCols. The user can pass this parameter; if the user is not supplying any value in this parameter, then, by default, it will have a value of 3. This variable will specify the number of columns to be rendered.

<xsl:call-template name="renderColumns">
  <xsl:with-param name="listrows" select="//product"/>
  <xsl:with-param name="startindex" select="1"/>
  <xsl:with-param name="numofCols" select="$numCols"/>
</xsl:call-template>

We are calling the renderColumns template, to which we are passing three parameters. In listrows, we are selecting all product elements, startindex signifies the starting index, and numofCols will control the number of rows to be rendered.

<xsl:template name="renderColumns">
  <xsl:param name="listrows" />
  <xsl:param name="startindex"/>
  <xsl:param name="numofCols"/>
  <xsl:if test="count($listrows) > 0">
     <tr>
         <xsl:apply-templates 
             select="$listrows[position() >= 
                     $startindex and position() < 
                     ($startindex+$numofCols)]" 
             mode="rows">
         </xsl:apply-templates>
     </tr>
     <xsl:call-template name="renderColumns">
         <xsl:with-param name="listrows" 
            select="$listrows[position() >= $startindex+$numofCols]"/>
         <xsl:with-param name="startindex" select="$startindex"/>
         <xsl:with-param name="numofCols" select="$numofCols"/>
     </xsl:call-template>
  </xsl:if>
</xsl:template>

In the XSL template renderColumns, we are selecting the elements whose position will be greater than or equal to the starting index and whose position is less than or equal to the sum of startindex and numofcols. After rendering these subset of elements, we are recursively calling renderColumns by selecting a subset of elements whose position is greater than the sum of startindex and numofCols, which are already rendered. For exiting this recursive loop, we have a test condition at the start which checks for the count of elements in the listrows variable. As we are selecting only those elements which are yet to be rendered while calling recursively, the set of nodes by each call will be reduced by the number of elements rendered. For rendering rows, in this call template, we are using the following template:

<xsl:template match="node()" mode="rows">
  <td nowrap="true">
     <table style="width:100%;border-right: thin solid; 
                      border-top: thin solid; border-left: 
                      thin solid; border-bottom: thin solid;"> 
       <xsl:apply-templates select="@*"/>
     </table>
  </td>
</xsl:template>

in which we are converting the attribute nodes into elements and calling another template:

<xsl:template match="@*">
  <tr>
    <td style="font-size: larger; text-transform: 
                  uppercase; background-color: gainsboro">
      <xsl:value-of select="name()"/>
    </td>
    <td>
      <xsl:value-of select="."/>
    </td>
  </tr>
</xsl:template>

which does the job of rendering the product details in a cell.

Conclusion

Even though variables are constants through out the life time of a variable in XSL, we can achieve things which, at first glance, look impossible due to the declarative nature of XSL. Upon close look and thinking in a declarative manner, we can solve the problem.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Kiran Kumar G

United States United States
No Biography provided

Comments and Discussions

 
Questionnewbie Pinmemberwas830920-Feb-07 12:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140827.1 | Last Updated 22 Dec 2006
Article Copyright 2006 by Kiran Kumar G
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid