假设我有以下源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处理器不喜欢在排序之前使用变量。
有什么想法吗?
答案 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>