Search code examples
xslt

Move all XML siblings inside the first one


I've searched through similar questions, but couldn't make any of the suggestions to work. I have the following XML that I need to modify:

  <XDB>
     <ROOT>
           <KEY><ID>12345</ID><DATE>5/10/2011</DATE></KEY>
           <PERSONAL><ID>1</ID><INFO><LASTNAME>Smith</LASTNAME>...</INFO></PERSONAL>
           <CONTACT><ID>1</ID><EMAIL>asmith@yahoo.com</EMAIL>...</CONTACT>
     </ROOT>
    <ROOT>
           <KEY><ID>98765</ID><DATE>5/10/2013</DATE></KEY>
           <CONTACT><ID>2</ID><EMAIL>psmithton@yahoo.com</EMAIL>...</CONTACT>
     </ROOT>

     ...

  </XDB>

And it needs to look like this:

 <XDB>
     <ROOT>
       <KEY><ID>12345</ID><DATE>5/10/2011</DATE>
           <PERSONAL><ID>1</ID><INFO><LASTNAME>Smith</LASTNAME>...</INFO></PERSONAL>
           <CONTACT><ID>1</ID><EMAIL>asmith@yahoo.com</EMAIL>...</CONTACT>
        </KEY>
           
     </ROOT>
    <ROOT>
       <KEY><ID>98765</ID><DATE>5/10/2013</DATE>
           <CONTACT><ID>2</ID><EMAIL>psmithton@yahoo.com</EMAIL>...</CONTACT>
       </KEY>
     </ROOT>

     ...

  </XDB>

I need to make 2...n siblings as children of the first 'key' sibling. Essentially, I need to remove the closing </KEY> and put it before the closing </ROOT>.


Solution

  • Following xslt based on Identity transform could make this job

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
        <!-- Copy everything you find... -->
        <xsl:template match="node()|@*">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" />
            </xsl:copy>
        </xsl:template>
    
        <!-- ... but if you find first element inside ROOT ... -->
        <xsl:template match="ROOT/node()[1]">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" />
                <!-- ... copy its sibling into it ... -->
                <xsl:copy-of select="following-sibling::*" />
            </xsl:copy>
        </xsl:template>
    
        <!-- ignore other elements inside ROOT element since they are copied in template matching first element -->
        <xsl:template match="ROOT/node()[position() &gt; 1]" />
    
    </xsl:stylesheet>