XSLT: Flat XML to Nested HTML List
I'm new to XSLT. I know I need to use xsl:for-each-group, but I can't figure out anything other than a basic list. Would some sort of recursion work better? Any XSLT 1.0 or 2.0 solution would be fine.
Below is the example XML. Note the most important attribute for organizing data into a tree structure is @taxonomy. Other attributes @taxonomyName and @level are provided as optional helper attributes.
<?xml version="1.0" encoding="utf-8"?>
<documents>
<document level="0" title="Root document test" taxonomy="" taxonomyName="" />
<document level="1" title="Level one document test" taxonomy="\CategoryI" taxonomyName="CategoryI" />
<document level="1" title="Level one document test #2" taxonomy="\CategoryII" taxonomyName="CategoryII" />
<document level="2" title="Level two document test" taxonomy="\CategoryII\SubcategoryA" taxonomyName="SubcategoryA" />
<document level="2" title="Level two document test #2" taxonomy="\CategoryII\SubcategoryA" taxonomyName="SubcategoryA" />
<document level="3" title="Level three document test" taxonomy="\CategoryII\SubcategoryA\Microcategory1" taxonomyName="Microcategory1" />
<document level="2" title="Level two, no level one test" taxonomy="\CategoryIII\SubcategoryZ" taxonomyName="SubcategoryZ" />
</documents>
Here's the expected output. (Please note that indenting is not necessary in the output. I've done it here for readability.)
<ul>
<li>Root document test</li>
<li>CategoryI
<ul>
<li>Level one document test</li>
</ul>
</li>
<li>CategoryII
<ul>
<li>Level one document test #2</li>
<li>SubcategoryA
<ul>
<li>Level two document test</li>
<li>Level two document test #2</li>
<li>Microcategory1
<ul>
<li>Level three document test</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>CategoryIII
<ul>
<li>SubcategoryZ
<ul>
<li>Level two, no subcategory test</li>
</ul>
</li>
</ul>
</li>
</ul>
Here's the best I can do.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:key name="contacts-by-taxonomy" match="document" use="@taxonomy" />
<xsl:template match="documents">
<ul开发者_运维百科>
<xsl:for-each-group select="document" group-by="@taxonomy">
<xsl:sort select="@taxonomy" />
<li>
<h3><xsl:value-of select="current-grouping-key()"/></h3>
<ul>
<xsl:for-each select="current-group()">
<li><xsl:value-of select="@title"/></li>
</xsl:for-each>
</ul>
</li>
</xsl:for-each-group>
</ul>
</xsl:template>
</xsl:stylesheet>
I'll keep chugging away at it, but would be eternally grateful if someone could throw me a life jacket. Thanks!
OK, here's my solution at last. :-) Basically it recurses through the tree, and at each level, it does a for-each-group group-by="the next level of @taxonomy".
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" indent="yes" />
<xsl:template match="documents">
<ul>
<xsl:call-template name="tree-depth-n">
<xsl:with-param name="population" select="document"/>
<xsl:with-param name="depth" select="0"/>
<xsl:with-param name="taxonomy-so-far" select="''"/>
</xsl:call-template>
</ul>
</xsl:template>
<!-- This template is called with a population that are all descendants
of the same ancestors up to level n. -->
<xsl:template name="tree-depth-n">
<xsl:param name="depth" required="yes"/>
<xsl:param name="population" required="yes"/>
<xsl:param name="taxonomy-so-far" required="yes"/>
<!-- output a <li> for each document that is a leaf at this level,
and a <li> for each sub-taxon of this level. -->
<xsl:for-each-group select="$population"
group-by="string(tokenize(@taxonomy, '\\')[$depth + 2])">
<xsl:sort select="@taxonomy" />
<xsl:choose>
<!-- process documents at this level. -->
<xsl:when test="current-grouping-key() = ''">
<xsl:for-each select="current-group()">
<li><xsl:value-of select="@title"/></li>
</xsl:for-each>
</xsl:when>
<!-- process subcategories -->
<xsl:otherwise>
<li>
<h3><xsl:value-of select="current-grouping-key()"/></h3>
<ul>
<!-- recurse -->
<xsl:call-template name="tree-depth-n">
<xsl:with-param name="population" select="current-group()"/>
<xsl:with-param name="depth" select="$depth + 1"/>
<xsl:with-param name="taxonomy-so-far"
select="concat($taxonomy-so-far, '\\', current-grouping-key())"/>
</xsl:call-template>
</ul>
</li>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
With the given input, the output is:
<ul>
<li>Root document test</li>
<li>
<h3>CategoryI</h3>
<ul>
<li>Level one document test</li>
</ul>
</li>
<li>
<h3>CategoryII</h3>
<ul>
<li>Level one document test #2</li>
<li>
<h3>SubcategoryA</h3>
<ul>
<li>Level two document test</li>
<li>Level two document test #2</li>
<li>
<h3>Microcategory1</h3>
<ul>
<li>Level three document test</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<h3>CategoryIII</h3>
<ul>
<li>
<h3>SubcategoryZ</h3>
<ul>
<li>Level two, no level one test</li>
</ul>
</li>
</ul>
</li>
</ul>
Which I believe is what you wanted. (I put <h3>
s in there as you did in your XSL attempt, for the category names and not for the document titles.)
精彩评论