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
Post a Comment