Home > Back-end >  Merging AND concatenation elements withing one SubNode and one element with XSLT 1.0
Merging AND concatenation elements withing one SubNode and one element with XSLT 1.0

Time:12-22

For a few hours I'm struggling to find a solution for this. I've seen many answers to questions like this but I can't seem to get them work for me.

So what I need is not only to merge same SubNodes (with the same names) but also to concatenate elements of these SubNodes into one element and then to merge (concatinate) the result into one element in one SubNode. I neeed to do it using XSLT transformation and I can only use XLST 1.0 (but I will also appreciate ways with 2.0 and 3.0).

Let's say I have the following XML:

 <ns0:INVOIC>
 <ns0:node1></ns0:node1>
 <ns0:node2></ns0:node2>
 .......
 <ns0:LOOPLin>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  .......... 
  <ns0:FTX_5>
   <FTX01>ZZZ</FTX01>
   <ns0:С108_05>
     <C10801>1</C10801>
     <C10802>2</C10802>
     <C10803>3</C10803>
     <C10804>4</C10804>
     <C10805>5</C10805>
   </ns0:С108_05>
  </ns0:FTX_5>
  <ns0:FTX_5>
   <FTX01>ZZZ</FTX01>
   <ns0:С108_05>
     <C10801>6</C10801>
     <C10802>7</C10802>
     <C10803>8</C10803>
     <C10804>9</C10804>
   </ns0:С108_05>
  </ns0:FTX_5>
  ..........
  <ns0:SubNodeN></ns0:SubNodeN>
 </ns0:LOOPLin> 
 
 <ns0:LOOPLin>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  .......... 
  <ns0:FTX_5>
   <FTX01>ZZZ</FTX01>
   <ns0:С108_05>
     <C10801>11</C10801>
     <C10802>12</C10802> 
   </С108_05>
  </ns0:FTX_5>
  <ns0:FTX_5>
   <FTX01>ZZZ</FTX01>
   <ns0:С108_05>
     <C10801>16</C10801>
     <C10802>17</C10802>
     <C10803>18</C10803>
   </ns0:С108_05>
  </ns0:FTX_5>
  ..........
  <ns0:SubNodeN></ns0:SubNodeN>
 </ns0:LOOPLin>

 <ns0:LOOPLin>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  .......... <!-- NO FTX segments in this LINLoop -->
  <ns0:SubNodeN></ns0:SubNodeN>
 </ns0:LOOPLin>
 ...........
 <ns0:nodeN></ns0:nodeN>
</ns0:INVOIC>

What I want to achieve:

<ns0:INVOIC>
 <ns0:node1></ns0:node1>
 <ns0:node2></ns0:node2>
 .......
 <ns0:LOOPLin>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  .......... 
  <ns0:FTX_5>
   <FTX01>ZZZ</FTX01>
   <ns0:С108_05>
     <C10801>1 2 3 4 5 6 7 8 9</C10801>
   </ns0:С108_05>
  </ns0:FTX_5>
  ..........
  <ns0:SubNodeN></ns0:SubNodeN>
 </ns0:LOOPLin> 
 
 <ns0:LOOPLin>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  .......... 
  <ns0:FTX_5>
   <FTX01>ZZZ</FTX01>
   <ns0:С108_05>
     <C10801>11 12 16 17 18</C10801>
   </ns0:С108_05>
  </ns0:FTX_5>
  ..........
  <ns0:SubNodeN></ns0:SubNodeN>
 </ns0:LOOPLin>

 <ns0:LOOPLin1>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  ..........  <!-- NO FTX because there's no FTX in source LINLoop -->
  <ns0:SubNodeN></ns0:SubNodeN>
 </ns0:LOOPLin>
 ...........
 <ns0:nodeN></ns0:nodeN>
</INVOIC> 

I tried to apply solution provided and modified by John Ernst and it works almost perfect BUT I loose all SubNodes in every LINLoop. That's what I get:

   <ns0:INVOIC>
     <ns0:node1></ns0:node1>
     <ns0:node2></ns0:node2>
     .......
     <ns0:LOOPLin>
      <ns0:FTX_5>
       <FTX01>ZZZ</FTX01>
       <ns0:С108_05>
         <C10801>1 2 3 4 5 6 7 8 9 </C10801>
       </ns0:С108_05>
      </ns0:FTX_5>
     </ns0:LOOPLin> 
     
     <ns0:LOOPLin>
      <ns0:FTX_5>
       <FTX01>ZZZ</FTX01>
       <ns0:С108_05>
         <C10801>11 12  16 17 18  </C10801>
       </ns0:С108_05>
      </ns0:FTX_5>
     </ns0:LOOPLin>
    
     <ns0:LOOPLin>
      <ns0:FTX_5/>
     </ns0:LOOPLin>

     <ns0:nodeN></ns0:nodeN>
    </INVOIC>

So as you can see I miss all SubNodes and I have extra spaces in C10801 in case if not all C10801..5 are present and I have empty FTX_5 in case if source LINLoop misses FTX_5.

I added few lines and now I'm trying to use this XSLT:

  <xsl:template match="ns0:INVOIC">
    <ns0:INVOIC>      
      <xsl:apply-templates />
    </ns0:INVOIC>
  </xsl:template>

    <!-- This approach uses the MUENCHIAN METHOD -->

    <!-- Match the LoopSubNodeN using the parent LoopNode id and child ConstantElement -->
    <xsl:key name="myKey" match="ns0:FTX_5" use="concat(generate-id(..), FTX01)"/>

    <xsl:template match="ns0:LINLoop">
       <ns0:LINLoop> <!-- Added -->
        <xsl:variable name="LoopNodeID" select="generate-id(.)"/>
        <xsl:copy-of select="./*[not(self::ns0:FTX_5)]" /> <!-- Added -->       
            <ns0:FTX_5>
                <!-- Group on parent LoopNode and ConstantElement-->
                <xsl:for-each select="ns0:FTX_5[generate-id(.) = generate-id(key('myKey', concat($LoopNodeID, FTX01))[1])]">
                    <FTX01>
                        <xsl:value-of select="FTX01" />
                    </FTX01>
                    <ns0:C108_5>
                        <C10801>
                            <!-- Loop through each LoopSubSubNode for the current LoopNode parent. -->
                            <xsl:for-each select="key('myKey', concat($LoopNodeID, FTX01))">
                                <xsl:value-of select="concat(normalize-space(concat(ns0:C108_5/C10801/text(), ' ', ns0:C108_5/C10802/text(), ' ', ns0:C108_5/C10803/text(), ' ', ns0:C108_5/C10804/text(), ' ', ns0:C108_5/C10805/text())), ' ')"/>
                            </xsl:for-each>
                        </C10801>
                    </ns0:C108_5>
                </xsl:for-each>
            </ns0:FTX_5>
       </ns0:LINLoop>      
    </xsl:template>

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

Now it's almost perfect. That's what I get:

<ns0:INVOIC>
 <ns0:node1></ns0:node1>
 <ns0:node2></ns0:node2>
 .......
 <ns0:LOOPLin>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  .......... 
  <ns0:SubNodeN></ns0:SubNodeN>
  <ns0:FTX_5>
   <FTX01>ZZZ</FTX01>
   <ns0:С108_05>
     <C10801>1 2 3 4 5 6 7 8 9 </C10801>
   </ns0:С108_05>
  </ns0:FTX_5>
 </ns0:LOOPLin> 
 
 <ns0:LOOPLin1>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  ..........
  <ns0:SubNodeN></ns0:SubNodeN>
  <ns0:FTX_5>
   <FTX01>ZZZ</FTX01>
   <ns0:С108_05>
     <C10801>11 12 16 17 18 </C10801>
   </ns0:С108_05>
  </ns0:FTX_5>
 </ns0:LOOPLin>

 <ns0:LOOPLin1>
  <ns0:SubNode1></ns0:SubNode1>
  <ns0:SubNode2></ns0:SubNode2>
  ..........
  <ns0:SubNodeN></ns0:SubNodeN>
  <ns0:FTX_5/>
 </ns0:LOOPLin>
 ...........
 <ns0:nodeN></ns0:nodeN>
</INVOIC>   

So everything is present but FTX_5 moved to the end of every LINLoop. It's not critical for me but nevertheless my final questions are:

  1. Is there any way to achieve the desired result in more smart way, without <xsl:copy-of select="./*[not(self::ns0:FTX_5)]" />?
  2. Is there a way to leave FTX_5 on it's place, not in the end of LINLoop (probably related to Q1)?
  3. Is there a way to get rid of empty FTX_5 if source LINLoop does not contain it?

John Ernst, thank you so much for your help!

CodePudding user response:

<!-- This approach uses the MUENCHIAN METHOD -->

<!-- Match the LoopSubNodeN using the parent LoopNode id and child ConstantElement -->
<xsl:key name="myKey" match="LoopSubNodeN" use="concat(generate-id(..), ConstantElement)"/>

<xsl:template match="LoopNode">
  <xsl:variable name="LoopNodeID" select="generate-id(.)"/>
  <xsl:copy>
    <xsl:element name="LoopSubNodeN">
      <!-- Group on parent LoopNode and ConstantElement-->
      <xsl:for-each select="LoopSubNodeN[generate-id(.) = generate-id(key('myKey', concat($LoopNodeID, ConstantElement))[1])]">
        <xsl:element name="ConstantValue">
          <xsl:value-of select="ConstantElement"/>
        </xsl:element>
        <xsl:element name="LoopSubSubNode">
          <xsl:element name="Element1">
            <!-- Loop through each LoopSubSubNode for the current LoopNode parent. -->
            <xsl:for-each select="key('myKey', concat($LoopNodeID, ConstantElement))">
              <xsl:value-of select="concat(LoopSubSubNode/Element1, ' [', LoopSubSubNode/Element2, ' ', LoopSubSubNode/Element3, ' ', LoopSubSubNode/Element4, ' ', LoopSubSubNode/Element5, '] ')"/>
            </xsl:for-each>      
          </xsl:element>
        </xsl:element>
      </xsl:for-each>
    </xsl:element>
  </xsl:copy>
</xsl:template>

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