Grouping is not working in XSLT 1.0

Tag: xslt-1.0 , xslt-grouping Author: oraclec Date: 2013-06-19

I have a XML like below.

<minimums type="table">       
      <minLine>
        <minId>S-4LS16L</minId>
        <catA>5550</catA>
        <catA>1800</catA>      
        <catB>5550</catB>
        <catB>1800</catB>     
        <catC>5550</catC>
        <catC>1800</catC>      
      </minLine>    
      <minLine>
        <minId>S-LOC16L</minId>
        <catA>5660</catA>
        <catA>2400</catA>        
        <catB>5660</catB>
        <catB>2400</catB>           
        <catC>2400</catC>
        <catC>310</catC>      
      </minLine>   
 </minimums>

Now i want to group catA, catB, catC etc repeated elements using XSL.

Below is the part of my XSLT.

 <xsl:key name ="groupElement" match ="*" use="name(.)"/>

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

  <xsl:template match="minLine">  

    <xsl:for-each select="*[generate-id()= generate-id(key('groupElement', name(.)))]">

      <xsl:comment> This is Not get Printed during second match of minLine element</xsl:comment>

    </xsl:for-each>

  </xsl:template>

is working fine during the first match of . The issue is that the during the match of second element , is not get printed. I must be doing some silly mistake.

Where am i doing wrong ?

Other Answer1

You have three issues here:

  1. your key includes all elements
  2. generate-id() takes only the first node (in document order) of a nodeset
  3. the simple 'muenchian' grouping with generate-id() or count(), where one just takes the first node of the looked-up nodes, isn't suitable for nested structures

When we verify this with the 'catA' element then you can easily comprehend the problem;
By outputting all indexed 'catA' elements, we get the following list of nodes:
<xsl:copy-of select="key('groupElement', 'catA')"/>
->
<catA>5550</catA>
<catA>1800</catA>
<catA>5660</catA>
<catA>2400</catA>

Now, because generate-id() always takes the first node, it becomes clear that nothing happens during the match of the second 'minLine' element.

Solution

You can achieve what you want by:

  1. indexing only the first of sequentially occurring 'minLine/*' children
  2. selecting only indexed 'minLine/*' children
<!-- index first of sequentially occurring minLine children -->
<xsl:key name ="minLine_key" match="minLine/*[name() != name(preceding-sibling::*[1])]" use="name()"/>

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

<xsl:template match="minLine">
    <!-- select indexed children: look up nodes by name and test wether current is included in the node-set -->
    <xsl:for-each select="*[count(. | key('minLine_key', name())) = count(key('minLine_key', name()))]">
        <xsl:copy-of select="."/>
    </xsl:for-each>
</xsl:template>