HeisenBerg
HeisenBerg

Reputation: 127

Nested grouping using XSLT muenchian-grouping

I had to group a xml document in xslt 1.0 using Oracle Service Bus.

This is the sample input file(Simplified):

    <?xml version="1.0" encoding="UTF-8"?>
    <EMailData>
       <property name="A">
          <property name="B">
               <property name="C">
                <row>
                   <property name="C1">
                      <value>ValC1</value>
                   </property>
                   <property name="C2">
                      <value>ValC2</value>
                   </property>
                   <property name="C3">
                      <value>Valc3</value>
                   </property>
                   <property name="C4">
                      <value>Valc4</value>
                   </property>
                 </row>  
             </property> 
             <property name="C">
                <row>
                   <property name="C1">
                      <value>ValC1</value>
                   </property>
                   <property name="C2">
                      <value>ValC2</value>
                   </property>
                   <property name="C3">
                      <value>Valc3</value>
                   </property>
                   <property name="C4">
                      <value>Valc4</value>
                   </property>
                 </row>  
             </property> 
             <property name="D">
                <row>
                   <property name="D1">
                      <value>ValD1</value>
                   </property>
                   <property name="D2">
                      <value>VALd2</value>
                   </property>
                   <property name="D3-InnerElement"> //Need to Group this too
                      <row>
                         <property name="Status">
                            <value>Status122</value>
                         </property>
                      </row>
                   </property>
                   <property name="D3-InnerElement">
                      <row>
                         <property name="Status">
                            <value>Status123</value>
                         </property>
                      </row>
                   </property>
                   <property name="D3-InnerElement">
                      <row>
                         <property name="Status">
                            <value>Status124</value>
                         </property>
                      </row>
                   </property>
                </row>
            </property>
             <property name="D">
                <row>
                   <property name="D1">
                      <value>ValD1</value>
                   </property>
                   <property name="D2">
                      <value>VALd2</value>
                   </property>
                   <property name="D3-InnerElement">
                      <row>
                         <property name="Status">
                            <value>Status122</value>
                         </property>
                      </row>
                   </property>
                   <property name="D3-InnerElement">
                      <row>
                         <property name="Status">
                            <value>Status123</value>
                         </property>
                      </row>
                   </property>
                   <property name="D3-InnerElement">
                      <row>
                         <property name="Status">
                            <value>Status124</value>
                         </property>
                      </row>
                   </property>
                </row>
            </property>             
          </property>
       </property>
    </EMailData>

My XSLT Logic:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>

<xsl:key name="group" match="/*/*/*/property" use="@name"/>

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

<xsl:template match="/*/*/*[property[@name]]">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:for-each select="*[generate-id() = generate-id(key('group', @name)[1])]">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates select="key('group', @name)/*"/>
  </xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>

<!--Change for Inner Hierarchy-->

<xsl:key name="inner-group" match="/*/*/*/*/property" use="@name"/>

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

<xsl:template match="/*/*/*/*[property[@name]]">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:for-each select="*[generate-id() = generate-id(key('inner-group', @name)[1])]">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates select="key('inner-group', @name)/*"/>
  </xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Anticipated o/p

<?xml version="1.0" encoding="UTF-8"?>
<EMailData>
   <property name="A">
      <property name="B">
         <property name="C">
            <row>
               <property name="C1">
                  <value>ValC1</value>
               </property>
               <property name="C2">
                  <value>ValC2</value>
               </property>
               <property name="C3">
                  <value>Valc3</value>
               </property>
               <property name="C4">
                  <value>Valc4</value>
               </property>
            </row>
            <row>
               <property name="C1">
                  <value>ValC1</value>
               </property>
               <property name="C2">
                  <value>ValC2</value>
               </property>
               <property name="C3">
                  <value>Valc3</value>
               </property>
               <property name="C4">
                  <value>Valc4</value>
               </property>
            </row>
         </property>
         <property name="D">
            <row>
               <property name="D1">
                  <value>ValD1</value>
               </property>
               <property name="D2">
                  <value>VALd2</value>
               </property>
               <property name="D3-InnerElement"> //Need to Group this too
                      <row>
                     <property name="Status">
                        <value>Status122</value>
                     </property>
                  </row>
               </property>
               <property name="D3-InnerElement">
                  <row>
                     <property name="Status">
                        <value>Status123</value>
                     </property>
                  </row>
                  <row>
                     <property name="Status">
                        <value>Status124</value>
                     </property>
                  </row>
               </property>
            </row>
            <row>
               <property name="D1">
                  <value>ValD1</value>
               </property>
               <property name="D2">
                  <value>VALd2</value>
               </property>
               <property name="D3-InnerElement">
                  <row>
                     <property name="Status">
                        <value>Status122</value>
                     </property>
                  </row>
                  <row>
                     <property name="Status">
                        <value>Status123</value>
                     </property>
                  </row>
                  <row>
                     <property name="Status">
                        <value>Status124</value>
                     </property>
                  </row>
               </property>
            </row>
         </property>
      </property>
   </property>
</EMailData>

But The D3-innerelement is not grouped. Tell me Where I went wrong!!

o/p For my XSLT

    <?xml version="1.0" encoding="UTF-8"?>
<EMailData>
   <property name="A">
      <property name="B">
         <property name="C">
            <row>
               <property name="C1">
                  <value>ValC1</value>
               </property>
               <property name="C2">
                  <value>ValC2</value>
               </property>
               <property name="C3">
                  <value>Valc3</value>
               </property>
               <property name="C4">
                  <value>Valc4</value>
               </property>
            </row>
            <row>
               <property name="C1">
                  <value>ValC1</value>
               </property>
               <property name="C2">
                  <value>ValC2</value>
               </property>
               <property name="C3">
                  <value>Valc3</value>
               </property>
               <property name="C4">
                  <value>Valc4</value>
               </property>
            </row>
         </property>
         <property name="D">
            <row>
               <property name="D1">
                  <value>ValD1</value>
               </property>
               <property name="D2">
                  <value>VALd2</value>
               </property>
               <property name="D3-InnerElement"> //Need to Group this too
                          <row>
                     <property name="Status">
                        <value>Status122</value>
                     </property>
                  </row>
               </property>
               <property name="D3-InnerElement">
                  <row>
                     <property name="Status">
                        <value>Status123</value>
                     </property>
                  </row>
               </property>
               <property name="D3-InnerElement">
                  <row>
                     <property name="Status">
                        <value>Status124</value>
                     </property>
                  </row>
               </property>
            </row>
            <row>
               <property name="D1">
                  <value>ValD1</value>
               </property>
               <property name="D2">
                  <value>VALd2</value>
               </property>
               <property name="D3-InnerElement">
                  <row>
                     <property name="Status">
                        <value>Status122</value>
                     </property>
                  </row>
               </property>
               <property name="D3-InnerElement">
                  <row>
                     <property name="Status">
                        <value>Status123</value>
                     </property>
                  </row>
               </property>
               <property name="D3-InnerElement">
                  <row>
                     <property name="Status">
                        <value>Status124</value>
                     </property>
                  </row>
               </property>
            </row>
         </property>
      </property>
   </property>
</EMailData>

Thanks in Advance!

Upvotes: 2

Views: 974

Answers (2)

Linga Murthy C S
Linga Murthy C S

Reputation: 5432

And here goes the solution using Muenchian's grouping:

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

<xsl:key name="group" match="property" use="@name"/>

<xsl:template match="/EMailData/property/property | /EMailData/property/property/property/row">
    <xsl:variable name="id" select="generate-id()"/>
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:for-each select="property[count(. | key('group', @name)[$id = generate-id(parent::*)][1]) = 1]">
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:apply-templates select="key('group', @name)[$id = generate-id(parent::*)]/*"/>
            </xsl:copy>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>
<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
    </xsl:copy>
</xsl:template>
</xsl:stylesheet>

Upvotes: 2

Linga Murthy C S
Linga Murthy C S

Reputation: 5432

This solution isn't based on Muenchian's grouping, but thought it would be helpful:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[property]">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:for-each select="property[not(@name = preceding-sibling::property/@name)]">
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:apply-templates select="../property[@name = current()/@name]/*"/>
            </xsl:copy>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>
<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
    </xsl:copy>
</xsl:template>
</xsl:stylesheet>

Here, the second template is the identity transform template, used to copy all attributes and nodes.

The first template matches elements with at least one property child, or in simple words, "parents of the property elements to be grouped by @name". You may, as well, change the template match to:

<xsl:template match="/EMailData/property/property | /EMailData/property/property/property/row">

The for-each is on the first property with a specific @name value in the current parent(see the condition using preceding-sibling). And for every iteration, templates are applied for all the child elements of property elements with the current(for-each element's) @name, i.e., grouping property elements of a single parent by their @name's value.

The same template is called for the inner property elements, grouping even those by @name.

Upvotes: 1

Related Questions