xslt 1.0合并空元素名称

时间:2015-04-28 01:13:15

标签: xml xslt xslt-1.0

关于使用xslt 1.0的一个快速问题,你可以帮助我。我有一个输入xml,如下所示

<Root>
    <FirstName>Bob</FirstName>
    <LastName>Marley</LastName>
    <ID>BM1234</ID>
    <Songs>
        <Song>
            <EmptyElements></EmptyElements>
            <SongName>No woman no cry</SongName>
            <Year>1974</Year>
            <album></album>
            <studio></studio>
            <rating></rating>
        </Song>
    </Songs>
</Root>

输出需要看起来像

<Root>
    <FirstName>Bob</FirstName>
    <LastName>Marley</LastName>
    <ID>BM1234</ID>
    <Songs>
        <Song>
            <EmptyElements>album, studio, rating</EmptyElements>
            <SongName>No woman no cry</SongName>
            <Year>1974</Year>
        </Song>
    </Songs>
</Root>

所以基本上是一个逗号分隔的所有空元素列表到EmptyElements标记中。

2 个答案:

答案 0 :(得分:2)

或者简单地说:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="Song">
    <xsl:copy>
        <EmptyElements>
            <xsl:for-each select="*[not(node() or self::EmptyElements)]">
                 <xsl:value-of select="name()"/>
                 <xsl:if test="position()!=last()">
                    <xsl:text>, </xsl:text>
                 </xsl:if>
            </xsl:for-each> 
        </EmptyElements>
        <xsl:apply-templates select="*[node()]"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

注意:

此解决方案自豪地使用last()功能。没有与使用此功能相关的性能问题。

XPath规范声明:

  

最后一个函数返回一个等于上下文大小的数字   表达评估背景。

XSLT规范告诉我们:

  

关于上下文发生表达式评估。 ......   上下文包括:

     

•节点(上下文节点)
   •一对非零正整数(上下文位置和上下文大小)
    ...

对于列表中的每个节点,处理器将一次又一次地重新计算当前节点列表中的所有节点的想法简直是荒谬的。一旦建立了上下文(通过调用xsl:for-eachxsl:apply-templates),上下文大小就已知并且不会改变。

这个结论也可以很容易地进行测试:使用10k项目列表,在评估时没有发现可辨别的差异:

<xsl:for-each select="item">
    <xsl:value-of select="position()!=last()"/>
</xsl:for-each>

针对:

<xsl:for-each select="item">
    <xsl:value-of select="not(position() = 1)"/>
</xsl:for-each>

(用libxslt,Xalan和Saxon测试)。

答案 1 :(得分:1)

此转化

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="EmptyElements" priority="5">
    <xsl:copy>
      <xsl:apply-templates mode="enumerate" select=
      "../*[not(self::EmptyElements) and not(node())]" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Songs/Song/*" mode="enumerate">
    <xsl:value-of select="substring(',', not(position() = 1), 1)"/>
    <xsl:value-of select="name()"/>
  </xsl:template>
  <xsl:template match="Songs/Song/*[not(node())]"/>
</xsl:stylesheet>

应用于提供的源XML文档

<Root>
    <FirstName>Bob</FirstName>
    <LastName>Marley</LastName>
    <ID>BM1234</ID>
    <Songs>
        <Song>
            <EmptyElements></EmptyElements>
            <SongName>No woman no cry</SongName>
            <Year>1974</Year>
            <album></album>
            <studio></studio>
            <rating></rating>
        </Song>
    </Songs>
</Root>

生成想要的正确结果

<Root>
   <FirstName>Bob</FirstName>
   <LastName>Marley</LastName>
   <ID>BM1234</ID>
   <Songs>
      <Song>
         <EmptyElements>album,studio,rating</EmptyElements>
         <SongName>No woman no cry</SongName>
         <Year>1974</Year>
      </Song>
   </Songs>
</Root>

<强>解释

  1. 选择执行时,身份规则会复制匹配的节点&#34; 原样&#34;
  2. 匹配Songs/Song/*[not(node())]的模板在选择执行时不执行任何操作,从而导致&#34;删除&#34; (不复制)输出中的匹配节点。
  3. 模板匹配EmptyElements的优先级高于&#34;删除&#34;上面提到的模板,因此选择在任何EmptyElements元素上执行。
  4. 匹配的EmptyElements元素被浅层复制到输出中,然后通过将模式enumerate中的模板应用于所有空的兄弟元素来生成其内容(正文)。
  5. 最后,模式enumerate中的模板匹配Song元素的Songs元素的任何子元素。它被上面的步骤<xsl:apply-templates>中的4.指令选中执行,并且仅应用于EmptyElements元素的空元素兄弟。此模板执行两项操作:a)输出逗号,如果这不是node-list中的第一个节点; b)输出匹配元素的名称。通过这种方式,输出EmptyElements元素的空兄弟元素的所有名称,用逗号分隔。
  6. <强>更新

    亲爱的读者, 我们对此问题有一个配套的答案,该问题以Or simply:开头 并且暗示它比这个答案中的代码更简单。

    我没有告诉你这个答案比Or simply: - 答案更简单,而是总结了与简单相关的一些事实,你可以自己做出结论。在下表中,每个左子列中的值都是针对此的当前解决方案。每个右侧子列中的值均适用于Or simply: - 解决方案:

    enter image description here

    除此之外, Or simply: - 解决方案还具有潜在的性能和一定的流量问题 - 请参阅此片段:

                 <xsl:if test="position()!=last()">
                    <xsl:text>, </xsl:text>
                 </xsl:if>
    

    与当前解决方案使用的内容进行比较:

    not(position() = 1)
    

    请参阅 Dr. Michael Kay's recommendation ,后者是#34;这是一种更好的编码方式&#34;比前者和他解释原因:

      

    &#34;为什么呢?因为无论优化器如何工作,last()函数都很难   工作:它涉及某种前瞻性。使用&#34; position()ne last()&#34;该   前瞻可能仅限于一个元素,但它仍然要多得多   比测试位置是否为1更复杂。

         

    随着流媒体的出现,后一种表述也更有可能   是流媒体的(因为流式传输是不可能的。)&#34;

    <强>结论: 每当有人告诉我们:&#34; Or simply:&#34;,在将他们的陈述视为理所当然之前,最好采取一些指标......