XSLT 1.0中的变量排序

时间:2017-08-23 10:15:10

标签: sorting xslt

假设我有以下源XML:

class RandomRunner {
    List<Runnable> runnables = new ArrayList<>();
    public void add(int value, Runnable toRun) {
        // add the methods as often as their weight indicates.
        // this should be fine for smaller numbers;
        // if you get lists with millions of entries, optimize
        for (int i = 0; i < value; i++) {
            runnables.add(toRun);
        }
    }
    public void remove(Runnable r) {
        Iterator<Runnable> myRunnables = runnables.iterator();
        while (myRunnables.hasNext()) {
            if (myRunnables.next() == r) {
                myRunnables.remove();
            }
    }
    public void runRandomly() {
        if (runnables.isEmpty()) return;
        // roll n-sided die
        int runIndex = ThreadLocalRandom.current().nextInt(0, runnables.size());
        runnables.get(runIndex).run();
    }
}

真实情况显然更复杂,但这个例子应足以说明。

输出应该是这样的:

<products>
   <product type="x" titleOne="some title" titleTwo="some other title"/>
   <product type="y" titleOne="one more title" titleTwo="and another title"/>
</products>

请注意:

  • 所选标题取决于<products> <product title="and another title"/> <product title="some title"/> </products> 属性。 (易)
  • 我希望根据所选标题对列表进行排序。 (难)

这种类型的动态排序在XSLT 1.0中是否可行?或者,有没有办法在XSLT 2/3中执行此操作?

理想的解决方案是:

type

但遗憾的是我的XSLT处理器不喜欢在排序之前使用变量。

有什么想法吗?

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:template match="/products">
    <xsl:copy>
        <xsl:for-each select="product">
            <xsl:sort select="@titleOne[../@type='x'] | @titleTwo[../@type='y']"/>
            <product title="{@titleOne[../@type='x'] | @titleTwo[../@type='y']}"/>      
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

或使用变量预处理具有正确标题的产品,然后对结果进行排序:

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:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/products">
    <!-- first pass -->
    <xsl:variable name="products">
        <xsl:for-each select="product">
            <product>       
                <xsl:attribute name="title">
                    <xsl:choose>
                        <xsl:when test="@type='x'">
                           <xsl:value-of select="@titleOne"/>
                        </xsl:when>
                        <xsl:when test="@type='y'">
                           <xsl:value-of select="@titleTwo"/>
                        </xsl:when>
                    </xsl:choose>
                </xsl:attribute>
            </product>
        </xsl:for-each>
    </xsl:variable>
    <!-- output -->
    <xsl:copy>
        <xsl:for-each select="exsl:node-set($products)/product">
            <xsl:sort select="@title"/>
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

答案 1 :(得分:0)

我将尝试回答这个子问题:

  

或者,有没有办法在XSLT 2/3中执行此操作?

在2.0中执行此操作的一种方法是将排序键定义为函数:

   <xsl:function name="f:myTitle" as="xs:string">
     <xsl:param name="product" as="element(product)"/>
     <xsl:choose>
        <xsl:when test="$product/@type = 'x'">
           <xsl:value-of select="$product/@titleOne"/>
        </xsl:when>
        <xsl:when test="$product/@type = 'y'">
           <xsl:value-of select="$product/@titleTwo"/>
        </xsl:when>
        <xsl:otherwise>
           <xsl:value-of select="$product/subproduct/@title"/>
        </xsl:otherwise>
     </xsl:choose>
  </xsl:function>

然后调用此函数来计算排序键:

   <xsl:for-each select="product">
      <xsl:sort select="f:myTitle(.)"/>
      <product title="{f:myTitle(.)}"/>      
   </xsl:for-each>

这仍然有两次计算标题的缺点。您可能希望优化器会避免这种情况(尽管Saxon没有)。在3.0中,您可以将其定义为备忘录函数,以便记住结果,但这可能会带来与优势相关的大量内存。

在3.0中,您可以创建一个包含密钥和相关产品的地图(我假设您实际上想要输出有关产品的更多信息,而不仅仅是计算出的标题):

   <xsl:variable name="product-map" 
        select="map:merge(product!map{f:myTitle(.) : .})"/>

   <xsl:for-each select="map:keys($product-map)">
      <xsl:sort select="."/>
      <product title="{.}">
        <xsl:apply-templates select="$product-map(.)"/>
      </product>      
   </xsl:for-each>

如果产品标题是唯一的,那么你可以进行伪分组操作,以利用current-grouping-key()函数,对于该函数没有类似的排序:

   <xsl:for-each-group select="product" group-by="f:myTitle(.)">
      <xsl:sort select="current-grouping-key()"/>
      <product title="{current-grouping-key()}"/>      
   </xsl:for-each>

最后,使用XSLT 3.0 + XPath 3.1,您可以利用fn:sort函数和数组数据类型:

<xsl:for-each select="sort(product![f:myTitle#1, .], 
                       function($p){$p(1)})">
   <product title="{.(1)}">
     <xsl:apply-templates select=".(2)"/>
   </product>
</xsl:for-each>