Reputation: 219
I have to group values based on certain attributs. Therefore I'd like to use for-each-group.
This is the input XML I'm using:
<?xml version="1.0" encoding="ISO-8859-1"?>
<cities>
<city country="Germany" continent="Europe">Munich</city>
<city country="Germany" continent="Europe">Dortmund</city>
<city country="France" continent="Europe">Brest</city>
<city country="Japan" continent="Asia" >Tokyo</city>
<city country="Korea" continent="Asia" >Seoul</city>
<city country="Germany" continent="Europe">Hannover</city>
<city country="Poland" continent="Europe">Krakau</city>
<city country="Russia" continent="Asia" >Omsk</city>
<city country="Japan" continent="Asia" >Kobe</city>
<city country="Japan" continent="Asia" >Ibaraki</city>
<city country="Russia" continent="Europe">St. Petersburg</city>
<city country="Saudi-Arabia" continent="Africa">Riad</city>
<city country="Ireland" continent="Europe">Galway</city>
<city country="Cameroon" continent="Africa">Yaoundé</city>
<city country="Austria" continent="Europe">Vienna</city>
</cities>
Now, I'd like to create an output file that has the format of a nested list.
First of all I'd like to group by contient, and then internally by country, i.e. grouping of cities within countries and continents.
The output should look somehow like this:
<ul>
<li>Europe
<ul>
<li>Germany
<ul>
<li>Munich</li>
<li>Dortmund</li>
<li>...</li>
</ul>
</li>
<li>France
<ul>
<li>...</li>
</ul>
</li>
</ul>
</li>
<li>Asia
<ul>...</ul>
</li>
<li>Africa
<ul>...</ul>
</li>
</ul>
What I have so far is the following XSLT stylesheet:
<xsl:template match="cities">
<ul>
<xsl:for-each-group select="city" group-by="@continent">
<li>
<xsl:value-of select="@continent" />
<xsl:for-each-group select="stadt" group-by="@land">
<ul>
<li>
<xsl:value-of select="@land" />
</li>
</ul>
</xsl:for-each-group>
</li>
</xsl:for-each-group>
</ul>
</xsl:template>
This works fine for the first level, i.e. grouping by continents. But it doesn't work for the nested levels in the list. I would need a way to refere to the current grouping-element in order to group by country just for the current continent.
What would be the best/most convenient way to do this?
Upvotes: 2
Views: 10828
Reputation: 70618
Your XSLT doesn't seem to quite match the XML, because your nested xsl:for-each-group refers to an element stadt and an attribute @land which do not appear in the XML. However, what I think you really need to use is the current-group() function to iterate over the elements in the group. In this case, you are grouping by countries within the current continent.
<xsl:for-each-group select="current-group()" group-by="@country">
And then you will need another nested loop, to iterate over the cities in the country.
<xsl:for-each select="current-group()">
Here is the full XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="cities">
<ul>
<xsl:for-each-group select="city" group-by="@continent">
<li>
<xsl:value-of select="@continent"/>
<ul>
<xsl:for-each-group select="current-group()" group-by="@country">
<li>
<xsl:value-of select="@country"/>
<ul>
<xsl:for-each select="current-group()">
<li>
<xsl:value-of select="."/>
</li>
</xsl:for-each>
</ul>
</li>
</xsl:for-each-group>
</ul>
</li>
</xsl:for-each-group>
</ul>
</xsl:template>
</xsl:stylesheet>
When run on the given XML, the following is output
<ul>
<li>Europe
<ul>
<li>Germany
<ul>
<li>Munich</li>
<li>Dortmund</li>
<li>Hannover</li>
</ul></li>
<li>France
<ul>
<li>Brest</li>
</ul></li>
<li>Poland
<ul>
<li>Krakau</li>
</ul></li>
<li>Russia
<ul>
<li>St. Petersburg</li>
</ul></li>
<li>Ireland
<ul>
<li>Galway</li>
</ul></li>
<li>Austria
<ul>
<li>Vienna</li>
</ul></li>
</ul></li>
<li>Asia
<ul>
<li>Japan
<ul>
<li>Tokyo</li>
<li>Kobe</li>
<li>Ibaraki</li>
</ul></li>
<li>Korea
<ul>
<li>Seoul</li>
</ul></li>
<li>Russia
<ul>
<li>Omsk</li>
</ul></li>
</ul></li>
<li>Africa
<ul>
<li>Saudi-Arabia
<ul>
<li>Riad</li>
</ul></li>
<li>Cameroon
<ul>
<li>Yaound</li>
</ul></li>
</ul></li>
</ul>
Note, you might want to replace the most inner xsl:for-each with an xsl:apply-templates to avoid excessive indentation!
Upvotes: 4