Project

Profile

Help

Changing tunnel parameter from simple aggregated value to map with two aggregated values gives me XTSE3430: Template rule is not streamable

Added by Martin Honnen over 5 years ago

When I am using a stylesheet that uses streaming and xsl:fork with two nested xsl:sequence/xsl:for-each-group and then in the body of an xsl:for-each-group try to pass on an aggregrated value of the current-group() this works fine, but when I try to pass on a map with two aggregated values I get an error (using Saxon 9.8.0.12 EE Java from the command line) for lines higher up the hierarchy saying

Error on line 23 of group-two-sequences-streaming1-3.xsl: XTSE3430: Template rule is not streamable

  • There is more than one consuming operand: {TaxTrans!xsl:copy-of} on line 37, and {xsl:copy} on line 39 Template rule is not streamable
  • There is more than one consuming operand: {TaxTrans!xsl:copy-of} on line 37, and {xsl:copy} on line 39

So the working version has sum(current-group()/TaxAmount) as an xsl:with-param select, the rejected version has a select of map { 'TaxAmount' : sum(current-group()/TaxAmount), 'TaxBaseAmount' : sum(current-group()/TaxBaseAmount) } .

I have never managed to do all the details of the streamability analysis by hand but as https://www.w3.org/TR/xslt-30/#maps-streaming says

The xsl:map instruction, and the XPath MapConstructor construct, are exceptions to the general rule that during streaming, only one downward selection (one consuming subexpression) is permitted

I fail to understand why the map with two aggregated values can't be used.

The XSLT details are, first the version with a simple aggregated value:

<?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:map="http://www.w3.org/2005/xpath-functions/map"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">
    
    <xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
    
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>
    
    <xsl:accumulator name="inventtransid" as="xs:string?" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="CustInvoiceTrans" select="()"/>
        <xsl:accumulator-rule match="CustInvoiceTrans/InventTransId/text()" select="string()"/>
    </xsl:accumulator>
    
    <xsl:accumulator name="map-inventtrans-to-itemid" as="map(xs:string, xs:integer)" initial-value="map{}" streamable="yes">
        <xsl:accumulator-rule match="CustInvoiceTrans/ItemId/text()" select="map:put($value, accumulator-before('inventtransid'), xs:integer(.))"/>
    </xsl:accumulator>
    
    <xsl:template match="/*">
        <xsl:copy>
            <xsl:fork>
                <xsl:sequence>
                    <xsl:for-each-group select="CustInvoiceTrans!copy-of()" group-by="ItemId">
                        <xsl:copy>
                            <xsl:apply-templates select="@*"/>
                            <xsl:apply-templates>
                                <xsl:with-param name="sum" tunnel="yes" select="sum(current-group()/LineAmount)"/>
                            </xsl:apply-templates>
                        </xsl:copy>
                    </xsl:for-each-group>
                </xsl:sequence>
                <xsl:sequence>
                    <xsl:for-each-group select="TaxTrans!copy-of()" group-by="accumulator-before('map-inventtrans-to-itemid')(InventTransId)">
                        <xsl:copy>
                            <xsl:apply-templates select="@*"/>
                            <xsl:apply-templates>
                                <xsl:with-param name="sum" tunnel="yes" select="sum(current-group()/TaxAmount)"/>
                            </xsl:apply-templates>
                        </xsl:copy>
                    </xsl:for-each-group>
                </xsl:sequence>
            </xsl:fork>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="CustInvoiceTrans/LineAmount">
        <xsl:param name="sum" tunnel="yes"/>
        <xsl:copy>{$sum}</xsl:copy>
    </xsl:template>
    
    <xsl:template match="TaxTrans/TaxAmount">
        <xsl:param name="sum" tunnel="yes"/>
        <xsl:copy>{$sum}</xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

then the version given the error:

<?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:map="http://www.w3.org/2005/xpath-functions/map"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">
    
    <xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
    
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>
    
    <xsl:accumulator name="inventtransid" as="xs:string?" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="CustInvoiceTrans" select="()"/>
        <xsl:accumulator-rule match="CustInvoiceTrans/InventTransId/text()" select="string()"/>
    </xsl:accumulator>
    
    <xsl:accumulator name="map-inventtrans-to-itemid" as="map(xs:string, xs:integer)" initial-value="map{}" streamable="yes">
        <xsl:accumulator-rule match="CustInvoiceTrans/ItemId/text()" select="map:put($value, accumulator-before('inventtransid'), xs:integer(.))"/>
    </xsl:accumulator>
    
    <xsl:template match="/*">
        <xsl:copy>
            <xsl:fork>
                <xsl:sequence>
                    <xsl:for-each-group select="CustInvoiceTrans!copy-of()" group-by="ItemId">
                        <xsl:copy>
                            <xsl:apply-templates select="@*"/>
                            <xsl:apply-templates>
                                <xsl:with-param name="sum" tunnel="yes" select="sum(current-group()/LineAmount)"/>
                            </xsl:apply-templates>
                        </xsl:copy>
                    </xsl:for-each-group>
                </xsl:sequence>
                <xsl:sequence>
                    <xsl:for-each-group select="TaxTrans!copy-of()" group-by="accumulator-before('map-inventtrans-to-itemid')(InventTransId)">
                        <xsl:copy>
                            <xsl:apply-templates select="@*"/>
                            <xsl:apply-templates>
                                <xsl:with-param name="sums" tunnel="yes" select="map { 'TaxAmount' : sum(current-group()/TaxAmount), 'TaxBaseAmount' : sum(current-group()/TaxBaseAmount) }"/>
                            </xsl:apply-templates>
                        </xsl:copy>
                    </xsl:for-each-group>
                </xsl:sequence>
            </xsl:fork>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="CustInvoiceTrans/LineAmount">
        <xsl:param name="sum" tunnel="yes"/>
        <xsl:copy>{$sum}</xsl:copy>
    </xsl:template>
    
    <xsl:template match="TaxTrans/TaxAmount">
        <xsl:param name="sums" tunnel="yes"/>
        <xsl:copy>{$sums?TaxAmount}</xsl:copy>
    </xsl:template>
    
    <xsl:template match="TaxTrans/TaxBaseAmount">
        <xsl:param name="sums" tunnel="yes"/>
        <xsl:copy>{$sums?TaxBaseAmount}</xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

Input and problem is taken from StackOverflow:

<?xml version="1.0" encoding="UTF-8"?>
<CustInvoiceJour class="entity">
    <CustInvoiceTrans class="entity">
        <InventTransId>AABB</InventTransId>
        <InvoiceId>SI100</InvoiceId>
        <ItemId>444</ItemId>
        <LineAmount>100</LineAmount>
    </CustInvoiceTrans>
    <CustInvoiceTrans class="entity">
        <InventTransId>BBCC</InventTransId>
        <InvoiceId>SI100</InvoiceId>
        <ItemId>444</ItemId>
        <LineAmount>25</LineAmount>
    </CustInvoiceTrans>
    <CustInvoiceTrans class="entity">
        <InventTransId>CCDD</InventTransId>
        <InvoiceId>SI100</InvoiceId>
        <ItemId>555</ItemId>
        <LineAmount>200</LineAmount>
    </CustInvoiceTrans>
    <CustInvoiceTrans class="entity">
        <InventTransId>DDEE</InventTransId>
        <InvoiceId>SI100</InvoiceId>
        <ItemId>555</ItemId>
        <LineAmount>15</LineAmount>
    </CustInvoiceTrans>         
    <CustInvoiceTrans class="entity">
        <InventTransId>EEFF</InventTransId>
        <InvoiceId>SI100</InvoiceId>
        <ItemId>12345</ItemId>
        <LineAmount>40</LineAmount>
    </CustInvoiceTrans>
    <TaxTrans class="entity">
        <InventTransId>AABB</InventTransId>
        <TaxAmount>-21</TaxAmount>
        <TaxBaseAmount>-100</TaxBaseAmount>
        <TaxValue>21.00</TaxValue>
    </TaxTrans>
    <TaxTrans class="entity">
        <InventTransId>BBCC</InventTransId>
        <TaxAmount>-5.25</TaxAmount>
        <TaxBaseAmount>-25</TaxBaseAmount>
        <TaxValue>21.00</TaxValue>
    </TaxTrans>
    <TaxTrans class="entity">
        <InventTransId>CCDD</InventTransId>
        <TaxAmount>-42</TaxAmount>
        <TaxBaseAmount>-200</TaxBaseAmount>
        <TaxValue>21.00</TaxValue>
    </TaxTrans>
    <TaxTrans class="entity">
        <InventTransId>DDEE</InventTransId>
        <TaxAmount>-3.15</TaxAmount>
        <TaxBaseAmount>-15</TaxBaseAmount>
        <TaxValue>15</TaxValue>
    </TaxTrans>
    <TaxTrans class="entity">
        <InventTransId>EEFF</InventTransId>
        <TaxAmount>-8.40</TaxAmount>
        <TaxBaseAmount>-40</TaxBaseAmount>
        <TaxValue>15</TaxValue>
    </TaxTrans>         
</CustInvoiceJour>

Basically the task is to group the CustInvoiceTrans by the ItemId and sum up the LineAmount and group the TaxTrans by the ItemId of any CustInvoiceTrans element referenced by the InventTransId where for the TaskTrans groups both TaxAmount and TaxBaseAmount should be summed.


Replies (3)

Please register to reply

RE: Changing tunnel parameter from simple aggregated value to map with two aggregated values gives me XTSE3430: Template rule is not streamable - Added by Martin Honnen over 5 years ago

I have tried to reduce the problem, the 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"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map" exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

    <xsl:mode on-no-match="shallow-copy" streamable="yes"/>
    
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="items">
        <xsl:copy>
            <xsl:for-each-group select="item!copy-of()" group-by="@id">
                <xsl:copy>
                    <xsl:apply-templates select="@*"/>
                    <xsl:apply-templates>
                        <xsl:with-param name="sum" tunnel="yes" select="sum(current-group()/foo)"/>
                    </xsl:apply-templates>
                </xsl:copy>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="item/foo">
        <xsl:param name="sum" tunnel="yes"/>
        <xsl:copy>{$sum}</xsl:copy>
    </xsl:template>

</xsl:stylesheet>

works fine with Saxon 9.8.0.12 EE against the input

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <items>
        <item id="i1">
            <foo>1</foo>
            <bar>5</bar>
        </item>
        <item id="i1">
            <foo>1</foo>
            <bar>5</bar>
        </item>
        <item id="i2">
            <foo>1</foo>
            <bar>5</bar>
        </item>
        <item id="i2">
            <foo>1</foo>
            <bar>5</bar>
        </item>
    </items>
</root>

while the 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"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map" exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">
    
    <xsl:mode on-no-match="shallow-copy" streamable="yes"/>
    
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="items">
        <xsl:copy>
            <xsl:for-each-group select="item!copy-of()" group-by="@id">
                <xsl:copy>
                    <xsl:apply-templates select="@*"/>
                    <xsl:apply-templates>
                        <xsl:with-param name="sums" tunnel="yes" select="map { 'foo' : sum(current-group()/foo), 'bar' : sum(current-group()/bar) }"/>
                    </xsl:apply-templates>
                </xsl:copy>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="item/foo">
        <xsl:param name="sums" tunnel="yes"/>
        <xsl:copy>{$sums?foo}</xsl:copy>
    </xsl:template>
    
    <xsl:template match="item/bar">
        <xsl:param name="sums" tunnel="yes"/>
        <xsl:copy>{$sums?bar}</xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

gives the error

Error on line 13 of streamed-grouping-pass-map2-1.xsl: XTSE3430: Template rule is not streamable

  • There is more than one consuming operand: {item!xsl:copy-of} on line 15, and {xsl:copy} on line 17 Template rule is not streamable
  • There is more than one consuming operand: {item!xsl:copy-of} on line 15, and {xsl:copy} on line 17

so this seems the same error but with a reduced test case that only a single xsl:for-each-group select="item!copy-of()".

RE: Changing tunnel parameter from simple aggregated value to map with two aggregated values gives me XTSE3430: Template rule is not streamable - Added by Michael Kay over 5 years ago

There seems to be a blatant error in the streamability analysis for a map constructor: if all the key-value pairs are grounded and motionless, then it sets the posture and sweep of the map constructor to grounded and consuming.

    (1-3/3)

    Please register to reply