7 Repetition

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.

7.1 The 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:

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.

Example: Using 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>

7.2 The 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 lastFO30 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:

]

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

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.

Example: Using 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"/>.

 

Example: Collecting Multiple Values in a Single Pass

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.

 

Example: Processing the Last Item in a Sequence Specially

When streaming, it is not possible to determine whether the item being processed is the last in a sequence without reading ahead. The lastFO30 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>