簡體   English   中英

如何使用XSLT基於屬性值遞歸嵌套XML節點?

[英]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的價值Segmenttype與屬性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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM