Tony Marston's Blog About software development, PHP and OOP

Generating a tree view using XSL and XML

Posted on 1st April 2003 by Tony Marston

Amended on 26th August 2004

1. Introduction

In an earlier article, 'Generating dynamic web pages using XSL and XML', I showed the XSL code that I have created in order to transform XML documents, which I create dynamically, into HTML documents for output to a web browser. This is a standard method of generating web pages that is supported by the World Wide Web Consortium and which gained popularity with the Apache Cocoon Project. In this article I will show you the XSL stylesheet that I use to transform XML data into a tree view.

2. What is a Tree View?

A tree view shows nodes in a tree structure, like the file explorer on your PC. This starts with one or more root nodes at the first level (level 1), and for each node it will show if child nodes exist at the next level (level 2) going all the way down the structure to the lowest level. Rather than showing the whole structure down to the lowest level in one go it is common practice, as with the file explorer on your PC, not to show any child nodes unless specifically requested. Each node that has children will have one of the following buttons next to it:

Note that this button has a toggle effect, which means that once it has been pressed and the task has been completed it will switch to indicate the opposite task.

A node's children are incuded in the display immediately following it, and they are also indented by one unit in order to better signify the parent/child relationship. Those nodes which exist at level 2 or lower will also have a angle (1K) sign which helps to indicate their parent node.

If a node does not have any children then a blank space will be shown instead of a 'collapse/expand' button.

Note that the following screen shots also contain buttons labelled EXPAND and COLLAPSE at the bottom of the display. These will cause the entire tree structure to be expanded or collapsed in one go.

The following web pages were created using the PHP scripting language talking to a MySQL database running under the Apache web server. Samples of the .xsl and .xml files used in this article can be downloaded here (tree-view.zip 12KB)

Figure 1 - the root node collapsed

xml-and-xsl-treeview-01 (8K)

Figure 1 shows the initial display for a tree view which contains only those nodes that exist at the first level. In this example there is only one root node, but there could be more.

Figure 2 - expanded to level 2

xml-and-xsl-treeview-02 (10K)

Figure 2 shows the result of pressing the 'expand' button on Figure 1. The 'expand' button switched to 'collapse', and the node's children are included in the display. As these nodes have children of their own, but which are not yet included in the display, they will each show their own 'expand' button.

Figure 3 - expanded to level 3

xml-and-xsl-treeview-03 (13K)

Figure 3 shows the result of pressing the 'expand' button for one of the child nodes on Figure 2. Its three children are now shown, and because each of these nodes have children of their own they also will have an 'expand' button. Note how each sucessive level has been indented.

Figure 4 - expanded to level 4

xml-and-xsl-treeview-04 (21K)

Figure 4 shows the result of pressing the 'expand' button for each of the new child nodes on Figure 3. Note that none of the new nodes have children, so a blank space is shown instead of an 'expand' or 'collapse' button.

Figure 5 - all nodes expanded

xml-and-xsl-treeview-05 (32K)

Figure 5 shows the entire tree structure after pressing the 'expand' button for each and every node, or by pressing the global EXPAND button at the botton of the screen.

3. The XML/XSL files for the tree View

The transformation process requires an XML file to provide the data and an XSL file to define the transformation rules.

3.1. The XML data file

This is an extract of the XML file that was used to create the HTML document shown in Figure 2.

<?xml version="1.0"?>
<tree_node_jnr.xample>
  <tree_type> (1)
    <tree_type_id>ORG</tree_type_id>
    <tree_type_desc>Organisation</tree_type_desc>
    <tree_node_jnr> (2)
      <node_id>25</node_id>
      <tree_type_id>ORG</tree_type_id>
      <node_desc>The AJM Group</node_desc>
      <tree_level_seq>1</tree_level_seq>
      <child_count>2</child_count>
      <expanded>y</expanded>
    </tree_node_jnr>
    <tree_node_jnr> (2)
      <node_id>26</node_id>
      <tree_type_id>ORG</tree_type_id>
      <node_desc>AJM Products Limited</node_desc>
      <tree_level_seq>2</tree_level_seq>
      <child_count>3</child_count>
    </tree_node_jnr>
    <tree_node_jnr> (2)
      <node_id>1</node_id>
      <tree_type_id>ORG</tree_type_id>
      <node_desc>AJM Systems Limited</node_desc>
      <tree_level_seq>2</tree_level_seq>
      <child_count>4</child_count>
    </tree_node_jnr>
  </tree_type>
  <navbar> (3)
    <button id="tree_node_enq.php">Enquire</button>
  </navbar>
  <actbar> (4)
    <button id="expand">EXPAND</button>
    <button id="collapse">COLLAPSE</button>
    <button id="close">CLOSE</button>
  </actbar>
</tree_node_jnr.xample>

Here is the description of the numbered items:

3.2. The XSL script file

This is an extract of the XSL file that was used to transform the previous XML data into the HTML document shown in Figure 2.

<?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 --> (1)
<xsl:param name="title">title</xsl:param>
<xsl:param name="script">unknown</xsl:param>
<xsl:param name="orderby"></xsl:param>
<xsl:param name="order"></xsl:param>
<xsl:param name="numrows">0</xsl:param>
<xsl:param name="curpage">1</xsl:param>
<xsl:param name="lastpage">1</xsl:param>
<xsl:param name="script_time">0.0</xsl:param>

<!-- include common templates --> (2)
<xsl:include href="std.head.xsl"/>
<xsl:include href="std.navbar.xsl"/>
<xsl:include href="std.actionbar.xsl"/>
<xsl:include href="std.selectbox.xsl"/>
<xsl:include href="std.message.xsl"/>
<xsl:include href="std.treenode.xsl"/>

<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" /> (3)
  <body>
  
  <form method="post" action="{$script}"> (4)
  <div class="center">

  <table border="0" class="outertable">
    <!-- the outer table has 3 cells - navbar+spacer+body -->
    <tr>
      <td></td>
      <td></td>
      <td class="caption"><xsl:value-of select="$title"/></td>
    </tr>
    <tr>
      <!-- create navigation buttons -->
      <xsl:call-template name="navbar" /> (5)
	
      <!-- this is a space between the navbar and body cells -->
      <td class="spacer"></td>

      <td class="body">
	
        <!-- This is the parent table -->
        <table border="0" class="parent">
          <tr class="outer">
            <xsl:for-each select="//tree_type[1]">
              <td class="outerlabel">Tree Type</td>
              <td class="outer">
                <xsl:value-of select="tree_type_desc"/>
              </td>
            </xsl:for-each>
          </tr>
        </table>
	
        <!-- this is the child table -->
        <table border="0" class="datatable">
          <colgroup align="center" />
          <colgroup width="350" />
	  
        <thead>
          <tr>
            <!-- set up the column headings -->
            <th>Select</th>
            <th>Node Description</th>
          </tr>
        </thead>
	  
        <tbody>
          <!-- add a table row for each inner record in the XML file -->
          <xsl:apply-templates select="//tree_type[1]/tree_node_jnr" /> (6)
        </tbody>
	  
      </table>
	
      <!-- look for optional messages -->
      <xsl:call-template name="message"/> (7)
	
      <!-- create standard action buttons -->
      <xsl:call-template name="actbar"/> (8)

    </td>
  </tr>
</table>  
</div>
</form>
</body>
</html>

</xsl:template>

<xsl:template match="tree_node_jnr"> (9)

  <!-- set the row class to 'odd' or 'even' to determine the colour -->
  <tr>
    <xsl:attribute name="class"> (10)
      <xsl:choose>
        <xsl:when test="position()mod 2">odd</xsl:when>
        <xsl:otherwise>even</xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
    
    <td>
      <!-- this cell contains the checkbox to make selections -->
      <xsl:call-template name="selectbox"/> (11)
    </td>

    <!-- these table cells contain the actual data -->
    <td>
      <xsl:call-template name="tree_node"> (12)
        <xsl:with-param name="id" select="node_id"/>
        <xsl:with-param name="desc" select="node_desc"/>
        <xsl:with-param name="depth" select="tree_level_seq"/>
        <xsl:with-param name="child_count" select="child_count"/>
        <xsl:with-param name="expanded" select="expanded"/>
      </xsl:call-template>
    </td>
		
  </tr>

</xsl:template>

</xsl:stylesheet>

Here is the description of the numbered items:

3.3. The XSL 'include' files

These are stylesheet templates that are defined in their own separate files, but then 'included' into other XSL files during the transformation process. In this way changes can be made to one template and instantly inherited throughout the entire system.

3.3.1. std.treenode.xsl

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

<xsl:template name="tree_node"> (1)
  <xsl:param name="id"/>
  <xsl:param name="desc"/>
  <xsl:param name="depth"/>
  <xsl:param name="child_count"/>
  <xsl:param name="expanded"/>

  <!-- insert a bookmark -->
  <a name="{$id}"></a> (2)
  
  <xsl:call-template name="indent"> (3)
    <xsl:with-param name="depth" select="$depth"/>
  </xsl:call-template>
    
  <xsl:choose>
    <xsl:when test="$child_count > 0"> (4)
      <!-- insert button for 'expand' or 'collapse', as appropriate -->
      <xsl:choose>
        <xsl:when test="$expanded"> (5)
          <!-- item is expanded, so insert 'collapse' button -->
          <a href="{$script}?collapse={$id}#{$id}">
            <img src="images/minus.gif" height="12" width="12" alt="Collapse Thread" />
          </a>
        </xsl:when>
        <xsl:otherwise> (6)
          <!-- item is collapsed, so insert 'expand' button -->
          <a href="{$script}?expand={$id}#{$id}">
            <img src="images/plus.gif" height="12" width="12" alt="Expand Thread" />
          </a>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise> (7)
      <!-- no children, so insert blank spacer -->
      <img src="images/spacer.gif" height="12" width="12" alt='' />
    </xsl:otherwise>
  </xsl:choose>

  <xsl:text> </xsl:text> (8)
  <xsl:value-of select="$desc" />
  
</xsl:template>

<xsl:template name="indent"> (9)
  <xsl:param name="depth"/>

  <!-- for each of $depth > 1 insert a spacer --> 
  <xsl:if test="$depth > 1"> (10)
    
    <xsl:choose>
      <xsl:when test="$depth=2">
        <!-- insert angle symbol --> (11)
        <img src="images/angle.gif" height="16" width="12" alt='' />
      </xsl:when>
      <xsl:otherwise>
        <!-- insert spacer --> (12)
        <img src="images/spacer.gif" height="12" width="12" alt='' />
      </xsl:otherwise>
    </xsl:choose>
    
    <!-- recursive call with $depth decremented -->
    <xsl:call-template name="indent"> (13)
      <xsl:with-param name="depth" select="$depth -1"/>
    </xsl:call-template>
  
  </xsl:if>

</xsl:template>

</xsl:stylesheet>

Here is the description of the numbered items:


4. Summary

I have heard it said by several so-called 'experts' that you cannot have tree structures in an HTML document without using javascript. Not only are they wrong, but because it can be done in HTML it can also be done using a combination of XML and XSL. XSL is not a 'flimsy' or 'immature' scripting language as some people would lead you to believe, as whatever HTML code you can produce from your favourite scripting language you can also produce from XSL stylesheets.

Samples of the .xsl and .xml files used in this article can be downloaded here (tree-view.zip 12KB).

I have written another article at A Flexible Tree Structure which gives access to a sample application written in PHP/MySQL/XML/XSL. This contains all the necessary functions to build and view a Tree structure. This sample application can be run online from my website, and you can also download all the source code to run it within your own environment.


Amendment History

26th August 2004 Updated the Summary section to add a link to a new document entitled A Flexible Tree Structure which gives access to an online demonstration with downloadable code.

counter