Grouping in a node with 3 attributes in xslt 1.0

Tag: xslt Author: lby717323738 Date: 2011-04-08

i need some help regarding grouping nodes in xslt 1.0. I am able to this in xslt 2.0 with for-each-group but i don't know how to do it in 1.0. Shown below is sample xml, in which i want to group the 'CLM' node using a combination of ServiceId, WorkerId and Date of service on 'ClaimId' attribute and sum of 'Units'. If service given on same day by same worker more than once than combine those 'CLM' nodes.

<?xml version="1.0" encoding="UTF-8"?>
<PRV ProviderId="100" PName="Giga health" Provsuv="1563">
    <CLT ClientId="4444" ClientFName="John" ClientLastName="Pulaski" Phone="56462561">
        <CLM Claimid="1"  DateOfService="01/02/2011" EndOfService="05/05/2011" ServiceId="S1" WorkerName="WORK1" WorkerId="6446" Unit= '5' Amount= '5000'/>
        <CLM Claimid="2"  DateOfService="01/02/2011" EndOfService="05/05/2011" ServiceId="S1" WorkerName="WORK1" WorkerId="6446" Unit= '6' Amount= '5000'/>
        <CLM Claimid="3"  DateOfService="01/02/2011" EndOfService="05/05/2011" ServiceId="S2" WorkerName="WORK1" WorkerId="2006" Unit= '7' Amount= '5000'/>
        <CLM Claimid="4"  DateOfService="01/03/2011" EndOfService="05/05/2011" ServiceId="S1" WorkerName="WOK2" WorkerId="6446" Unit= '3' Amount= '5000'/>
        <CLM Claimid="5"  DateOfService="01/03/2011" EndOfService="05/05/2011" ServiceId="S2" WorkerName="WORK2" WorkerId="6446" Unit= '8' Amount= '5000'/>
        <CLM Claimid="6" DateOfService="01/03/2011" EndOfService="05/05/2011" ServiceId="S2" WorkerName="WORK1" WorkerId="6446" Unit= '1' Amount= '5000'/>
    </CLT>

My output should look like

<?xml version="1.0" encoding="UTF-8"?>
<PRV ProviderId="100" PName="Giga health" Provsuv="1563">
    <CLT ClientId="4444" ClientFName="John" ClientLastName="Pulaski" Phone="56462561">
        <CLM Claimid="1,2"  DateOfService="01/02/2011" EndOfService="05/05/2011" ServiceId="S1" WorkerName="WORK1" WorkerId="6446" Unit= '11' Amount= '10000'/>
        <CLM Claimid="3"  DateOfService="01/02/2011" EndOfService="05/05/2011" ServiceId="S2" WorkerName="WORK1" WorkerId="2006" Unit= '7' Amount= '5000'/>
        <CLM Claimid="4"  DateOfService="01/03/2011" EndOfService="05/05/2011" ServiceId="S1" WorkerName="WOK2" WorkerId="6446" Unit= '3' Amount= '5000'/>
        <CLM Claimid="5,6"  DateOfService="01/03/2011" EndOfService="05/05/2011" ServiceId="S2" WorkerName="WORK2" WorkerId="6446" Unit= '9' Amount= '10000'/>
    </CLT>

My output xml should have 'Claimid' combined and Units to be summed up for same serviceId on same DateofService for a specific 'WorkerId'. Thanks in advance.

Good question, +1. See my answer for a complete and easy solution. :)

Best Answer

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kCLMByAttribs" match="CLM" use=
 "concat(@ServiceId,'+',@WorkerId,'+',@DateOfService)"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match=
 "CLM[generate-id()
     =
      generate-id(key('kCLMByAttribs',
                      concat(@ServiceId,
                            '+',@WorkerId,
                            '+',@DateOfService)
                     )
                     [1]
                 )
     ]
 ">
  <xsl:copy>
   <xsl:copy-of select="@*"/>

   <xsl:variable name="vGroup" select=
   "key('kCLMByAttribs',
         concat(@ServiceId,
                '+',@WorkerId,
                '+',@DateOfService)
       )
   "/>

   <xsl:variable name="vClaimIds">
    <xsl:for-each select="$vGroup">
      <xsl:if test="not(position()=1)">
       <xsl:value-of select="','"/>
      </xsl:if>
      <xsl:value-of select="@Claimid"/>
    </xsl:for-each>
   </xsl:variable>

   <xsl:attribute name="Claimid">
    <xsl:value-of select="$vClaimIds"/>
   </xsl:attribute>

   <xsl:attribute name="Unit">
    <xsl:value-of select="sum($vGroup/@Unit)"/>
   </xsl:attribute>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="CLM"/>
</xsl:stylesheet>

when applied on the provided XML document:

<PRV ProviderId="100" PName="Giga health"
Provsuv="1563">
    <CLT ClientId="4444" ClientFName="John"
      ClientLastName="Pulaski" Phone="56462561">
        <CLM Claimid="1"  DateOfService="01/02/2011"
          EndOfService="05/05/2011" ServiceId="S1"
          WorkerName="WORK1" WorkerId="6446"
          Unit= '5' Amount= '5000'/>
        <CLM Claimid="2"  DateOfService="01/02/2011"
         EndOfService="05/05/2011" ServiceId="S1"
         WorkerName="WORK1" WorkerId="6446"
         Unit= '6' Amount= '5000'/>
        <CLM Claimid="3"  DateOfService="01/02/2011"
         EndOfService="05/05/2011" ServiceId="S2"
         WorkerName="WORK1" WorkerId="2006"
         Unit= '7' Amount= '5000'/>
        <CLM Claimid="4"  DateOfService="01/03/2011"
         EndOfService="05/05/2011" ServiceId="S1"
         WorkerName="WOK2" WorkerId="6446"
         Unit= '3' Amount= '5000'/>
        <CLM Claimid="5"  DateOfService="01/03/2011"
         EndOfService="05/05/2011" ServiceId="S2"
         WorkerName="WORK2" WorkerId="6446"
         Unit= '8' Amount= '5000'/>
        <CLM Claimid="6" DateOfService="01/03/2011"
         EndOfService="05/05/2011" ServiceId="S2"
         WorkerName="WORK1" WorkerId="6446"
         Unit= '1' Amount= '5000'/>
    </CLT>
</PRV>

produces exactly the wanted, correct result:

<PRV ProviderId="100" PName="Giga health" Provsuv="1563">
   <CLT ClientId="4444" ClientFName="John"
        ClientLastName="Pulaski" Phone="56462561">
      <CLM Claimid="1,2" DateOfService="01/02/2011"
       EndOfService="05/05/2011" ServiceId="S1"
       WorkerName="WORK1" WorkerId="6446"
       Unit="11" Amount="5000"/>
      <CLM Claimid="3" DateOfService="01/02/2011"
       EndOfService="05/05/2011" ServiceId="S2"
       WorkerName="WORK1" WorkerId="2006"
       Unit="7" Amount="5000"/>
      <CLM Claimid="4" DateOfService="01/03/2011"
       EndOfService="05/05/2011" ServiceId="S1"
       WorkerName="WOK2" WorkerId="6446"
       Unit="3" Amount="5000"/>
      <CLM Claimid="5,6" DateOfService="01/03/2011"
       EndOfService="05/05/2011" ServiceId="S2"
       WorkerName="WORK2" WorkerId="6446"
       Unit="9" Amount="5000"/>
   </CLT>
</PRV>

Explanation:

Muenchian grouping with a key that is the concatenation of three parts.

comments:

@Dimitre...Thank you very much. I was banging my head up and down for this. Thanks for help. I was using Muenchian method but was generate different key for each attribute. i appreciate your help.
@bluesnowxyz: I am glad my answer was useful. You may not be aware, but at SO the officially-endorsed way of expressing gratitude is to accept the right answer. This can simply be done by clicking on the green check-mark next to the answer.
@Dimitre....oops ! sorry for that. the xsl you provided runs fine and i was trying to sort claimId's. In sample they are in sorted order 1,2 but if they come unsorted 11,2,5,1 will it be possible to sort them using your answer.
@bluesnowxyz: Yes, just replace <xsl:for-each select="$vGroup"> with <xsl:for-each select="$vGroup"> <xsl:sort select="@Claimid" data-type="number"/>
@Dimitre: Thanks again. :)