Recursive xslt template for parent data

I am trying to get around xslt. A few questions here are about stackoverflow help ( XSLT templates and recursion as well as XSLT for each loop, variable based filter ), but I'm still a bit puzzled. I guess I'm β€œthinking of a template as a function” ( https://stackoverflow.com/questions/506348/how-do-i-know-my-xsl-is-efficient-and-beautiful )

Anyway ... my details

<Entities> <Entity ID="8" SortValue="0" Name="test" ParentID="0" /> <Entity ID="14" SortValue="2" Name="test2" ParentID="8" /> <Entity ID="16" SortValue="1" Name="test3" ParentID="8" /> <Entity ID="17" SortValue="3" Name="test4" ParentID="14" /> <Entity ID="18" SortValue="3" Name="test5" ParentID="0" /> </Entities> 

What I would like as an output is basically a "treeview"

 <ul> <li id="entity8"> test <ul> <li id="entity16"> test3 </li> <li id="entity14"> test2 <ul> <li id="entity17"> test4 </li> </ul> </li> </ul> </li> <li id="entity18"> test5 </li> </ul> 

XSLT I'm still mistaken in that he definitely "thinks of templates as a function" and also throws a StackOverflowException (:-)) when executed

 <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"> <xsl:output method="html" indent="yes"/> <xsl:template match="Entities"> <ul> <li> <xsl:value-of select="local-name()"/> <xsl:apply-templates/> </li> </ul> </xsl:template> <xsl:template match="//Entities/Entity[@ParentID=0]"> <xsl:call-template name="recursive"> <xsl:with-param name="parentEntityID" select="0"></xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template name="recursive"> <xsl:param name="parentEntityID"></xsl:param> <xsl:variable name="counter" select="//Entities/Entity[@ParentID=$parentEntityID]"></xsl:variable> <xsl:if test="count($counter) > 0"> <xsl:if test="$parentEntityID > 0"> </xsl:if> <li> <xsl:variable name="entityID" select="@ID"></xsl:variable> <xsl:variable name="sortValue" select="@SortValue"></xsl:variable> <xsl:variable name="name" select="@Name"></xsl:variable> <xsl:variable name="parentID" select="@ParentID"></xsl:variable> <a href=?ID={$entityID}&amp;ParentEntityID={$parentID}"> <xsl:value-of select="$name"/> </a> <xsl:call-template name="recursive"> <xsl:with-param name="parentEntityID" select="$entityID"></xsl:with-param> </xsl:call-template> </li> </xsl:if> </xsl:template> </xsl:stylesheet> 

I know how to do this with code, no problem. This time, however, I am looking for a solution in xslt, and any help would be very appreciated for this.

+4
source share
2 answers

Although call-template and named templates are a very useful feature of the language, if you prefer apply-templates , this may be a sign that you still think in functions rather than in templates. This is especially true if the first thing you do in a named template is to select the set of nodes that it will run on.

Here is a simple version of what you are trying to do.

 <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" /> <xsl:template match="/"> <ul> <xsl:apply-templates select="Entities/Entity[@ParentID=0]" /> </ul> </xsl:template> <xsl:template match="Entity"> <li> <xsl:value-of select="@Name" /> <xsl:apply-templates select="../Entity[@ParentID=current()/@ID]" /> </li> </xsl:template> </xsl:stylesheet> 

Please note: there is no need for a counter, since the value "parent" provides the necessary context.

Also note that all Entities behave the same, regardless of where they are in the tree, they contain their @Name value and apply the template to any Entity whose @parentID matches the @ID of the current level.

+6
source

The correct and effective solution (the currently accepted answer does not create the desired ul embedded elements:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="kChildren" match="Entity" use="@ParentID"/> <xsl:template match="/*[Entity]"> <ul> <xsl:apply-templates select="key('kChildren', '0')"> <xsl:sort select="@SortValue" data-type="number"/> </xsl:apply-templates> </ul> </xsl:template> <xsl:template match="Entity"> <li id="entity{@ID}"> <xsl:value-of select="concat('&#xA; ', @Name, '&#xA;')"/> <xsl:if test="key('kChildren', @ID)"> <ul> <xsl:apply-templates select="key('kChildren', @ID)"> <xsl:sort select="@SortValue" data-type="number"/> </xsl:apply-templates> </ul> </xsl:if> </li> </xsl:template> </xsl:stylesheet> 

When this conversion is applied to the provided XML document:

 <Entities> <Entity ID="8" SortValue="0" Name="test" ParentID="0" /> <Entity ID="14" SortValue="2" Name="test2" ParentID="8" /> <Entity ID="16" SortValue="1" Name="test3" ParentID="8" /> <Entity ID="17" SortValue="3" Name="test4" ParentID="14" /> <Entity ID="18" SortValue="3" Name="test5" ParentID="0" /> </Entities> 

the desired, correct result is output:

 <ul> <li id="entity8"> test <ul> <li id="entity16"> test3 </li> <li id="entity14"> test2 <ul> <li id="entity17"> test4 </li> </ul> </li> </ul> </li> <li id="entity18"> test5 </li> </ul> 
+1
source

All Articles