Search code examples
xmlxsltxslt-1.0

XSLT code to insert segment under parent segment


I have a input XML document in which need to move few segments under parent segment with condition <c> should come under <b> segment when <b2> is "8R" only. For other segments <e> and <f> there is no condition, just need to move under <d>.

<?xml version="1.0" encoding="UTF-8"?>
<ns0:TEST_Report xmlns:ns0="urn:abc">
   <Record>
      <a>
         <a1>123</a1>
         <a2>11</a2>
      </a>
      <b>
         <b1>23</b1>
         <b2>SJ</b2>
      </b>
      <b>
         <b1>67</b1>
         <b2>8S</b2>
      </b>
      <b>
         <b1>90</b1>
         <b2>8R</b2>
      </b>
      <c>
         <c1>asd</c1>
         <c2>2342445435346</c2>
      </c>
      <c>
         <c1>wsx</c1>
         <c2>8798987978</c2>
      </c>
      <d>
         <d1>XYZ</d1>
         <d2>cvb</d2>
      </d>
      <e>
         <e1>1212</e1>
      </e>
      <e>
         <e1>4321</e1>
      </e>
      <f>
         <f1>11</f1>
         <f2>11</f2>
      </f>
      <f>
         <f1>133</f1>
         <f2>1133</f2>
      </f>
      <f>
         <f1>1345</f1>
         <f2>54434</f2>
      </f>
      <d>
         <d1>XYZ</d1>
         <d2>cvb</d2>
      </d>
      <e>
         <e1>1212</e1>
      </e>
      <f>
         <f1>11</f1>
         <f2>11</f2>
      </f>
      <f>
         <f1>133</f1>
         <f2>1133</f2>
      </f>
   </Record>
</ns0:TEST_Report>

XSLT code i have tried:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:abc">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="TEST_Report">
        <ns0:TEST_Report>
            <xsl:apply-templates select="@*|node()"/>
        </ns0:TEST_Report>
    </xsl:template>
    <xsl:template match="B">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
            <xsl:apply-templates select="following-sibling::C[1]"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="C">
        <xsl:if test="preceding-sibling::B[3]">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Required Output:

<?xml version="1.0" encoding="UTF-8"?>
<ns0:TEST_Report xmlns:ns0="urn:abc">
   <Record>
      <a>
         <a1>123</a1>
         <a2>11</a2>
      </a>
      <b>
         <b1>23</b1>
         <b2>SJ</b2>
      </b>
      <b>
         <b1>67</b1>
         <b2>8S</b2>
      </b>
      <b>
         <b1>90</b1>
         <b2>8R</b2>
         <c>
            <c1>asd</c1>
            <c2>2342445435346</c2>
         </c>
         <c>
            <c1>wsx</c1>
            <c2>8798987978</c2>
         </c>
      </b>
      <d>
         <d1>XYZ</d1>
         <d2>cvb</d2>
         <e>
            <e1>1212</e1>
         </e>
         <e>
            <e1>4321</e1>
         </e>
         <f>
            <f1>11</f1>
            <f2>11</f2>
         </f>
         <f>
            <f1>133</f1>
            <f2>1133</f2>
         </f>
         <f>
            <f1>1345</f1>
            <f2>54434</f2>
         </f>
      </d>
      <d>
         <d1>XYZ</d1>
         <d2>cvb</d2>
         <e>
            <e1>1212</e1>
         </e>
         <f>
            <f1>11</f1>
            <f2>11</f2>
         </f>
         <f>
            <f1>133</f1>
            <f2>1133</f2>
         </f>
      </d>
   </Record>
</ns0:TEST_Report>

Kindly help to achieve this using xslt 1.0 code. I have tried few other codes as well but it is not working for this requirement.


Solution

  • This is more complicated than it might seem - not because of the condition on b, but because you have more than one d element, each with its own "tail" of e and f elements.

    From the context of the first d, all subsequent e and f elements are on the following-sibling axis - regardless of any "intervening" d elements placed along the way. So basically you are looking for an XSLT 1.0 implementation of group-starting-with, which could be handled as:

    XSLT 1.0

    <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"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:key name="c" match="c" use="generate-id(preceding-sibling::b[b2='8R'][1])" />
    <xsl:key name="ef" match="e|f" use="generate-id(preceding-sibling::d[1])" />
    
    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="b[b2='8R']">
        <xsl:copy>
            <xsl:copy-of select="*"/>
            <xsl:copy-of select="key('c', generate-id())"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="d">
        <xsl:copy>
            <xsl:copy-of select="*"/>
            <xsl:copy-of select="key('ef', generate-id())"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="c[preceding-sibling::b[1]/b2='8R'] | e | f"/>
    
    </xsl:stylesheet>
    

    If every c has a preceding sibling b that contains <b2>8R</b2>, then you can simplify the last template to:

    <xsl:template match="c|e|f"/>