Reputation: 11
I want to sort nodes base on attributes. Say there are three attributes A, B and C in element E1. I know that a sub-group of nodes have the same value of attribute A and B. How can I get this sub-group retrieve the node that has the max value of C? The tricky part here is that I don't know what value of A is. I just know that sub-group share the same value of A. Just like a dual-key index.
I am thinking to use for-each underneath for-each-group.
example
<masterNodes>
<Node>
<Element1 A="123" B="LEFT" C="1">
<Element2>...</Element2>
</Node>
<Node>
<Element1 A="123" B="DOWN" C="5">
<Element2>...</Element2>
</Node>
<Node>
<Element1 A="abc" B="RIGHT" C="2">
<Element2>...</Element2>
</Node>
<Node>
<Element1 A="123" B="LEFT" C="3">
<Element2>...</Element2>
</Node>
<Node>
<Element1 A="4XX" B="LEFT" C="4">
<Element2>...</Element2>
</Node>
<Node>
<Element1 A="abc" B="RIGHT" C="1">
<Element2>...</Element2>
</Node>
<Node>
<Element1 A="4XX" B="LEFT" C="5">
<Element2>...</Element2>
</Node>
<Node>
<Element1 A="4XX" B="UP" C="0">
<Element2>...</Element2>
</Node>
</masterNodes>
How can I only write out the max value of C for node with the same value of A and B?
Here is how I structure my code. But I never get it work.
<xsl:for-each-group select="/Node/Element1" group-by="@A">
<xsl:for-each select=".[@B='LEFT']">
<xsl:sort select="@C" data-type="number" order="descending"/>
<xsl:if test="position()=1"><xsl:value-of select="@C"/></xsl:if>
</xsl:for-each><xsl:text>
</xsl:text>
<xsl:for-each select=".[@B='RIGHT']">
<xsl:sort select="@C" data-type="number" order="descending"/>
<xsl:if test="position()=1"><xsl:value-of select="@C"/></xsl:if>
</xsl:for-each><xsl:text>
</xsl:text>
<same for other direction>
</xsl:for-each-group>
Anything wrong?
Upvotes: 1
Views: 1297
Reputation: 34044
If I understand the spec correctly, the context item inside for-each-group is set to the first element of that group. In order to sort the group you need to use the current-group() function. The following template seems to work:
<xsl:template match="/masterNodes">
<xsl:for-each-group select="Node/Element1" group-by="@A">
<xsl:for-each select="current-group()[@B='LEFT']">
<xsl:sort select="@C" data-type="number" order="descending"/>
<xsl:if test="position()=1"><xsl:value-of select="concat(@A, ' ', @B, ' ', @C)"/></xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:for-each select="current-group()[@B='RIGHT']">
<xsl:sort select="@C" data-type="number" order="descending"/>
<xsl:if test="position()=1"><xsl:value-of select="concat(@A, ' ', @B, ' ', @C)"/></xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
Upvotes: 1
Reputation: 70648
I think this could be achieved by 'muenchian grouping' which makes use of the xsl:key element to group nodes.
Firstly, define a key of Element1 elements using the A and B attributes
<xsl:key name="Elements" match="Element1" use="concat(@A,@B)"/>
Next you would need to loop throught the unique combinations of A and B using this key
<xsl:for-each select="//Element1[generate-id(.) = generate-id(key('Elements', concat(@A,@B))[1])]">
Within this loop, you would loop again, but only on the elements with the matching keys, this time in order of the C attribute
<xsl:for-each select="key('Elements', concat(@A,@B))">
<xsl:sort select="@C" order="descending"/>
As you only want the first element, which has the highest value of the C attribute, you would then test the position()
<xsl:if test="position() = 1">
Putting this altogether gives
<xsl:key name="Elements" match="Element1" use="concat(@A,@B)"/>
<xsl:template match="/">
<xsl:for-each select="//Element1[generate-id(.) = generate-id(key('Elements', concat(@A,@B))[1])]">
<xsl:sort select="@C" order="descending"/>
<xsl:for-each select="key('Elements', concat(@A,@B))">
<xsl:sort select="@C" order="descending"/>
<xsl:if test="position() = 1">
(<xsl:value-of select="@A"/>,<xsl:value-of select="@B"/>,<xsl:value-of select="@C"/>)
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
Upvotes: 0