I'm trying to generate a multi level nested html list from xml/xsl.
For example, a preferred html output would be:
<ul>
<li>Level 1 - Item 1</li>
<ul>
<li>Level 2 - Item 1-1</li>
<li>Level 2 - Item 1-2</li>
</ul>
<li> Level 1 - Item 2</li>
<ul>
<li>Level 2 - Item 2-1
<ul>
<li>Level 3 - Item 2-1-1</li>
<li>Level 3 - Item 2-1-2</li>
<li>Level 3 - Item 2-1-3</li>
</ul>
</li>
<li>Level 2 - Item 2-2
<ul>
<li>Level 3 - Item 2-2-1</li>
<li>Level 3 - Item 2-2-2</li>
</ul>
</li>
</ul>
XML:
<doc>
<item>
<one>Level 1 - Item 1</one>
<two>Level 2 - Item 1-1</two>
<two>Level 2 - Item 1-2</two>
</item>
<item>
<one>Level 2 - Item 2</one>
<two>Level 2 - Item 2-1</two>
<three>Level 3 - Item 2-1-1</three>
<three>Level 3 - Item 2-1-2</three>
<three>Level 3 - Item 2-1-3</three>
<two>Level 2 - Item 2-2</two>
<three>Level 3 - Item 2-2-1</three>
<three>Level 3 - Item 2-2-2</three>
</item>
</doc>
My poor attempt XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="doc/item">
<li><xsl:value-of select="one" />
<ul>
<xsl:for-each select="two">
<li><xsl:value-of select="."/>
<xsl:for-each select="../three"><ul><li><xsl:value-of select="."/></li></ul></xsl:for-each>
</li>
</xsl:for-each>
</ul>
</li>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This is what I'm getting below... Notice that when there's a level 3 item then all of items have merged and then displaying under both.
<li>Level 1 - Item 1<ul>
<li>Level 2 - Item 1-1</li>
<li>Level 2 - Item 1-2</li>
</ul>
</li>
<li>Level 2 - Item 2<ul>
<li>Level 2 - Item 2-1<ul>
<li>Level 3 - Item 2-1-1</li>
</ul>
<ul>
<li>Level 3 - Item 2-1-2</li>
</ul>
<ul>
<li>Level 3 - Item 2-1-3</li>
</ul>
<ul>
<li>Level 3 - Item 2-2-1</li>
</ul>
<ul>
<li>Level 3 - Item 2-2-2</li>
</ul>
</li>
<li>Level 2 - Item 2-2<ul>
<li>Level 3 - Item 2-1-1</li>
</ul>
<ul>
<li>Level 3 - Item 2-1-2</li>
</ul>
<ul>
<li>Level 3 - Item 2-1-3</li>
</ul>
<ul>
<li>Level 3 - Item 2-2-1</li>
</ul>
<ul>
<li>Level 3 - Item 2-2-2</li>
</ul>
</li>
</ul>
</li>
Please provide me with 1.0 solutions and then of course show 2.0 examples to help others as well.
thank you!
Here is an XSLT 1.0 solution that works with your input XML.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://tempuri.org"
exclude-result-prefixes="my"
>
<xsl:output indent="yes" />
<!-- define which elements are where in the hierarchy -->
<my:level name="one" higher="" deeper="two,three" />
<my:level name="two" higher="one" deeper="three" />
<my:level name="three" higher="one,two" deeper="" />
<xsl:template match="doc">
<body>
<xsl:apply-templates mode="ul" select="item/*[1]" />
</body>
</xsl:template>
<xsl:template match="one|two|three" mode="ul">
<ul>
<xsl:apply-templates mode="li" select="." />
</ul>
</xsl:template>
<xsl:template match="one|two|three" mode="li">
<xsl:variable name="myName" select="name()" />
<xsl:variable name="myID" select="generate-id()" />
<!-- select the appropriate hierarchy info for this node -->
<xsl:variable name="level" select="
document('')/*/my:level[@name = $myName]
" />
<li>
<xsl:value-of select="." />
<!-- create <ul> if immediately follwing sibling is deeper -->
<xsl:apply-templates mode="ul" select="
following-sibling::*[1][contains($level/@deeper, name())]
" />
</li>
<!-- process contiguous following siblings of same level -->
<xsl:apply-templates mode="li" select="
following-sibling::*[name() = $myName][
generate-id(
preceding-sibling::*[contains($level/@higher, name())][1]/following-sibling::*[1]
)
= $myID
]
" />
</xsl:template>
</xsl:stylesheet>
Given the input document from your question, it produces this output:
<body>
<ul>
<li>Level 1 - Item 1
<ul>
<li>Level 2 - Item 1-1</li>
<li>Level 2 - Item 1-2</li>
</ul>
</li>
</ul>
<ul>
<li>Level 2 - Item 2
<ul>
<li>Level 2 - Item 2-1
<ul>
<li>Level 3 - Item 2-1-1</li>
<li>Level 3 - Item 2-1-2</li>
<li>Level 3 - Item 2-1-3</li>
</ul>
</li>
<li>Level 2 - Item 2-2
<ul>
<li>Level 3 - Item 2-2-1</li>
<li>Level 3 - Item 2-2-2</li>
</ul>
</li>
</ul>
</li>
</ul>
</body>
Frankly, I'm too tired right now to explain the solution in detail. I've left a few comments though. Suffice it to say that it is pretty complicated.
If your XML would look like this (i.e. properly nested):
<doc>
<item title="Level 1 - Item 1">
<item title="Level 2 - Item 1-1" />
<item title="Level 2 - Item 1-2" />
</item>
<item title="Level 2 - Item 2">
<item title="Level 2 - Item 2-1">
<item title="Level 3 - Item 2-1-1" />
<item title="Level 3 - Item 2-1-2" />
<item title="Level 3 - Item 2-1-3" />
</item>
<item title="Level 2 - Item 2-2">
<item title="Level 3 - Item 2-2-1" />
<item title="Level 3 - Item 2-2-2" />
</item>
</item>
</doc>
a solution that would produce the same HTML result as above would look like this:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output indent="yes" />
<xsl:template match="doc">
<body>
<xsl:for-each select="item">
<ul>
<xsl:apply-templates select="." />
</ul>
</xsl:for-each>
</body>
</xsl:template>
<xsl:template match="item">
<li>
<xsl:value-of select="@title" />
<xsl:if test="item">
<ul>
<xsl:apply-templates select="item" />
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With