选择具有最低属性值的两个元素,并将它们合并到输出中的一个元素中

时间:2009-08-18 10:31:25

标签: xml xslt xpath

使用xslt / xpath,我需要能够在某种程度上选择具有最低属性值的两个元素并将它们合并。让我们说例如我有:

<root>
    <integer val="14"/>
    <integer val="7"/>
    <integer val="2"/>
    <integer val="1"/>
    <integer val="4"/>  
    <integer val="8"/>
</root>

我想选择两个最低值(1和2)并将它们表示为输出中的一个元素。属性值应该是这两个最低值的总和,所以我想:

<root>
    <integer val="3"/>
</root>

我也被限制使用仅xslt 1.0 ,因为xml将使用java 1.5 api进行处理,这似乎不支持xslt 2.0。我该怎样做才能使我的样式表解决这个看似简单的任务?


我的第一次尝试是使用排序:

<xsl:template match="root">
<xsl:copy>
    <xsl:apply-templates select="integer">
        <xsl:sort data-type="number" select="@val"/>        
    </xsl:apply-templates>
</xsl:copy>
</xsl:template>



<xsl:template match="integer[1]"> 
<xsl:copy>
    <xsl:attribute name="val">
        <xsl:value-of select="@val + ../integer[2]/@val"/>
    </xsl:attribute>
</xsl:copy>
</xsl:template>

然而,这没有任何结果。只有一个空的根节点。显然,&lt; xsl:sort&gt;禁用执行&lt; xsl:template match =“integer [1]”&gt;的功能([1]部分是与该种类不兼容的部分)。并且,即使它确实有效,[1]似乎也指的是文档顺序,而不是排序顺序。将第二个模板更改为:

<xsl:template match="integer"> 
<xsl:copy>
    <xsl:attribute name="val">
        <xsl:value-of select="../integer[2]/@val"/>
    </xsl:attribute>
</xsl:copy>
</xsl:template>

输出结果,其中所有输出val属性为7(而不是2,我希望它是)

另一种方法是使用min()xpath函数。但是,由于min()在1.0中不可用,因此快速失败。并且,即使min可用,找到两个最小元素并合并它们也不是一件容易的事。

6 个答案:

答案 0 :(得分:2)

我的提案(请注意,这不会创建不同值的总和):

<xsl:template match="root">
  <xsl:copy>
    <xsl:variable name="limit" select="2" />

    <!-- construct a comma-separated list of relevant IDs -->
    <xsl:variable name="idlist">
      <xsl:value-of select="','" />
      <xsl:for-each select="integer">
        <xsl:sort select="@val" data-type="number" order="ascending" />
        <xsl:if test="position() &lt;= $limit">
          <xsl:value-of select="generate-id()" />
          <xsl:value-of select="','" />
        </xsl:if>
      </xsl:for-each>
    </xsl:variable>

    <!-- sum up all nodes that are contained in the list -->
    <integer>
      <xsl:value-of select="
        sum(
          integer[
            contains(
              $idlist,
              concat(',', generate-id(), ',')
            )
          ]/@val
        )
      " />
    </integer>
  </xsl:copy>
</xsl:template>

这不是很优雅,也许有更好的方法来做到这一点。我的目标是保持硬编码部分的最小化。

要创建一个独特的总和,需要在for-each中应用某种分组。想一想,这个问题看起来有点像我的家庭作业。

答案 1 :(得分:1)

不是最好的解决方案,但它确实有效:

<xsl:template match="/">
<xsl:variable name="first">
<xsl:call-template name="getNum">
    <xsl:with-param name="pos" select="1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="second">
<xsl:call-template name="getNum">
        <xsl:with-param name="pos" select="2"/>
</xsl:call-template>
</xsl:variable>

<p><xsl:value-of select="$first + $second"/></p>
</xsl:template>

<xsl:template name="getNum">
    <xsl:param name="pos"/>
    <xsl:for-each select="/root/integer">
        <xsl:sort data-type="number" select="@val" order="ascending"/>
        <xsl:if test="position()=$pos">
                <xsl:value-of select="@val"/>
        </xsl:if>
    </xsl:for-each>

</xsl:template>

答案 2 :(得分:0)

这是一个“10的起点”,它的丑陋但它的作用: -

<xsl:template match="root">
  <xsl:copy>
    <xsl:for-each select="integer">
      <xsl:sort data-type="number" select="@val"/>
      <xsl:if test="position() = 1">
        <xsl:variable name="lowest" select="." />
        <xsl:for-each select="../integer[count(. | $lowest) &gt; 1]">
          <xsl:sort data-type="number" select="@val"/>
          <xsl:if test="position() = 1">
            <integer val="{number(@val) + number($lowest/@val)}" />
          </xsl:if>
        </xsl:for-each>
      </xsl:if>
    </xsl:for-each>
  </xsl:copy>
</xsl:template>

答案 3 :(得分:0)

与Tomalak的解决方案类似,如果您能够在XSLT中使用扩展函数(许多XSLT 1.0处理器都支持它们),您可以创建一个整数元素排序列表的节点集,然后求和两个值

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:exsl="urn:schemas-microsoft-com:xslt" 
    version="1.0" 
    extension-element-prefixes="exsl">

    <xsl:output method="xml" omit-xml-declaration="yes"/>

    <xsl:template match="/root">
        <root>
            <xsl:variable name="limit" select="2"/>
            <xsl:variable name="sortedlist">
                <xsl:for-each select="integer">
                    <xsl:sort select="@val" data-type="number" order="ascending"/>
                        <xsl:copy>
                            <xsl:copy-of select="@*" />
                        </xsl:copy>
                </xsl:for-each>
            </xsl:variable>
            <integer>
                <xsl:attribute name="val">
                    <xsl:value-of select="sum(exsl:node-set($sortedlist)/integer[position() &lt;= $limit]/@val)"/>
                </xsl:attribute>
            </integer>
        </root>
    </xsl:template>
</xsl:stylesheet>

答案 4 :(得分:0)

这就是诀窍,但有一点需要注意:

<xsl:template match="root">
  <xsl:variable name="integers">
    <xsl:for-each select="integer">
      <xsl:sort select="@val" data-type="number"/>
      <xsl:if test="position() &lt;= 2">
        <xsl:copy-of select="."/>
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>
  <root>
    <integer val="{sum(msxsl:node-set($integers)/integer/@val)}"/>
  </root>
</xsl:template>

需要注意的是,在for-each中使用apply-templatesvariable会返回结果树片段。除非您要将其复制到输出中,否则您无法对结果树片段执行任何操作;要在XPath表达式中使用它,必须将其转换为节点集。

该转换需要一个扩展函数,该函数可能在Java中可用,也可能没有(此示例使用Microsoft的XSLT处理器)。

答案 5 :(得分:0)

我们可以这样做:

<xsl:variable name="sorted">
    <xsl:for-each select="/root/integer">
        <xsl:sort select="number(@val)" order="ascending"/>
        <integer val="{@val}"/>
    </xsl:for-each>
</xsl:variable>

<xsl:template match="root">
    <root>
        <integer val="{$sorted/integer[1]/@val+$sorted/integer[2]/@val}"/>
    </root>
</xsl:template>