Re: [xsl] Identity Transform Grouping Question

Subject: Re: [xsl] Identity Transform Grouping Question
From: Geert Josten <Geert.Josten@xxxxxxxxxxx>
Date: Tue, 09 Nov 2004 08:26:05 +0100
Hi Ethan,

I added commented patches below..

Cheers,
Geert

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">

<!-- xmlns:exsl="http://exslt.org/common"; extension-element-prefixes="exsl" -->

	<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
	<xsl:strip-space elements="*"/>

	<!-- keys for grouping -->
	<xsl:key name="cities" match="cities" use="city[@lang='en']"/>
	<xsl:key name="offices" match="office" use="concat(cities/city[@lang='en'],'-',names/name[@lang='en'])"/>

For the index using solution, add: <xsl:key name="offices-by-country" match="offices" use="../@name"/>


<xsl:template match="/"> <xsl:comment> with apply-templates </xsl:comment> <xsl:apply-templates/> </xsl:template>

	<!-- identity transform: copy all elements -->
	<xsl:template match="*">
		<xsl:copy>
			<xsl:copy-of select="@*"/>
			<xsl:apply-templates/>
		</xsl:copy>
	</xsl:template>

	<!-- sort divisions by @id -->
	<xsl:template match="divisions">
		<divisions>
			<xsl:apply-templates select="division">
				<xsl:sort select="@id"/>
			</xsl:apply-templates>
		</divisions>
	</xsl:template>

	<!-- sort regions and countries by @name -->
	<xsl:template match="regions|countries">
		<xsl:copy>
			<xsl:apply-templates select="region|country">
				<xsl:sort select="@name"/>
			</xsl:apply-templates>
		</xsl:copy>
	</xsl:template>

	<!-- reorganize 'country' elements -->
	<xsl:template match="country">
		<country>
			<xsl:copy-of select="@*"/>
			<cities>
				<!-- group 'city' elements within this country -->
				<xsl:variable name="offices-in-this-country">
					<xsl:copy-of select="offices"/>
					<!-- this is a trick, to restrict all subsequent key usage to this country only -->
				</xsl:variable>
				<xsl:apply-templates select="msxsl:node-set($offices-in-this-country)/offices/office/cities[count(.|key('cities',city[@lang='en'])[1])=1]">
					<xsl:sort select="city[@lang='en']"/>
				</xsl:apply-templates>

I wonder if something like:


<xsl:variable name="offices-in-this-country" select="offices" />

wouldn't have worked just as fine.

Note that the select of apply-templates should be changed as well into:

   <xsl:apply-templates
     select="$offices-in-this-country/office
             /cities[count(. | key('cities', city[@lang='en'])[1]) = 1]">
     ...
   </xsl:apply-templates>

However, this is not the suggestion I tried to make. I said 'why not use an index'. Then, you will no longer need the variable but only the index I added at the top. Change apply-templates into:

   <xsl:apply-templates
     select="key('offices-by-country', @name)/office
             /cities[count(. | key('cities', city[@lang='en'])[1]) = 1]">
     ...
   </xsl:apply-templates>

			</cities>
		</country>
	</xsl:template>

	<!-- new 'city' elements -->
	<xsl:template match="cities">
		<city name="{city[@lang='en']}">
		<!-- (uncomment this if you need multilingual city names in the output)
		<city>
			<xsl:apply-templates select="city" mode="name"/>
			-->
			<offices>
				<!-- group 'office' elements located in this city by their english name -->
				<xsl:variable name="this-city" select="city[@lang='en']"/>
				<xsl:variable name="offices-in-this-city" select="../../../offices/office[cities/city[@lang='en']=$this-city]"/>
				<xsl:apply-templates select="$offices-in-this-city
					[count(.|key('offices', concat(cities/city[@lang='en'],'-',names/name[@lang='en']))[1])=1]">
					<xsl:sort select="names/name[@lang='en']"/>
				</xsl:apply-templates>
			</offices>
		</city>
	</xsl:template>

	<!-- new city name elements -->
	<xsl:template match="city" mode="name">
		<name>
			<xsl:copy-of select="@*"/>
			<xsl:value-of select="."/>
		</name>
	</xsl:template>

	<!-- new office elements -->
	<xsl:template match="office">
		<office>
			<xsl:apply-templates select="names/name"/>
			<!-- add locations for each office with this name -->
			<xsl:apply-templates select="key('offices',
				concat(cities/city[@lang='en'],'-',names/name[@lang='en']))" mode="location">
				<xsl:sort select="address"/>
			</xsl:apply-templates>
		</office>
	</xsl:template>

	<!-- new location elements -->
	<xsl:template match="office" mode="location">
		<location>
			<xsl:copy-of select="@*"/>
			<xsl:apply-templates select="address|phone"/>
		</location>
	</xsl:template>

</xsl:stylesheet>

Current Thread