Project

Profile

Help

Use of group-starting-with="*[accumulator-before('window')?window-start]" is not considered motionless

Added by Martin Honnen almost 4 years ago

I have written some XSLT 3 code using an accumulator returning a map, when I try to use streaming Saxon doesn't complain about the accumulator declaration as not passing the streamability rules but in a group-starting-with="*[accumulator-before('window')?window-start]" pattern tells me that the pattern is not motionless.

Here is the full code:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">

    <xsl:param name="max" as="xs:integer" select="10"/>

    <xsl:mode on-no-match="shallow-copy" use-accumulators="#all" streamable="yes"/>

    <xsl:output method="xml" indent="yes"/>

    <xsl:accumulator name="window" as="map(xs:string, xs:anyAtomicType)?" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="root"
            select="
                map {
                    'sum': 0,
                    'window-start': true()
                }"/>
        <xsl:accumulator-rule match="root/row"
            select="
                let $val := xs:integer(@val),
                    $sum := $value?sum,
                    $window-start := $value?window-start,
                    $current-sum := $sum + $val
                return
                    if ($current-sum gt $max)
                    then
                        map {
                            'sum': $val,
                            'window-start': true()
                        }
                    else
                        map {
                            'sum': $current-sum,
                            'window-start': false()
                        }"
        />
    </xsl:accumulator>

    <xsl:template match="root">
        <xsl:copy>
            <xsl:for-each-group select="row"
                group-starting-with="*[accumulator-before('window')?window-start]">
                <grouped>
                    <xsl:apply-templates select="current-group()"/>
                </grouped>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

A sample input would be

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <row id="AAA" val="2"/>
    <row id="BBB" val="3"/>
    <row id="CCC" val="1"/>
    <row id="DDD" val="4"/>
    <row id="EEE" val="6"/>
    <row id="FFF" val="3"/>
    <row id="GGG" val="6"/>
    <row id="HHH" val="8"/>
    <row id="III" val="3"/>
    <row id="JJJ" val="4"/>
    <row id="KKK" val="2"/>
    <row id="LLL" val="1"/>
</root>

the error Saxon EE 10.1 gives me is as follows:

Processing file:/C:/Users/SomePath/./adjacent-sum-input1.xml
Streaming file:/C:/Users/SomePath/./adjacent-sum-input1.xml
Using parser com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser
Error on line 39 of accumulate-group-starting-with3.xsl:
  XTSE3430  Template rule is not streamable
  * The xsl:for-each-group/@group-starting-with pattern is not motionless
  * The result of the template rule can contain streamed nodes
Template rule is not streamable
  * The xsl:for-each-group/@group-starting-with pattern is not motionless
  * The result of the template rule can contain streamed nodes

Shouldn't the accumulator-before() call with the ?property selection be motionless given that the accumulator select expression seem to pass the streamability analysis?

Any suggestion to make that code streamable (without using Saxon extensions)?

Interestingly enough when I use a Saxon extension with a tuple type and type alias the code works with streaming as wanted:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:saxon="http://saxon.sf.net/"
    xmlns:mt="http://example.com/mt"
    exclude-result-prefixes="#all" 
    version="3.0">

    <xsl:param name="max" as="xs:integer" select="10"/>

    <xsl:mode on-no-match="shallow-copy" use-accumulators="#all" streamable="yes"/>

    <xsl:output method="xml" indent="yes"/>
    
    <saxon:item-type name="mt:window-condition" as="tuple(sum as xs:decimal, window-start as xs:boolean)"/>
    
    <xsl:function name="mt:window-condition" as="type(mt:window-condition)">
        <xsl:param name="sum" as="xs:decimal"/>
        <xsl:param name="window-start" as="xs:boolean"/>
        <xsl:sequence select="map { 'sum' : $sum, 'window-start' : $window-start }"/>
    </xsl:function>

    <xsl:accumulator name="window" as="type(mt:window-condition)?" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="root" select="mt:window-condition(0, true())"/>
        <xsl:accumulator-rule match="root/row"
            select="
                let $val := xs:integer(@val),
                    $sum := $value?sum,
                    $window-start := $value?window-start,
                    $current-sum := $sum + $val
                return
                    if ($current-sum gt $max)
                    then
                        mt:window-condition($val, true())
                    else
                        mt:window-condition($current-sum, false())"
        />
    </xsl:accumulator>

    <xsl:template match="root">
        <xsl:copy>
            <xsl:for-each-group select="row"
                group-starting-with="*[accumulator-before('window')?window-start]">
                <grouped>
                    <xsl:apply-templates select="current-group()"/>
                </grouped>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Replies (1)

RE: Use of group-starting-with="*[accumulator-before('window')?window-start]" is not considered motionless - Added by Michael Kay almost 4 years ago

The group-starting-with pattern is not motionless because the predicate is potentially numeric (it is a positional predicate in the language of §19.8.10). That is, the value of

accumulator-before('window')?window-start

could turn out to be a number. Wrapping it in a call on boolean() solves this:

group-starting-with="*[boolean(accumulator-before('window')?window-start)]

Using a tuple type enables Saxon to calculate a more precise static type for the predicate, which is another way of solving the problem.

    (1-1/1)

    Please register to reply