带有分组和总和/累加器的 XSLT 3.0 流 [英] XSLT 3.0 Streaming with Grouping and Sum/Accumulator
问题描述
我试图弄清楚如何在需要分组(使用任意数量的组)并对组求和的场景中使用 XSLT 流(以减少内存使用).到目前为止,我还没有找到任何例子.这是一个示例 XML
I'm trying to figure out how to use XSLT Streaming (to reduce memory usage) in a scenario that requires grouping (with an arbitrary number of groups) and summing the group. So far I haven't been able to find any examples. Here's an example XML
<?xml version='1.0' encoding='UTF-8'?>
<Data>
<Entry>
<Genre>Fantasy</Genre>
<Condition>New</Condition>
<Format>Hardback</Format>
<Title>Birds</Title>
<Count>3</Count>
</Entry>
<Entry>
<Genre>Fantasy</Genre>
<Condition>New</Condition>
<Format>Hardback</Format>
<Title>Cats</Title>
<Count>2</Count>
</Entry>
<Entry>
<Genre>Non-Fiction</Genre>
<Condition>New</Condition>
<Format>Paperback</Format>
<Title>Dogs</Title>
<Count>4</Count>
</Entry>
</Data>
在 XSLT 2.0 中,我将使用它按流派、条件和格式进行分组并对计数求和.
In XSLT 2.0 I would use this to group by Genre, Condition and Format and Sum the counts.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" />
<xsl:template match="/">
<xsl:call-template name="body"/>
</xsl:template>
<xsl:template name="body">
<xsl:for-each-group select="Data/Entry" group-by="concat(Genre,Condition,Format)">
<xsl:value-of select="Genre"/>
<xsl:value-of select="Condition"/>
<xsl:value-of select="Format"/>
<xsl:value-of select="sum(current-group()/Count)"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
对于输出,我会得到两行,幻想、新、精装的总和为 5,非小说、新、平装的总和为 4.
For output I would get two lines, a sum of 5 for Fantasy, New, Hardback and a sum of 4 for Non-Fiction, New, Paperback.
显然这不适用于 Streaming,因为总和访问整个组.我想我需要遍历文档两次.我第一次可以构建组的地图(如果尚不存在,则创建一个新组).第二次的问题是我还需要为每个组设置一个累加器,并带有匹配该组的规则,而您似乎无法创建动态累加器.
Obviously this won't work with Streaming because the sum accesses the whole group. I think I need to iterate through the document twice. The first time I could build a map of the groups (creating a new group if one doesn't exist yet). The second time The problem is I also need an accumulator for each group with a rule that matches the group, and it doesn't seem you can create dynamic accumulators.
有没有办法即时创建累加器?有没有其他/更简单的方法可以通过流式传输来做到这一点?
Is there a way to create accumulators on the fly? Is there another/easier way to do this with Streaming?
推荐答案
为了能够在 XSLT 3.0 中使用流式分组,我看到的一个选项是首先使用样式表将您拥有的基于元素的数据转换为基于属性的数据
To be able to use streamed grouping with XSLT 3.0 one option that I see is to first transform the element based data you have into attribute based data using a stylesheet like
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:mode streamable="yes" on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Entry/*">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}" select="."/>
</xsl:template>
</xsl:stylesheet>
然后您可以完美地使用流式分组(就流式group-by
而言是可能的,据我所知会有一些必要的缓冲)如下:
then you can perfectly used streamed grouping (as far as a streamed group-by
is possible at all, as far as I understand there will be some buffering necessary) as follows:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:mode streamable="yes"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:fork>
<xsl:for-each-group select="Data/Entry" composite="yes" group-by="@Genre, @Condition, @Format">
<xsl:value-of select="current-grouping-key(), sum(current-group()/@Count)"/>
<xsl:text> </xsl:text>
</xsl:for-each-group>
</xsl:fork>
</xsl:template>
</xsl:stylesheet>
我不知道首先创建一个以属性为中心的文档是否是一个选项,但我认为最好在答案中与代码共享建议,而不是试图将它们放入评论中.XSLT Streaming Chained Transform 中的答案展示了如何使用 Saxon 9 和 Java 或 Scala 进行链接两个流转换,无需为第一个转换步骤编写临时输出文件.
I don't know whether first creating an attribute centric document is an option but I think it is better to share suggestions with code in an answer instead of trying to put them into a comment. And the answer in XSLT Streaming Chained Transform shows how to use Saxon 9 with Java or Scala to chain two streaming transformations without the need to write a temporary output file for the first transformation step.
至于在原始输入格式上使用 copy-of
执行此操作,Saxon 9.7 EE 将以下内容评估为可流式传输并以正确的结果执行它:
As for doing it with copy-of
on the original input format, Saxon 9.7 EE assesses the following as streamable and executes it with the right result:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math"
version="3.0">
<xsl:mode streamable="yes"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each-group select="copy-of(Data/Entry)" composite="yes"
group-by="Genre, Condition, Format">
<xsl:value-of select="current-grouping-key(), sum(current-group()/Count)"/>
<xsl:text> </xsl:text>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
我不确定它消耗的内存比正常的基于树的分组少.也许你可以用你的真实输入数据来衡量.
I am not sure it consumes less memory however than normal, tree based grouping. Perhaps you can measure with your real input data.
作为第三种选择,要使用您似乎想要做的地图,这里是一个 xsl:iterate
示例,它遍历 Entry
元素,收集地图中累积的 Count
值:
As a third alternative, to use a map as you seemed to want to do, here is an xsl:iterate
example that iterates through the Entry
elements, collecting the accumulated Count
value in a map:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:map="http://www.w3.org/2005/xpath-functions/map" exclude-result-prefixes="xs math map"
version="3.0">
<xsl:mode streamable="yes"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:iterate select="Data/Entry">
<xsl:param name="groups" as="map(xs:string, xs:integer)" select="map{}"/>
<xsl:on-completion>
<xsl:value-of select="map:keys($groups)!(. || ' ' || $groups(.))" separator=" "/>
</xsl:on-completion>
<xsl:variable name="current-entry" select="copy-of()"/>
<xsl:variable name="key"
select="string-join($current-entry/(Genre, Condition, Format), '|')"/>
<xsl:next-iteration>
<xsl:with-param name="groups"
select="
if (map:contains($groups, $key)) then
map:put($groups, $key, map:get($groups, $key) + xs:integer($current-entry/Count))
else
map:put($groups, $key, xs:integer($current-entry/Count))"
/>
</xsl:next-iteration>
</xsl:iterate>
</xsl:template>
</xsl:stylesheet>
这篇关于带有分组和总和/累加器的 XSLT 3.0 流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!