首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用XSLT递归地嵌套基于属性值的XML节点?

如何使用XSLT递归地嵌套基于属性值的XML节点?
EN

Stack Overflow用户
提问于 2016-04-21 20:28:09
回答 2查看 276关注 0票数 0

我需要在Visual 2013中使用XLST1.0转换一些XML。

我有以下XML:

代码语言:javascript
复制
<?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>

cardinality节点的Segment属性是顺序的,first是最高的,third是最低的。我需要基于cardinality创建嵌套级别,如下所示:

代码语言:javascript
复制
<?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>

我尝试过几种不同的方法来转换这个文件,但是都失败了。我已经搜索和阅读了几十个帖子,但没有发现任何与我想做的事情相匹配的案例。我还尝试寻找实现目标的增量方法,例如每次只使用递归模板调用处理一个Segment,最近的是使用以下XSLT:

代码语言:javascript
复制
<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:

代码语言:javascript
复制
<?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节点中。然后,如果下一个SegmentSegment值低于当前Segmentcardinality值,我希望将所有下面的Segment节点包装在Cardinality节点中,只要cardinality值是相同的。我希望每个cardinality级别都会发生这种情况。最后,我希望将SegmentSegment值移动到Cardinality节点的type属性。必须维护Segment节点的顺序。

任何帮助都将不胜感激。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-04-22 16:25:59

这里是一种递归方法。它确实产生了所需的输出,至少对于给定的示例是这样的。我对此并不满意。它不是真正可靠的,也不是快速的,也不是可维护的,但至少它给了您基本的想法。(如果没有更好的)

代码语言:javascript
复制
<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>

生成以下输出:

代码语言:javascript
复制
<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>
票数 1
EN

Stack Overflow用户

发布于 2016-04-21 22:14:19

这里有些东西你可以用作为你的起点。

它使用门窗分组法生成一个独立的基数列表,按照它们在源XML文档中的出现顺序。

从列表中的第一个基数开始,每个基数获取匹配段,然后递归到列表上的下一个基数,从而实现所需的嵌套。

XSLT1.0

代码语言:javascript
复制
<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> 

应用于示例输入,结果如下:

代码语言:javascript
复制
<?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>

请注意,这不符合“必须维护节节点的顺序”的要求。我不完全理解这个要求。如果您有一些条件来对基数的子级(即它的段和下一个更高的基数)进行排序,那么您可以在另一次传递中这样做。但是由于下一个更高的基数可以包含几个片段,其中一些可能先于当前的部分,而另一些则没有,我不太明白什么是“正确的”顺序。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/36779806

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档