Search code examples
xmlxsltxslt-grouping

Merging and rearranging xml using xslt


I am using XSLT for converting a XML derived from web and convert the same on the fly into the target xml file denoted as output . I am still unable to do so even after trying a lot , Can anyone please help me out with this conversion .

Source XML

<allocelement>
    <hd1>12</hd1>
    <hd2>14</hd2>
    <hd3>87</hd3>
        <alc>1</alc>
    <amount>4587</amount>
    <code>1111</code>
</allocelement>
<alloclement>
        <hd1>12</hd1>
    <hd2>14</hd2>
    <hd3>87</hd3>
    <alc>2</alc>
    <amount>80000</amount>
    <code>1111</code>
</alloclement>
<alloclement>
    <hd1>875</hd1>
    <hd2>455</hd2>
    <hd3>455</hd3>
    <alc>2</alc>
    <amount>80000</amount>
    <code>1112</code>
 </alloclement>

Output Desired

<allocelement>
    <Codeheader>
    <code>1111</code>
        <hd1>12</hd1>
        <hd2>14</hd2>
        <hd3>87</hd3>
                <alc>1</alc>
                    <amount>4587</amount>
                <alc>2</alc>
                    <amount>80000</amount>
    </codeHeader>
        <CodeHeader>
    <code>1112</code>
        <hd1>875</hd1>
        <hd2>455</hd2>
        <hd3>455</hd3>
            <alc>2</alc>
              <amount>80000</amount>
    </CodeHeader>
</allocelement>

The Grouping is on the basis of Code,[hd1,hd2,hd3] such that the different elements within which have the same Code and [hd1,hd2,hd3] will be merged and only show fields which are different viz. the and . Also I am using xslt 1.0 .


Solution

  • A significantly shorter and simpler XSLT 1.0 solution:

    <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="kCodeByVal" match="code" use="."/>
    
     <xsl:template match="/*">
      <allocelement>
       <xsl:apply-templates/>
      </allocelement>
     </xsl:template>
    
    <xsl:template match="allocelement"/>
    
     <xsl:template match=
      "allocelement
        [generate-id(code) = generate-id(key('kCodeByVal', code)[1])]">
      <code><xsl:value-of select="code"/>
        <xsl:copy-of select=
        "node()[not(self::code)]
        | key('kCodeByVal', code)/../*[self::alc or self::amount]"/>
      </code>
     </xsl:template>
    </xsl:stylesheet>
    

    When this transformation is applied on the following XML document (a single top element wrapping the provided XML fragment):

    <t>
        <allocelement>
            <hd1>12</hd1>
            <hd2>14</hd2>
            <hd3>87</hd3>
            <alc>1</alc>
            <amount>4587</amount>
            <code>1111</code>
        </allocelement>
        <allocelement>
            <hd1>12</hd1>
            <hd2>14</hd2>
            <hd3>87</hd3>
            <alc>2</alc>
            <amount>80000</amount>
            <code>1111</code>
        </allocelement>
        <allocelement>
            <hd1>875</hd1>
            <hd2>455</hd2>
            <hd3>455</hd3>
            <alc>2</alc>
            <amount>80000</amount>
            <code>1112</code>
        </allocelement>
    </t>
    

    the wanted, correct result is produced:

    <allocelement>
       <code>1111<hd1>12</hd1>
          <hd2>14</hd2>
          <hd3>87</hd3>
          <alc>1</alc>
          <amount>4587</amount>
          <alc>2</alc>
          <amount>80000</amount>
       </code>
       <code>1112<hd1>875</hd1>
          <hd2>455</hd2>
          <hd3>455</hd3>
          <alc>2</alc>
          <amount>80000</amount>
       </code>
    </allocelement>
    

    Explanation: Proper use of the Muenchian grouping method.


    II. XSLT 2.0 solution -- again shorter, and much more importantly -- syntactically and semantically correct:

    <xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
      <xsl:template match="/*">
      <allocelement>
       <xsl:for-each-group select="*" group-by="code">
         <code><xsl:value-of select="code"/>
            <xsl:sequence select=
            "node()[not(self::code)]
            | current-group()/*[self::alc or self::amount]"/>
      </code>
       </xsl:for-each-group>
      </allocelement>
     </xsl:template>
    </xsl:stylesheet>
    

    UPDATE: The OP has changed the requirements for the outpu.

    Here is the corresponding modified XSLT 1.0 solution:

    <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="kCodeByVal" match="code" use="."/>
    
         <xsl:template match="/*">
          <allocelement>
           <xsl:apply-templates/>
          </allocelement>
         </xsl:template>
    
        <xsl:template match="allocelement"/>
    
         <xsl:template match=
          "allocelement
            [generate-id(code) = generate-id(key('kCodeByVal', code)[1])]">
          <codeHeader>
           <code><xsl:value-of select="code"/></code>
             <xsl:copy-of select=
             "node()[not(self::code)]
             | key('kCodeByVal', code)/../*[self::alc or self::amount]"/>
          </codeHeader>
         </xsl:template>
    </xsl:stylesheet>