Subject: Re: Constructing multi-level lists - any better than this?
From: "Andrew Welch" <andrew.j.welch@xxxxxxxxx>
Date: Sun, 16 Sep 2007 13:52:38 +0100
|
On 9/15/07, Michael M|ller-Hillebrand <mmh@xxxxxxxxxxxxx> wrote:
> Hello,
>
> 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"?>
> <levels>
> <p/>
> <li1>1</li1>
> <li1>2</li1>
> <li1>3</li1>
> <li1>4</li1>
> <p/>
> <li1>5</li1>
> <li1>6</li1>
> <li2>7.1</li2>
> <li2>7.2</li2>
> <p/>
> <li2>8.1</li2>
> <li2>8.2</li2>
> <li1>9</li1>
> <li1>10</li1>
> <p/>
> </levels>
>
> ---------
>
> <xsl:stylesheet version="2.0"
> xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
> <xsl:strip-space elements="*"/>
> <xsl:output method="xml" indent="yes" />
>
> <!-- Root element -->
> <xsl:template match="levels">
> <xsl:copy>
> <xsl:apply-templates />
> </xsl:copy>
> </xsl:template>
>
> <!-- 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')]]"
> use="generate-id(
> preceding-sibling::*[
> starts-with(name(), 'li')
> and not(preceding-sibling::*[1][starts-with(name(), 'li')])
> ][1])" />
>
> <xsl:key name="list2-other"
> match="li2[preceding-sibling::*[1][self::li2]]"
> use="generate-id(
> preceding-sibling::li2[
> not(preceding-sibling::*[1][self::li2])
> ][1])" />
>
> <!-- List 1 Container-->
> <xsl:template match="*[starts-with(name(), 'li')
> and not(preceding-sibling::*[1][starts-with(name(), 'li')])]"
> priority="1">
> <ul level="1">
> <xsl:apply-templates mode="list1"
> select=". | key('list12-other', generate-id())" />
> </ul>
> </xsl:template>
> <!-- List 1 elements -->
> <xsl:template match="li1" mode="list1">
> <li tag="{name()}" pos="{position()}">
> <xsl:apply-templates />
> </li>
> </xsl:template>
>
> <!-- 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())" />
> </ul>
> </xsl:template>
> <!-- List 2 elements -->
> <xsl:template match="li2" mode="list2">
> <li tag="{name()}" pos="{position()}">
> <xsl:apply-templates />
> </li>
> </xsl:template>
>
> <!-- 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:copy>
> <xsl:apply-templates />
> </xsl:copy>
> </xsl:template>
>
> </xsl:stylesheet>
Hi Michael,
Here's an XSLT 2.0 version:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="levels">
<xsl:copy>
<xsl:for-each-group select="*[not(position() = last())]"
group-starting-with="*[self::p]">
<p/>
<ul level="1">
<xsl:for-each-group select="current-group()"
group-by="name()">
<xsl:choose>
<xsl:when test="self::li1">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:when test="self::li2">
<ul level="2">
<xsl:apply-templates
select="current-group()"/>
</ul>
</xsl:when>
</xsl:choose>
</xsl:for-each-group>
</ul>
</xsl:for-each-group>
<p/>
</xsl:copy>
</xsl:template>
<xsl:template match="li1|li2">
<li tag="{name()}" pos="{position()}">
<xsl:apply-templates/>
</li>
</xsl:template>
</xsl:stylesheet>
cheers
--
Andrew Welch
http://andrewjwelch.com
Kernow: http://kernowforsaxon.sf.net/
|