Conditional Logic
XQuery supports XPath 2.0, and consequently works with the conditional if/then/else keywords of XPath. This makes it possible to create fairly sophisticated logical expressions, depending on specific characteristics in the source data. For instance, suppose you want to list the characters from the characters.xml file in a table, with ledger printing (one row white, the next row light green, the next row white, and so forth). This could be incredibly difficult to do with FLWOR notation, but by incorporating the conditional if keyword (and a little CSS), it becomes much easier (see Figure 3.3):
<html> <head> <style> .evenRow {{background-color:white;color:black;}} .oddRow {{background-color:lightGreen;color:black;}} </style> </head> <body> <h1>Characters</h1> <table cellspacing="0" cellpadding="3"> <tr> <th>Name</th> <th>Gender</th> <th>Species</th> <th>Vocation</th> <th>Level</th> </tr> { let $characters := input()//character for $character in $characters let $class := if (index-of($characters,$character) mod 2 = 0) then 'evenRow' else 'oddRow' return <tr class="{$class}"> <td>{string($character/name)}</td> <td>{string($character/gender)}</td> <td>{string($character/species)}</td> <td>{string($character/vocation)}</td> <td>{string($character/level)}</td> </tr> } </table> </body> </html>Figure 3.3 Conditional logic can be used to create changes in both content and stylistic output.
This particular example works by applying CSS classes (evenRow and oddRow) to alternating lines in the output table. The conditional test relies upon the expression
let $class := if (index-of($characters,$character) mod 2 = 0) then 'evenRow' else 'oddRow'
where the index-of() function returns the position of the character relative to the $characters sequence. The mod keyword from XPath performs a modulus (or remainder) on the expression, returning 0 if the expression is divisible by two, or 1 if it is not. The then and else functions have implicit return elements associated with them, so they can include complex XQuery statements.
In addition to illustrating the use of the if/then/else statement (covered in greater detail in Chapter 2), this sample also illustrates another feature: escaping the bracket {} characters. CSS uses brackets to indicate the CSS rule definitions, but in the sample, the XQuery processor would attempt to interpret the contents as XQuery expressions. To escape this behavior, use double brackets rather than single brackets (that is, {{ and }} instead of { and }).
Note as well that, unlike other languages, you must include both a then and an else in an if expression in XQuery. Should you run into a situation where you don't need to return anything in the then or else block, return an empty string '' as the result:
If ($a) then 'b' else ''
I Ain't Got No...
The conditions that can be evaluated in the if block include anything that can be cast to a Boolean value. However, there are a few expressions that XQuery specifically provides that can significantly improve performance, namely some ... satisfies and every ... satisfies. These two expressions make it possible to determine whether there exists at least one item in a sequence that satisfies a given condition, and whether all items in a sequence satisfy a given condition, respectively, without having to create counting functions to perform the same tests.
As an example, for the set of $characters defined previously, you can test to see whether the group contains at least one mage (see Figure 3.4):
let $characters := input()//character let $response := if (some $character in $characters satisfies $character/ vocation="Mage") then 'Party has a mage' else 'Party does not have a mage.' return <html> <head> </head> <body> <h1>Mage Query</h1> <div>{$response}</div> </body> </html>
The $response variable determines, for the set of $characters, whether at least one character has a vocation element of value "Mage", and returns the appropriate response string.
Figure 3.4 This query determines whether a party of characters includes a mage.Similarly, the every keyword is used to perform a blanket test to determine whether all items in the set satisfy a condition, returning the value false() if even one item does not. For instance, assume that the cutoff level for a character to enter into a party is the fifth level. If even one character is below the fifth level, the party is underqualified:
let $characters := input()//character let $response := if (every $character in $characters satisfies $character/ level ge 5) then 'Party is qualified to depart' else 'Party is not experienced enough.' return <html> <head> </head> <body> <h1>Is Party Qualified?</h1> <div>{$response}</div> </body> </html>
Once again, it's worth noting how much XQuery is geared toward set manipulation. The every and some functions are extremely efficient; for instance, some will stop evaluating the moment it discovers one item that satisfies the query. Because many databases also have the capability to do fast queries based upon generalized some or every queries, XQuery can leverage these to significantly speed up the evaluation of expressions.