October 25, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

XQuery Language Expressions

  • March 19, 2004
  • By Kurt Cagle
  • Send Email »
  • More Articles »

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

<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
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 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 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.

Defining Functions

XPath has a fairly comprehensive set of functions for doing everything from performing date calculations to evaluating regular expressions. However, sometimes it's useful to be able to build more sophisticated functions out of this core set of basic functions for doing business logic-types of evaluations.

XQuery consequently also supports the capability to create user-definable functions. These functions are XQuery/XPath in origin, and are called in the same context as XPath expressions. For instance, suppose you want to take a date in the standard XSD notation (YYYY-MM-DD) and convert it into the American standard notation MM/DD/YYYY, and you want to do it for several different instances of data that have the following structure (directory.xml):

<directory>
   <file name="chapter3.xml" dateCreated="2002-11-25" dateModified=
"2002-11-28"/>
   <file name="chapter3app1.xml" dateCreated="2002-11-25" dateModified=
"2002-11-29"/>
   <file name="chapter3.toc" dateCreated="2002-11-25" dateModified=
"2002-11-30"/>
   <file name="chapter2.xml" dateCreated="2002-11-18" dateModified=
"2002-11-21"/>
</directory>

You can create an XQuery to provide a report:

<html>
   <body>
   <h1>File Report</h1>
   <table>
      <tr>
         <th>File Name</th>
         <th>Date Created</th>
         <th>Date Last Modified</th>
      </tr>
{for $file in document('directory.xml')//file
return
   <tr>
      <td>{$file/@name}</td>
      <td>{let $refDateStr := string($file/@dateCreated)
   let $year := substring($refDateStr,1,4)
   let $month := substring($refDateStr,6,2)
   let $day := substring($refDateStr,9,2)
   return concat($month,'/',$day,'/',$year)}</td>
   <td>{let $refDateStr := string($file/@dateModified)
   let $year := substring($refDateStr,1,4)
   let $month := substring($refDateStr,6,2)
   let $day := substring($refDateStr,9,2)
   return concat($month,'/',$day,'/',$year)}</td></tr>
}
      </table>
   </body>
</html>

However, there are two difficulties. First, you have a certain degree of code duplication, with similar routines given for formatting the date in the new order. A second related problem is that it is difficult to ascertain exactly what the script is designed to do.

This is a case where working with functions can ameliorate your problems somewhat. You can define a new function called format-date() that takes an XSD type date string as a parameter and formats the date into an American Standard notation:

define function format-date($dt as xs:dateTime) as xs:String
{
let $refDateStr := string($dt)
   let $year := substring($refDateStr,1,4)
   let $month := substring($refDateStr,6,2)
   let $day := substring($refDateStr,9,2)
   return concat($month,'/',$day,'/',$year)
}

First the code creates a new function called format-date in the immediate environment:

define function format-date

The name of the function can include alphanumeric characters along with the underscore (_) and dash (-) characters only.

You can set up zero or more parameters, separated by commas:

define function format-date($dt as xs:dateTime)

The parameters (such as $dt) must be preceded by a dollar sign. A parameter doesn't have to include a data type, as the data type can be inferred dynamically at runtime, but it's generally a good idea to include one if needed. Note, however, that this also precludes overloading (more than one function with the same name and different parametric signatures). The parameter-passing model is thus more akin to languages like JavaScript (or XSLT) than it is Java.

The xs: namespace prefix, discussed earlier, is necessary if you include schema types. Note that some XQuery parsers might support other schema languages, and as such will probably have different data type prefixes:

define function format-date($dt as xs:dateTime) as xs:string

The result type, similarly, need not be specified (as with parameters, it defaults to string and/or numeric general types depending upon the processor) but can be useful to ensure that the results are type-safe. The implication here, of course, is that the resulting output will be a string, rather than a specific nodal type.

The body of the function is itself an XQuery expression—here, a series of lets that breaks the initial text string into chronological pieces:

define function format-date($dt as xs:dateTime) as xs:string
{
   let $refDateStr := string($dt)
   let $year := substring($refDateStr,1,4)
   let $month:= substring($refDateStr,6,2)
   let $day := substring($refDateStr,9,2)
   return concat($month,'/',$day,'/',$year)
}

Note that there are also date-specific functions that do the same thing; however, they are still being finalized in the XQuery working draft.

The outer return clause for the function returns the specific contents of the function to the outside world, and of course should have the data type specified by the return type in the function declaration (here, xs:string):

define function format-date($dt as xs:dateTime) as xs:string
{
   let $refDateStr := string($dt)
   let $year := substring($refDateStr,1,4)
   let $month:= substring($refDateStr,6,2)
   let $day := substring($refDateStr,9,2)
   return concat($month,'/',$day,'/',$year)
}

You can have subordinate return values that create intermediate results, but you must have at least one final outer return value.

There is one implication of XQuery being a "side effect-free" language: You cannot, within XQuery, have a situation where the function changes some external, global variable. Anything passed into an XQuery function is passed by value, not reference. An immediate consequence is that a function must always return something of value—you cannot have a void function type (although you can have one with an empty string or sequence).

The functions are defined ahead of time within the XQuery command, and then are invoked as you would expect for functions. Taking the date-modifying report code mentioned earlier, the functional notation simplifies it considerably (see Figure 5):

define function format-date($dt as xs:dateTime) as xs:string
{
   let $refDateStr := string($dt)
   let $year := substring($refDateStr,1,4)
   let $month:= substring($refDateStr,6,2)
   let $day := substring($refDateStr,9,2)
   return concat($month,'/',$day,'/',$year)
}

<html>
   <body>
   <h1>File Report</h1>
   <table>
      <tr>
         <th>File Name</th>
         <th>Date Created</th>
         <th>Date Last Modified</th>
      </tr>
{for $file in document('directory.xml')//file
return
   <tr>
      <td>{string($file/@name)}</td>
      <td>{format-date($file/@dateCreated)}</td>
      <td>{format-date($file/@dateModified)}</td>
   </tr>
}
      </table>
   </body>
</html>

The body of the code now contains much less code, and the intent of the programming becomes considerably clearer in this example. The expressions

<td>{format-date($file/@dateCreated)}</td>
<td>{format-date($file/@dateModified)}</td>

indicate that the dateCreated attribute and then the dateModified attribute of the file element (here contained in the $file variable) be changed into the MM/DD/YYYY notation, to produce the final output in HTML:

Figure 5
Consolidating reused code into functions can significantly simplify the source query, and encourages the development of code libraries.

<html>
   <body>
      <h1>File Report</h1>
      <table>
         <tr>
            <th>File Name</th>
            <th>Date Created</th>
            <th>Date Last Modified</th>
         </tr>
         <tr>
            <td>chapter3.xml</td>
            <td>11/25/2002</td>
            <td>11/28/2002</td>
         </tr>
         <tr>
            <td>chapter3app1.xml</td>
            <td>11/25/2002</td>
            <td>11/29/2002</td>
         </tr>
         <tr>
            <td>chapter3.toc</td>
            <td>11/25/2002</td>
            <td>11/30/2002</td>
         </tr>
         <tr>
            <td>chapter2.xml</td>
            <td>11/18/2002</td>
            <td>11/21/2002</td>
         </tr>
      </table>
   </body>
</html>

There are two issues to be aware of when dealing with functions: namespaces and libraries. The following sections describe these issues.





Page 4 of 5



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel