Recipe1.5.Eliminating Recursion with for Expressions


Recipe 1.5. Eliminating Recursion with for Expressions

Problem

You want to derive an output sequence from an input sequence where each item in the output is an arbitrarily complex function of the input and the sizes of each sequence are not necessarily the same.

Solution

XPath 1.0

Not applicable in 1.0. Use a recursive XSLT template.

XPath 2.0

Use XPath 2.0's for expression. Here we show four cases demonstrating how the for expression can map sequences of differing input and output sizes.

Aggregation
(: Sum of squares. :) sum(for $x in $numbers return $x * $x)   (: Average of squares. :) avg(for $x in $numbers return $x * $x)

Mapping
(: Map a sequence of words in all paragraphs to a sequence of word lengths. :) for $x in //para/tokenize(., ' ')  return string-length($x)  (: Map a sequence of words in a paragraph to a sequence of word lengths for words greater than three letters. :) for $x in //para/tokenize(., ' ')  return if (string-length($x) gt 3) the string-length($x) else ( )  (: Same as above but with a condition on the input sequence. :) for $x in //para/tokenize(., ' ')[string-length(.) gt 3] return string-length($x)

Generating
(: Generate a sequence  of squares of the first 100 integers. :) for $i in 1 to 100 return $i * $i  (: Generate a sequence of squares in reverse order. :) for $i in 0 to 10 return  (10 - $i) * (10 - $i)

Expanding
(: Map a sequence of paragraphs to a duped sequence of paragraphs. :) for $x in //para return ($x, $x) (: Duplicate words. :) for $x in //para/tokenize(., ' ') return ($x, $x) (: Map words to word followed by word length. :) for $x in //para/tokenize(., ' ') return ($x, string-length($x))

Joining
(: For each customer,  output an id and the total of all the customers orders. :) for $cust in doc('customer.xml')/*/customer      return      ($cust/id/text( ),        sum(for $ord in doc('orders.xml')/*/order[custID eq $cust/id]                return ($ord/total)) )

Discussion

As I indicated in Recipe 1.4, the addition of control flow constructs into an expression language like XPath might at first be perceived as odd or even misguided. You will quickly overcome your doubts, however, when you experience the liberating power of these XPath 2.0 constructs. This is especially true for the XPath 2.0 for expression.

The power of for becomes most apparent when one considers how it can be applied to reduce many complicated recursive XSLT 1.0 solutions to just a single XPath 2.0 expression. Consider the problem of computing sums in XSLT 1.0. If all you need is a simple sum, there is no problem because the built-in XPath 1.0 sum function will do fine. However, if you need to compute the sum of squares, you are forced to write a larger, more awkward, and less transparent recursive template. In fact, a good portion of the first edition of this book was recipes for canned solutions to these recursive gymnastics. With XPath 2.0, a sum of squares becomes nothing more than sum(for $x in $numbers return $x * $x) where $numbers contains the sequence of numbers we wish to sum over. Think of the trees that I could have saved if this facility was in XPath 1.0!

However, the for expression is hiding even more power. You are not limited to just one iteration variable. Several variables can be combined to create nested loops that create sequences from interrelated nodes in a complex document.

(:Return a sequence consisting of para ids and the ids those para elements reference. :) for $s in /*/section,     $p in $s/para,     $r in $p/ref        return ($p/@id, $r)

You should note that, other than being more compact, the preceding expression is not semantically different from the following:

for $s in /*/section        return for $p in $s/para             return for $r in $p/ref           return ($p/@id, $r)

You should also note that there is no need to use a nested for when the sequence you are producing is more elegantly represent by a traditional path expression.

(: This use of for is just a long-winded way of writing /*/section/para/ref. :) for $s in /*/section,        $p in $s/para,        $r in $p/ref return $r

Sometimes you might want to know the position of each item in a sequence as you process it. You cannot use the position() function as you would in an xsl:for-each because an XPath for expression does not alter the context position. However, you can achieve the effect you want with the following expression:

for $pos in 1 to count($sequence),    $item in $sequence[$pos]          return $item , $pos




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net