Iteration with xsl:iterate¶
xsl:for-each visits every item, but each visit is independent — one
iteration cannot see what the previous one computed. Whenever you need to thread
a value from one item to the next — a running total, a previous row, a
counter, a "have I seen this yet?" flag — 1.0 forced you into recursive named
templates. 3.0 gives you xsl:iterate: an ordinary, readable loop that carries
state forward.
This is a Saxon-HE feature — no licence needed
xsl:iterate is plain XSLT 3.0 and runs in the free Saxon-HE. It is
often introduced alongside streaming because it is also the
one loop that streams, but it is not streaming-only — reach for it on
ordinary in-memory trees whenever a loop needs memory of its past.
The shape¶
xsl:iterate selects a sequence, declares its carried state as xsl:param
children, and ends each iteration with xsl:next-iteration to supply the next
values:
- The sequence to walk — same
selectyou would givexsl:for-each. - Carried state, with its starting value. The
xsl:paramchildren must come first, before any other instruction. - Per-item work, reading the incoming
$running. - Output for this item — here, the cumulative total including this CD.
- Hand the updated value to the next turn. Omit a
with-paramand that parameter keeps its current value into the next iteration.
The running total is exactly the state that xsl:for-each cannot keep:
inside a for-each, every cd would see $running still at its initial 0.
xsl:param first, and select must be a single sequence
The carried parameters must be the first children of xsl:iterate.
xsl:next-iteration may appear only as the last thing executed on a
path, and only inside the iteration (not in a called template). Every
parameter you do not re-supply simply carries its present value forward.
Stopping early with xsl:break¶
A for-each always runs to the end. xsl:iterate can quit the moment a
condition is met — and can emit a final result as it leaves:
xsl:breakends the loop immediately. An optionalselect(or sequence constructor body) contributes a final value to the result, just like any other instruction.
Because it can short-circuit, xsl:iterate is the natural fit for "find the
first item where…" and "take items until…" — work that for-each can only fake
by visiting everything and filtering afterwards.
Running once at the end with xsl:on-completion¶
When the loop finishes normally (not via xsl:break), an optional
xsl:on-completion block runs once, with the final carried state in scope —
ideal for a summary footer after the per-item rows:
xsl:on-completionmust be the first child after thexsl:params. It fires only when the sequence is exhausted —xsl:breakskips it.
Why not just recurse, fold, or accumulate?¶
These all thread state. xsl:iterate is usually the clearest, but each has its
place:
| Tool | Threads state | Reads as | Best when |
|---|---|---|---|
xsl:for-each |
no | a loop | items are independent |
xsl:iterate |
yes | a loop | state flows item→item; you may stop early; you emit per-item output |
| Recursive named template | yes | recursion | the structure itself is recursive (a tree, not a flat list) |
fold-left |
yes | one expression | you want a single reduced value, no per-item nodes |
| Accumulator | yes | a declaration | the running value is needed in many templates, or you are streaming |
A fold-left is the tightest way to get one number out of a sequence; reach for
xsl:iterate when each step also produces output or you need to break.
xsl:iterate vs fold-left — same total, different shape
The cumulative-total example above could be a fold-left if you only wanted
the final 30.70. But here every CD emits a <cd running-total="…">
element as the loop runs — that interleaving of output with threaded state
is exactly what xsl:iterate expresses and fold-left does not.
Where to go next¶
- Higher-order functions —
fold-left/fold-right, the expression-level way to thread state. - Streaming — where
xsl:iterateand accumulators become mandatory, not optional. - Grouping —
xsl:for-each-group, the other structured replacement for hand-rolled loops.