Search code examples
xmlxsltintegrationxslt-2.0

Iterate over Child-Nodes with Same Value - XSLT


Was wondering if it is possible to achieve below using XSLT: I have a requirement to iterate over child nodes of an XML tree and club the ones with similar value in the field auditid tag. The requirement is to club the "attachments" node for every "records" that has same auditid.

Sample InputXML:

<?xml version='1.0' encoding='UTF-8'?>
<Root>
  <total_count>53</total_count>
  <records>
    <auditid>00000001</auditid>
    <completationdate>2021-08-04</completationdate>
    <organization>testcompany1</organization>
    <streetaddress>1 peel street, melbourne</streetaddress>
    <suburb>South Melbourne</suburb>
  </records>
  <records>
    <auditid>00000002</auditid>
    <completationdate>2021-07-07</completationdate>
    <organization>testcompany2</organization>
    <streetaddress>1 test road, West Footscray</streetaddress>
    <suburb>West Footscray</suburb>
    <attachments>
      <label>71270-2_A</label>
      <url>http://test/53X/00000002/71270-2_A.pdf</url>
    </attachments>
  </records>
  <records>
    <auditid>00000002</auditid>
    <completationdate>2021-07-07</completationdate>
    <organization>testcompany2</organization>
    <streetaddress>1 test road, West Footscray</streetaddress>
    <suburb>West Footscray</suburb>
    <attachments>
      <label>71270-2_B</label>
      <url>http://test/53X/00000002-2_B.pdf</url>
    </attachments>
  </records>
  <records>
    <auditid>00000002</auditid>
    <completationdate>2021-07-07</completationdate>
    <organization>testcompany2</organization>
    <streetaddress>1 test road, West Footscray</streetaddress>
    <suburb>West Footscray</suburb>
    <attachments>
      <label>71270-2_C</label>
      <url>http://test/53X/00000002/71270-2_C.pdf</url>
    </attachments>
  </records>
  <records>
    <auditid>00000003</auditid>
    <completationdate>2021-07-08</completationdate>
    <organization>testcompany3</organization>
    <streetaddress>1 test road, Hampton Park</streetaddress>
    <suburb>Hampton Park</suburb>
    <attachments>
      <label>60797-25_A</label>
      <url>http://test/53V/00000003/60797-25_A.pdf</url>
    </attachments>
  </records>
  <records>
    <auditid>00000003</auditid>
    <completationdate>2021-07-08</completationdate>
    <organization>testcompany3</organization>
    <streetaddress>1 test road, Hampton Park</streetaddress>
    <suburb>Hampton Park</suburb>
    <attachments>
      <label>60797-25_B</label>
      <url>http://test/53V/00000003/60797-25_B.pdf</url>
    </attachments>
  </records>
</Root>

My XSLT: So far I've been able to generate a count of each auditid that has same value. But cannot figure out how to iterate over those.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="dogo" match="Root/records" use="auditid" />
<xsl:template match="/Root">
<root>
    <total_count>
        <xsl:value-of select="total_count"/>
    </total_count>
    <xsl:for-each select="records[count(key('dogo', auditid)) = 1]">
    <xsl:variable name="count">
        <xsl:value-of select="count(key('dogo', auditid))"/>
    </xsl:variable>
        <records>
            <count>
                <xsl:value-of select="$count"/>
            </count>
            <xsl:copy-of select="auditid"/>
            <xsl:copy-of select="completationdate"/>
            <xsl:copy-of select="organization"/>
            <xsl:copy-of select="streetaddress"/>
            <xsl:copy-of select="suburb"/>
            <xsl:copy-of select="attachments"/>
        </records>
    </xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>

Expected output:

<?xml version='1.0' encoding='UTF-8'?>
<Root>
  <total_count>53</total_count>
  <records>
    <auditid>00000001</auditid>
    <completationdate>2021-07-20</completationdate>
    <organization>testcompany1</organization>
    <streetaddress>1 peel street, melbourne</streetaddress>
    <suburb>South Melbourne</suburb>
  </records>
  <records>
    <auditid>00000002</auditid>
    <completationdate>2021-07-07</completationdate>
    <organization>testcompany2</organization>
    <streetaddress>1 test road, West Footscray</streetaddress>
    <suburb>West Footscray</suburb>
    <attachments>
      <label>71270-2_A</label>
      <url>http://test/53X/00000002/71270-2_A.pdf</url>
    </attachments>
    <attachments>
      <label>71270-2_B</label>
      <url>http://test/53X/00000002-2_B.pdf</url>
    </attachments>
    <attachments>
      <label>71270-2_C</label>
      <url>http://test/53X/00000002/71270-2_C.pdf</url>
    </attachments>
  </records>
  <records>
    <auditid>00000003</auditid>
    <completationdate>2021-07-08</completationdate>
    <organization>testcompany3</organization>
    <streetaddress>1 test road, Hampton Park</streetaddress>
    <suburb>Hampton Park</suburb>
    <attachments>
      <label>60797-25_A</label>
      <url>http://test/53V/00000003/60797-25_A.pdf</url>
    </attachments>
    <attachments>
      <label>60797-25_B</label>
      <url>http://test/53V/00000003/60797-25_B.pdf</url>
    </attachments>
  </records>
</Root>

Solution

  • If you're able to use XSLT 2.0 then why don't you take advantage of its grouping feature and do simply:

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:template match="/Root">
        <xsl:copy>
            <xsl:copy-of select="total_count"/>
            <xsl:for-each-group select="records" group-by="auditid">
                <records>
                    <xsl:copy-of select="* except attachments"/>
                    <xsl:copy-of select="current-group()/attachments"/>
                </records>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>