Srinivas Bajina
Srinivas Bajina

Reputation: 17

Select sibling records until next tag using XSLT 1.0

I have following input XML. The successive E_Records are optional and it should be populated into L_Record. I have written the below XSLT coding. Is there any changes should i have to do?

Input XML

<?xml version="1.0" encoding="UTF-8"?>
  <Record>
     <H_Record>
        <Rec_Type>H</Rec_Type>
     </H_Record>
     <C_Record>
        <Rec_Type>C</Rec_Type>
     </C_Record>
     <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>
     </L_Record>
     <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>3</E_Qty>
     </E_Record>
     <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>4</E_Qty>
     </E_Record>
     <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>
     </L_Record>
     <R_Record>
        <Rec_Type>R</Rec_Type>
     </R_Record>
  </Record>
  <Record>
     <H_Record>
        <Rec_Type>H</Rec_Type>
     </H_Record>
     <C_Record>
        <Rec_Type>C</Rec_Type>
     </C_Record>
     <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>
     </L_Record>
     <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>
     </L_Record>
     <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>1</E_Qty>
     </E_Record>
     <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>2</E_Qty>
     </E_Record>
     <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>
     </L_Record>
     <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>5</E_Qty>
     </E_Record>
     <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>6</E_Qty>
     </E_Record>
     <R_Record>
        <Rec_Type>R</Rec_Type>
     </R_Record>
  </Record>

The Output XML i am expecting is

 <Record>
   <H_Record>
        <Rec_Type>H</Rec_Type>
   </H_Record>
   <C_Record>
        <Rec_Type>C</Rec_Type>
   </C_Record>
   <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>
      <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>3</E_Qty>
      </E_Record>
      <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>4</E_Qty>
      </E_Record>
    </L_Record>
    <L_Record>
       <Rec_Type>L</Rec_Type>
       <L_Level>2</L_Level>
    </L_Record>
    <R_Record>
       <Rec_Type>R</Rec_Type>
    </R_Record>
  </Record>
  <Record>
     <H_Record>
        <Rec_Type>H</Rec_Type>
     </H_Record>
     <C_Record>
        <Rec_Type>C</Rec_Type>
     </C_Record>
     <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>
     </L_Record>
     <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>      
       <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>1</E_Qty>
       </E_Record>
       <E_Record>
        <Rec_Type>E</Rec_Type>
        <E_Qty>2</E_Qty>
       </E_Record>
     </L_Record>
     <L_Record>
        <Rec_Type>L</Rec_Type>
        <L_Level>2</L_Level>      
        <E_Record>
          <Rec_Type>E</Rec_Type>
          <E_Qty>5</E_Qty>
        </E_Record>
        <E_Record>
          <Rec_Type>E</Rec_Type>
          <E_Qty>6</E_Qty>
        </E_Record>
    </L_Record>
    <R_Record>
        <Rec_Type>R</Rec_Type>
     </R_Record>
  </Record>

I have written the XSLT mapping for this as below but i am not getting the required output. Could you please help me on this?

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output omit-xml-declaration="yes" indent="yes"/>
         <xsl:template match="@* | node()">
      <xsl:copy>
         <xsl:apply-templates select="@* | node()"/>
      </xsl:copy>
   </xsl:template>
   <xsl:template match="Record/L_Record">
   <L_Record>
    <xsl:variable name="header" select="."/>
            <xsl:apply-templates/>
            <xsl:if test = "not(following-sibling::L_Record)">
            <xsl:for-each select="following-sibling::E_Record[preceding-sibling::L_Record = $header]">
            <xsl:copy-of select="."/>
            </xsl:for-each>
            </xsl:if>
    </L_Record> 
    </xsl:template>
 </xsl:stylesheet>

Please help me on this?
When i am executing the above code, Record 1 is working fine, but the record 2 is not working properly. The E-Record segment is not appearing in the L-Record segment.

Upvotes: 0

Views: 97

Answers (3)

Daniel Haley
Daniel Haley

Reputation: 52888

You should be able to create an xsl:key matching E_Record elements and use the generated id of the first preceding L_Record sibling as the key.

Then you can match L_Record elements and copy the node set with the matching key.

Example...

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

  <xsl:key name="e_recs" match="E_Record" use="generate-id(preceding-sibling::L_Record[1])"/>

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

  <xsl:template match="L_Record">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
      <xsl:copy-of select="key('e_recs',generate-id())"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="E_Record"/>

</xsl:stylesheet>

Fiddle: http://xsltfiddle.liberty-development.net/94Acsmd

Upvotes: 0

Sudeep Rawat
Sudeep Rawat

Reputation: 239

Hi Please test this code:

  <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="L_Record">
        <xsl:copy>
            <xsl:apply-templates/>
            <xsl:if test="following-sibling::*[1][self::E_Record]">
            <xsl:call-template name="Next_E_Record">
                <xsl:with-param name="next" select="following-sibling::*[1][self::E_Record]"></xsl:with-param>
            </xsl:call-template>
            </xsl:if>
        </xsl:copy>
    </xsl:template>

   <xsl:template name="Next_E_Record">
       <xsl:param name="next"/>
       <xsl:copy-of select="$next"/>
       <xsl:if test="$next/following-sibling::*[1][self::E_Record]">
           <xsl:call-template name="Next_E_Record">
               <xsl:with-param name="next" select="$next/following-sibling::*[1][self::E_Record]"></xsl:with-param>
           </xsl:call-template>
       </xsl:if>
   </xsl:template>

    <xsl:template match="E_Record"/>


</xsl:stylesheet>

See Transformation at https://xsltfiddle.liberty-development.net/bFWRApe

Upvotes: 2

FonnParr
FonnParr

Reputation: 335

One way would be to loop through the children of record until you find an L_Record then add siblings until you find another L_Record

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Record">
        <Record>
            <xsl:for-each select='*'>
                <xsl:choose>
                    <xsl:when test='name()="E_Record"'/>
                    <xsl:when test='name()="L_Record"'>
                        <L_Record>

                            <xsl:copy-of select="*"/>
                            <xsl:call-template name='next'>
                                <xsl:with-param name='current_L' select='.'/>
                                <xsl:with-param name='remainder' select='following-sibling::*'/>
                            </xsl:call-template>
                        </L_Record>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="."/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>
        </Record>
    </xsl:template>

    <xsl:template name='next'>
        <xsl:param name='current_L'/>
        <xsl:param name='remainder'/>
        <xsl:param name='first' select='$remainder[1]'/>

        <xsl:if test='$remainder and not(name($first)="L_Record")'>
            <xsl:copy-of select="$first"/>

            <xsl:call-template name='next'>
                <xsl:with-param name='current_L' select='$current_L'/>
                <xsl:with-param name='remainder' select='$remainder[position() &gt; 1]'/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Related Questions