How Does xsl:mode streamable="yes" Work in XSLT 3.0?
In XSLT 3.0, declaring xsl:mode streamable="yes"
instructs the stylesheet processor to enforce static streamability
rules, ensuring that large XML documents can be processed sequentially
in a single forward pass without loading the entire document tree into
memory. This article explains how the XSLT 3.0 processor validates
streaming stylesheets, the mechanics of posture and sweep analysis, the
structural restrictions placed on XPath expressions and template rules,
and the built-in functions used to manage memory boundaries.
The Role of Streamable Mode
The xsl:mode declaration controls how templates matching
nodes in a given mode behave. Setting the streamable
attribute to yes acts as a strict contract between the
stylesheet developer and the XSLT engine. It guarantees that any
document processed under that mode will adhere to bounded-memory
constraints.
Rather than relying on runtime heuristics or dynamic buffering, an XSLT 3.0 processor performs static streamability analysis at compile time. If any XPath expression, template rule, or instruction within that mode violates the streamability grammar defined by the W3C specification, the processor rejects the stylesheet with a static error before processing any data.
Static Streamability Analysis: Posture and Sweep
The core mechanism enforcing streamability is the formal analysis of two properties for every expression: posture and sweep.
Posture
Posture describes the relationship of an expression’s result to the streamed input document:
- Grounded: The result is completely independent of
the stream, such as atomic values (
xs:integer,xs:string) or in-memory node trees created by constructing elements or copying nodes. - Striding: The result consists of nodes at a uniform
depth in the streaming tree (e.g., selecting child elements via
child::*). - Crawling: The result consists of nodes at arbitrary
descendant depths (e.g., using
descendant::*). - Roaming: The result cannot be guaranteed to follow a disciplined streaming structure.
Sweep
Sweep indicates how evaluating an expression moves the stream pointer:
- Motionless: Evaluating the expression does not advance the input stream pointer (e.g., reading an attribute on the current node or checking string literals).
- Consuming: Evaluating the expression advances the stream pointer through descendant nodes.
- Free-ranging: The expression requires moving back and forth or inspecting unconstrained nodes, which is incompatible with streaming.
For an expression or instruction to be streamable, the combination of posture and sweep must satisfy specific compatibility rules. For instance, an instruction cannot contain two sibling sub-expressions that are both consuming, as this would require reading the same stream fragment twice.
Key Restrictions Enforced by Streamable Mode
When streamable="yes" is active, the engine strictly
forbids navigation and evaluation patterns that would require caching or
backtracking over ungrounded streamed nodes.
1. Restricted Axis Navigation
In streaming mode, only forward downward axes are permitted on streamed nodes:
- Allowed:
child,attribute,self, andnamespace. - Disallowed:
parent,ancestor,ancestor-or-self,preceding,preceding-sibling,following, andfollowing-sibling.
Navigating upward or sideways on an ungrounded streamed node requires holding previous parts of the document in memory, which violates bounded-memory processing.
2. Single Consuming Operation per Iteration
A streamed node can only be consumed once. A stylesheet cannot
execute multiple operations that read the children of a streamed element
sequentially unless the nodes are first grounded into memory. For
example, calculating count(child::item) and then
immediately iterating over
xsl:for-each select="child::item" will fail static
validation because both operations consume the child stream.
3. Context Retention Restrictions
Higher-order constructs such as sorting (xsl:sort),
grouping (xsl:for-each-group), and distinct-value
extraction require specific structural arrangements. Sorting an
ungrounded stream is not permitted directly because all items must be
read and held before the first sorted item can be output.
Bridging Streamability with Built-in Functions
To handle tasks that inherently require multi-pass evaluation or local tree navigation, XSLT 3.0 provides explicit bridging functions:
copy-of()andsnapshot(): These functions ground a specific subtree into memory as an in-memory document fragment.copy-of()creates a standalone copy of the subtree, whilesnapshot()creates a copy that preserves ancestor and sibling context. Once grounded, standard non-streaming XPath operations can be executed freely on that fragment.xsl:accumulator: When information from earlier elements or ancestor contexts is needed downstream, accumulators maintain lightweight, user-defined state without caching the raw XML nodes. Accumulators update values during the stream's pre-descent and post-descent phases, providing a compliant way to aggregate data.
By rejecting non-compliant stylesheets ahead of execution,
xsl:mode streamable="yes" ensures predictable, low-memory
performance across arbitrarily large XML datasets.