Search code examples
xsltxslt-1.0xslt-2.0xslt-groupingxslt-3.0

XSLT Nested Groups


I am trying to create an XSLT to transform an XML document and having trouble in forming Nested Groups.Below is my xml

 <?xml version="1.0" encoding="UTF-8"?>
        <catalog>

           <mheader>
              <mid>1</mid>
              <mname>mn</mname>
           </mheader>
           <cheader>
              <cid>1</cid>
              <cname>cn</cname>
           </cheader>
           <lheader>
              <lid>1</lid>
              <lname>ln</lname>
           </lheader>
           <aheader>
              <aid>1</aid>
              <aname>an</aname>
           </aheader>
           <pos>
              <pid>1</pid>
              <pname>pay</pname>
           </pos>
           <pos>
              <pid>2</pid>
              <pname>pay1</pname>
           </pos>

    <lheader>
              <lid>1</lid>
              <lname>ln</lname>
           </lheader>
           <aheader>
              <aid>1</aid>
              <aname>an</aname>
           </aheader>
           <pos>
              <pid>1</pid>
              <pname>pay</pname>
           </pos>
           <pos>
              <pid>2</pid>
              <pname>pay1</pname>
           </pos>

           <mheader>
              <mid>2</mid>
              <mname>mh1</mname>
           </mheader>
           <cheader>
              <cid>2</cid>
              <cname>ch1</cname>
           </cheader>
           <lheader>
              <lid>2</lid>
              <lname>lh1</lname>
           </lheader>
           <aheader>
              <aid>2</aid>
              <aname>ah1</aname>
           </aheader>
           <pos>
              <pid>1</pid>
              <pname>pay</pname>
           </pos>
           <pos>
              <pid>2</pid>
              <pname>pay3</pname>
           </pos>
           <pos>
              <pid>3</pid>
              <pname>pay4</pname>
           </pos>

        </catalog>

I have to transform my xml like the one below

        <?xml version="1.0" encoding="UTF-8"?>
    <catalog>
        <record>
            <mheader>
                <mid>1</mid>
                <mname>mn</mname>
            </mheader>
            <cheader>
                <cid>1</cid>
                <cname>cn</cname>
            </cheader>
            <location>
                <lheader>
                    <lid>1</lid>
                    <lname>ln</lname>
                </lheader>
                <aheader>
                    <aid>1</aid>
                    <aname>an</aname>
                </aheader>
                <pos>
                    <pid>1</pid>
                    <pname>pay</pname>
                </pos>
                <pos>
                    <pid>2</pid>
                    <pname>pay1</pname>
                </pos>
                </location>
                    <location>
                        <lheader>
                            <lid>1</lid>
                            <lname>ln</lname>
                        </lheader>
                        <aheader>
                            <aid>1</aid>
                            <aname>an</aname>
                        </aheader>
                        <pos>
                            <pid>1</pid>
                            <pname>pay</pname>
                        </pos>
                        <pos>
                            <pid>2</pid>
                            <pname>pay1</pname>
                        </pos>
                        </location>
        </record>
        <record>
            <mheader>
                <mid>2</mid>
                <mname>mh1</mname>
            </mheader>
            <cheader>
                <cid>2</cid>
                <cname>ch1</cname>
            </cheader>
<location>
            <lheader>
                <lid>2</lid>
                <lname>lh1</lname>
            </lheader>
            <aheader>
                <aid>2</aid>
                <aname>ah1</aname>
            </aheader>
            <pos>
                <pid>1</pid>
                <pname>pay</pname>
            </pos>
            <pos>
                <pid>2</pid>
                <pname>pay3</pname>
            </pos>
            <pos>
                <pid>3</pid>
                <pname>pay4</pname>
            </pos>
</location>
            <location>
                <lheader>
                    <lid>2</lid>
                    <lname>lh1</lname>
                </lheader>
                <aheader>
                    <aid>2</aid>
                    <aname>ah1</aname>
                </aheader>
                <pos>
                    <pid>1</pid>
                    <pname>pay</pname>
                </pos>
                <pos>
                    <pid>2</pid>
                    <pname>pay3</pname>
                </pos>
                <pos>
                    <pid>3</pid>
                    <pname>pay4</pname>
                </pos>
            </location>
        </record>
    </catalog>

This is what i have done till now using group by feature of XSLT

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

    <xsl:preserve-space elements="xsl:text"/>
    <xsl:strip-space elements="*"/>
    <xsl:output encoding="utf-8" method="xml" indent="yes"/>

 <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="catalog">
        <xsl:copy>
            <xsl:for-each-group select="*" group-starting-with="mheader">
                <Record>
                    <xsl:for-each-group select="current-group()" group-starting-with="lheader">
                    <location>
                        <xsl:copy-of select="current-group()"/>
                    </location>
          </xsl:for-each-group>
                </Record>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

But in the transformed xml mheader and cheader tags are wrapped inside location tag like the one below

<location>
            <mheader>
                <mid>1</mid>
                <mname>mn</mname>
            </mheader>
            <cheader>
                <cid>1</cid>
                <cname>cn</cname>
            </cheader>
            </location>

Could anyone please help me on creating the right hierarchy of grouping

Thanks in Advance.


Solution

  • As said in a comment, depending on your needs it might suffice to copy the two elements to the output before the inner for-each-group and to then exclude them from the inner grouping:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="xs"
        version="3.0">
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:output method="xml" indent="yes"/>
      <xsl:strip-space elements="*"/>
    
        <xsl:template match="catalog">
            <xsl:copy>
                <xsl:for-each-group select="*" group-starting-with="mheader">
                    <Record>
                        <xsl:copy-of select="current-group()[self::mheader | self::cheader]"/>
                        <xsl:for-each-group select="current-group()[not(self::mheader | self::cheader)]" group-starting-with="lheader">
                        <location>
                            <xsl:copy-of select="current-group()"/>
                        </location>
              </xsl:for-each-group>
                    </Record>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/eiZQaFv has an online sample.