I'm trying to build a navigation that in theory could recurse infinitely with XSL. Unfortunately my skills in this arena are still being built. Can anyone tell me where I'm going wrong with this code?
The kicker here is that the first 2 levels of the nav are somewhat unique (class names and such) but after level 3 the nav is pretty much the same elements being nested over and over.
To Update: There's no recursion after nav level 2, the output is non existent after level 2, and I can't figure out how to apply the recursion. I feel like this should be easier but my skills in XSL are not that great.
HTML (What we need it to produce):
<ul class="nav-l1">
<li class="nav-active"><a href="nav2.html" class="nav-item trigger-chan nav-next"><span><span class="trigger-cntr">First Level Parent</span></span></a>
<ul class="nav-l2 nav-hidden">
<li class="nav-active"><a href="nav2.html" class="nav-item"><span>Overview</span></a></li>
<li><a href="#" class="nav-item trigger-chan nav-next"><span><span class="trigger-cntr">Second Level Parent</span></span></a>
<ul class="nav-ls nav-hidden">
<li><a href="#" class="nav-item"><span>Third Level</span></a></li>
<li><a href="#" class="nav-item"><span>Third Level</span></a></li>
</ul>
</li>
<li><a href="#" class="nav-item"><span>Second Level</span></a></li>
</ul>
</li>
<li><a href="#" class="nav-item trigger-chan nav-next"><span><span class="trigger-cntr">First Level Parent</span></span></a>
<!-- 2nd level of navigation. -->
<ul class="nav-l2 nav-hidden">
<li><a href="#" class="nav-item"><span>Overview</span></a></li>
<li><a href="#" class="nav-item trigger-chan nav-next"><span><span class="trigger-cntr">Second Level Parent</span></span></a>
<!-- 3rd level of navigation. -->
<ul class="nav-ls nav-hidden">
<li><a href="#" class="nav-item"><span>Third Level</span></a></li>
<li><a href="#" class="nav-item"><span>Third Level</span></a></li>
<li><a href="#" class="nav-item"><span>Third Level</span></a></li>
</ul>
</li>
<li><a href="#" class="nav-item"><span>Second Level</span></a></li>
<li><a href="#" class="nav-item"><span>Second Level</span></a></li>
</ul>
</li>
</ul>
Sample XML (The format of the initial data feed):
<data>
<folders level="1">
<folder clickable="Y" url="/lorem/ipsum.html" name="Lorem Ipsum"/>
<folder clickable="Y" url="/level/one.html" name="Level One"/>
<folder clickable="Y" url="/foo/bar.html" name="Foo Bar">
<folders level="2">
<folder clickable="Y" url="/level/two.html" name="Level two"/>
<folder clickable="Y" url="/child/item.html" name="Child Item">
<folders level="3">
<folder clickable="Y" url="/child/child/item.html" name="Child's Child Item">
<folders level="4">
<folder clickable="Y" url="/destiny/child/item.html" name="Destiny's Child"/>
</folders>
</folder>
</folders>
</folder>
</folders>
</folder>
</folders>
</data>
XSL (What we need to transform the XML):
<?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" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="/data/folders">
<!-- NAVIGATION BEGINS HERE -->
<ul class="nav-l1">
<!-- LEVEL 1 -->
<xsl:for-each select="/data/folders[@level=1]/folder[not (@clickable ) or @clickable ='Y' ]">
<li>
<xsl:if test="@selected='Y'">
<xsl:attribute name="class">nav-active</xsl:attribute>
</xsl:if>
<a href="{@url}" class="nav-item">
<xsl:choose>
<xsl:when test="child::*">
<xsl:attribute name="class">trigger-chan nav-next</xsl:attribute>
<span>
<span class="trigger-cntr">
<xsl:value-of select="@name"/>
</span>
</span>
</xsl:when>
<xsl:otherwise>
<span><xsl:value-of select="@name" /></span>
</xsl:otherwise>
</xsl:choose>
</a>
<!-- LEVEL 2 -->
<xsl:choose>
<xsl:when test="child::*">
<ul class="nav-l2 nav-hidden">
<xsl:for-each select="folders[@level=2]/folder[not (@clickable ) or @clickable ='Y' ]">
<li>
<xsl:if test="@selected='Y'">
<xsl:attribute name="class">nav-active</xsl:attribute>
</xsl:if>
<a href="{@url}" class="nav-item">
<xsl:choose>
<xsl:when test="child::*">
<xsl:attribute name="class">trigger-chan nav-next</xsl:attribute>
<span>
<span class="trigger-cntr">
<xsl:value-of select="@name"/>
</span>
</span>
</xsl:when>
<xsl:otherwise>
<span><xsl:value-of select="@name" /></span>
</xsl:otherwise>
</xsl:choose>
</a>
<!-- LEVEL 3 and beyond ... -->
<xsl:apply-templates />
</li>
</xsl:for-each>
</ul>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</li>
</xsl:for-each>
</ul>
</xsl:when>
</xsl:choose>
</xsl:template>
<!-- Infinity and beyond (=> Level 3) -->
<xsl:template name="folder">
<ul class="nav-ls nav-hidden">
<li>
<xsl:if test="@selected='Y'">
<xsl:attribute name="class">nav-active</xsl:attribute>
</xsl:if>
<a href="{@url}" class="nav-item">
<xsl:choose>
<xsl:when test="child::*">
<xsl:attribute name="class">trigger-chan nav-next</xsl:attribute>
<span>
<span class="trigger-cntr">
<xsl:value-of select="@name"/>
</span>
</span>
</xsl:when>
<xsl:otherwise>
<span><xsl:value-of select="@name" /></span>
</xsl:otherwise>
</xsl:choose>
</a>
<xsl:apply-templates select="folders[@level>3]/folder" />
</li>
</ul>
</xsl:template>
</xsl:stylesheet>
Any help is greatly appreciated.
Thanks!
Your XSLT is very "pull" focused rather than "push" focused and could use some re-engineering to be more functional (in the functional programming sense).
But the main error is here:
This is declaring a "named template" but it's never called. Not a bad thing, as name/call-templates should be avoided where possible.
Change it to the below and it should work as expected when using the
apply-templates
you already have in the XSLT.edit: A push style approach
This might not be perfect, but a few things: * You skip over
<folders>
entirely, when they are the list, where as the<folder>
s are the list items. * You appear to duplicate the item code for levels 1, 2 and 3. * You only ever make one item in the level 3 and above, all others would be ignored?Notice below, that I've defined how I want
<folders>
to be rendered, based on level, and how<folder>
s are rendered independently.Notice how the above template is shorter than your initial one, as repeated elements are removed. All
<folder>
s render the same, so only one template is needed. In fact, the<folder>
s don't need to know their level, only the parent<folders>
do, so that logic is moved to the correct template.People who are new to XSLT often overlook how
apply-templates
can be used effectively to define recursion and allow very clear separations between elements. When creating templates, try and focus on how this particular element should be represented, and knowing how its child elements may be required. Then, focus on a new template for the child elements and how those should be rendered and so on.Adding on to the above, always be on the look out for
../
in@select
s or<value-of>
s as it implies that your logic is dependant on elements that are outside the current "scope". Not always bad, but something to be aware of.Try and minimise
for-each
where possible and see if they can be rewritten to useapply-templates
, and unless absolutely required for some odd reason try and never usecall-templates
as they are less functional.