tags:

views:

516

answers:

2

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>&#xD;</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>&#xD;</xsl:text>
    <same for other direction>
</xsl:for-each-group>

Anything wrong?

A: 

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(&apos;Elements&apos;, 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>
Tim C
+1  A: 

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>&#xD;</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>&#xD;</xsl:text>
    </xsl:for-each-group>

</xsl:template>
Jörn Horstmann
+1, but I think the OP wants `group-by="concat(@A, @B)"`
Tomalak