The following is the solution of Muenchian grouping.
Based on the original XML that you provided, I thought that grouping by AreaID would be enough, but it turns out that a second grouping by UnitID is also required.
Here is my modified XSLT 1.0 solution. This is not much more complicated than the original solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <xsl:key name="kPlanByArea" match="Plan" use="@AreaID" /> <xsl:key name="kPlanByAreaAndUnit" match="Plan" use="concat(@AreaID, ',', @UnitID)" /> <xsl:template match="/"> <xsl:apply-templates select="Root/Plans" /> </xsl:template> <xsl:template match="Plans"> <ol> <xsl:apply-templates mode="area-group" select=" Plan[ generate-id() = generate-id( key('kPlanByArea', @AreaID)[1] ) ] "> <xsl:sort select="@AreaID" data-type="number" /> </xsl:apply-templates> </ol> </xsl:template> <xsl:template match="Plan" mode="area-group"> <li> <xsl:value-of select="concat('Area ', @AreaID)" /> <ol> <xsl:apply-templates mode="unit-group" select=" key('kPlanByArea', @AreaID)[ generate-id() = generate-id( key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1] ) ] "> <xsl:sort select="@UnitID" data-type="number" /> </xsl:apply-templates> </ol> </li> </xsl:template> <xsl:template match="Plan" mode="unit-group"> <li> <xsl:value-of select="concat('Unit ', @UnitID)" /> <ol> <xsl:apply-templates select=" key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))/Part "> <xsl:sort select="@UnitID" data-type="number" /> </xsl:apply-templates> </ol> </li> </xsl:template> <xsl:template match="Part"> <li> <xsl:value-of select="concat('Part ', @ID, ' (', @Name ,')')" /> </li> </xsl:template> </xsl:stylesheet>
Since your XML is missing, I added a UnitID to group:
<Plan AreaID="1" UnitID="86"> <Part ID="8651" Name="zzz" /> </Plan>
And here is the conclusion:
<ol> <li>Area 1 <ol> <li>Unit 83 <ol> <li>Part 9122 (foo)</li> <li>Part 9126 (bar)</li> </ol> </li> <li>Unit 86 <ol> <li>Part 8650 (baz)</li> <li>Part 8651 (zzz)</li> </ol> </li> <li>Unit 95 <ol> <li>Part 7350 (meh)</li> </ol> </li> </ol> </li> <li>Area 2 <ol> <li>Unit 26 <ol> <li>Part 215 (quux)</li> </ol> </li> </ol> </li> </ol>
Since it seems to you very difficult with the XSL key, here is my attempt to explain:
An <xsl:key> absolutely equivalent to the associative array (map, hash, as you call it), known to many programming languages. It:
<xsl:key name="kPlanByAreaAndUnit" match="Plan" use="concat(@AreaID, ',', @UnitID)" />
creates a data structure that can be expressed in JavaScript as follows:
var kPlanByAreaAndUnit = { "1,83": ['array of all <Plan> nodes with @AreaID="1" and @UnitID="83"'], "1,86": ['array of all <Plan> nodes with @AreaID="1" and @UnitID="86"'], "1,95": ['array of all <Plan> nodes with @AreaID="1" and @UnitID="95"'] };
The data structure access function is called key() . So this is an XPath expression:
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))
is the logical equivalent (in JavaScript again):
kPlanByAreaAndUnit[this.AreaID + ',' + this.UnitID];
returns an array (a node-set, or rather) of all nodes corresponding to the given key string (the key is always a string). This node-set can be used like any other node-set in XSLT, i.e. The one you get through the "traditional" XPath. This means that you can apply conditions (predicates) to it:
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1] key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[Part]
or use it as a basis for XPath navigation:
key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))/Part
etc. It also means that we can use it as the "select" expression for <xsl:apply-templates> , and we can use it as a base for grouping. This brings us to the core of the aforementioned stylesheet (if you wrapped your head around this, you also understood the rest of the solution):
key('kPlanByArea', @AreaID)[ generate-id() = generate-id( key('kPlanByAreaAndUnit', concat(@AreaID, ',', @UnitID))[1] ) ]
In JavaScript, again this can be expressed as:
After the expression is done, only those nodes are selected that are the first ones with the given combination “AreaID, UnitID” - effectively we grouped them by their combination “AreaID, UnitID”.
Applying a template to this node -set only causes each combination to appear once. My <xsl:template match="Plan" mode="unit-group"> then again gets a complete list to achieve full output for each group.
I hope that using JavaScript to explain the concept was a useful idea.