TalShyar
TalShyar

Reputation: 119

How to get a XSL to display results in multi-columns?

I am creating an application that creates an XML as follows:

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="MyCDCatalog.xsl"?>
<catalog>
    <cd>
        <title>Empire Burlesque</title>
        <artist>Bob Dylan</artist>
        <price>10.90</price>
        <year>1985</year>
    </cd>
    <cd>
        <title>Hide your heart</title>
        <artist>Bonnie Tyler</artist>
        <price>9.90</price>
        <year>1988</year>
    </cd>
    <cd>
        <title>Greatest Hits</title>
        <artist>Dolly Parton</artist>
        <price>9.90</price>
        <year>1982</year>
    </cd>
    <cd>
        <title>One night only</title>
        <artist>Bee Gees</artist>
        <price>10.90</price>
        <year>1998</year>
    </cd>
    <cd>
        <title>Sylvias Mother</title>
        <artist>Dr.Hook</artist>
        <price>8.10</price>
        <year>1973</year>
    </cd>
    <cd>
        <title>Maggie May</title>
        <artist>Rod Stewart</artist>
        <price>8.50</price>
        <year>1990</year>
    </cd>
</catalog>

I also a XSL file called MyCDCatalog.xsl (below) that transforms the above XML file.

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
  <html>
  <body>
    <h2>My CD Collection</h2>
    <table border="1">
      <xsl:for-each select="catalog/cd">
      <tr>
        <td><xsl:value-of select="artist" />'s <xsl:value-of select="title" /> (<xsl:value-of select="year" />):   $<xsl:value-of select="price" /></td>
      </tr>
      </xsl:for-each>
    </table>
  </body>
  </html>
</xsl:template>
</xsl:stylesheet>

Currently, the XSL file returns all the entries in a single column. How do I get my output in 3 or more columns?

I want the output as follows:

[cd1] [cd2] [cd3]
[cd4] [cd5] [cd6]....

where

cd1 = Bob Dylan's Empire Burlesque (1985): $10.90
cd2 = Bonnie Tyler's Hide your heart (1988): $9.90
etc.

Thanks for the help.

Upvotes: 2

Views: 4444

Answers (3)

Tim C
Tim C

Reputation: 70648

Another way to do this is to match the cd in the first, fourth, seventh (etc) position.

<xsl:apply-templates select="cd[(position() - 1) mod $columns = 0]" mode="first"/>

Where $columns is a parameter containing the number of columns

You can then match the cd elements in the row by looking at the relevant number of following siblings:

<xsl:apply-templates select=".|following-sibling::cd[position() &lt; $columns]"/>

Here is the full XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="html" indent="yes" omit-xml-declaration="yes" />
   <xsl:param name="columns" select="3"/>

   <xsl:template match="/catalog">
      <html>
         <body>
            <h2>My CD Collection</h2> 
            <table>
               <xsl:apply-templates select="cd[(position() - 1) mod $columns = 0]" mode="first"/>
            </table>
         </body>
      </html>
   </xsl:template>

   <xsl:template match="cd" mode="first">
      <tr>
         <xsl:apply-templates select=".|following-sibling::cd[position() &lt; $columns]"/>
         <xsl:if test="count(following-sibling::cd) &lt; ($columns - 1)">
            <xsl:call-template name="emptycell">
               <xsl:with-param name="cells" select="$columns - 1 - count(following-sibling::cd)"/>
            </xsl:call-template>
         </xsl:if>
      </tr>
   </xsl:template>

   <xsl:template match="cd">
      <td>
         <xsl:value-of select="concat(title, &apos; &apos;, artist)"/>
      </td>
   </xsl:template>

   <xsl:template name="emptycell">
      <xsl:param name="cells"/>
      <td/>
      <xsl:if test="$cells &gt; 1">
         <xsl:call-template name="emptycell">
            <xsl:with-param name="cells" select="$cells - 1"/>
         </xsl:call-template>
      </xsl:if>
   </xsl:template>
</xsl:stylesheet>

When applied to your sample XML, the following is output:

<html>
   <body>
      <h2>My CD Collection</h2>
      <table>
         <tr>
            <td>Empire Burlesque Bob Dylan</td>
            <td>Hide your heart Bonnie Tyler</td>
            <td>Greatest Hits Dolly Parton</td>
         </tr>
         <tr>
            <td>One night only Bee Gees</td>
            <td>Sylvias Mother Dr.Hook</td>
            <td>Maggie May Rod Stewart</td>
         </tr>
      </table>
   </body>
</html>

(I'm only outputing name and artist here, but I am sure you can see how to change it to show more information)

Note that there is bit of nasty recursive template to output empty cells should the number of remaining cd elements be less than the number of columns in the row.

Change the paramater to 4, for example, then the following is output:

<table>
  <tr>
    <td>Empire Burlesque Bob Dylan</td>
    <td>Hide your heart Bonnie Tyler</td>
    <td>Greatest Hits Dolly Parton</td>
    <td>One night only Bee Gees</td>
  </tr>
  <tr>
    <td>Sylvias Mother Dr.Hook</td>
    <td>Maggie May Rod Stewart</td>
    <td />
    <td />
  </tr>
</table>

Upvotes: 3

Maestro13
Maestro13

Reputation: 3696

In XSLT 1.0, use the following:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" indent="yes"/>
    <xsl:key name="grp" use="floor(count(preceding-sibling::cd) div 3)" match="catalog/cd"/>
    <xsl:template match="/">
        <html>
            <body>
                <h2>My CD Collection</h2>
                <table border="1">
                    <xsl:apply-templates
                        select="catalog/cd[generate-id() =
                            generate-id(key('grp',floor((position() - 1) div 3))[1])]"
                        mode="group"/>
                    <!-- note: index [1] is superfluous, because generate-id applied on node-set returns the id of the first element in the set -->
                </table>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="catalog/cd" mode="group">
      <xsl:variable
          name="groupitems"
          select="key('grp', floor(count(preceding-sibling::cd) div 3))"/>
        <tr>
            <xsl:for-each select="$groupitems">
                <td>
                    <xsl:value-of select="artist" />'s <xsl:value-of select="title" /> (<xsl:value-of select="year" />): $<xsl:value-of select="price" />
                </td>
            </xsl:for-each>
        </tr>
    </xsl:template>
</xsl:stylesheet>

Explanation: Muenchian grouping. Constructing the key gave me a little headache, because at first I tried to assign the use attribute a value related to position() - but that does not work properly. I found another thread on this site explaining what to use instead: key and position() problem.

The output is the desired one:

<html>
    <body>
        <h2>My CD Collection</h2>
        <table border="1">
            <tr>
                <td>Bob Dylan's Empire Burlesque (1985): $10.90</td>
                <td>Bonnie Tyler's Hide your heart (1988): $9.90</td>
                <td>Dolly Parton's Greatest Hits (1982): $9.90</td>
            </tr>
            <tr>
                <td>Bee Gees's One night only (1998): $10.90</td>
                <td>Dr.Hook's Sylvias Mother (1973): $8.10</td>
                <td>Rod Stewart's Maggie May (1990): $8.50</td>
            </tr>
        </table>
    </body>
</html>

Upvotes: 2

Rookie Programmer Aravind
Rookie Programmer Aravind

Reputation: 12154

Edit3:

The two templates table_format and table_format2 will do the job .. set the count in the global variable:

  <xsl:variable name="col_count" select="'3'"/>

col_count decides number of columns :)

also remember that I have used

    <xsl:for-each select="catalog/cd[1]">

in the first template ..

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:variable name="col_count" select="'3'"/>

  <xsl:template match="/">
    <html>
      <body>
        <h2>My CD Collection</h2>
        <table border="1">
        <xsl:for-each select="catalog/cd[1]">
          <xsl:call-template name="table_format"/>
        </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template name="table_format">
    <xsl:param name="count" select="'1'"/>
      <tr>
        <xsl:call-template name="table_format2"/>
      </tr>
      <xsl:for-each select="../cd[$col_count + $count]">
        <xsl:call-template name="table_format">
          <xsl:with-param name="count" select="$count + $col_count"/>
        </xsl:call-template>
      </xsl:for-each>
  </xsl:template>

  <xsl:template name="table_format2">
    <xsl:param name="col_count2" select="1"/>
    <xsl:if test="$col_count2 &lt;= $col_count">
      <td>
        <xsl:value-of select="artist" />'s <xsl:value-of select="title" /> (<xsl:value-of select="year" />): $<xsl:value-of select="price" />
      </td>
      <xsl:for-each select="following-sibling::cd[1]">
        <xsl:call-template name="table_format2">
          <xsl:with-param name="col_count2" select="$col_count2 + 1"/>
        </xsl:call-template>
      </xsl:for-each>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Upvotes: 0

Related Questions