[xsl] Constructing multi-level lists - any better than this?

Subject: [xsl] Constructing multi-level lists - any better than this?
From: Michael Müller-Hillebrand <mmh@xxxxxxxxxxxxx>
Date: Sat, 15 Sep 2007 18:43:31 +0200

I have a working stylesheet, but it uses modes when I think it could
be a bit slimmer and maybe more flexible.

The task is to create list containers (<ul>) around list elements in
a flat element tree. I tried to follow Jeni Tennison's advice for
constructing hierarchies <http://jenitennison.com/xslt/hierarchies-
out.xml> and also evaluated xsl:for-each-group, but the latter seems
not to work very well for a stylesheet in push mode (no changes to
the element order).

To correctly group the <li1>s and <li2>s (even in the third case) in
this example I successfully used the XSL below. I found no good
enough example in the FAQs, so I dare to ask, whether there is a more
elegant solution. (I simplified the case, in reality there are
multiple elements that are either level 1 or level 2 list elements,
and I do not use starts-with() to detect element names.)

Any advice is greatly appreciated!

- Michael M|ller-Hillebrand

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet version="2.0"
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" />

<!-- Root element -->
<xsl:template match="levels">
    <xsl:apply-templates />

<!-- Keys to identify list elements after first element -->
<xsl:key name="list12-other"
  match="*[self::*[starts-with(name(), 'li')]
    and preceding-sibling::*[1][starts-with(name(), 'li')]]"
      starts-with(name(), 'li')
      and not(preceding-sibling::*[1][starts-with(name(), 'li')])
    ][1])" />

<xsl:key name="list2-other"
    ][1])" />

<!-- List 1 Container-->
<xsl:template match="*[starts-with(name(), 'li')
  and not(preceding-sibling::*[1][starts-with(name(), 'li')])]"
  <ul level="1">
    <xsl:apply-templates mode="list1"
      select=". | key('list12-other', generate-id())" />
<!-- List 1 elements -->
<xsl:template match="li1" mode="list1">
  <li tag="{name()}" pos="{position()}">
    <xsl:apply-templates />

<!-- or List 2 Container-->
<xsl:template match="li2[not(preceding-sibling::*[1][self::li2])]"
  mode="list1" priority="1">
  <ul level="2">
    <xsl:apply-templates mode="list2"
      select=". | key('list2-other', generate-id())" />
<!-- List 2 elements -->
<xsl:template match="li2" mode="list2">
  <li tag="{name()}" pos="{position()}">
    <xsl:apply-templates />

<!-- skip list elements when matched outside list -->
<xsl:template match="li2" mode="list1"/>
<xsl:template match="*[starts-with(name(), 'li')]"/>

<!-- all other nodes -->
<xsl:template match="node()">
    <xsl:apply-templates />


Current Thread