XSLT 2.0 and XPath 2.0 Programmer's Reference, 4th Edition (697 page)

BOOK: XSLT 2.0 and XPath 2.0 Programmer's Reference, 4th Edition
5.13Mb size Format: txt, pdf, ePub

Is recursion expensive? The answer is, not necessarily. Generally, a recursive scan of a sequence using the head/tail method illustrated above has O(n) performance, which means that the time it takes is directly proportional to the size of the list—exactly the same as with a conventional loop. In fact, it's quite possible for a reasonably smart compiler to generate exactly the same code for a recursive procedure as for an iterative one. A common compilation technique with functional programming languages is that of
tail call optimization
, which recognizes that when a function calls itself as the last thing it does (as in the proforma code above) there's no need to allocate a new stack frame or new variables; you can just loop back to the beginning.

Unfortunately, not all XSLT processors implement tail call optimization, which means that a recursive scan of a list can run out of memory, typically after processing 500 or 1000 items. Among the currently available XSLT 2.0 processors, Saxon and Gestalt implement tail call optimization, while Altova apparently does not. A programming technique that is often useful in such cases is called
divide-and-conquer
recursion. With head-tail recursion, the function processes one item, and then calls itself to process the rest of the sequence, which means that the maximum depth of recursive calls is equal to the number of items in the sequence. With divide-and-conquer recursion, by contrast, the function calls itself to process the first half of the sequence, and then calls itself again to process the second half. Although the number of function calls is the same, the maximum depth of recursion is now the logarithm of the size of the sequence: for example, to process a sequence of 1000 items the maximum depth of recursion will be 10. With a little ingenuity, many recursive algorithms that process a sequence of items can be written using this divide-and-conquer approach.

Time for some coding: the next example shows head-tail recursion in practice.

Example: Aggregating a List of Numbers

The following example uses a recursive template to process a whitespace-separated list of numbers.

Input

Suppose you have a string that holds a white-space-separated list of numbers, for example
(12,
34.5,
18.2,
5)
, and you want to replace this with a cumulative sequence
(12,
46.5,
64.7,
69.7)
in which each number is the sum of the previous values. An example of an application that might need to do this is a billing application, where the input sequence contains the values of individual transactions, and the output sequence shows a running balance.

This could be done with an XPath expression such as follows:

for $i in 1 to count($seq)

     return sum($seq[position() = 1 to $i])

However, this involves
n
2
/2 additions, which gets more and more expensive as the size of the sequence increases. It's reasonable to look for a solution that is more scalable than this.

For the sake of an example, this is the entire content of the document,
number-list.xml
.

12 34.5 18.2 5

We'll suppose that there is a schema that validates this as a sequence of
xs:decimal
values, so we don't need to be concerned with the parsing of the string; we can simply access the typed value of the element as a sequence.

To make this work in AltovaXML 2008, we added an
xsi:noNamespaceSchemaLocation
attribute to the source file. Altova uses this as a signal that validation is required.

Schema

The schema used to validate this trivial source document is
number-list.xsd
.




  



Stylesheet

Here's the recursive stylesheet (in file
number-total.xsl
):

   xmlns:xs=“http://www.w3.org/2001/XMLSchema”

   xmlns:f=“local-functions.uri”

   exclude-result-prefixes=“xs f”

   version=“2.0”>



   

   

   

     

                    select=“$input[1] + $total”/>

     

     

   



  



To run this, you need a schema-aware XSLT2.0 processor, such as Altova or Saxon-SA. (With Saxon-SA, remember to set
-val:strict
on the command line. With Altova, the source document needs to contain a reference to the schema.)

Notice how closely the function
f:total-numbers
mirrors the pseudocode structure given earlier.

If the supplied list is empty, the function returns nothing (an empty sequence).

The first time the function is called, it returns the value of the first item in the sequence (obtained by adding the value of this item to the running total, which is initialized to zero). It then calls itself to process the rest of the list and returns the result of this processing.

Each subsequent time the function is called, it processes the first value in what's left of the input sequence, adds this to the running total that was supplied as a parameter, outputs this value, and then calls itself to process the tail of the sequence.

Eventually, the function will call itself with an empty sequence as the argument, at which point it returns, unwinding the entire stack of function calls.

At first sight this function looks tail-recursive: the recursive call is the last thing it does. But this is a false impression; on return from the recursive call, it has to append the result of that call with the first item in the sequence, to construct a new result. So tail-call optimization might not apply here. However, there are ways that a clever processor can get round this problem.

Output



Here's another example, this time processing a sequence of nodes. XPath provides built-in functions for counting nodes and for totaling their values, but they aren't always flexible enough; sometimes you need to walk round the nodes yourself.

Example: Using Interleaved Structures

XML makes it very easy to represent hierarchic structures, such as the chapters, sections, and paragraphs of a book. But what do you do when there are structures that are nonhierarchic? An example is the text of a play: one way of splitting the text is according to who is speaking, and another way is to follow the meter of the verse. The problem is that lines of verse aren't neatly nested inside speeches, and speeches aren't nested inside lines of verse: the two structures are interleaved. The usual solution to this problem is to use the hierarchic XML tagging to represent one of the structures (say the speeches) and to use empty element tags to mark the boundaries in the other structure.

(Another design approach is referred to as
standoff markup
: the markup for either or both of the structures is held separately from the text itself, using XPointer references to identify the text to which it relates. Markup of parallel structures is a complicated subject, of great concern to linguistics scholars, and many academic papers are devoted to the topic.)

Input

This example (
scene4-3.xml
) is an extract from Shakespeare's
Othello
, Act IV, Scene 3. I have departed from Jon Bosak's markup to show the line endings as they are given in the Arden Shakespeare edition.



Enter OTHELLO, LODOVICO, DESDEMONA, EMILIA and

attendants


LODOVICO

I do beseech you, sir, trouble yourself no further.



OTHELLO

O, pardon me: ‘twill do me good to walk.



LODOVICO

Madam, good night; I humbly thank your ladyship.



DESDEMONA

Your honour is most welcome.



OTHELLO

Will you walk, sir?

O, Desdemona, -



DESDEMONA

My lord?



OTHELLO

Get you to bed on th’ instant; I will be returned forthwith

dismiss your attendant there: look't be done.



DESDEMONA

I will, my lord.


Exeunt Othello, Lodovico, and attendants


Output

Typically, a printed edition of this play is formatted with the kind of layout shown in
Figure 17-4
, in which lines that are split across multiple speeches are indented to show their relationship.

Achieving this format is not easy (especially details such as the omission of a new line if the indented text fits on the same line as the speaker's name), and it certainly requires a computational stylesheet to achieve it. Rather than attempt this, I will tackle a simpler problem, which is to invert the structure so that the primary XML hierarchy shows the lines, and the start of each speech is tagged by an empty element.


SCENE III. Another room In the castle.

Enter OTHELLO, LODOVICO, DESDEMONA, EMILIA and

attendants


I do beseech you, sir, trouble yourself no further.


O, pardon me: ‘twill do me good to walk.


Madam, good night; I humbly thank your ladyship.


Your honour is most welcome.


Will you walk, sir?

O, Desdemona, -


My lord?


Get you to bed on th’ instant; I will be returned

 forthwith

dismiss your attendant there: look't be done.


I will, my lord.

Exeunt Othello, Lodovico, and attendants


Stylesheet

I tackle this by processing the text sequentially, using a recursive template. The structure I have followed below is a two-phase approach. The first phase flattens the structure so that both the speech boundaries and the line boundaries are represented by empty elements, appearing as siblings of the text nodes. The result of the first phase is held in the variable
$flat
. This variable holds a sequence of (parentless) element and text nodes. The second phase then adds the hierarchic structure of

elements containing the text and

nodes.

Here is the top-level logic of the stylesheet
invert.xsl
. Note how the second phase starts by processing only the first node in the sequence
$flat
. after the initial

.

   xmlns:xsl=“http://www.w3.org/1999/XSL/Transform”

   version=“2.0”>


   

      

      

   

   

                        mode=“phase2”/>


Now the template rules for the first phase, the flattening phase. This is handled by a single rule which processes a

by first creating a

element containing the name of the speaker and then copying all the nodes from the original except for the

element. We are assuming here that each speech has only one speaker.


   

   


We now have a flat structure containing

elements, empty

elements, text nodes, and

elements. Phase 2 is structured so a template rule is called once for each node in the sequence
$flat
. In each case, this template rule calls

to process the next node in the sequence (and thus, by implication, the rest of the sequence beyond). I refer to this style of processing as
sibling recursion
. When an

element is encountered, a new

element is written to the result tree, and the following elements generate children of this

element. When any other kind of node is encountered, it is simply copied to the result tree at the current level.


  

    

         select=“following-sibling::node()[1][not(self::NL)]”/>

         mode=“phase2”/>

  

  



  

  

       select=“following-sibling::node()[1][not(self::NL)]”

       mode=“phase2”/>



The expression
following-sibling::node()[1][not(self::NL)]
selects the next sibling node, provided it is not an

element; if the next sibling is an

element, it selects an empty sequence.

There is very little that is specific to XSLT 2.0 in this stylesheet, with the exception of the sequence of nodes used as an intermediate variable. The second phase could have been written without explicit recursion, using

, but in my view the recursive solution in this case is not really any more difficult.

This stylesheet also demonstrates that recursive processing of a sequence can often be carried out very elegantly, using

. I find that there is a tendency when writing recursive code to reach straight for

(or, in XSLT 2.0,

) when

can often do the job far better.

Superficially, the logic of this stylesheet doesn't bear much resemblance to the prototypical head/tail recursive structure described earlier. But in fact, this is precisely the underlying structure. The
match=“SCENE”
template selects all the

elements, each one implicitly identifying a sequence that starts with that

and continues up to the next

. The processing of this sequence is split between the two
mode=“phase2”
template rules, each of which has the structure “handle this node, then recursively process the next sibling”. Processing the next sibling here is effectively processing the remainder of the list. The test for the terminating condition is implicit, because

does nothing when the selected node sequence is empty.

Other books

Intercambio by David Lodge
All Hail the Queen by Meesha Mink
The Sunset Witness by Hayes, Gayle
Coco Chanel & Igor Stravinsky by Chris Greenhalgh
Little Women and Me by Lauren Baratz-Logsted
Night Prey by John Sandford
Book Deal by Les Standiford