A streaming processor reads the document once, keeping only the current node's ancestors; it may read a node's subtree once and never look back, and it checks each path for streamability at compile time. An accumulator carries a value forward, updated by xsl:accumulator-rule as matching nodes pass and read with accumulator-before() or accumulator-after(). Run xslt3 -s:../s2-15/orders.xml -xsl:running.xsl | head -n 3:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" expand-text="yes">
<xsl:output method="text"/>
<xsl:accumulator name="copies" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="order" select="$value + xs:integer(@qty)"/>
</xsl:accumulator>
<xsl:mode streamable="yes" use-accumulators="#all" on-no-match="shallow-skip"/>
<xsl:template match="order">
<xsl:text>{@date} {@id}: {accumulator-after('copies')} copies so far </xsl:text>
</xsl:template>
</xsl:stylesheet>Output
Warning at xsl:accumulator on line 4 column 85 of running.xsl: SXST0068 Request for streaming ignored: this Saxon configuration does not support streaming Warning at xsl:mode on line 7 column 82 of running.xsl: SXST0068 Request for streaming ignored: this Saxon configuration does not support streaming 2026-09-28 o1001: 2 copies so far 2026-09-28 o1002: 3 copies so far 2026-09-29 o1003: 6 copies so far
Saxon-HE 726,956 warns SXST0068 and builds the tree anyway, so the same stylesheet runs everywhere and streams on Saxon-EE. Accumulators themselves work in every edition, streamed or not.
Streaming Accumulators and Maps
Streaming reads an XML document on the fly, without loading the whole document into memory, saving memory usage. Processing can be "forked" so that more than one computation happens during the same pass, effectively in parallel.<xsl:source-document>, <xsl:fork>
The example below streams company.xml and forks the single pass into two independent computations, one finding the minimum salary and one finding the maximum.<?xml version="1.0" encoding="UTF-8"?>
<company>
<employee salary="1000"/>
<employee salary="3000"/>
<employee salary="2000"/>
</company>
<?xml version="1.0"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:source-document href="company.xml" streamable="yes">
<xsl:fork>
<xsl:sequence>
<min><xsl:value-of
select="min(//employee/@salary)"/>
</min>
</xsl:sequence>
<xsl:sequence>
<max><xsl:value-of
select="max(//employee/@salary)"/>
</max>
</xsl:sequence>
</xsl:fork>
</xsl:source-document>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?> <min>1000</min><max>3000</max>
<xsl:accumulator>, <xsl:accumulator-rule>
An accumulator computes a value progressively, as a side effect of reading the document during streamed processing, and lets that value be accessed as a function of any node in the document, without compromising the functional nature of the XSLT language. The example below maintains two accumulators — a running chapter count and a running figure count that resets at the start of each chapter — and annotates each element with the accumulated values via comments, appending a summary paragraph after each chapter.<?xml version="1.0" encoding="UTF-8"?>
<book>
<chapter>
<figure/>
<figure/>
<figure/>
</chapter>
<chapter>
<figure/>
<figure/>
<figure/>
</chapter>
<chapter>
<figure/>
<figure/>
<figure/>
</chapter>
</book>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
expand-text="yes"
exclude-result-prefixes="#all">
<xsl:accumulator name="chapterNum" as="xs:integer"
initial-value="0">
<xsl:accumulator-rule match="chapter" select="$value+1"/>
</xsl:accumulator>
<xsl:accumulator name="figNum" as="xs:integer"
initial-value="0">
<xsl:accumulator-rule match="chapter" select="0"/>
<xsl:accumulator-rule match="figure" select="$value+1"/>
</xsl:accumulator>
<xsl:mode on-no-match="shallow-copy"
use-accumulators="#all"/>
<xsl:template match="figure">
<xsl:comment>{local-name()} {accumulator-before('figNum')} in {local-name(..)} {accumulator-before('chapterNum')}
</xsl:comment>
<xsl:next-match/>
</xsl:template>
<xsl:template match="chapter">
<xsl:comment>{local-name()} {accumulator-before('chapterNum')}
</xsl:comment>
<xsl:next-match/>
<p>Figures in {local-name()} {accumulator-before('chapterNum')}: {accumulator-after('figNum')}</p>
</xsl:template>
<xsl:output indent="yes"/>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?>
<book>
<!--chapter 1-->
<chapter>
<!--figure 1 in chapter 1-->
<figure/>
<!--figure 2 in chapter 1-->
<figure/>
<!--figure 3 in chapter 1-->
<figure/>
</chapter>
<p>Figures in chapter 1: 3</p>
<!--chapter 2-->
<chapter>
<!--figure 1 in chapter 2-->
<figure/>
<!--figure 2 in chapter 2-->
<figure/>
<!--figure 3 in chapter 2-->
<figure/>
</chapter>
<p>Figures in chapter 2: 3</p>
<!--chapter 3-->
<chapter>
<!--figure 1 in chapter 3-->
<figure/>
<!--figure 2 in chapter 3-->
<figure/>
<!--figure 3 in chapter 3-->
<figure/>
</chapter>
<p>Figures in chapter 3: 3</p>
</book>
<xsl:accumulator> can have the following attributes: as (sequence-type), visibility ("public" | "private" | "final" | "abstract"), streamable ("yes" | "no").
<xsl:accumulator-rule> can have the following attribute: phase ("start" | "end").
To retrieve an accumulator's value, use accumulator-before() or accumulator-after(), which respectively get the value before or after visiting a node's descendants.
<xsl:map>, <xsl:map-entry>
Streaming use cases motivated the introduction of maps into XSLT, since their flexible structure allows data to be stored temporarily. (At the time of writing, it had been proposed that the map construct, along with its related functions, be incorporated into XPath itself rather than XSLT specifically.) The example below builds one map with a literal map constructor and another with <xsl:map>/<xsl:map-entry>, then exercises several map functions.<xsl:stylesheet version="3.0"
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">
<xsl:variable name="m1" as="map(*)"
select="map{0:'hello',1:'world'}"/>
<xsl:variable name="m2" as="map(xs:string, xs:string)">
<xsl:map>
<xsl:map-entry key="'Mo'" select="'Monday'"/>
<xsl:map-entry key="'Tu'" select="'Tuesday'"/>
<xsl:map-entry key="'We'" select="'Wednesday'"/>
<xsl:map-entry key="'Th'" select="'Thursday'"/>
<xsl:map-entry key="'Fr'" select="'Friday'"/>
<xsl:map-entry key="'Sa'" select="'Saturday'"/>
<xsl:map-entry key="'Su'" select="'Sunday'"/>
</xsl:map>
</xsl:variable>
<xsl:variable name="m3" select="map:merge($m2)" />
<xsl:variable name="m4" select="map:merge(
(map:entry($m2,'Mo'),
map:entry($m2,'Mo')))" />
<xsl:variable name="m5" select=
"map:remove($m2,'Su')"/>
<xsl:variable name="m6" select="map:merge(
map:for-each($m1,
function($k,$v){map:entry($k, $v+1)}))"/>
<xsl:template match="/">
<xsl:value-of select="$m1(0)"/>
<xsl:value-of select="$m2('Mo')"/>
<xsl:value-of select="map:keys($m2)"/>
<xsl:value-of select="map:contains($m2,'Mo')"/>
<xsl:value-of select="map:get($m2,'Mo')"/>
<xsl:value-of select="deep-equal($m2,$m3)"/>
<xsl:value-of select="deep-equal(/. , /)"/>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?> helloMondayFr Mo Sa Su Th Tu WetrueMondaytruetrue