Reputation: 752
I am trying to represent an XML structure like a directory tree. It's a navigation editor, hence the element names links (the group), and link (the item within the group). So I have different images: i-node, l-node, t-node and e-node images (where e is empty the others are the dotted lines). The HTML is supposed to be a standard table. Each link is in its new tr.
So far, so good...
But:
In the first case the images are as expected but in the second one you see where I got stuck: as soon as there is an L node (last element on that level) all the subsequent links should show an empty-node image for that level - but instead there's an I-node right now. So I'd need some advice on how to achieve this. Would I need to check the XML for its preceding siblings? Or can I do this by passing another parameter to the recursive call? Any help appreciated... I'd rather do it properly than starting to fix up the HTML with JS ;)
The XML:
<navigation>
<links>
<link>
<text>Google</text>
<links>
<link>
<text>Yahoo</text>
</link>
<link>
<text>Amazon</text>
</link>
</links>
</link>
<link />
<link />
<link />
...
</links>
</navigation>
The XSL:
<xsl:template match="navigation">
<table>
<xsl:for-each select="links">
<xsl:apply-templates select=".">
<xsl:with-param name="level" select="'1'" />
<xsl:with-param name="children" select="count(*[links/link])" />
</xsl:apply-templates>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="links">
<xsl:param name="level" />
<xsl:param name="children" />
<xsl:variable name="count" select="count(link)" />
<xsl:for-each select="link">
<tr>
<xsl:attribute name="class">level<xsl:value-of select="$level" /></xsl:attribute>
<td>
<xsl:call-template name="nodeimage.loop">
<xsl:with-param name="level" select="$level"></xsl:with-param>
<xsl:with-param name="position" select="position()"></xsl:with-param>
<xsl:with-param name="count" select="$count" />
</xsl:call-template>
<strong><xsl:value-of select="text" /></strong>
</td>
</tr>
<!-- if a link has children... -->
<xsl:if test="*[link]">
<xsl:apply-templates select="links">
<xsl:with-param name="level" select="$level + 1"/>
<xsl:with-param name="children" select="count(*[link])"/>
</xsl:apply-templates>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="nodeimage.loop">
<xsl:param name="level"/>
<xsl:param name="position"/>
<xsl:param name="count"/>
<!-- debug this
<xsl:value-of select="$position" />of<xsl:value-of select="$count" />, level<xsl:value-of select="$level" />
-->
<xsl:if test="$level = 1">
<xsl:choose>
<xsl:when test="$position = $count"><!-- last one on same level -->
<img class="textmiddle" src="/images/backend/l-node.png" />
</xsl:when>
<xsl:otherwise>
<img class="textmiddle" src="/images/backend/t-node.png" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<xsl:if test="$level > 1">
<img class="textmiddle" src="/images/backend/i-node.png" />
</xsl:if>
<xsl:if test="$level > 1">
<xsl:call-template name="nodeimage.loop">
<xsl:with-param name="level">
<xsl:value-of select="$level - 1"/>
</xsl:with-param>
<xsl:with-param name="position">
<xsl:value-of select="$position"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
Upvotes: 0
Views: 526
Reputation: 44
These templates (written for XSLT 1.0) should be able to render your tree structure - they're not using recursion, but rather match-templates for processing the general structure, and then a moded template for the images (by processing each ancestor of the context node):
<xsl:template match="navigation">
<table>
<thead>
<xsl:call-template name="headers" />
</thead>
<tbody>
<xsl:apply-templates select="links" />
</tbody>
</table>
</xsl:template>
<xsl:template match="link">
<xsl:variable name="chain" select="ancestor-or-self::link" />
<tr class="level{count($chain)}">
<td>
<xsl:apply-templates select="$chain" mode="tracks">
<xsl:with-param name="context" select="." />
</xsl:apply-templates>
<strong><xsl:value-of select="text" /></strong>
</td>
<td><button>Append new link</button></td>
<td><xsl:value-of select="url" /></td>
</tr>
<xsl:apply-templates select="links" />
</xsl:template>
<xsl:template match="link" mode="tracks">
<xsl:param name="context" />
<xsl:variable name="isSame" select="generate-id(.) = generate-id($context)" />
<xsl:variable name="pos" select="count(preceding-sibling::link)" />
<xsl:variable name="isLast" select="not(following-sibling::link)" />
<xsl:variable name="type">
<xsl:choose>
<xsl:when test="$isSame">
<xsl:if test="(following::link or links/link) and not($isLast)">t</xsl:if>
<xsl:if test="$isLast">l</xsl:if>
</xsl:when>
<xsl:when test="$isLast and links/link">e</xsl:when>
<xsl:when test="following::link and links/link">i</xsl:when>
<xsl:when test="not(following::link)">e</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="node-type">
<xsl:with-param name="type" select="$type" />
</xsl:call-template>
</xsl:template>
<xsl:template name="node-type">
<xsl:param name="type" select="'t'" />
<img class="textmiddle" src="/images/backend/{$type}-node.png" />
</xsl:template>
<xsl:template name="headers">
<tr>
<th>Link</th>
<th> </th>
<th>URL</th>
</tr>
</xsl:template>
(Note that applying templates to the links
nodes will use a built-in template to subsequently process the link
nodes if no template matches links
.)
Upvotes: 1