Search code examples
xmlxsltsimple-xml-converter

XML transformation using XSLT by grouping the tags based on some value


I am trying to transform one XML by creating a new tag by grouping the tags. like group the items which are having same parent value.

 <root>
   <item id="100">
      <properties>
         <property attribute-id="parent">10</property>
      </properties>
      <properties>
         <property attribute-id="amount">1.1</property>
      </properties>
   </item>
   <item id="101">
      <properties>
         <property attribute-id="parent">10</property>
      </properties>
      <properties>
         <property attribute-id="amount">1.1</property>
      </properties>
   </item>
   <item id="102">
      <properties>
         <property attribute-id="parent">11</property>
      </properties>
      <properties>
         <property attribute-id="amount">1.1</property>
      </properties>
   </item>
   <item id="103">
      <properties>
         <property attribute-id="parent">10</property>
      </properties>
      <properties>
         <property attribute-id="amount">1.1</property>
      </properties>
   </item>
   <item id="104">
      <properties>
         <property attribute-id="parent">11</property>
      </properties>
      <properties>
         <property attribute-id="amount">1.1</property>
      </properties>
   </item>
</root>

New tag should be:

<item id = "10">  
       <childs>
         <child id="100" />
         <child id="101" />
         <child id="102" />
       </childs>
    </item>       
    <item id = "11">
       <childs>
         <child id="104" />
         <child id="105" />
       </childs>
    </item> 

Is this possible with XSLT?
How can this be done in XSLT?

Editing the initial post i have faced some issues when the tag was in attribute format. Tried updating the first solution but the attribute form is causing many problems.


Solution

  • You can achieve with this the XSLT-1.0 method Muenchian Grouping. If you search on SO, you'll find a lot of examples. Applying this method, your stylesheet could look like this (I added a hypothetical <root> element around your source XML to make it well-formed):

    The xsl:key and the select expression of the outer xsl:for-each do implement the Muenchian Grouping. The inside of the loop should be kind of self-explaining.

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:key name="id" match="item" use="properties/property[@attribute-id='parent']" />   
    
    <xsl:template match="/root">
        <xsl:copy>
            <xsl:for-each select="item[generate-id() = generate-id(key('id',properties/property[@attribute-id='parent'])[1])]">
                <item id="{parent}">
                    <childs>
                        <xsl:for-each select="key('id',properties/property[@attribute-id='parent'])">
                            <child id="{@id}" />
                        </xsl:for-each>
                    </childs>
                </item>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

    If you can use XSLT-2.0 or above, you can make use of xsl:for-each-group like this:

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    
    <xsl:template match="/root">
        <xsl:copy>
            <xsl:for-each-group select="item" group-by="properties/property[@attribute-id='parent']">
                <item id="{parent}">
                    <childs>
                        <xsl:for-each select="current-group()">
                            <child id="{@id}" />
                        </xsl:for-each>
                    </childs>
                </item>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

    It is slightly more simple, but does the same.


    Its output, in both cases, is:

    <?xml version="1.0" encoding="UTF-8"?>
    <root>
       <item id="">
          <childs>
             <child id="100"/>
             <child id="101"/>
             <child id="103"/>
          </childs>
       </item>
       <item id="">
          <childs>
             <child id="102"/>
             <child id="104"/>
          </childs>
       </item>
    </root>
    

    It's not exactly what you want, but I guess that the last child id's of your example are wrong.