[xsl] Can fold-left process the attributes nodes of a streamed element?

Subject: [xsl] Can fold-left process the attributes nodes of a streamed element?
From: "Martin Honnen martin.honnen@xxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Tue, 27 Jul 2021 14:02:21 -0000
I am struggling to find a compact way to use XSLT 3 streaming and an
accumulator to sum up the attribute values of certain elements,e.g.
xsl:accumulator name="attribute-sums" as="map(xs:QName, xs:decimal)"
initial-value="map{}" streamable="yes" to accumulate the sum of each
attribute by the name of the attribute.


One problem is that accumulators don't fire on attribute nodes, only on element nodes, therefore I have to write the accumulator-rule to match on the parent element of the attributes and compute all the sums in a single rule;

B  fold-left(@*, $value, function($v, $a) { map:put($v, node-name($a),
(map:get($v, node-name($a)), 0)[1] + xs:decimal($a)) })

seemed like a compact and elegant use of XPath 3.1 but using a dynamic
inline function as the argument to fold-left doesn't seem to pass the
streamability analysis.

Then I tried to set up a user-defined function to be passed in as the
third argument to fold-left, but somehow that approach doesn't seem to
work either as a streamable stylesheet function needs to take the
streamable nodes as the first parameter while the signature of the
function $f of fold-left would make the attribute node the second parameter.

So the only options I could think of is not passing the attribute nodes
themselves to fold-left but rather an array or map of the name and value
I need to store in the accumulator e.g.

fold-left(@* ! [node-name(), xs:decimal(.)], $value, function($v, $anv)
{ map:put($v, $anv?1, (map:get($v, $anv?1), 0)[1] + $anv?2) })

or to abandon fold-left and use xsl:iterate in the accumulator rules

B B <xsl:accumulator name="attribute-sums" as="map(xs:QName,
xs:decimal)" initial-value="map{}" streamable="yes">
B B B B B B B <xsl:accumulator-rule match="Book">
B B B B B B B B B B B <xsl:iterate select="@*">
B B B B B B B B B B B B B B B <xsl:param name="accumulator-map"
select="$value"/>
B B B B B B B B B B B B B B B <xsl:on-completion
select="$accumulator-map"/>
B B B B B B B B B B B B B B B <xsl:next-iteration>
B B B B B B B B B B B B B B B B B B B <xsl:with-param
name="accumulator-map"
B B B B B B B B B B B B B B B B B B B B B B B select="map:put($accumulator-map, node-name(),
(map:get($accumulator-map, node-name()), 0)[1] + xs:decimal(.))"/>
B B B B B B B B B B B B B B B </xsl:next-iteration>
B B B B B B B B B B B </xsl:iterate>
B B B B B B B </xsl:accumulator-rule>
B B B </xsl:accumulator>



The xsl:iterate approach feels too verbose, I kind of think fold-left should suffice to process the sequence of attributes (without that escape hatch to map them first to an array or map).

Any ideas or opinions whether I have missed an approach to use fold-left?

Current Thread