[英]How can I recursively nest XML nodes, based on an attribute value, using XSLT?
我需要在Visual Studio 2013中使用XLST 1.0轉換一些XML。
我有以下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>
Segment
節點的cardinality
屬性為序數, first
為最高, third
為最低。 我需要基於cardinality
創建嵌套級別,如下所示:
<?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>
我嘗試了幾種不同的方法來轉換此文件,但是都失敗了。 我已經搜索了SO,閱讀了數十篇文章,但沒有找到與我嘗試的內容相符的案例。 我還嘗試過尋找實現我的目標的增量方式,例如一次只使用遞歸模板調用處理一個Segment
等。我最接近的是以下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() and 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>
產生了以下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>
基本上,我想要將所有 Segment
節點都包裝在一個Cardinality
節點中。 然后,如果cardinality
在未來的價值Segment
比低cardinality
的電流值Segment
,我想所有的包裹下 Segment
節點的Cardinality
節點,只要cardinality
值是一樣的。 我希望每個cardinality
級別都發生這種情況。 最后,我想給移動cardinality
的價值Segment
的type
與屬性Cardinality
節點。 Segment
節點的順序必須保持。
任何幫助將不勝感激。
您可以以此為起點。
它使用Muenchian分組方法來生成基數的唯一列表,並按順序顯示基數在源XML文檔中的順序。
從列表中的第一個基數開始,每個基數都提取匹配的段,然后遞歸到列表中的下一個基數-這樣就實現了所需的嵌套。
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="segment-by-cardinality" match="Segment" use="@cardinality" />
<xsl:variable name="cardinalities">
<!-- generate a distinct list of cardinalities -->
<xsl:for-each select="root/MessageTemplates/MessageTemplate/Segment[count(. | key('segment-by-cardinality', @cardinality)[1]) = 1]">
<Cardinality type="{@cardinality}"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cardinalities-set" select="exsl:node-set($cardinalities)/Cardinality" />
<xsl:variable name="source-doc" select="/" />
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="MessageTemplate">
<xsl:copy>
<!-- start with the top-level cardinality -->
<xsl:apply-templates select="$cardinalities-set[1]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Cardinality">
<xsl:variable name="type" select="@type" />
<xsl:copy>
<xsl:copy-of select="@*"/>
<!-- switch the context back to the XML source in order to use key -->
<xsl:for-each select="$source-doc">
<xsl:apply-templates select="key('segment-by-cardinality', $type)"/>
</xsl:for-each>
<!-- proceed to the next cardinality in the list -->
<xsl:apply-templates select="following-sibling::Cardinality[1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
應用於示例輸入,結果將是:
<?xml version="1.0" encoding="utf-8"?>
<root>
<MessageTemplates>
<MessageTemplate>
<Cardinality type="first">
<Segment name="Uno" cardinality="first">
<value>something</value>
</Segment>
<Segment name="Siete" cardinality="first">
<value>something</value>
</Segment>
<Cardinality type="second">
<Segment name="Dos" cardinality="second">
<value>something</value>
</Segment>
<Segment name="Cinco" cardinality="second">
<value>something</value>
</Segment>
<Cardinality type="third">
<Segment name="Tres" cardinality="third">
<value>something</value>
</Segment>
<Segment name="Quatro" cardinality="third">
<value>something</value>
</Segment>
<Segment name="Seis" cardinality="third">
<value>something</value>
</Segment>
</Cardinality>
</Cardinality>
</Cardinality>
</MessageTemplate>
</MessageTemplates>
</root>
請注意,這與您“ 必須保持細分節點的順序 ” 的要求不符。 我不完全了解此要求。 如果您有一些標准可以對基數的子級(即其段和更高的基數)進行排序,則可以另一遍操作。 但是由於更高的基數可以包含多個細分,其中一些細分可能位於當前細分之前,而有些則不然,所以我不太清楚“正確”的順序是什么。
這是一種遞歸方法。 至少對於給定的示例,它確實會產生所需的輸出。 我對此並不滿意。 它不是真正可靠,快速或不可維護,但至少它為您提供了基本概念。 (如果沒有更好的選擇)
<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' and ($next = 'second' or $next = 'third') ) or
($this='second' and ( $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 but 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 but 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>
生成以下輸出:
<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>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.