使用复杂的计算变量进行XSL排序

时间:2014-04-26 19:03:28

标签: xml xslt

我在XML中有一个很长的列表,我必须使用派生属性对其进行排序,该属性的计算选择存储在同一XML文件中的其他元素的值。可以这样做(最好使用XSLT 1.0)?

我发布了一个示例代码。

这是我的XML:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="example.xslt"?>
<example>
    <references>
        <value x="0" y="0" res="0" />
        <value x="1" y="1" res="5" />
        <value x="1" y="2" res="3" />
        <value x="2" y="1" res="1" />
    </references>
    <list>
        <item x="0" y="0">...</item>
        <item x="1" y="2">...</item>
        <item x="2" y="1">...</item>
    </list>
</example>

这是XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <xsl:output method="html" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <html>
            <head>
            </head>
            <body>
                <table>
                    <xsl:for-each select="//list/item">
                        <xsl:variable name="x">
                            <xsl:value-of select="@x" />
                        </xsl:variable>
                        <xsl:variable name="y">
                            <xsl:value-of select="@y" />
                        </xsl:variable>
                        <xsl:variable name="order">
                            <xsl:for-each select="//references/value">
                                <xsl:if test="@x=$x and @y=$y">
                                    <xsl:value-of select="@res" />
                                </xsl:if>
                            </xsl:for-each>
                        </xsl:variable>
                        <!-- I want to do something like this: -->
                        <!--<xsl:sort select="$order" />-->
                        <tr>
                            <td><xsl:value-of select="text()" /></td>
                            <td><xsl:value-of select="$order" /></td>
                        </tr>
                    </xsl:for-each>
                </table>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

感谢您的帮助!

3 个答案:

答案 0 :(得分:3)

在XSLT 1.0中,排序键必须是单个XPath表达式,但正如其他人已经表明的那样,您可以在单个XPath表达式中做很多事情。在XSLT 2.0中,事情变得更容易,因为你的XPath表达式可以调用样式表函数来进行计算,你也可以使用xsl:sort体内的指令。你仍然无法在xsl:sort之前构造变量,但是 - 我们看了它并且它变得非常复杂。

关于代码的几点意见。

你不需要写这个:

<xsl:variable name="x">
     <xsl:value-of select="@x" />
</xsl:variable>

当你可以写这个(这也可能更有效率):

<xsl:variable name="x" select="@x"/>

你不需要写这个:

<xsl:for-each select="//references/value">
  <xsl:if test="@x=$x and @y=$y">
      <xsl:value-of select="@res" />
  </xsl:if>
</xsl:for-each>

什么时候可以这样写:

<xsl:value-of select="//references/value[@x=$x and @y=$y]/@res"/>

一般来说,你并没有充分利用该语言的全部力量。

答案 1 :(得分:1)

它并不像你想象的那么复杂。你需要的是current()

通过将“remote”节点与当前节点相匹配,您可以在单个XPath表达式中找到相关的value,而不需要变量来捕获它们。因此,您可以在XSLT 1.0中直接进行排序:

<xsl:sort select="//references/value
                    [@x = current()/@x]
                    [@y = current()/@y]
                  /@res"
          data-type="number" />

以下转换适用于您的输入

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <xsl:output method="html" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <html>
            <head>
            </head>
            <body>
                <table>
                    <xsl:for-each select="//list/item">
                        <xsl:sort select="//references/value
                                                            [@x = current()/@x]
                                                            [@y = current()/@y]
                                                            /@res"
                                            data-type="number" />

                        <xsl:variable name="order"
                                                    select="//references/value
                                                                    [@x = current()/@x]
                                                                    [@y = current()/@y]
                                                                    /@res"/>
                        <tr>
                            <td><xsl:value-of select="text()" /></td>
                            <td><xsl:value-of select="$order" /></td>
                        </tr>
                    </xsl:for-each>
                </table>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

提供以下输出:

<html xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <head>
        <META http-equiv="Content-Type" content="text/html; charset=utf-8">
    </head>
    <body>
        <table>
            <tr>
                <td>...</td>
                <td>0</td>
            </tr>
            <tr>
                <td>...</td>
                <td>1</td>
            </tr>
            <tr>
                <td>...</td>
                <td>3</td>
            </tr>
        </table>
    </body>
</html>

但是,如果要在模板中获取@res的值,请注意您必须重复表达式。或者,您可以使用key

<xsl:stylesheet>
...
    <xsl:key name="xy" match="value" use="concat(@x, ':', @y)" />
...
    <xsl:for-each select="//list/item">
        <xsl:sort select="key('xy', concat(@x, ':', @y))/@res" data-type="number" />
        <xsl:variable name="order"
    select="key('xy', concat(@x, ':', @y))/@res"/>
...

以相同的重复次数提供相同的输出。

答案 2 :(得分:1)

定义一个键<xsl:key name="k1" match="references/value" use="concat(@x, '|', @y)"/>,然后简单地将<xsl:sort select="key('k1', concat(@x, '|', @y))/@res"/>写为for-each的第一个子项。