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 about 6 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 about 6 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 about 6 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.
Please register to reply