How can I recursively nest XML nodes, based on an attribute value, using XSLT? -


i need transform xml using xlst 1.0 in visual studio 2013.

i have following xml:

<?xml version="1.0" encoding="utf-8"?> <root>   <messagetemplates>     <messagetemplate>       <segment name="uno" cardinality="first">         <value>something</value>       </segment>       <segment name="dos" cardinality="second">         <value>something</value>       </segment>       <segment name="tres" cardinality="third">         <value>something</value>       </segment>       <segment name="quatro" cardinality="third">         <value>something</value>       </segment>       <segment name="cinco" cardinality="second">         <value>something</value>       </segment>       <segment name="seis" cardinality="third">         <value>something</value>       </segment>       <segment name="siete" cardinality="first">         <value>something</value>       </segment>     </messagetemplate>   </messagetemplates> </root> 

the cardinality attribute of segment node ordinal, first being highest, , third being lowest. need create nested levels, based on cardinality, follows:

<?xml version="1.0" encoding="utf-8"?> <root>   <messagetemplates>     <messagetemplate>       <cardinality type="first">         <segment name="uno">           <value>something</value>         </segment>         <cardinality type="second">           <segment name="dos">             <value>something</value>           </segment>           <cardinality type="third">             <segment name="tres">               <value>something</value>             </segment>             <segment name="quatro">               <value>something</value>             </segment>           </cardinality>           <segment name="cinco">             <value>something</value>           </segment>           <cardinality type="third">             <segment name="seis">               <value>something</value>             </segment>           </cardinality>         </cardinality>         <segment name="siete">           <value>something</value>         </segment>       </cardinality>     </messagetemplate>   </messagetemplates> </root> 

i have tried several different ways transform file, have failed. i've searched , read dozens of posts, haven't found cases match trying do. have tried searching incremental ways accomplish goal, such processing 1 segment @ time recursive template calls, etc. closest have come following xslt:

<xsl:template match="messagetemplates/messagetemplate">   <messagetemplate>     <xsl:copy-of select="@*"/>     <xsl:call-template name="cardinality"/>   </messagetemplate> </xsl:template>  <xsl:template name="cardinality" match="messagetemplates/messagetemplate/segment">   <xsl:choose>     <xsl:when test="position() = 1">       <cardinality type="{segment/@cardinality}">         <segment>           <xsl:apply-templates select="@*[name() != 'cardinality'] | node()" />         </segment>       </cardinality>     </xsl:when>      <xsl:when test="position() != last() , following-sibling::segment/@cardinality != @cardinality">       <cardinality type="{@cardinality}">         <segment>           <xsl:apply-templates select="@*[name() != 'cardinality'] | node()" />         </segment>       </cardinality>     </xsl:when>      <xsl:when test="position() = last()">       <segment>         <xsl:apply-templates select="@*[name() != 'cardinality'] | node()" />       </segment>     </xsl:when>   </xsl:choose> </xsl:template> 

which produced following xml:

<?xml version="1.0" encoding="utf-8"?> <root>   <version>1.0</version>   <messagetemplates>     <messagetemplate>       <cardinality type="first">         <segment>           <cardinality type="">             <segment name="uno">               <value>something</value>             </segment>           </cardinality>           <cardinality type="second">             <segment name="dos">               <value>something</value>             </segment>           </cardinality>           <cardinality type="third">             <segment name="tres">               <value>something</value>             </segment>           </cardinality>           <cardinality type="third">             <segment name="quatro">               <value>something</value>             </segment>           </cardinality>           <cardinality type="second">             <segment name="cinco">               <value>something</value>             </segment>           </cardinality>           <cardinality type="third">             <segment name="seis">               <value>something</value>             </segment>           </cardinality>           <segment name="siete">             <value>something</value>           </segment>         </segment>       </cardinality>     </messagetemplate>   </messagetemplates> </root> 

basically, want wrap all segment nodes in single cardinality node. then, if cardinality value of next segment lower cardinality value of current segment, want wrap following segment nodes in cardinality node, long cardinality value same. want happen each cardinality level. finally, want move cardinality value of segment type attribute of cardinality node. order of segment nodes must maintained.

any appreciated.

here recursive approach. produce required output, @ least given example. i'm not happy it. not reliable, nor fast, nor maintainable, @ least gives basic idea. (if there no better one)

<xsl:stylesheet version="1.0"      xmlns:xsl="http://www.w3.org/1999/xsl/transform">     <xsl:output method="xml" indent="yes" />       <xsl:template match="@* | node()">         <xsl:copy>             <xsl:apply-templates select="@* | node()"/>         </xsl:copy>     </xsl:template>      <xsl:template match="segment/@cardinality"  />      <xsl:template match="messagetemplate">         <xsl:copy>             <cardinality type="first">                 <xsl:apply-templates select="segment[1]" mode="nested" >                     <xsl:with-param name="currentcardinality" select="'first'" />                 </xsl:apply-templates>             </cardinality>         </xsl:copy>     </xsl:template>      <xsl:template name="comaprenext">         <xsl:variable name="this" select="@cardinality" />         <xsl:variable name="next" select="following-sibling::segment[1]/@cardinality" />         <xsl:choose>             <xsl:when test="$this= $next" >                 <xsl:text>eq</xsl:text>             </xsl:when>             <xsl:when test="($this='first' , ($next = 'second' or $next = 'third') ) or                                 ($this='second' , ( $next = 'third') )" >                 <xsl:text>lt</xsl:text>             </xsl:when>             <xsl:otherwise>                 <xsl:text>gt</xsl:text>             </xsl:otherwise>         </xsl:choose>     </xsl:template>      <xsl:template match="segment"  mode="nested">         <xsl:param name="currentcardinality"/>         <xsl:variable name="this" select="." />         <xsl:variable name="next">             <xsl:call-template name="comaprenext"/>         </xsl:variable>         <xsl:variable name="next_le" select="$next='lt' or $next = 'eq'" />         <xsl:choose>             <xsl:when test="@cardinality = $currentcardinality  ">                 <!-- copy segment without cardinality -->                 <xsl:apply-templates select="."  />                 <xsl:apply-templates select="following-sibling::segment[1][$next_le]" mode="nested" >                     <xsl:with-param name="currentcardinality" select="@cardinality" />                 </xsl:apply-templates>             </xsl:when>             <xsl:otherwise>                 <cardinality type="{@cardinality}" >                     <xsl:apply-templates select="."  />                     <xsl:apply-templates select="following-sibling::segment[1][$next_le]" mode="nested" >                         <xsl:with-param name="currentcardinality" select="@cardinality" />                     </xsl:apply-templates>                     <xsl:if test="@cardinality = 'second'  ">                         <!-- find same cardinality not next -->                         <xsl:apply-templates select="(following-sibling::segment[position() != 1][not(@cardinality ='third')])[1][@cardinality = $this/@cardinality]" mode="nested" >                             <xsl:with-param name="currentcardinality" select="@cardinality" />                         </xsl:apply-templates>                     </xsl:if>                 </cardinality>             </xsl:otherwise>         </xsl:choose>         <xsl:if test="@cardinality = 'first'  ">             <!-- find same cardinality not next -->             <xsl:apply-templates select="(following-sibling::segment[position() != 1])[@cardinality = $this/@cardinality][1]" mode="nested" >                 <xsl:with-param name="currentcardinality" select="@cardinality" />             </xsl:apply-templates>         </xsl:if>     </xsl:template> </xsl:stylesheet> 

which generate following output:

<messagetemplates>   <messagetemplate>   <cardinality type="first">     <segment name="uno">       <value>something</value>     </segment>     <cardinality type="second">       <segment name="dos">         <value>something</value>       </segment>       <cardinality type="third">         <segment name="tres">           <value>something</value>         </segment>         <segment name="quatro">           <value>something</value>         </segment>       </cardinality>       <segment name="cinco">         <value>something</value>       </segment>       <cardinality type="third">         <segment name="seis">           <value>something</value>         </segment>       </cardinality>     </cardinality>     <segment name="siete">       <value>something</value>     </segment>   </cardinality>  </messagetemplate> </messagetemplates> 

Comments

Popular posts from this blog

Django REST Framework perform_create: You cannot call `.save()` after accessing `serializer.data` -

Why does Go error when trying to marshal this JSON? -