XQuery is designed to extract parts of an XML file. It is therefore quite appropriate for selecting a subset of wines and displaying it as an HTML page.
Selecting only red wines in our wine catalog (Example 2.3 (page )) and outputting an HTML table of a subset of the available information for each (Figure 5.1) can be achieved with the XQuery query given in Example 6.1. In order to be compared to Example 5.2, we deliberately organized the XQuery script with a structure similar to the one used in the XSL stylesheet.
The query in Example 6.1 first defines a function for the root
node (line 13‑). Because the wine catalog is defined in
a specific namespace, its prefix must be declared (line 1‑
) and used for selection. This function outputs the
overall structure of the XHTML file with direct constructors. Within an enclosed
expression, it calls the
wine-catalog
function to create the content of the
body. For comparison purposes, on line 23‑ we show an alternate way of defining the
root
function using computed constructors instead of giving the
HTML structure. The wine-catalog
function (line 30‑) outputs a global heading and then starts a
table and defines its headers. The lines of the table will be filled by selecting
(line 37‑
) wines whose color property is
$color
. To set up this color filter, a value must be assigned to the
global color
external variable (line 4‑). This
value is set in an implementation-dependent way when the query is executed by the
XQuery processor.
The output for each selected wine is defined with a function that is called for each
cat:wine
. It outputs, on a single row of the table, the values of its
attributes, its color, its year (right-aligned)
and formats its price to start with a dollar sign (right-aligned). It finally renders
the volume of each bottle in milliliters and in liters. Because the information in the
wine catalog is not stored in milliliters, we call two local
functions to transform it appropriately.
Example 6.1. [WineCatalog.xq
] XQuery script to select the red wines in the
catalog (Example 2.3) and to produce
Example 5.1 displayed as Figure 5.1. Compare this with
Example 5.2.
1 declare namespace cat = "http://www.iro.umontreal.ca/lapalme/wine-catalog"; declare default element namespace "http://www.w3.org/1999/xhtml"; declare variable $color external;
5 (: to produce legal and validable XHTML ... :)
declare option saxon:output "method=xml"; declare option saxon:output "doctype-public=-//W3C//DTD XHTML 1.0 Strict//EN"; declare option saxon:output "doctype-system=http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"; 10 declare option saxon:output "indent=yes"; (: using a direct element constructor, with an enclosed expression :) declare function local:root($catalog){
<html> 15 <head><title>Wine Catalog</title></head> <body> {local:wine-catalog($catalog)} </body> </html> 20 }; (:alternate definition of the function above using computed constructors :) declare function local:root-bis($catalog){
element html { 25 element head {element title {"Wine Catalog"}}, element body {local:wine-catalog($catalog)} } }; 30 declare function local:wine-catalog($catalog){
element h1 {concat("Wine Catalog (",$color," only)")}, element table { attribute border {1}, element tr {
35 for $t in ('Wine Name','Code','Color','Year','Price','ml','l') return element th{$t}}, for $wine in $catalog/cat:wine
where $wine/cat:properties/cat:color=$color return local:wine($wine) 40 } }; declare function local:wine($wine){
<tr>{ 45 <td>{data($wine/@name)}</td>, <td>{data($wine/@code)}</td>, <td>{data($wine/cat:properties/cat:color)}</td>, <td align="right">{data($wine/cat:year)}</td>, (: format-number is not available in XQuery 1.0 !! :) 50 <td align="right">{concat('$',$wine/cat:price)}</td>, <td align="right">{local:toML($wine/@format)}</td>, <td align="right">{local:toL($wine/@format)}</td> } </tr> 55 }; declare function local:toML($fmt){
if ($fmt='375ml') then '375' else if ($fmt='750ml') then '750' 60 else if ($fmt='1l') then '1000' else if ($fmt='magnum') then '1500' else 'big' }; 65 declare function local:toL($fmt){
let $ml := local:toML($fmt) return if ($ml castable as xs:integer) then number($ml) div 1000 70 else $ml }; local:root(/cat:wine-catalog)
![]()
|
Definition of the |
|
Global variable that should be initialized when executing the query. |
|
Declarations telling the SAXON transformation engine to serialize with the appropriate headers to produce valid XHTML. |
|
Function called on the root node defining the skeleton of the HTML page: a head and a body with a call to a function filling the content of the HTML skeleton. |
|
Alternate definition of the previous function to illustrate the difference in style between the direct and computed constructors. |
|
Function for the catalog node: a header with a title and a table. |
|
Uses a |
|
A |
|
Outputs properties of a wine. The first four are written as they appear
in the source but we must use the |
|
a multi-line XPath expression to select the appropriate case
within a cascaded |
|
Uses the output of the previous function to get the number of milliliters, which is then divided by 1000 if it is a number. Otherwise it is returned as is. |
|
Query the document starting from the root element node. |
Here we show how XQuery can be used for more complex selections and transformations. We will explain how to create a web page presenting the content of the cellar and integrating information from the wine catalog. This is equivalent to the program shown in Example 5.4. The end result is shown in Figure 5.2 (an outline of the underlying HTML code is shown in Example 5.3). There are external links in order to retrieve more information about the wines by googling the name of the wine. There are two similar links for each wine, created for the sole purpose of comparing ways of creating them in XSL.
The root
function (line 11‑) creates the high-level
structure of the XHTML file. The title of the page, also displayed at the top of the
page, refers to the name of the owner. In Example 2.2, element
name
(line 12‑) is subdivided in two
elements:
first
and family
. When the value of such an element
is accessed in an enclosed expression, it is returned as an XML element. This would
not be valid in an XHTML document, so we call the data
function to get the text
content of this element, including the whitespace nodes.
The content of the cellar book is obtained by the cellar-book
function
on line 20‑ creating a table with the address of the
owner (line 28‑
) and the
cellar (line 31‑
). It then calls the
cellar
function (line 37‑). The lines of the addresses are obtained by
looping over all elements with a
for
and writing a
<br/>
between the text values of each element of the owner
and location
elements. Because we want to skip the first element (the
name of the owner has already been given at the top), we only keep
elements (line 28‑) with a position number greater than 1.
The cellar
(line 40‑) function produces a
table of information about wines in the cellar, sorted by their code. This is why the
for
is followed by an order by
specification. The last
line of the table contains an estimated total value of the cellar. The value of each
type of wine is computed by creating a sequences of values obtained by a join (two
nested expressions on a for
) between the codes of wines in the cellar and
the codes of the wines in the catalog. The values are then summed with the predefined
sum
function. To compute the total number of bottles (line 60‑), we can use the
sum
function. Finally, if there are any comments in the wine
elements of the
cellar (line 65‑), we add a
Comments
section and write each of them, also in increasing order of wine code. This way, they are
in the same order as in the table of wines.
We define the wine
function (line 75‑) to
create a row in the table of wines. We first define a variable
$code
(line 76‑) holding the value of the
code
attribute. In order to retrieve some information about this wine from the wine catalog, we
pass the wine node from the wine catalog as a parameter when calling the
nameAndUrl
function (line 80‑).
The link between the current element and the corresponding element in the catalog is
made using the value of the
code
variable given in the XPath
expression. The remaining elements of the row are the purchase date (right-aligned), a
number of stars corresponding to the rating and the quantity (right-aligned). The
estimated value of the cellar is computed using XPath expressions. Note the use of
the data
function to get the value of the elements.
nameAndUrl
is a function that receives a wine
element as a
parameter. From this wine
element, it creates an XHTML link with an
a
element. Its href
attribute value is a string specifying
the query to send to Google when searching for this wine using its name. Because the
link must be created dynamically, we use a computed attribute constructor within a
computed a
element which uses the data
function to get the
value of the name
attribute. Note that if only $wine/@name
had
been specified, then the attribute
name
would have been added to the a
element and not its value
as content of the element.
The comment
function (line 97‑) outputs
the value of the
code
of the parent node, then a colon and the text value of the
comment, followed by a line break. When getting the content of the comment, the
cat:bold
elements will be processed by the bold
function
(line 101‑). It will transform them in HTML
b
tags. In XSL, this call was implicit via the application of
templates but in XQuery the function must be called explicitly.
Example 6.2. [CellarBook.xq
] XQuery script to produce information
about the cellar (Example 2.2). The resulting HTML code
(Example 5.3) is rendered as Figure 5.2.
Compare this with Example 5.4.
1 declare namespace cat = "http://www.iro.umontreal.ca/lapalme/wine-catalog"; (: to produce legal and validable XHTML ... :) declare option saxon:output "method=xml"; 5 declare option saxon:output "doctype-public=-//W3C//DTD XHTML 1.0 Strict//EN"; declare option saxon:output "doctype-system=http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"; declare option saxon:output "indent=yes"; declare variable $GoogleStart := "http://www.google.com/search?q=";10 declare function local:root($cellar-book){
<html> <head><title>Cellar of {data($cellar-book/owner/name)}</title></head> <body> 15 {local:cellar-book($cellar-book)} </body> </html> }; 20 declare function local:cellar-book($cellar-book){
<h1>Cellar of {data($cellar-book/owner/name)}</h1>, <table border="1"> <tr> <th>Personal address</th> 25 <th>Cellar address</th> </tr> <tr><td> {for $e in $cellar-book/owner/*[position()>1]
return (data($e),<br/>)} 30 </td> <td>{for $e in $cellar-book/location/*
return (data($e),<br/>)} </td> </tr> 35 </table>, <p/>, local:cellar($cellar-book/cellar,$cellar-book/cat:wine-catalog)
}; 40 declare function local:cellar($cellar,$catalog){
<table border="1"> <tr>{ for $t in ("Code","Name","Purchase Date","Rating","Nb Bottles") return <th>{$t}</th>} 45 </tr>{ for $w in $cellar/wine order by $w/@code ascending return local:wine($w,$catalog)}
<tr> 50 <td colspan="3">Estimated value</td> <td align="right">{ (: compute the estimated value of the cellar :) sum (for $w in $cellar/wine,
$codeInCat in $catalog/cat:wine/@code 55 where $codeInCat = $w/@code return $w/quantity * $codeInCat/../cat:price)} </td> <td align="right">{ (: compute the total number of bottles :) 60 sum($cellar/wine/quantity)}
</td> </tr> </table>, (: put comment section if at least one comment appears :) 65 if (count($cellar/wine/comment)>0)
then ( <h3>Comments</h3>, <p>{for $c in $cellar/wine/comment order by $c/../@code ascending 70 return local:comment($c)} </p> ) else () }; 75 declare function local:wine($wine,$catalog){
let $code := $wine/@code
return <tr> <td>{substring($code,2)}</td> 80 <td>{local:nameAndUrl($catalog/cat:wine[@code=$code])}</td>
<td align="right">{data($wine/purchaseDate)}</td> <td align="center">{substring('*****',1,
if ($wine/rating/@stars) then $wine/rating/@stars else 0)} </td> 85 <td align="right">{data($wine/quantity)}</td> </tr> }; 90 declare function local:nameAndUrl($wine){
element a { attribute href{concat($GoogleStart,encode-for-uri($wine/@name))}, data($wine/@name) } 95 }; declare function local:comment($comment){
data($comment/../@code),':',local:bold($comment),<br/> }; 100 declare function local:bold($mixed-content){
for $child in $mixed-content/node() return if ($child instance of element() and local-name($child)="bold") then element b{data($child)} 105 else $child }; (: change the namespace of a subtree starting at an element adapted from Priscilla Walmsley, XQuery, O'Reilly, p. 258 :) 110 declare function local:change-element-ns-deep($element,$new-ns){
element {QName($new-ns,local-name($element))} { $element/@*, for $child in $element/node() return if ($child instance of element()) 115 then local:change-element-ns-deep($child,$new-ns) else $child } }; 120 (: put the whole tree in the XHTML namespace :) local:change-element-ns-deep(
local:root(/cellar-book), "http://www.w3.org/1999/xhtml")
|
Parameter giving the start of the URL allowing a search for a given wine on Google. |
|
Function for the root node that defines the skeleton of the HTML page:
a head with a title indicating the name of the owner and a body to be
filled by the call to the |
|
Global header with the name of the owner followed by a table giving more information about the owner and the location of the cellar. |
|
Loops over all child nodes of the address, except the first one, the
name of the owner. After the text content of each node is rendered using the
|
|
Loops over all child nodes of the location. After the text content
of each node is written using the |
|
Calls the function to output the content of the cellar. |
|
The content of the cellar is given as a table with a header. It is followed by a series of lines corresponding to each wine. Finally a line holding the estimated value of the whole cellar is created. |
|
Calls the |
|
The total value of the cellar is computed using two nested |
|
The total number of bottles is the sum of the values of all
|
|
Comments appear at the bottom of the table if at least one wine
has a comment. They are also given in ascending order of |
|
Outputs a line of an HTML table for a given wine. |
|
Defines a local variable for the |
|
Outputs the name of the wine and its URL using
the function defined on line 90‑ |
|
Outputs a string of |
|
Produces the name of the wine as the anchor text for an HTML link to Google. It features the appropriate wine name in order to get further information about it. |
|
A comment is preceded with the value of the surrounding
|
|
A |
|
In order to produce a legal XHTML document, all elements must be in an appropriate namespace. Because in XQuery the default global default namespace declaration also applies to the XPath expressions on the input tree, it is not possible to query a tree in the default namespace and have the output tree constructor in another. The output is first created by the transformation in the empty namespace. This result is then copied but with nodes into the XHTML namespace. |
|
Calls the transformation on the |
As we did in the previous chapter, we now describe a query that can be applied to any XML file to show its indentation structure by means of nested HTML unnumbered lists.
To transform the cellar book (Example 2.2) into the HTML code of Example 5.6 (rendered in Figure 5.3), we can use the code given in Example 6.3 which features four main functions:
for the root element (line 20‑) the function produces
the overall structure of the HTML file with its
head
and
body
elements. The enclosed expression for processing subelements
(line 27‑) is called within an unnumbered list
delimited by
ul
tags.
for an attribute (line 33‑), the
function returns a space, the name of the attribute followed by an equal
sign and its value within double quotes.
for an element (line 37‑), the
function produces the name of the element returned by the function
local-name()
in bold (line 39‑) followed by its attributes.
If the element is a node without any children (it is a text node in this
case) then it is output with only its content, otherwise a new unnumbered
list is started and template matching is applied on child nodes.
a text node (line 54‑) is inserted into
an
li
element
Example 6.3. [compactHTML.xq
] XQuery script to produce a bulleted
outline (Example 5.6) from the cellar book
(Example 2.2). Compare this with Example 5.7.
1 (: to produce legal and validable XHTML ... :) declare option saxon:output "method=xml";declare option saxon:output "doctype-public=-//W3C//DTD XHTML 1.0 Strict//EN"; declare option saxon:output "doctype-system=http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"; 5 declare option saxon:output "indent=yes"; (: remove all whitespace nodes from elements of the input tree :) declare function local:strip-space($element){
element {local-name($element)} { 10 $element/@*, for $child in $element/node() return if ($child instance of text() and normalize-space($child)='') then () else if ($child instance of element()) 15 then local:strip-space($child) else $child } }; 20 declare function local:root($root,$uri){
<html> <head> <title>HTML compaction of "{replace($uri,'.*/(.*)','$1')}"</title>
</head> 25 <body> <ul> {local:element($root)}
</ul> </body> 30 </html> }; declare function local:attribute($attribute){
concat(" ",local-name($attribute),'="',$attribute,'"') 35 }; declare function local:element($element){
<li> <b>{local-name($element)}</b>
40 {for $attr in $element/@* return local:attribute($attr), if (count($element/*)=0) (: single text node ?:)
then concat(" ",data($element)) else <ul> {
45 for $child in $element/(*|text()) (: possible mixed node :) return if($child instance of element()) then local:element($child) else local:text($child) } </ul> 50 } </li> }; declare function local:text($text){
55 <li>{$text}</li> }; (: change the namespace of a subtree starting at an element adapted from Priscilla Walmsley, XQuery, O'Reilly, p. 258 :) 60 declare function local:change-element-ns-deep($element,$new-ns){
element {QName($new-ns,local-name($element))} { $element/@*, for $child in $element/node() return if ($child instance of element()) 65 then local:change-element-ns-deep($child,$new-ns) else $child } }; 70 local:change-element-ns-deep( (: put the whole tree in the XHTML namespace :)
local:root(local:strip-space(/*),document-uri(/)), "http://www.w3.org/1999/xhtml")
|
Declarations telling the SAXON transformation engine to serialize with the appropriate headers to produce valid XHTML. |
|
Function to remove all whitespace-only nodes in an element because we are only interested in the content of the input document, not its structure. |
|
Produces the XHTML skeleton for the document. |
|
Outputs the title of the HTML page. Because the |
|
Enclosed expression for starting the processing at the root element node. |
|
An attribute is output as a space, the name of the attribute and its
value between double quotes after an equal sign. This sequence of nodes
is within a call to |
|
An element node is a list-item. |
|
The name of the element in bold, followed by the attributes using the
function defined on line 33‑ |
|
If there are no children elements, i.e. it is a single text node, the textual content is inserted directly without adding a new list level. |
|
If there are children nodes, elements are processed recursively and text nodes (in the
case of mixed content) are processed with the |
|
If there are no children nodes, it is then a text node that is copied to the output. |
|
In order to produce a legal XHTML document, all elements must be in an appropriate namespace. Because in XQuery the default global default namespace declaration also applies to the XPath expressions on the input tree, it is not possible to query a tree in the default namespace and have the output tree constructor into another. The output is first created by the transformation in the empty namespace. This result is then copied but with nodes into the XHTML namespace. |
|
Calls the transformation on the |