XSLT offers two constructs for processing each item of a sequence:
xsl:for-each
and xsl:iterate
.
The main difference between the two constructs is that with
xsl:for-each
, the processing applied to each item in the sequence is
independent of the processing applied to any other item; this means that the items
may
be processed in any order or in parallel, though the order of the output sequence
is
well defined and corresponds to the order of the input (sorted if so requested). By
contrast, with xsl:iterate
, the processing is explicitly sequential:
while one item is being processed, values may be computed which are then available
for
use while the next item is being processed. This makes xsl:iterate
suitable for tasks such as creating a running total over a sequence of financial
transactions.
A further difference is that xsl:for-each
permits
sorting of the input sequence, while xsl:iterate
does not.
xsl:for-each
instruction<!-- Category: instruction -->
<xsl:for-each
select = expression >
<!-- Content: (xsl:sort*, sequence-constructor) -->
</xsl:for-each>
The xsl:for-each
instruction processes each item in a sequence of
items, evaluating the sequence
constructor within the xsl:for-each
instruction once
for each item in that sequence.
The select
attribute is required; it contains an
expression which is evaluated to produce a
sequence, called the input sequence. If there is an xsl:sort
element
present (see 13 Sorting) the input sequence is sorted to produce a
sorted sequence. Otherwise, the sorted sequence is the same as the input
sequence.
The xsl:for-each
instruction contains a sequence constructor. The sequence constructor is evaluated once for
each item in the sorted sequence, with the focus
set as follows:
The context item is the item being processed.
The context position is the position of this item in the sorted sequence.
The context size is the size of the sorted sequence (which is the same as the size of the input sequence).
For each item in the input sequence, evaluating the sequence constructor produces a sequence
of items (see 5.7 Sequence Constructors). These output sequences are
concatenated; if item Q follows item P in the sorted sequence,
then the result of evaluating the sequence constructor with Q as the
context item is concatenated after the result of evaluating the sequence constructor
with P as the context item. The result of the
xsl:for-each
instruction is the concatenated sequence of
items.
xsl:for-each
For example, given an XML document with this structure
<customers> <customer> <name>...</name> <order>...</order> <order>...</order> </customer> <customer> <name>...</name> <order>...</order> <order>...</order> </customer> </customers>
the following would create an HTML document containing a table with a row for each
customer
element
<xsl:template match="/"> <html> <head> <title>Customers</title> </head> <body> <table> <tbody> <xsl:for-each select="customers/customer"> <tr> <th> <xsl:apply-templates select="name"/> </th> <xsl:for-each select="order"> <td> <xsl:apply-templates/> </td> </xsl:for-each> </tr> </xsl:for-each> </tbody> </table> </body> </html> </xsl:template>
xsl:iterate
Instruction<!-- Category: instruction -->
<xsl:iterate
select = expression >
<!-- Content: (xsl:param*, xsl:on-completion?, sequence-constructor) -->
</xsl:iterate>
<!-- Category: instruction -->
<xsl:next-iteration>
<!-- Content: (xsl:with-param*) -->
</xsl:next-iteration>
<!-- Category: instruction -->
<xsl:break
select? = expression >
<!-- Content: sequence-constructor -->
</xsl:break>
<xsl:on-completion
select? = expression >
<!-- Content: sequence-constructor -->
</xsl:on-completion>
The select
attribute is required; it contains an
expression which is evaluated to produce a
sequence, called the input sequence.
The sequence constructor contained in the
xsl:iterate
instruction is evaluated once for each item in the
input sequence, in order, or until the loop exits by evaluating an
xsl:break
instruction, whichever is earlier. Within the sequence constructor that forms the body
of the xsl:iterate
instruction, the context item is set to each item from the value of the
select
expression in turn; the context position reflects the position of this item in the input
sequence, and the context size is the number
of items in the input sequence (which may be greater than the number of iterations,
if the loop exits prematurely using xsl:break
).
Note:
If xsl:iterate
is used in conjunction with
xsl:source-document
to achieve streaming, calls on the function
last
FO30 will be disallowed.
The xsl:break
and
xsl:on-completion
elements may have either a select
attribute or a non-empty contained sequence constructor but not
both. The effect of the element in both cases is obtained by evaluating the
select
expression if present or the contained sequence constructor
otherwise; if neither is present, the value is an empty sequence.
Note:
The xsl:on-completion
element appears before other children
of xsl:iterate
to ensure that variables declared in the sequence constructor are not in scope
within xsl:on-completion
, since such variables do not have a
defined value within xsl:on-completion
especially in the case
where the value of the select
attribute is an empty sequence.
The effect of xsl:next-iteration
is to cause the iteration to
continue by processing the next item in the input sequence, potentially with
different values for the iteration parameters. The effect of
xsl:break
is to cause the iteration to finish, whether or not all
the items in the input sequence have been processed. In both cases the affected
iteration is the one controlled by the innermost ancestor
xsl:iterate
element.
The instructions xsl:next-iteration
and xsl:break
are allowed only as descendants of an xsl:iterate
instruction, and
only in a tail position within the sequence constructor forming the body of
the xsl:iterate
instruction.
[Definition: An instruction J is in a tail position within a sequence constructor SC if it satisfies one of the following conditions:
J is the last instruction in SC, ignoring any
xsl:fallback
instructions.
J is in a tail position
within the sequence constructor that forms the body of an
xsl:if
instruction that is itself in a tail position within SC.
J is in a tail position
within the sequence constructor that forms the body of an
xsl:when
or xsl:otherwise
branch of an
xsl:choose
instruction that is itself in a tail position within SC.
J is in a tail position
within the sequence constructor that forms the body of an
xsl:try
instruction that is itself in a tail position within SC (that
is, it is immediately followed by an xsl:catch
element,
ignoring any xsl:fallback
elements).
J is in a tail position
within the sequence constructor that forms the body of an
xsl:catch
element within an xsl:try
instruction that is itself in a tail
position within SC.
]
[ERR XTSE3120] It is a static error if an
xsl:break
or xsl:next-iteration
element
appears other than in a tail position
within the sequence
constructor forming the body of an xsl:iterate
instruction.
[ERR XTSE3125] It is a static error if the
select
attribute of xsl:break
or
xsl:on-completion
is present and the instruction has
children.
[ERR XTSE3130] It is a static error if the
name
attribute of an xsl:with-param
child of
an xsl:next-iteration
element does not match the
name
attribute of an xsl:param
child of the
innermost containing
xsl:iterate
instruction.
Parameter names in xsl:with-param
must be unique: [see ERR XTSE0670].
The result of the xsl:iterate
instruction is the concatenation of
the sequences that result from the repeated evaluation of the contained sequence constructor, followed by the
sequence that results from evaluating the xsl:break
or
xsl:on-completion
element if any.
Any xsl:param
element that appears as a child of
xsl:iterate
declares a parameter whose value may vary from one
iteration to the next. The initial value of the parameter is the value obtained
according to the rules given in 9.3 Values of Variables and Parameters. The dynamic context
for evaluating the initial value of an xsl:param
element is the same
as the dynamic context for evaluating the select
expression of the
xsl:iterate
instruction (the context item is thus
not the first item in the input sequence).
On the first iteration a parameter always takes its initial value (which may depend on variables or other aspects of the dynamic context). Subsequently:
If an xsl:next-iteration
instruction is evaluated, then
parameter values for processing the next item in the input sequence can be set
in the xsl:with-param
children of that instruction; in the
absence of an xsl:with-param
element that names a particular
parameter, that parameter will retain its value from the previous
iteration.
If an xsl:break
instruction is evaluated, no further items in
the input sequence are processed.
If neither an xsl:next-iteration
nor an
xsl:break
instruction is evaluated, then the next item in
the input sequence is processed using parameter values that are unchanged from
the previous iteration.
The xsl:next-iteration
instruction contributes nothing to the result
sequence (technically, it returns an empty sequence). The instruction supplies
parameter values for the next iteration, which are evaluated according to the rules
given in 9.10 Setting Parameter Values; if there are no further items in the input
sequence then it supplies parameter values for use while evaluating the body of the
xsl:on-completion
element if any.
The xsl:break
instruction indicates that the iteration should
terminate without processing any remaining items from the input sequence. The select
expression or contained sequence
constructor is evaluated using the same context item, position, and size as the
xsl:break
instruction itself, and the result is appended to the
result of the xsl:iterate
instruction as a whole.
If neither an xsl:next-iteration
nor an xsl:break
instruction is evaluated, the next item in the input sequence is processed with
parameter values unchanged from the previous iteration; if there are no further items
in the input sequence, the iteration terminates.
The optional xsl:on-completion
element (which is not technically an
instruction and is not technically part of
the sequence constructor) is evaluated when the input sequence is
exhausted. It is not evaluated if the evaluation is terminated using
xsl:break
. During evaluation of its select
expression or sequence constructor
the context item, position, and size are absent
(that is, any reference to these values is an error). However, the values of the
parameters to xsl:iterate
are available, and take the values
supplied by the xsl:next-iteration
instruction evaluated while
processing the last item in the sequence.
If the input sequence is empty, then the result of the
xsl:iterate
instruction is the result of evaluating the select
attribute or sequence constructor forming the body of
the xsl:on-completion
element, using the initial values of the
xsl:param
elements. If there is no
xsl:on-completion
element, the result is an empty sequence.
Note:
Conceptually, xsl:iterate
behaves like a tail-recursive function.
The xsl:next-iteration
instruction then represents the recursive
call, supplying the tail of the input sequence as an implicit parameter. There are
two main reasons for providing the xsl:iterate
instruction. One
is that many XSLT users find writing recursive functions to be a difficult skill,
and this construct promises to be easier to learn. The other is that recursive
function calls are difficult for an optimizer to analyze. Because
xsl:iterate
is more constrained than a general-purpose
head-tail recursive function, it should be more amenable to optimization. In
particular, when the instruction is used in conjunction with
xsl:source-document
, it is designed to make it easy for the
implementation to use streaming techniques, processing the nodes in an input
document sequentially as they are read, without building the entire document tree
in memory.
The examples below use xsl:iterate
in conjunction with the
xsl:source-document
instruction. This is not the only way of using
xsl:iterate
, but it illustrates the way in which the two features
can be combined to achieve streaming of a large input document.
xsl:iterate
to Compute Cumulative Totals
Suppose that the input XML document has this structure
<transactions> <transaction date="2008-09-01" value="12.00"/> <transaction date="2008-09-01" value="8.00"/> <transaction date="2008-09-02" value="-2.00"/> <transaction date="2008-09-02" value="5.00"/> </transactions>
and that the requirement is to transform this to:
<account> <balance date="2008-09-01" value="12.00"/> <balance date="2008-09-01" value="20.00"/> <balance date="2008-09-02" value="18.00"/> <balance date="2008-09-02" value="23.00"/> </account>
This can be achieved using the following code, which is designed to process the transaction file using streaming:
<account> <xsl:source-document streamable="yes" href="transactions.xml"> <xsl:iterate select="transactions/transaction"> <xsl:param name="balance" select="0.00" as="xs:decimal"/> <xsl:variable name="newBalance" select="$balance + xs:decimal(@value)"/> <balance date="{@date}" value="{format-number($newBalance, '0.00')}"/> <xsl:next-iteration> <xsl:with-param name="balance" select="$newBalance"/> </xsl:next-iteration> </xsl:iterate> </xsl:source-document> </account>
The following example modifies this by only outputting the information for the first day’s transactions:
<account> <xsl:source-document streamable="yes" href="transactions.xml"> <xsl:iterate select="transactions/transaction"> <xsl:param name="balance" select="0.00" as="xs:decimal"/> <xsl:param name="prevDate" select="()" as="xs:date?"/> <xsl:variable name="newBalance" select="$balance + xs:decimal(@value)"/> <xsl:variable name="thisDate" select="xs:date(@date)"/> <xsl:choose> <xsl:when test="empty($prevDate) or $thisDate eq $prevDate"> <balance date="{$thisDate}" value="{format-number($newBalance, '0.00')}"/> <xsl:next-iteration> <xsl:with-param name="balance" select="$newBalance"/> <xsl:with-param name="prevDate" select="$thisDate"/> </xsl:next-iteration> </xsl:when> <xsl:otherwise> <xsl:break/> </xsl:otherwise> </xsl:choose> </xsl:iterate> </xsl:source-document> </account>
The following code outputs the balance only at the end of each day, together with the final balance:
<account> <xsl:source-document streamable="yes" href="transactions.xml"> <xsl:iterate select="transactions/transaction"> <xsl:param name="balance" select="0.00" as="xs:decimal"/> <xsl:param name="prevDate" select="()" as="xs:date?"/> <xsl:on-completion> <balance date="{$prevDate}" value="{format-number($balance, '0.00')}"/> </xsl:on-completion> <xsl:variable name="newBalance" select="$balance + xs:decimal(@value)"/> <xsl:variable name="thisDate" select="xs:date(@date)"/> <xsl:if test="exists($prevDate) and $thisDate ne $prevDate"> <balance date="{$prevDate}" value="{format-number($balance, '0.00')}"/> </xsl:if> <xsl:next-iteration> <xsl:with-param name="balance" select="$newBalance"/> <xsl:with-param name="prevDate" select="$thisDate"/> </xsl:next-iteration> </xsl:iterate> </xsl:source-document> </account>
If the sequence of transactions is empty, this code outputs a single element:
<balance date="" value="0.00"/>
.
Problem: Given a sequence of employee
elements, find the employees
having the highest and lowest salary, while processing each employee only
once.
Solution:
<xsl:source-document streamable="yes" href="si-iterate-035.xml"> <xsl:iterate select="employees/employee"> <xsl:param name="highest" as="element(employee)*"/> <xsl:param name="lowest" as="element(employee)*"/> <xsl:on-completion> <highest-paid-employees> <xsl:value-of select="$highest/name"/> </highest-paid-employees> <lowest-paid-employees> <xsl:value-of select="$lowest/name"/> </lowest-paid-employees> </xsl:on-completion> <xsl:variable name="this" select="copy-of()"/> <xsl:variable name="is-new-highest" as="xs:boolean" select="empty($highest[@salary ge current()/@salary])"/> <xsl:variable name="is-equal-highest" as="xs:boolean" select="exists($highest[@salary eq current()/@salary])"/> <xsl:variable name="is-new-lowest" as="xs:boolean" select="empty($lowest[@salary le current()/@salary])"/> <xsl:variable name="is-equal-lowest" as="xs:boolean" select="exists($lowest[@salary eq current()/@salary])"/> <xsl:variable name="new-highest-set" as="element(employee)*" select="if ($is-new-highest) then $this else if ($is-equal-highest) then ($highest, $this) else $highest"/> <xsl:variable name="new-lowest-set" as="element(employee)*" select="if ($is-new-lowest) then $this else if ($is-equal-lowest) then ($lowest, $this) else $lowest"/> <xsl:next-iteration> <xsl:with-param name="highest" select="$new-highest-set"/> <xsl:with-param name="lowest" select="$new-lowest-set"/> </xsl:next-iteration> </xsl:iterate> </xsl:source-document>
If the input sequence is empty, this code outputs an empty
highest-paid-employees
element and an empty
lowest-paid-employees
element.
When streaming, it is not possible to determine whether the item being processed
is the last in a sequence without reading ahead. The last
FO30
function therefore cannot be used in guaranteed-streamable
code. The xsl:iterate
instruction provides a solution to this
problem.
Problem: render the last paragraph in a section in some special way, for example
by using bold face. (The actual rendition is achieved by processing the paragraph
with mode last-para
.)
The solution uses xsl:iterate
together with the copy-of
function to maintain a one-element look-ahead by explicit coding:
<xsl:template match="section" mode="streaming"> <xsl:iterate select="para"> <xsl:param name="prev" select="()" as="element(para)?"/> <xsl:on-completion> <xsl:apply-templates select="$prev" mode="last-para"/> </xsl:on-completion> <xsl:if test="$prev"> <xsl:apply-templates select="$prev"/> </xsl:if> <xsl:next-iteration> <xsl:with-param name="prev" select="copy-of(.)"/> </xsl:next-iteration> </xsl:iterate> </xsl:template>