Scripting How-To: Implement "for loops" in SLAX version 1.1 and higher

By Elevate posted 08-09-2015 19:55


Implementing 'for loops' in SLAX Version 1.1 and Higher


There are two ways to implement loops in SLAX 1.0:
  • The for-each statement, which iterates through each member of a node-set
  • Recursive templates, which loop by calling themselves recursively for the desired number of repetitions
No native "for loop" exists in SLAX 1.0. It isn't possible to simply loop through a code stanza a specific number of times, at least not with native SLAX 1.0 statements. However, some scripts have worked around the limitation by creating a node-set of the desired size and then iterating through it using the for-each statement, as the following examples demonstrates:


Calling Code Example

1	var $loop-var := { call build-loop(); }
2	for-each( $loop-var/loop ) {
3	   ...loop code...
4	}

Template Code Example

1	template build-loop( $index = 1 ) {
2	   <loop>;
3	   if( $index < 500 ) {
4	      call build-loop( $index = $index + 1 );
5	   }
6	}
This technique works by first creating the node-set through a template that writes an XML element to the result tree before calling itself recursively the desired number of times. As a result, if the result tree is redirected into a node-set variable, the variable will consist of the desired number of elements. It enables the for-each statement to iterate through the elements, executing the code of the for-each stanza the specified number of times, just as if it were a for loop. 
This workaround has been used in SLAX scripts for years, but it has a couple of significant drawbacks:
  • First, the code itself is inelegant, requiring the inclusion of an otherwise unnecessary template that has nothing to do with the purpose of the script and could be confusing to a script writer tasked with editing the script in the future. 
  • Second, the context node is different inside of the loop than outside it.
This is intuitive when a script is purposefully looping through a node-set because the point of the loop is to process each node within the node-set. However, in the previous code example, the point of the loop is simply to loop, so the change of the context node might be unexpected to a novice script writer.


To address this deficiency, SLAX 1.1 added traditional for-loop functionality through a new for statement:


1	for $i( 1 ... 10 ) {
2	   ...loop code...
3	}

Now, using the new for statement, a script writer doesn't have to worry about the context node being different within the code block:



1	var $interface-set := { "fe-0/0/0"; "fe-0/0/1"; }
2	for-each ($interface-set/interface ) {
3	   for $i( 1 ... 5 ) {
4	      <output> "Context node: " _ .;
5	      <output> "Iteration: " _ $i;
6	   }
7	}



01	Context node: fe-0/0/0
02	Iteration: 1
03	Context node: fe-0/0/0
04	Iteration: 2
05	Context node: fe-0/0/0
06	Iteration: 3
07	Context node: fe-0/0/0
08	Iteration: 4
09	Context node: fe-0/0/0
10	Iteration: 5
11	Context node: fe-0/0/1
12	Iteration: 1
13	Context node: fe-0/0/1
14	Iteration: 2
15	Context node: fe-0/0/1
16	Iteration: 3
17	Context node: fe-0/0/1
18	Iteration: 4
19	Context node: fe-0/0/1
20	Iteration: 5



For Loop Syntax

As shown in the earlier code example, use of the for statement is straightforward. A variable is named following the for statement (in the previous examples, $i was used). This variable contains the current iteration value (from 1 to 5 in the example). In addition, a range is specified using the sequence operator, dictating how many times the enclosed code should loop and what the starting and ending values of the loop variable should be.



Caveat to Consider

However while the new for statement does correctly account for the change in the context node caused by the previously described workaround, it does not account for the change in both the position within and the size of the current node list, making those values unreliable within a for loop.
NOTE: Never use the position() or last() function within a for loop because they won't work as you expect.

To understand this deficiency, let's take a step back. A for-each loop works by selecting a list of nodes and then looping through those nodes. It works with four pieces of data&colon; the current node list (defined by the location path attached to the for-each statement), the position within the current node list, the size of the current node list, and the current/context node.


Of these four values, the current/context node and its position are the only ones that change after each iteration. So, why does the for statement render the position and size of the current node list useless? The underlying XSLT code provides the answer:




1	var $interface-set := { <interface> "fe-0/0/0"; <interface> "fe-0/0/1"; }
2	for-each ($interface-set/interface ) {
3	   for $i( 1 ... 5 ) {
4	      <output> "Iteration: " _ $i;
5	      <output> "Position: " _ position();
6	      <output> "Size: " _ last();
7	   }
8	}


XSLT Translation


01	<xsl:variable name="interface-set-temp-1">
02	 <interface>fe-0/0/0</interface>
03	 <interface>fe-0/0/1</interface>
04	</xsl:variable>
05	<xsl:variable xmlns:slax-ext="" name="interface-set" select="slax-ext:node-set($interface-set-temp-1)"/>
06	<xsl:for-each select="$interface-set/interface">
07	 <xsl:variable name="slax-dot-1" select="."/>
08	 <xsl:for-each xmlns:slax="" select="slax:build-sequence(1, 5)">
09	   <xsl:variable name="i" select="."/>
10	   <xsl:for-each select="$slax-dot-1">
11	     <output>
12	       <xsl:value-of select="concat(&quot;Iteration: &quot;, $i)"/>
13	     </output>
14	     <output>
15	       <xsl:value-of select="concat(&quot;Position: &quot;, position())"/>
16	     </output>
17	     <output>
18	       <xsl:value-of select="concat(&quot;Size: &quot;, last())"/>
19	     </output>
20	   </xsl:for-each>
21	 </xsl:for-each>
22	</xsl:for-each>


As you can see, the for statement works by first recording the current context node ($slax-dot-1 in the code above), next looping through a data structure created with the desired loop dimensions, and then looping again a single time to force the context node to be the same within and without the for loop.


As a result, the size and position of the current node list within a for loop will always be 1.




01	Iteration: 1
02	Position: 1
03	Size: 1
04	Iteration: 2
05	Position: 1
06	Size: 1
07	Iteration: 3
08	Position: 1
09	Size: 1
10	Iteration: 4
11	Position: 1
12	Size: 1
13	Iteration: 5
14	Position: 1
15	Size: 1
16	Iteration: 1
17	Position: 1
18	Size: 1
19	Iteration: 2
20	Position: 1
21	Size: 1
22	Iteration: 3
23	Position: 1
24	Size: 1
25	Iteration: 4
26	Position: 1
27	Size: 1
28	Iteration: 5
29	Position: 1
30	Size: 1


This defect  is easy to work around by recording the size and position in variables before the for loop and then using those variables within the loop rather than the position() and last() functions). Keep this in mind to avoid any unexpected behavior. The for statement is a worthy addition to SLAX and should prove useful in many scripts.