[xsl] Grouping By Column Heading (by Position, not by ID or element name)

Subject: [xsl] Grouping By Column Heading (by Position, not by ID or element name)
From: "G. T. Stresen-Reuter" <tedmasterweb@xxxxxxxxx>
Date: Tue, 29 Apr 2014 15:50:18 +0100
Hi,

I regularly find myself having to transform tables in XHTML. The data is
frequently organized by column headings. This seems like an obvious
opportunity for grouping using Keys, and I've gotten it to work, but I'm
having trouble understanding *why* it's working.

Please note that this is not the typical grouping example where an attribute
or element name identifies what group something belongs to but rather an
example in which an elements *position* relative to other elements is what
defines the group.

Given:

<table>
	<tr>
		<td>Fruits</td>
		<td>Veggies</td>
		<td>Lactose</td>
	</tr>
	<tr>
		<td>Apples</td>
		<td>Carrots</td>
		<td>Milk</td>
	</tr>
	<tr>
		<td>Bananas</td>
		<td>Peas</td>
		<td>Cheese</td>
	</tr>
	<tr>
		<td>Plums</td>
		<td>Celery</td>
		<td>Eggs</td>
	</tr>
</table>

I want to end up with:

<foods>
  <foodGroup name="Fruits">
    <food>Apples</food>
    <food>Bananas</food>
    <food>Plums</food>
  </foodGroup>
  <foodGroup name="Veggies">
    <food>Carrots</food>
    <food>Peas</food>
    <food>Celery</food>
  </foodGroup>
  <foodGroup name="Lactose">
    <food>Milk</food>
    <food>Cheese</food>
    <food>Eggs</food>
  </foodGroup>
</foods>

I've found that the following XSLT works, but I don't understand why and I
suspect that I am just getting lucky or that the Key element could be better
written (optimized?).

I would be very grateful if someone with more experience at this than I could
take a look at this XSLT and explain:

1) How is a TD element (found in the USE attribute of my Key) able to act as
unique identifier for a TR element (it's parent)?

2) How should this be done (assuming mine is not optimal)?

Please note that I realize this does not require using keys (I don't think)
but I like using keys because it makes sense semantically.

Here's the XSLT and thanks in advance.

<xsl:key name="food-group" match="//table/tr[position() &gt; 1]"
use="ancestor-or-self::table/tr[position() = 1]/td" />

<xsl:template match="/table">
	<xsl:element name="foods">
		<xsl:apply-templates select="tr[position() = 1]/td"/>
	</xsl:element>
</xsl:template>

<xsl:template match="td">
	<xsl:variable name="i" select="position()" />
	<xsl:element name="foodGroup">
		<xsl:attribute name="name"><xsl:value-of select="." /></xsl:attribute>
		<xsl:for-each select="key('food-group', .)">
			<xsl:element name="food">
				<xsl:value-of select="td[position() = $i]" />
			</xsl:element>
		</xsl:for-each>
	</xsl:element>
</xsl:template>


Ted Stresen-Reuter

Current Thread