Search

Ok this is a bit of a cop out but I can’t wrap my head around how, what I’d hoped would be a simple recursion template ( my first ! ). All the examples I’m reading seem to be complicated math recursions and I just want to have a switch to change the template output.

I could be completely wrong and don’t expect someone to provide a bang on solution but a nudge in the right direction would help.

I have a list of images I want to output these in an unordered list, but every fourth image I want to break it into a new unordered list, and closing the list if its not the fourth but is the last. So for example:

<ul>
<li><img></li>
<li><img></li>
<li><img></li>
</ul>

<ul>
<li><img></li>
<li><img></li>
<li><img></li>
</ul>

<ul>
<li><img></li>
</ul>

Sample XML:

        <range-images items="16">
            <item id="159">
                <image size="4.02 MB" path="/uploads" type="image/jpeg">
                    <filename>clyde-2-seat-sofa-1-arm-fabric-leather-mix.jpg</filename>
                    <meta creation="2010-08-06T12:55:22+01:00" width="4368" height="2912" />
                </image>
            </item>
            <item id="208">
                <image size="291 KB" path="/uploads" type="image/jpeg">
                    <filename>clyde-compact-armchair.jpg</filename>
                    <meta creation="2010-08-09T16:34:08+01:00" width="945" height="773" />
                </image>
            </item>

I’ve cobbled the following together from a book and some online stuff but struggling to put all the required conditionals in it, to check for the first to build the first UL, then each fourth and or last to close the UL

<xsl:template match="range-images/item" name="productImages">   
    <xsl:param name="counter" select="1" />

    <xsl:if test="$counter &lt; {../items}">
                <li>
                <a href="{$root}/image/1/500/0/uploads/{image/filename}">
                    <img src="{$root}/image/2/150/150/5/uploads/{image/filename}" />
                </a>
            </li>   
        <xsl:call-template name="productImages">
            <xsl:param name="counter" select="{$counter + 1}" />
        </xsl:call-template>
    </xsl:if>

</xsl:template>

I’ve got a bit of a procedural mentality having done little things like this inline in php before. Sorry to chuck this out there but am quite tired and frustrated :(

@Ryu - have you tried this video tutorial from Allen Chang on Understanding Recursion?

I was thinking this through the other day, but haven’t actually done it yet. I have exactly the same requirement myself. I built a PHP photo-app that I need to port over that did this beautifully…

I think it is something like

<xsl:template match="range-images/item" name="productImages">
    <xsl:param name="counter" select="1" />
    <xsl:param name="split" select="4" />
    <xsl:choose>
        <!-- First Node -->
        <xsl:when test="position() = 1">
            <ul>
                <li><xsl:value-of select="" /></li>
            <xsl:call-template name="productImages">
                <xsl:with-param name="counter" select="$counter + 1" />
            </xsl:call-template>
        </xsl:when>
        <!-- Last Node -->
        <xsl:when test="position() = last()">
                <li><xsl:value-of select="" /></li>
            </ul>
        </xsl:when>
        <!-- Node is not first and is a fourth Node -->
        <xsl:when test="not(position() = 1) and position() mod $split = 0">
                <li><xsl:value-of select="" /></li>
            </ul>
            <ul>
            <xsl:call-template name="productImages">
                <xsl:with-param name="counter" select="$counter + 1" />
            </xsl:call-template>
        </xsl:when>
        <!-- Any other Node -->
        <xsl:otherwise>
                <li><xsl:value-of select="" /></li>
            <xsl:call-template name="productImages">
                <xsl:with-param name="counter" select="$counter + 1" />
            </xsl:call-template>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Basically, the mod maths will return zero for every fourth node. My only concern is that mod test, as I’m half asleep and can’t remember how to do it.

Give it a go and let me know? I haven’t got a setup to test it on…

Edit: cleaned up the code…

Thanks for your response guys @bzerangue video was helpful in getting the fundamentals thanks, didn’t know that existed.

@designermonkey - Your solution, in principle is exactly what I’m needing, few things aren’t quite working. It doesn’t seem to like closing the UL tag in another < when > conditional.

Secondly I’m getting a bit confused as to when to use apply-template and template-match, as when I’m matching an xpath to a template its recursing that template automatically anyway. However when I use call-template instead, I can get the recursion working but it only ever selects the first node, so postion = first () is always true, when I swap out position for an $items parameter instead ( a count of all the nodes ), I can get the fundamentals of this working but it only ever pulls in attributes from the first node again. I appreciate I’m probably mis understanding some of the fundamentals of xslt and thank any one for their patience in helping me.

I’ve stripped it back to get the fundementals working but as mentioned its only ever returning values for the first node.

    <xsl:template name="productImages">
    <xsl:param name="counter" select="1" />
    <xsl:param name="split" select="4" />
    <xsl:param name="items" select="0" />
        <li>

            <p><xsl:value-of select="$items" /> / <xsl:value-of select="$counter" /></p>

            <a href="{$root}/image/1/500/0/uploads/{range-images/item/image/filename}">
                <img src="{$root}/image/2/150/150/5/uploads/{range-images/item/image/filename}" />
            </a>

        </li>

        <xsl:if test="$counter != $items">
            <xsl:call-template name="productImages">
                <xsl:with-param name="counter" select="$counter + 1" />
                <xsl:with-param name="split" select="4" />
                <xsl:with-param name="items" select="$items" />                 
            </xsl:call-template>
        </xsl:if>

</xsl:template>

And this is called:

    <xsl:template match="ranges/entry" mode="contentDetails">
<xsl:call-template name="productImages" >
                                        <xsl:with-param name="counter" select="1" />
                                        <xsl:with-param name="split" select="4" />
                                        <xsl:with-param name="items" select="range-images/@items" />
                                    </xsl:call-template>
</xsl:template>

The XML is the same as above.

I’m wondering whether I’d be better with apply-templates and just a few < xsl:if > statements, gonna take a break and try and read around it a bit more.

Quick reply, but the reason you’re only getting the first is that you don’t specify which item you want in your XPaths. Instead of {range-images/item/image/filename} you should be using the $counter variable as a positional XPath condition like this: {range-images/item[$counter]/image/filename}. item[$counter] is the same as saying item[position() = $counter].

Hope that explains why you only get the first item. But it won’t solve everything for you. Since XSLT is XML, you can’t start/end the

Secondly I’m getting a bit confused as to when to use apply-template and template-match, as when I’m matching an xpath to a template its recursing that template automatically anyway.

@Ryu - For the fundamentals of apply-templates, you might look at Allen’s video on the Basics of Apply Templates. Or you can get some basic XSLT tutorials at W3Schools.

I would say…

<xsl:apply-templates select="range-images/item[position() mod 3 = 1]" mode="groupleader"/>

<xsl:template match="range-images/item" mode="groupleader">
    <ul>
        <xsl:apply-templates select=". | following-sibling::item[position() &lt;= 2]" mode="groupentity" />
    </ul>
</xsl:template>

<xsl:template match="range-images/item" mode="groupentity">
    <li><xsl:value-of select="image/filename" /></li>
</xsl:template>

Thanks for taking the time phoque, i’ll test it out… you say your not happy with yours… check mine out as it stands:

    <xsl:template match="range-images/item" name="productImages">
    <xsl:param name="counter" select="1" />
    <xsl:param name="end" select="4" />
    <xsl:param name="start" select="1" />       
    <xsl:param name="items" select="0" />

        <ul class="group">
            <xsl:call-template name="productImageItem">
                <xsl:with-param name="startItem" select="$start"/>
                <xsl:with-param name="endItem" select="$end"/>
                <xsl:with-param name="itemCount" select="$items"/>      
            </xsl:call-template>
        </ul>

        <xsl:if test="$counter &lt; $items">
            <xsl:call-template name="productImages">
                <xsl:with-param name="counter" select="$counter + 4" />
                <xsl:with-param name="end" select="$end + 4" />
                <xsl:with-param name="start" select="$start + 4" /> 
                <xsl:with-param name="items" select="$items"/>                  
            </xsl:call-template>
        </xsl:if>

    </xsl:template>

    <xsl:template name="productImageItem">      
        <xsl:param name="startItem" select="1"/>
        <xsl:param name="endItem" select="4"/>
        <xsl:param name="itemCount" select="0" />

            <xsl:if test="$startItem &lt;= $itemCount">
                <li>
                    <p><xsl:value-of select="$startItem" /></p>
                    <a href="{$root}/image/1/500/0/uploads/{range-images/item[$startItem]/image/filename}">
                        <img src="{$root}/image/2/75/75/5/uploads/{range-images/item[$startItem]/image/filename}" />
                    </a>
                </li>
            </xsl:if>

        <xsl:if test="$startItem != $endItem">
            <xsl:call-template name="productImageItem">
                <xsl:with-param name="startItem" select="$startItem + 1"/>
                <xsl:with-param name="endItem" select="$endItem"/>
                <xsl:with-param name="itemCount" select="$itemCount" />              
            </xsl:call-template>
        </xsl:if>       

    </xsl:template>

Invoked by:

                                <xsl:call-template name="productImages" >
                                <xsl:with-param name="counter" select="1" />
                                <xsl:with-param name="split" select="4" />
                                <xsl:with-param name="items" select="range-images/@items" />
                                <xsl:with-param name="start" select="1" />
                            </xsl:call-template>

lol! :( it pretty much works, but .. god… my eyes.

I’m not happy because it’s behaving differently in different environments… In theory it should work fine though ;-)

Ah, I’m stupid. XSLT starts counting at 1, not at 0. I’ve changed it and it’s working fine now.

@phoque, did I have the same problem using position() mod 4 = 0 then? I’m still trying to get my head around modular maths. Does yours work now? I’m heading down the road that @Ryu is going down and it is too much code IMHO, so if yours works, brilliant!

I also didn’t realise that I can’t open an element in one where clause an close it in another… I had my procedural hat on, my declarative hat doesn’t quite fit properly yet ;o)

@phoque, did I have the same problem using position() mod 4 = 0 then?

Yes. position() always starts at 1, so comparing it to 0 will make it skip the first 3 items as the loop starts with 1 mod 4 = 1.

Does yours work now?

I’ve tested it, yes.

I see… Does yours work then? I can’t test it as I haven’t put the site together yet…

@designermonkey: “modular maths”, now that sounds crazy :) “mod” is short for “modulus”, and the basic principle of the operator should be quite simple to grok: If N mod M gives 0, that means N is evenly divided by M. So N mod 2 yields 0 for any even number (which is every other whole number).

More “formally”: For N mod M, the result is the remainder after subtracting from M the highest multiple of N that is still less than or equal to M. Ok, still sounds complicated, but think of it as “inverse division with whole numbers”.

M / N: 6 / 4 = 1.5 = 1 whole number + .5 fraction

So 4 can only be multiplied 1 whole time without getting bigger than 6. Thus:

N mod M: 4 mod 6 = 6 - (4 * 1) = 2

Did this help anyone in any way? Maybe I should change my name to mathmonkey :)

Haha, thanks for that mathmonkey! Yeah, I realised the modular thing was wrong… I have weird muscle memory in my fingers, it’s like they have tourette’s syndrome and type what they want!

I’ve been doing some reading, it’s so much more simple than first meets the eye…

@phoque, I’ve had a play with this and it works great!

Sorry I should have replied earlier @phoque this is brilliant, just when I’m getting frustrated with the “limitations” of xslt you prove that if you know what you’re doing its awesome lol.

Still trying to figure out how your magic works entirely but thats half the fun I guess :)

The first apply-templates calls each item at position 1,4,7 etc etc and then the second merges that with the next two following-siblings.

Pretty clever simple stuff!

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