Streamable creation of JSON XML works but similar approach to create JSON with xsl:map/saxon:array doesn't pass streamability analysis
Added by Martin Honnen over 5 years ago
While exploring a way to create JSON with XSLT 3 and streaming I have found a difference between creating the XML representation of JSON defined in the XSLT 3 spec and the direct creation of "JSON" by creating maps with xsl:map
and saxon:array
; while my first stylesheet passes the streamability analysis the one creating maps/arrays doesn't.
Tested with Saxon 9.9.0.1 in oXygen 21.
Here is a sample stylesheet creating the XML representation of JSON:
<?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:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:mode on-no-match="shallow-skip" streamable="yes" use-accumulators="#all"/>
<xsl:accumulator name="entry-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Report_Entry" select="$value + 1"/>
</xsl:accumulator>
<xsl:accumulator name="total-amount" as="xs:decimal" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Report_Entry/amount/text()" select="$value + xs:decimal(.)"/>
</xsl:accumulator>
<xsl:template match="/*">
<fn:map>
<fn:array key="entries">
<xsl:apply-templates select="Report_Data/Report_Entry"/>
</fn:array>
<fn:number key="entry-count">{accumulator-after('entry-count')}</fn:number>
<fn:number key="total-amount">{accumulator-after('total-amount')}</fn:number>
</fn:map>
</xsl:template>
<xsl:template match="Report_Entry">
<fn:map>
<xsl:apply-templates/>
</fn:map>
</xsl:template>
<xsl:template match="Report_Entry/company">
<fn:string key="name">{data()}</fn:string>
</xsl:template>
<xsl:template match="Report_Entry/customer_id">
<fn:string key="id">{data()}</fn:string>
</xsl:template>
<xsl:template match="Report_Entry/amount">
<fn:number key="amount">{number(.)}</fn:number>
</xsl:template>
</xsl:stylesheet>
And the same approach as I think creating maps/arrays with xsl:map
and saxon:array
:
<?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/"
extension-element-prefixes="saxon"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="json" indent="yes"/>
<xsl:mode streamable="yes" use-accumulators="#all"/>
<xsl:accumulator name="entry-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Report_Entry" select="$value + 1"/>
</xsl:accumulator>
<xsl:accumulator name="total-amount" as="xs:decimal" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Report_Entry/amount/text()" select="$value + xs:decimal(.)"/>
</xsl:accumulator>
<xsl:template match="/*">
<xsl:map>
<xsl:map-entry key="'entries'">
<saxon:array>
<xsl:apply-templates select="Report_Data/Report_Entry"/>
</saxon:array>
</xsl:map-entry>
<xsl:map-entry key="'entry-count'" select="accumulator-after('entry-count')"/>
<xsl:map-entry key="'total-amount'" select="accumulator-after('total-amount')"/>
</xsl:map>
</xsl:template>
<xsl:template match="Report_Entry">
<xsl:map>
<xsl:map-entry key="'name'" select="string(company)"/>
<xsl:map-entry key="'id'" select="string(customer_id)"/>
<xsl:map-entry key="'amount'" select="xs:decimal(amount)"/>
</xsl:map>
</xsl:template>
</xsl:stylesheet>
For that stylesheet I get the error message:
Cannot call accumulator-after except during the post-descent phase of a streaming template
for line 28 <xsl:map-entry key="'entry-count'" select="accumulator-after('entry-count')"/>
.
As both stylesheets use <xsl:apply-templates select="Report_Data/Report_Entry"/>
, is there some way to use the map construction in a streamable way so that the map entries after the apply-templates
can use the post-descent accumulator-after
values?
Replies (1)
RE: Streamable creation of JSON XML works but similar approach to create JSON with xsl:map/saxon:array doesn't pass streamability analysis - Added by Martin Honnen over 5 years ago
It seems moving the saxon:array
creation into a variable
<xsl:template match="/*">
<xsl:map>
<xsl:variable name="entries" as="array(map(xs:string, xs:anyAtomicType))">
<saxon:array>
<xsl:apply-templates select="Report_Data/Report_Entry"/>
</saxon:array>
</xsl:variable>
<xsl:map-entry key="'entries'" select="$entries"/>
<xsl:map-entry key="'entry-count'" select="accumulator-after('entry-count')"/>
<xsl:map-entry key="'total-amount'" select="accumulator-after('total-amount')"/>
</xsl:map>
</xsl:template>
solves the problem to pass the streamability analysis.
I wonder whether the JSON result is created in a streamable way "on the fly", as the property "total-amount":10
ends up in the result before the entries
property it seems this is not the case and some buffering is done.
It also seems I forgot to show the sample data I used, it is
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Report_Data>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_01</customer_id>
<company>Company 01</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_02</customer_id>
<company>Company 02</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_03</customer_id>
<company>Company 03</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_04</customer_id>
<company>Company 04</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_05</customer_id>
<company>Company 05</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_06</customer_id>
<company>Company 06</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_07</customer_id>
<company>Company 07</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_08</customer_id>
<company>Company 08</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_09</customer_id>
<company>Company 09</company>
</Report_Entry>
<Report_Entry>
<amount>1</amount>
<customer_id>cust_10</customer_id>
<company>Company 10</company>
</Report_Entry>
</Report_Data>
</Root>
Please register to reply