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>

10003000
<xsl:stream> can have the following attributes: validation ("strict" | "lax" | "preserve" | "strip"), type (eqname).

<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>

Figures in chapter 1: 3

Figures in chapter 2: 3

Figures in chapter 3: 3

Note the special variable $value, available only within an accumulator rule's context, holding the accumulator's value carried in from the previous rule.

<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>

helloMondayFr Mo Sa Su Th Tu WetrueMondaytruetrue
Values in a map can be nodes and functions, not just atomic values. <xsl:for-each> can also be used to iterate over the <xsl:map-entry> children of an <xsl:map>.