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

Producing a two-column, side-by-side view with XSL

Posted on 22nd December 2003 by Tony Marston

1. Introduction

When displaying a web page containing multiple rows of data it is quite common to use the single-column view, as shown in figure 1:

Figure 1 - a single column view

two-column-view-01 (1K)

But what happens if each row of data is quite narrow and you want to put more than one row on the same line? You want to display the rows side-by-side in a two-column view, as shown in figure 2:

Figure 2 - a two column view

two-column-view-02 (2K)

This may be quite simple when you are constructing the web page from within a PHP script, but how do you achieve the same result with an XSL stylesheet? As I have recently had cause to produce a side-by-side view with XSL this article will explain how I achieved it. It may not be the only way, so if you have found an alternative perhaps you would let me know so I can publish it.

2. XSL stylesheet for a single column view

Here is a typical XSL stylesheet that will produce a single-column view:

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

<xsl:output method='html'/>

<xsl:template match="/">

  <html>
  <head>
    <meta http-equiv="Content-Type" content="text/html" />
    <title>...title...</title>
    <link rel="stylesheet" type="text/css" href="style.css" />
  </head>
  <body>
  
  <form method="post" action="blah.php">
  
    <div id="content">
      <h1>...title...</h1>
      <table>
        <thead>
          <tr>
            <th>Heading 1</th>
            <th>Heading 2</th>
          </tr>
        </thead>
        <tbody>
          <xsl:apply-templates select="//stuff" />
        </tbody>
      </table>
    </div>
  </form>
  </body>
</html>

</xsl:template>

<xsl:template match="stuff">

  <tr>
    <!-- set the row class to 'odd' or 'even' to determine the colour -->
    <xsl:attribute name="class">
      <xsl:choose>
        <xsl:when test="position()mod 2">odd</xsl:when>
        <xsl:otherwise>even</xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
    <td><xsl:value-of select="stuff_1"/></td>
    <td><xsl:value-of select="stuff_2"/></td>
  </tr>

</xsl:template>

</xsl:stylesheet>

3. XSL stylesheet for a two column view

Here is the same stylesheet after it has been changed to output two columns of data side-by-side. See if you can spot the differences (hint: they are highlighted).

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

<xsl:output method='html'/>

<xsl:template match="/">

  <html>
  <head>
    <meta http-equiv="Content-Type" content="text/html" />
    <title>...title...</title>
    <link rel="stylesheet" type="text/css" href="style.css" />
  </head>
  <body>
  
  <form method="post" action="blah.php">
  
    <div id="content">
      <h1>...title...</h1>
      <table>
        <thead>
          <tr>
            <th>Heading 1</th>
            <th>Heading 2</th>
            <th>Heading 1</th> (1)
            <th>Heading 2</th>
          </tr>
        </thead>
        <tbody>
          <xsl:apply-templates select="//stuff[position()mod 2=1]" /> (2)
        </tbody>
      </table>
    </div>
  </form>
  </body>
</html>

</xsl:template>

<xsl:template match="stuff">

  <tr>
    <!-- set the row class to 'odd' or 'even' to determine the colour -->
    <xsl:attribute name="class">
      <xsl:choose>
        <xsl:when test="position()mod 2">odd</xsl:when>
        <xsl:otherwise>even</xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
    <!-- these table cells contain the data for 'odd' numbered rows -->
    <td><xsl:value-of select="stuff_1"/></td>
    <td><xsl:value-of select="stuff_2"/></td>
    
    <!-- these table cells contain the data for 'even' numbered rows --> (3)
    <xsl:choose>
      <!-- look for a sibling with same name as current node -->
      <xsl:when test="count(following-sibling::*[name()=name(current())])">
        <td><xsl:value-of select="following-sibling::*/stuff_1"/></td>
        <td><xsl:value-of select="following-sibling::*/stuff_2"/></td>
      </xsl:when>
      <!-- there is nothing to follow, so create empty cells -->
      <xsl:otherwise>
        <td></td>
        <td></td>
      </xsl:otherwise>
    </xsl:choose>
  </tr>

</xsl:template>

</xsl:stylesheet>

Here is a description of the changes which are highlighted and numbered:

  1. This simply produces headings above both columns.
  2. This invokes the template only for odd-numbered rows from the XML file.
  3. After processing the odd-numbered row this looks for the existence of a following sibling node of the same name in the same path. If one exists then it is processed, otherwise empty cells are output.

As you can see the "stuff" template is only called for odd numbered rows, but it processes an odd and an even numbered row at the same time. Isn't it easy when you know how?

Don't applaud, just throw money.


counter