Search

I’m uncertain if this is the way to go for a three column list where the setup has to be like so:

a d g

b e h

c f i

My code is working fine, and doesn’t appear to put an awful amount of strain on the system. But if there’s a better solution out there I’m all ears.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="../utilities/master.xsl" />

<xsl:template match="data">
    <xsl:call-template name="ul-list" />
</xsl:template>

<xsl:template name="ul-list">
    <xsl:param name="count-ul" select="0" />      
    <ul>
        <xsl:call-template name="list-items">
            <xsl:with-param name="list-number" select="$count-ul" />
        </xsl:call-template>
    </ul>
    <xsl:if test="$count-ul &lt; 2" >
        <xsl:call-template name="ul-list" >         
            <xsl:with-param name="count-ul" select="$count-ul + 1" />       
        </xsl:call-template>
    </xsl:if>
</xsl:template>

<xsl:template name="list-items">
    <xsl:param name="count-li" select="1" />
    <xsl:param name="list-number" />
    <xsl:variable name="total-list" select="count(menus/entry/uk-menu)"/>

    <li><xsl:value-of select="/data/menus/entry[$count-li + ($list-number * ceiling($total-list div 3))]/uk-menu" /></li>

    <xsl:if test="$count-li &lt; ceiling($total-list div 3) ">
        <xsl:call-template name="list-items" >
            <xsl:with-param name="count-li" select="$count-li + 1" />
            <xsl:with-param name="list-number" select="$list-number" />
        </xsl:call-template>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

I’m no expert in XSLT efficiency (I’ve ordered the XSLT Cookbook, hoping to learn more best practices), but I always try to utilize template matching more than call-template.

In your case it seems all your entries are in a single node list (/data/menus/entry), and you could describe the end result as the first column having the 1, 4, 7, … values, second column having the 2, 5, 8, … values and the third column having the 3, 6, 9, … values. Since I like math, this can be described as 1, 4, 7 => X % 3 == 1 and 2, 5, 8 => X % 3 == 2 and 3, 6, 9 == X % 3 == 0.

Thus you can have a template like this:

<xsl:template match="menus/entry">
    <li><xsl:value-of select="uk-menu" /></li>
</xsl:template>

Which can be invoked separately for the three lists like this:

<ul>
    <xsl:apply-templates select="menus/entry[position() mod 3 = 1]" />
</ul>
<ul>
    <xsl:apply-templates select="menus/entry[position() mod 3 = 2]" />
</ul>
<ul>
    <xsl:apply-templates select="menus/entry[position() mod 3 = 0]" />
</ul>

That template paired with those three invocations should do the same as your code, a bit more elegantly in my humble opinion :) You could probably write some clever template for the three lists as well, but since they don’t really relate to any data nodes I wouldn’t bother. Unless you want to make the number of columns easily configurable, this makes it very clear how many columns will be created.

I just tested your solution and yes it’s the less verbose one, but it lists the items

a b c

d e f

g h i

The data set is alphabetical, so the data set looks like this:

<menus>
  <entry>
    <uk-menu>a</uk-menu>
  </entry>
  <entry>
    <uk-menu>b</uk-menu>
  </entry>
  <entry>
    <uk-menu>c</uk-menu>
  </entry>
  <entry>
    <uk-menu>d</uk-menu>
  </entry>
  <entry>
    <uk-menu>e</uk-menu>
  </entry>
  <entry>
    <uk-menu>f</uk-menu>
  </entry>
  <entry>
    <uk-menu>g</uk-menu>
  </entry>
  <entry>
    <uk-menu>h</uk-menu>
  </entry>
  <entry>
    <uk-menu>i</uk-menu>
  </entry>
</menus>

For this to work I need to iterate through the first column of entries first, so get total/no-columns in this case = 3 then output the first 3 positions and then the next column.

I could keep it less verbose if I created a table instead of lists, but the content is list based, so I’d rather keep it that way.

This might help. Or this article from IBM, “Using recursion effectively.”

This XSLT Best Practices list might help as well.

Cheers bzerangue, already found a few things to improve upon. Most importantly using a variable to store the data set….

Sorry for my late reply, sunday became an endless rush to the finish line for a project.

Are you sure my templates gave that result? Because this is what I get with your example XML (using TestXSLT on my Mac):

<ul>
    <li>a</li>
    <li>d</li>
    <li>g</li>
</ul>
<ul>
    <li>b</li>
    <li>e</li>
    <li>h</li>
</ul>
<ul>
    <li>c</li>
    <li>f</li>
    <li>i</li>
</ul>

And I think that was what you were trying to do? And yes, my solution is less verbose, but I also feel it’s more readable and more in the spirit of XSLT (not procedural). Although in my concise example you do need to understand the modulus operator :)

(Also, of course you should avoid using tables in this case, which doesn’t seem to be tabular data.)

I get exactly what you posted and I need this:

<ul>
    <li>a</li>
    <li>b</li>
    <li>c</li>
</ul>
<ul>
    <li>d</li>
    <li>e</li>
    <li>f</li>
</ul>
<ul>
    <li>g</li>
    <li>h</li>
    <li>i</li>
</ul>

Should have just listed it as xml instead of trying to “draw” the the actual list setup.

edit: and you’re right a table would be the wrong markup for this kind of content, but it would be the easy and clean xslt transform ;)

I was trying to think of a solution as froded has done too, but wouldn’t have got anything better…

What numbers have you worked with here, how many <ul> elements to limit to, filled evenly with <li> elements or how many <li> elements a <ul> should contain, creating as many <ul> elements as needed?

I have to come up with a similar solution for a portfolio I have to build, and may save time here ;)

Ah, I see! But you could still use the principle of my solution, just instead of the modulus operator you’d use something like this:

<xsl:variable name="num-items" select="count(menus/entry)" />
<xsl:variable name="max-per-column" select="ceiling($num-items div 3)" />
<ul>
    <xsl:apply-templates select="menus/entry[ceiling(position() div $max-per-column) = 1]" />
</ul>
<ul>
    <xsl:apply-templates select="menus/entry[ceiling(position() div $max-per-column) = 2]" />
</ul>
<ul>
    <xsl:apply-templates select="menus/entry[ceiling(position() div $max-per-column) = 3]" />
</ul>

Might be improvements to this, I need to get some sleep soon, but it’s an simple idea based on my original concept.

Edit: Slightly less computationally intensive would be to calculate the min and max positions for items in each column and just use less/greater than in the XPath conditions. Then you’d avoid a division for each node test.

Froded that last solution might work very well. I will have to test that tomorrow.

@designermonkey It’s a bit of both really. But initially it was a set amount of UL’s and the li’s divided evenly (or close to evenly), and that is what I’ll be restraining it to in the first edition of the site.

I promised it Live Nov. 1st but that is 4 minutes ago, and it’s not even close to public display cries I just hate when what you think is a carefully thought out data structure blows up in your head because you missed one little detail :-(

@Aalandriel - I’m getting this from your code…

<ul>
  <li>a</li>
  <li>b</li>
  <li>c</li>
</ul>
<ul>
  <li>d</li>
  <li>e</li>
  <li>f</li>
</ul>
<ul>
  <li>g</li>
  <li>h</li>
  <li>i</li>
</ul>

from your XML…

<?xml version="1.0" encoding="utf-8"?>
<data>
<menus>
  <entry>
    <uk-menu>a</uk-menu>
  </entry>
  <entry>
    <uk-menu>b</uk-menu>
  </entry>
  <entry>
    <uk-menu>c</uk-menu>
  </entry>
  <entry>
    <uk-menu>d</uk-menu>
  </entry>
  <entry>
    <uk-menu>e</uk-menu>
  </entry>
  <entry>
    <uk-menu>f</uk-menu>
  </entry>
  <entry>
    <uk-menu>g</uk-menu>
  </entry>
  <entry>
    <uk-menu>h</uk-menu>
  </entry>
  <entry>
    <uk-menu>i</uk-menu>
  </entry>
</menus>
</data>

and your XSLT…

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

<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>

<xsl:template match="data">
    <xsl:call-template name="ul-list" />
</xsl:template>

<xsl:template name="ul-list">
    <xsl:param name="count-ul" select="0" />      
    <ul>
        <xsl:call-template name="list-items">
            <xsl:with-param name="list-number" select="$count-ul" />
        </xsl:call-template>
    </ul>
    <xsl:if test="$count-ul &lt; 2" >
        <xsl:call-template name="ul-list" >         
            <xsl:with-param name="count-ul" select="$count-ul + 1" />       
        </xsl:call-template>
    </xsl:if>
</xsl:template>

<xsl:template name="list-items">
    <xsl:param name="count-li" select="1" />
    <xsl:param name="list-number" />
    <xsl:variable name="total-list" select="count(menus/entry/uk-menu)"/>

    <li><xsl:value-of select="/data/menus/entry[$count-li + ($list-number * ceiling($total-list div 3))]/uk-menu" /></li>

    <xsl:if test="$count-li &lt; ceiling($total-list div 3) ">
        <xsl:call-template name="list-items" >
            <xsl:with-param name="count-li" select="$count-li + 1" />
            <xsl:with-param name="list-number" select="$list-number" />
        </xsl:call-template>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

Yes? My XML does the job, as stated in the first post. I was just wondering if there was a better way of doing it as my version seemed very verbose even for xslt.

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details