08 XQuery
08 XQuery
Sequences
The main structure of XQuery is the ordered sequence.
The sequence in XQuery corresponds to the node set in
XSLT, but they have different characteristics.
A sequence may contain items of two types: simple types or
nodes.
A sequence may not contain another sequence.
A sequence with one item is the same as the item itself.
XQuery
(: same as (1, 2, 3, 4, 5) :)
(1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 5)
(xs:date("2005-05-02"), xs:date("2005-05-04"),
xs:date("2005-05-06"))
("Hello", 45.8, 994, xs:date("1922-11-11"), true())
()
(: Empty sequence :)
XQuery
XQuery Expressions
XQuery is a language of expressions, each of which
produces an XQuery sequence.
Remember that a single item is identical to a singleton
sequence containing that item.
Variables in XQuery are XML qualified names preceded by $.
string-length("herky")
if ($m > 0) then $m - 1 else $m + 1
(: must be a space between m and - :)
XQuery
Path Expressions
XQuery allows XPath location paths as expressions.
We can build a literal node and select a subnode.
<one><two>herky</two><two>hawk</two></one>/two/text()
produces the value herkyhawk.
We want to use these paths to explore an existing XML
document.
One way to specify an XML document in XQuery is the function
doc, which takes the name of a file as a string parameter.
This function returns a single document as a source tree.
Example: Fetch student elements
doc("roster.xml")/roster/students/student
Running XQuery
The best (free) implementation of XQuery that I found was from
Saxon (Michael Kay).
Install the jar file saxon8.8.jar on your machine by putting it in
your bin directory and pointing CLASSPATH to its location.
The XQuery expression needs to be entered in a file to be
processed by Saxon.
For example, place the previous example into a text file with the
name students.
XQuery
Execution
% java net.sf.saxon.Query students
<?xml version="1.0" encoding="UTF-8"?>
<student id="94811">
<name>Rusty Nail</name>
<quizzes>
<quiz>16</quiz>
<quiz>12</quiz>
</quizzes>
<projects>
<project>44</project>
<project>52</project>
</projects>
<exams>
<exam>77</exam>
<exam>68</exam>
<exam>49</exam>
</exams>
</student>
<student id="2562">
<name>Guy Wire</name>
<quizzes>
<quiz>15</quiz>
<quiz>23</quiz>
</quizzes>
<projects>
<project>33</project>
<project>47</project>
</projects>
<exams>
<exam>78</exam>
<exam>86</exam>
<exam>88</exam>
</exams>
</student>
:
XQuery
<student id="137745">
<name>Barb Wire</name>
<quizzes>
<quiz>20</quiz>
<quiz>25</quiz>
</quizzes>
<projects>
<project>48</project>
<project>60</project>
</projects>
<exams>
<exam>38</exam>
<exam>48</exam>
<exam>66</exam>
</exams>
</student>%
Note that complete elements are returned as the value of the
expression, and that the result is not a well-formed XML
document because it does not have a single root.
Such a structure is called a forest.
You can see that Saxon always provides an XML declaration at
the beginning of the result.
Furthermore, it does not supply a new line at the end of the
result.
To stay in line with Saxon, we will define our queries to produce
legal XML as much as possible.
That means intermixing XML tags and XPath expressions that
need to be evaluated.
To separate literal data from expressions that must be
evaluated, XQuery uses braces to indicate what must be
evaluated.
I use an alias xquery that takes the file name as a parameter
and causes the execution of the Java class net.sf.saxon.Query.
6
XQuery
Results
<?xml version="1.0" encoding="UTF-8"?>
<newRoot>
<name>Rusty Nail</name>
<name>Guy Wire</name>
<name>Norman Conquest</name>
<name>Eileen Dover</name>
<name>Barb Wire</name>
</newRoot>%
Results
<?xml version="1.0" encoding="UTF-8"?>
<newRoot>Rusty NailGuy WireNorman ConquestEileen
DoverBarb Wire</newRoot>%
XQuery
Alternate Version
<newRoot>
{
doc("roster.xml")/roster/students/student/string(name)
}
</newRoot>
The results are the same.
Saxon Options
The results from an XQuery query under Saxon are displayed
on the screen.
How to Put Results in a File
Saxon has an option (-o outfile) that directs the results to the
named file.
% java net.sf.saxon.Query -o stdts.out students
Another way is to pipe the stream through the Unix cat function.
% java net.sf.saxon.Query students | cat > stds.out
To see the other Saxon options enter:
% java net.sf.saxon.Query
XQuery
FLWOR
XQuery queries can be written as FLWOR (pronounced
"flower") expressions.
F :
L :
W:
O:
R :
Example 1
for $m in 1 to 10
let $n := $m + 1
where $m > 4
order by $m descending
return $m * $n
Produces the result: 110 90 72 56 42 30
Observe that the let phrase uses := to describe the binding
of a variable. This symbol is the assignment operator in
many programming languages.
As in XSLT, variables may not have their values altered.
Some of the components of a FLWOR expression are optional.
A return is required, and it must be preceded by at least one
for or one let.
A FLOWR expression may have multiple for and let
components, and these can be nested in the expression.
XQuery
Example 2
<numbers>
{
for $m in (1 to 10)
(: parentheses are redundant :)
let $n := $m + 1
where $m > 4
order by $m descending
return <num>{ $m * $n }</num>
}
</numbers>
Results
<?xml version="1.0" encoding="UTF-8"?>
<numbers>
<num>110</num>
<num>90</num>
<num>72</num>
<num>56</num>
<num>42</num>
<num>30</num>
</numbers>%
We now turn to using XQuery to solve the problems that were
considered in the XSLT chapter.
You can compare these solutions with the XSLT stylesheets to
help you judge the usefulness of XQuery.
10
XQuery
Problem 1
Find the sum of all first exams, the number of first exams, and
the average on that exam.
File: p1.xq
<exam1>
{
let $doc := doc("roster.xml")
let $e1s := $doc/roster/students/student/exams/exam[1]
let $sum := sum($e1s)
let $num := count($e1s)
return (<sum>{ $sum }</sum>,
<num>{ $num }</num>,
<average>{ $sum div $num }</average>)
}
</exam1>
Observe how a sequence of elements is built using
parentheses and commas in the return expression.
Also, note how braces are used to force evaluation when
expressions are interspersed with literal output.
XQuery
11
Problem 2
Find the sum, the count, and the average for the quizzes for
each student.
An XQuery program is generally made up of a prolog and a
body.
So far our examples have only consisted of the body.
A prolog can be used to define global variables, functions, and
namespaces among a few other items.
Each prolog definition begins with the keyword declare and ends
with a semicolon.
In the next solution we define a global variable in the prolog.
File: p2.xq
declare variable $doc := doc("roster.xml");
<quizzes>
{
for $s in $doc/roster/students/student
let $sum := sum($s/quizzes/quiz)
let $num := count($s/quizzes/quiz)
return
<student>
{ $s/name }
<sum>{ $sum }</sum>
<num>{ $num }</num>
<average>{ $sum div $num }</average>
</student>
}
</quizzes>
In this solution, the XPath "$s/name" returns an element, so we
do not need to supply tags, but tags are required for the three
new elements, sum, num, and average.
12
XQuery
XQuery
13
Problem 3
Find out if there are exam scores below 50. If so, tell how many
there are.
This problem makes good use of the conditional expression.
Do not confuse a conditional expression (if-then-else) with the
conditional commands (if and if-else) in Java.
File: p3.xq
declare variable $doc := doc("roster.xml");
<exams>
{
let $num := count($doc/roster/students/student/
exams/exam[. < 50])
return
<answer>
{
if ($num > 0)
then concat($num, " exams below 50")
else "No exams less than 50."
}
</answer>
}
</exams>
14
XQuery
gt
ge
Note:
Using comparison operators can be very tricky.
Comparing the contents of elements will normally be made
as string comparisons.
If we want to compare values as numbers, we must convert
the strings to numbers.
XQuery
15
Problem 4
Find the names of all students who scored higher on exam 1
than on exam 3.
This problem can be solved by using a conditional expression
or by using a where clause in the FLWOR expression.
File: p4a.xq
<higher>
{
for $s in doc("roster.xml")/roster/students/student
let $e1 := $s/exams/exam[1]
let $e3 := $s/exams/exam[3]
return
if ($e1 gt $e3)
(: beware :)
then
<student>
{ ($s/name, $s/exams/exam[1], $s/exams/exam[2]) }
</student>
else ()
}
</higher>
When the test fails, the query returns an empty sequence,
which is equivalent to returning nothing.
16
XQuery
File: p4b.xq
<higher>
{
for $s in doc("roster.xml")/roster/students/student
let $e1 := $s/exams/exam[1]
let $e3 := $s/exams/exam[3]
where $e1 gt $e3
(: beware :)
return
<student>
{ ($s/name, $s/exams/exam[1], $s/exams/exam[2]) }
</student>
}
</higher>
Change Rusty Nail's first exam to 7.
XQuery
17
The problem with these queries is that the two exams are being
compared as strings. The value comparison operator gt does
not promote the values to integers.
Solution: Cast each value to an integer explicitly.
if (xs:integer($e1) gt xs:integer($e3)) in p4a.xq
where xs:integer($e1) gt xs:integer($e3) in p4b.xq
Now the queries work correctly.
Problem 5
Find all students who scored 80 or higher on exam 3.
File: p5.xq
<exams>
{
for $s in doc("roster.xml")/roster/students/student
let $e := $s/exams/exam[3]
where $e ge 80
(: beware :)
return
<student>
{ $s/name }
<exam>{ $e/text() }</exam>
</student>
}
</exams>
Alternative
<student>{ ($s/name, $e) }</student>
18
XQuery
XQuery
19
General Comparisons
These operators are used to compare two sequences.
They return true if any pair of elements from the two sequences
satisfy the relation.
If the sequences are singleton values, these comparisons will
be similar to value comparison operators.
The general comparison operators:
=
!=
<
<=
>
>=
Examples
Expression
(1, 2, 3) = (3, 4)
(1, 2, 3) != (3, 4)
(1, 2, 3) >= (3, 4)
(1, 2, 3) < (3, 4)
(1, 2) = (3, 4)
Value
true
true
true
true
false
XQuery
Node Comparison: is
The is operator is used to compare single nodes and empty
sequences.
This operator tests for node identity in the same way that
the Java == operator tests for identity between objects.
Examples
Expression
<tag>content</tag> is <tag>content</tag>
Value
false
let $x := <tag>content</tag>
let $y := $x return $x is $y
true
doc('roster.xml') is doc('roster.xml'))
true
(1, 2) is (1, 2)
error
Problem
Produce an XML document that contains each pair of distinct
students in roster.xml.
Since this property describe a symmetric relation, we get
each pair twice in different orders.
File: pairs.xq
declare variable $doc := doc("roster.xml");
<answer>
{
for $s1 in $doc/roster/students/student
for $s2 in $doc/roster/students/student
where not($s1 is $s2)
return
<pair>
{ ($s1/name, $s2/name) }
</pair>
}
</answer>
XQuery
21
22
XQuery
<pair>
<name>Barb Wire</name>
<name>Norman Conquest</name>
</pair>
<pair>
<name>Barb Wire</name>
<name>Eileen Dover</name>
</pair>
</answer>
Value
deep-equal(<tag>123</tag>, <tag>123</tag>)
true
true
deep-equal(doc('roster.xml'), doc('roster.xml'))
true
false
true
false
true
XQuery
23
>>
Problem
Produce an XML document that contains each pair of distinct
students in roster.xml, but produce each pair only once
independent of order.
This solution creates the pairs as an asymmetic relation.
File: pairsA.xq
declare variable $doc := doc("roster.xml");
<answer>
{
for $s1 in $doc/roster/students/student
for $s2 in $doc/roster/students/student
where $s1 << $s2
return
<pair>
{ ($s1/name, $s2/name) }
</pair>
}
</answer>
24
XQuery
25
<pair>
<name>Eileen Dover</name>
<name>Barb Wire</name>
</pair>
</answer>
Problem 6
Find all students who scored 80 or higher on any exam,
indicating which exam it is.
The following solution uses a variable to remember the position
of each exam in the sequence containing the three exams for
each student.
File: p6.xq
<exams>
{
for $s in doc("roster.xml")/roster/students/student
for $e at $pos in $s/exams/exam
where xs:integer($e) ge 80
return
<student>
{ $e/ancestor::student/name (: or $s/name :) }
{ $e }
<position>{ $pos }</position>
</student>
}
</exams>
26
XQuery
XQuery
27
Sorting
The order by clause in a FLWOR expression controls the
order that the sequence will be output.
It takes an expression that specifies the properties of the
sequence items that are used to sort the sequence.
The property specification may be followed by ascending
(the default) or descending modifiers.
Multiple properties can be listed, separated by commas, with
the early properties taking precedence over the later properties.
Problem 7
Show the total on projects for each student, listing the
students by name, sorted alphabetically.
File: p7a.xq
<projects>
{
for $s in doc("roster.xml")/roster/students/student
let $proj := sum($s/projects/project)
order by $s/name
return
<student>
{ $s/name }
<total>{ $proj }</total>
</student>
}
</projects>
28
XQuery
File: p7b.xq
<projects>
{
for $s in doc("roster.xml")/roster/students/student
let $proj := sum($s/projects/project)
order by substring-after($s/name,' '),
substring-before($s/name,' ')
XQuery
29
return
<student>
{ $s/name }
<total>{ $proj }</total>
</student>
}
</projects>
30
XQuery
File: p7c.xq
<projects>
{
for $s in doc("roster.xml")/roster/students/student
let $proj := sum($s/projects/project)
order by $s/@id
return
<student>
<id>{ data($s/@id) }</id>
<total>{ $proj }</total>
</student>
}
</projects>
31
File: p7d.xq
<projects>
{
for $s in doc("roster.xml")/roster/students/student
let $proj := sum($s/projects/project)
order by xs:integer($s/@id)
return
<student>
<id>{ string($s/@id) }</id>
<total>{ $proj }</total>
</student>
}
</projects>
32
XQuery
<student>
<id>49194</id>
<total>85</total>
</student>
<student>
<id>94811</id>
<total>96</total>
</student>
<student>
<id>132987</id>
<total>96</total>
</student>
<student>
<id>137745</id>
<total>108</total>
</student>
</projects>
Problem 8
Find the average scores on the exams for each of the students.
For a change of pace, we sort the student names by surname
from the end of the alphabet.
File: p8.xq
<exams>
{
for $s in doc("roster.xml")/roster/students/student
let $avg := avg($s/exams/exam)
order by substring-after($s/name,' ') descending
return
<student>
{ $s/name }
<average>{ $avg }</average>
</student>
}
</exams>
XQuery
33
Function Definitions
The prolog of an XQuery program allows the definition of
functions as well as variables.
Basic Syntax
declare function funName($p1, $p2, $p3)
{
an expression that defines the result of the
function
};
(: do not forget the semicolon :)
34
XQuery
The Saxon implementation of XQuery requires that all userdefined functions must lie in an explicit namespace.
A namespace and its prefix are defined in the prolog using the
following syntax.
declare namespace prefix = "unique.uri";
In the next solution to Problem 8, we define a namespace and a
function in that namespace to calculate the average of a
sequence of nodes.
File: p8f.xq
Suppose for the sake of this example that XQuery has no
function to compute the average of a sequence of numbers.
In this solution we will write our own function for this computation.
declare namespace myfun = "myfun.slonnegr.cs.uiowa.edu";
declare function myfun:average($nodes)
{
let $sum := sum($nodes)
let $num := count($nodes)
return $sum div $num
};
<exams>
{
for $s in doc("roster.xml")/roster/students/student
let $avg := myfun:average($s/exams/exam)
order by substring-after($s/name, ' ') descending
return
<student>
{ $s/name }
<average>{ $avg }</average>
</student>
}
</exams>
XQuery
35
The results are the same as those with the previous solution to
Problem 8.
File: p8fa.xq
<exams>
{
for $s at $p in doc("roster.xml")/roster/students/student
let $avg := myfun:average($s/exams/exam)
order by substring-after($s/name, ' ') descending
return
<student index="{ $p }">
{ $s/name }
<average>{ $avg }</average>
</student>
}
</exams>
36
XQuery
XQuery
37
File: p8fb.xq
element exams
{
for $s at $p in doc("roster.xml")/roster/students/student
let $avg := myfun:average($s/exams/exam)
order by substring-after($s/name, ' ') descending
return element student
{
attribute index { $p },
$s/name,
element average { $avg }
}
}
The results using this body will be identical to those from the
previous solution with the index attribute.
38
XQuery
XQuery
39
Building Attributes
Observe that in this XQuery program, we assume that we do
not know the names of the attributes for the item elements,
but we can still form them into new elements.
File: da.xq
<newroot>
<newitems>
{
for $item in doc("dynamic.xml")/root/items/item
return <newitem>
{
for $at in $item/@*
return element {$at/name()} { data($at) }
}
</newitem>
}
</newitems>
</newroot>
XQuery
File: dea.xq
<newroot>
<newitems>
{
for $item in doc("dynamic.xml")/root/items/item
return element newitem
{
for $e in $item/*
return attribute { $e/name() } { $e/text() },
for $at in $item/@*
return element { $at/name() } { string($at) }
}
}
</newitems>
</newroot>
41
Quantified Expressions
Two expressions in Xquery implement the quantifiers from
predicate logic: every and some.
Each takes a variables and two expressions as arguments and
produces a boolean value.
These quantified expressions are evaluated in the same way
they are read.
some var in seq-expr satisfies bool-expr
returns true if and only if there exists a value in the
sequence that makes the boolean expression true.
every var in seq-expr satisfies bool-expr
returns true if and only if every value in the sequence
makes the boolean expression true.
Both can be defined in terms of other Xquery expressions.
some var in seq-expr satisfies bool-expr
has the same meaning as
exists(for var in seq-expr where bool-expr return 1)
every var in seq-expr satisfies bool-expr
has the same meaning as
empty(for var in seq-expr where not(bool-expr) return 1)
42
XQuery
Simple Examples
Expression
Value
every $n in 1 to 10 satisfies $n gt 0
true
false
true
some $n in 1 to 10 satisfies $n lt 0
false
Problem
Find out whether or not anyone scored higher than 95 on
any exam in the XML document roster.xml.
This problem can be reworded: Tell whether there exists
an exam score higher than 95 in the student information.
File: p95.xq
declare variable $d := doc("roster.xml");
<answer>
{
if (some $e in
$d/roster/students/student/exams/exam
satisfies number($e) gt 95)
then "Someone scored higher than 95 on an exam."
else "No one scored higher than 95 on an exam."
}
</answer>
43
Problem
Find all pairs of students where the first student scored higher
than the second student on each of the exams.
The idea with the solution to this problem is to generate all pairs
of students (in both orders), and for each pair verify that the two
students have the same number of exams and that every exam
for the first student is greater than the corresponding exam of
the second student.
File: higher.xq
declare variable $doc := doc("roster.xml");
<answer>
{
for $s1 in $doc/roster/students/student
for $s2 in $doc/roster/students/student
where not($s1 is $s2)
(: want all distinct pairs :)
return
let $e1 := $s1/exams/exam
let $e2 := $s2/exams/exam
where
count($e1) = count($e2)
and
(every $p in 1 to count($e1)
satisfies number($e1[$p])>number($e2[$p]))
return
<pair>
{ ($s1/name, $s2/name) }
</pair>
}
</answer>
Note: It was necessary to place the every expression
inside parentheses.
44
XQuery
XQuery
45
File: primes.xq
declare namespace myfun = "myfun.slonnegr.cs.uiowa.edu";
declare function myfun:prime($n)
{
$n = 2 or
($n > 2 and
(every $d in 2 to $n idiv 2 satisfies $n mod $d > 0))
};
<primes>
{
for $k in (1 to 100)
where myfun:prime($k)
return
<prime>{ $k }</prime>
}
</primes>
46
XQuery
XQuery
47
Joins
The join is a principal operation in the world of relational
databases.
The idea is to meld the information stored in two or more
database tables by comparing values in specific columns in
such a way to define those rows of information that are desired.
Joins can be performed in Xquery to combine the information in
two or more XML documents, selecting only that information that
satisfies certain conditions on the elements in the documents.
In the following examples we consider various problems that can
be solved using join operations that combine the information in
roster.xml and a new version of the phone XML document.
File: phone.xml
<?xml version="1.0" standalone="no"?>
<!DOCTYPE phoneNumbers SYSTEM "phone.dtd">
<phoneNumbers>
<title>Phone Numbers</title>
<entries>
<entry>
<name gender="male">Rusty Nail</name>
<phone>335-0055</phone>
<city>Iowa City</city>
</entry>
<entry>
<name gender="male">Guy Wire</name>
<phone>354-9876</phone>
<city>Coralville</city>
</entry>
48
XQuery
<entry>
<name gender="female">Eileen Dover</name>
<phone>354-9876</phone>
<city>Coralville</city>
</entry>
<entry>
<name gender="female">Candy Barr</name>
<phone>335-4582</phone>
<city>North Liberty</city>
</entry>
<entry>
<name gender="female">Barb Wire</name>
<phone>337-5967</phone>
<city>Coralville</city>
</entry>
</entries>
</phoneNumbers>
Cartesian Product
The most general sort of join includes all pairs of items from
two sequences derived from XML documents.
AxB = { (a,b) | aA and bB }
Problem
Find all pairs of persons from the XML documents
roster.xml and phone.xml.
XQuery
49
File: cartesian.xq
declare variable $roster := doc("roster.xml");
declare variable $phone := doc("phone.xml");
<pairs>
{
for $s in $roster/roster/students/student
for $e in $phone/phoneNumbers/entries/entry
return
<pair>
{ concat("(", $s/name/text(), ", ", $ e/name/text(), ")") }
</pair>
}
</pairs>
XQuery
Inner Joins
Usually we do not want all possible ordered pairs of elements
from the two sequences.
With an inner join we select only those pairs that are equal or
are related in some particular way.
Problem
Find all students in roster.xml who are in the phone
document as well.
File: equi-join.xq
<students>
{
for $s in doc("roster.xml")/roster/students/student
for $e in doc("phone.xml")/phoneNumbers/entries/entry
where $s/name = $e/name
return
<student>
{ $s/name }
</student>
}
</students>
XQuery
51
52
XQuery
Problem
Find all students who are in the roster document and
the phone document and are females from Coralville,
showing their names and id numbers.
File: coralville.xq
<students>
{
for $s in doc("roster.xml")/roster/students/student
for $e in doc("phone.xml")/phoneNumbers/entries/entry
where $s/name=$e/name and
$e/name/@gender="female" and
$e/city="Coralville"
return
<student>
{ $s/@id, $s/name }
</student>
}
</students>
XQuery
53
Problem
Find all female students in roster.xml whose projects
scores are all greater than 20.
File: project20.xq
<students>
{
for $s in doc("roster.xml")/roster/students/student
for $e in doc("phone.xml")/phoneNumbers/entries/entry
where $s/name=$e/name and
$e/name/@gender="female" and
(every $p in $s/projects/project
satisfies number($p) gt 20)
return
<student>
{ $s/name }
</student>
}
</students>
54
XQuery
Problem
Find the phone number(s) of the student(s) who
scored highest on exam 3.
In the solution to this problem we need to allow for the case
where there are no exams in the sequence defined by the
XPath expression.
To make this query more challenging we have changed the
third exam score of Rusty Nail to 89 so that two students
have the maximum score.
File: max3.xq
declare variable $roster := doc("roster.xml");
declare variable $phone := doc("phone.xml");
<students>
{
let $exams :=
(for $s in $roster/roster/students/student
for $e in $phone/phoneNumbers/entries/entry
where $s/name=$e/name
return $s)/exams/exam[3]
return
if (count($exams) > gt 0)
then
let $mx := max($exams)
return
for $s in $roster/roster/students/student
for $e in $phone/phoneNumbers/entries/entry
where $s/name=$e/name and
xs:integer($s/exams/exam[3])
=xs:integer($mx)
XQuery
55
return
<student>
{ $e/phone }
</student>
else ()
}
</students>
Self-Joins
The sequences in a join may come from the same XML
document creating what is know as a self-join.
Problem
For each person in the phone.xml document, find all
others who live in the same city.
56
XQuery
File: samecity.xq
declare variable $phone := doc("phone.xml");
<people>
{
for $p1 in $phone/phoneNumbers/entries/entry
return
<person>
<name>{ $p1/name/text() }</name>
<neighbors>
{
for $p2 in $phone/phoneNumbers/entries/entry
where not($p1 is $p2) and $p1/city=$p2/city
return
<name>{ $p2/name/text() }</name>
}
</neighbors>
</person>
}
</people>
57
<person>
<name>Eileen Dover</name>
<neighbors>
<name>Guy Wire</name>
<name>Barb Wire</name>
</neighbors>
</person>
<person>
<name>Candy Barr</name>
<neighbors/>
</person>
<person>
<name>Barb Wire</name>
<neighbors>
<name>Guy Wire</name>
<name>Eileen Dover</name>
</neighbors>
</person>
</people>
Problem
For each student in roster.xml, find all other students
who have higher scores on project 1 and on project 2.
58
XQuery
File: higherp.xq
declare variable $roster := doc("roster.xml");
<students>
{
for $s1 in $roster/roster/students/student
return
<student>
{
$s1/name,
<higher>
{
for $s2 in $roster/roster/students/student
where
$s2/projects/project[1] gt $s1/projects/project[1]
and
$s2/projects/project[2] gt $s1/projects/project[2]
return
$s2/name
}
</higher>
}
</student>
}
</students>
XQuery
59
<student>
<name>Guy Wire</name>
<higher>
<name>Rusty Nail</name>
<name>Norman Conquest</name>
<name>Barb Wire</name>
</higher>
</student>
<student>
<name>Norman Conquest</name>
<higher>
<name>Barb Wire</name>
</higher>
</student>
<student>
<name>Eileen Dover</name>
<higher>
<name>Rusty Nail</name>
<name>Norman Conquest</name>
<name>Barb Wire</name>
</higher>
</student>
<student>
<name>Barb Wire</name>
<higher/>
</student>
</students>
60
XQuery
File: dow.xq
declare namespace myfun = "myfun.slonnegr.cs.uiowa.edu";
declare function myfun:dow($m, $d, $y)
{
let $mn := if ($m > 2) then $m - 2 else $m + 10
let $yr := if ($m > 2) then $y else $y - 1
let $ct := $yr idiv 100
let $an := $yr mod 100
let $base := (13 * $mn - 1) idiv 5 + $an idiv 4 + $ct idiv 4
let $rem := ($base + $an + $d - 2 * $ct) mod 7
let $offset := if ($rem < 0) then $rem + 7 else $rem
return if ($offset = 0) then "Sunday"
else if ($offset = 1) then "Monday"
else if ($offset = 2) then "Tuesday"
else if ($offset = 3) then "Wednesday"
else if ($offset = 4) then "Thursday"
else if ($offset = 5) then "Friday"
else if ($offset = 6) then "Saturday"
else "error"
};
<dow>
{
element last
element ww1
element ww2
element ny
element first
}
</dow>
XQuery
{ myfun:dow(12, 8, 2006) },
{ myfun:dow(11, 11, 1918) },
{ myfun:dow(12, 7, 1941) },
{ myfun:dow(9, 11, 2001) },
{ myfun:dow(1, 1, 2001) }
61
Sqrt in Java
static double sqrt(double x)
{
double oldx, newx = x/2.0;
for (int k=1; k<=20; k++)
{
oldx = newx;
newx = (oldx * oldx + x)/(2.0 * oldx);
}
return newx;
}
62
XQuery
XQuery
63
Sqrt in XQuery
The last Java definition translates into XQuery in a
straightforward manner.
Since we want to perform the calculations using the xs:double
type, we need to express literals using e or E.
Numeric literals with a decimal point are viewed as xs:decimal, a
type that will not be implemented as efficiently for arithmetic.
File: sqrt.xq
declare namespace myfun = "myfun.slonnegr.cs.uiowa.edu";
declare function myfun:sqrt($x)
{
myfun:step(20, $x, $x div 2e1)
};
declare function myfun:step($k, $x, $oldx)
{
let $newx := ($oldx * $oldx + $x) div ($oldx + $oldx)
return if ($k <= 0)
then $newx
else myfun:step($k - 1, $x, $newx)
};
<squareRoots>
{
for $x in (1 to 15)
return <root num="{ $x }">
{ myfun:sqrt($x) }
</root>
}
</squareRoots>
64
XQuery
XQuery
65
File: dowt.xq
declare function myfun:dow( $m as xs:integer,
$d as xs:integer,
$y as xs:integer) as xs:string
{
let $mn as xs:integer := if ($m > 2) then $m - 2 else $m + 10
let $yr as xs:integer := if ($m > 2) then $y else $y - 1
let $ct as xs:integer := $yr idiv 100
let $an as xs:integer := $yr mod 100
let $base as xs:integer :=
(13 * $mn - 1) idiv 5 + $an idiv 4 + $ct idiv 4
let $rem as xs:integer := ($base + $an + $d - 2 * $ct) mod 7
let $offset as xs:integer :=
if ($rem < 0) then $rem + 7 else $rem
return let $days := ("Sunday", "Monday", "Tuesday",
"Wednesday","Thursday", "Friday", "Saturday")
return $days[$offset + 1]
};
66
XQuery
Notes on Typing
Although an XQuery implementation is not required to
provide static type checking, with these declarations,
static type checking is possible and dynamic type checking
will be more effective.
XQuery
67
68
XQuery
File: german.xq
<Telefonnummern>
{
let $doc := doc("phoneA.xml")
return
(
<Titel>{ $doc/phoneNumbers/title/text() }</Titel>,
<Eintraege>
{
for $e in $doc/phoneNumbers/entries/entry
let $gender := $e/name/@gender
let $middle := $e/name/middle/text()
let $city := $e/city/text()
XQuery
69
return
<Eintrag>
<Name>
{
if ($gender)
then attribute Geschlect { $gender } else ()
}
<Vorname1>{ $e/name/first/text() }</Vorname1>
{
if ($middle)
then <Vorname2>{ $middle }</Vorname2>
else ()
}
<Nachname>{ $e/name/last/text() }
</Nachname>
</Name>
<Telefonnummer>
{ $e/phone/text() }
</Telefonnummer>
{
if ($city) then <Stadt>{ $city }</Stadt> else ()
}
</Eintrag>
)
}
</Eintraege>
}
</Telefonnummern>
Recall that $e/name/last produces the element <last>Nail</last>,
whereas $e/name/last/text() produces just the string "Nail".
The output from executing this XQuery program is identical to
that obtained from the stylesheet in the XSLT chapter.
You might want to compare the XQuery program with the XSLT
stylesheet to form an opinion about the effectiveness of these
two technologies.
70
XQuery
XQuery in Java
No standard has been developed for executing XQuery in
Java at this time.
However, several systems that link XQuery to Java have
been proposed and developed.
We consider one that appears to be fairly complete (and
free): Saxon.
71
net.sf.saxon.query.DynamicQueryContext
An object of this class contains a dynamic context for
query execution.
We need to create one object of this class using the
Configuration object.
DynamicQueryContext dqc =
new DynamicQueryContext(config);
This object can hold a reference to a stream that
encapsulates the XML document to be queried.
dqc.setContextItem(
sqc.buildDocument(
new StreamSource("phoneA.xml")));
net.sf.saxon.query.XQueryExpression
An object of this class represents a compiled XQuery
query. It is created by calling an overloading instance
method on the StaticQueryContext object.
XQueryExpression exp1 =
sqc.compileQuery(queryString);
XQueryExpression exp2 =
sqc.compileQuery(readerObject);
XQueryExpression contains several instance methods for
evaluating a compiled XQuery query.
java.util.List evaluate(DynamicQueryContent dqc)
The evaluate method executes the compiled query
using the DynamicQueryContext object and returning a
the result as a sequence (a node set).
72
XQuery
XQuery
73
net.sf.saxon.trans.XPathException
This exception may be thrown by buildDocument,
compileQuery, evaluate, evaluateSingle, run, and iterator
javax.xml.transform.OutputKeys
This class contains a collection of useful constants (static
final variables), including METHOD, INDENT, ENCODING,
STANDALONE, and DOCTYPE_PUBLIC.
javax.xml.transform.stream.StreamSource
The objects of this class encapsulate an input stream of
XML markup data. It has constructors that take a Reader,
an InputStream, a File, or a String (a URI) as a parameter.
javax.xml.transform.stream.StreamResult
The objects of this class encapsulate an output stream of
XML markup data. It has constructors that take a Writer,
an OutputStream, a File, or a String (a URI) as a parameter.
Example Applications
Now we write several Java programs that use the Saxon API
to execute XQuery queries to solve various problems, some
of which were solved using DOM, SAX, or both.
The first example provides an XQuery evaluator that takes a
string specifying the output method ("xml", "html", or "text")
and the name of a file that contains an XQuery query.
Sample Execution
java Execute xml german.xq
java Execute html table.xq
where table.xq is a query that builds an HTML table containing
the information in phoneA.xml.
74
XQuery
File: Execute.java
import
import
import
import
import
net.sf.saxon.Configuration;
net.sf.saxon.query.DynamicQueryContext;
net.sf.saxon.query.StaticQueryContext;
net.sf.saxon.query.XQueryExpression;
net.sf.saxon.trans.XPathException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.Properties;
public class Execute
{
public static void main(String [] args)
{
if (args.length != 2)
throw new IllegalArgumentException(
"Usage: java Execute method queryFile");
String meth = args[0];
String xqFile = args[1];
if (!meth.equals("xml") &&
!meth.equals("html") &&
!meth.equals("text"))
throw new IllegalArgumentException(
"Bad method specification.");
try
{
Configuration config = new Configuration();
StaticQueryContext sqc =
new StaticQueryContext(config);
XQuery
75
DynamicQueryContext dqc =
new DynamicQueryContext(config);
XQueryExpression exp =
sqc.compileQuery(new FileReader(xqFile));
Properties props = new Properties();
props.setProperty(OutputKeys.METHOD, meth);
if (meth.equals("html"))
props.setProperty(OutputKeys.DOCTYPE_PUBLIC,
"-//W3C//DTD HTML 4.01 Transitional//EN");
int pos = xqFile.indexOf(".");
if (pos >= 0) xqFile = xqFile.substring(0, pos);
File file = new File(xqFile + "." + meth);
exp.run(dqc, new StreamResult(file), props);
}
catch (XPathException e)
{ System.out.println(e); }
catch (IOException e)
{ System.out.println(e); }
Testing Execute.java
The XML example using german.xq works fine with Execute,
creating a file named german.xml.
For HTML we have a query file table.xq that analyzes the XML
document phoneA.xml and generates an HTML document that
creates a table to display the information.
The query file is shown on the next page.
Observe that we need to run the gender attribute value
through the string function to get the query to work properly.
Otherwise the gender attribute is added to the td element in
the HTML.
76
XQuery
File: table.xq
<html>
<head>
<title>A list of phone numbers</title>
</head>
<body>
<h1>Phone Numbers</h1>
<table border="2" cellpadding="5">
<tr bgcolor="ffaacc">
<th>Name</th> <th>Gender</th>
<th>Phone</th> <th>City</th>
</tr>
{
for $e in
doc("phoneA.xml")/phoneNumbers/entries/entry
let $first := $e/name/first/text()
let $last := $e/name/last/text()
let $phone := $e/phone/text()
let $city := $e/city/text()
order by $e/name/last
return
<tr>
<td> { concat($first," ",$last) } </td>
<td> { string($e/name/@gender) } </td>
<td> { $phone } </td>
<td> { $city } </td>
</tr>
}
</table>
</body>
</html>
XQuery
77
Result: table.html
<!DOCTYPEhtml
PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8">
<title>A list of phone numbers</title>
</head>
<body>
<h1>Phone Numbers</h1>
<table border="2" cellpadding="5">
<tr bgcolor="ffaacc">
<th>Name</th>
<th>Gender</th>
<th>Phone</th>
<th>City</th>
</tr>
<tr>
<td>Helen Back</td>
<td>female</td>
<td>337-5967</td>
<td></td>
</tr>
<tr>
<td>Justin Case</td>
<td>male</td>
<td>354-9876</td>
<td>Coralville</td>
</tr>
<tr>
<td>Pearl Gates</td>
<td>female</td>
<td>335-4582</td>
<td>North Liberty</td>
</tr>
78
XQuery
<tr>
<td>Rusty Nail</td>
<td></td>
<td>335-0055</td>
<td>Iowa City</td>
</tr>
</table>
</body>
</html>
File: phoneText.xq
for $e in doc("phoneA.xml")/phoneNumbers/entries/entry
let $cr := "
"
let $g := $e/name/@gender
let $gender := if ($g) then concat("Gender = ", $g, $cr)
else ()
let $c := $e/city/text()
let $city := if ($c) then concat("City = ", $c, $cr)
else ()
order by $e/name/last
return
concat ( "Name = ", $e/name/first/text(), " ",
$e/name/last/text(), $cr,
$gender,
"Phone = ", $e/phone/text(), $cr,
$city, $cr
)
XQuery
79
Observations
The extra space in front of the last three Name labels is
inexplicable.
The string function was not needed for the attribute value
unlike the previous example.
Escaped line feed and return characters (\n and \r) are
viewed as plain text and not the control characters we
want. That is why the variable $cr is defined as it is.
Conclusion: XQuery is not as well suited to create text
output as it is to build XML and HTML documents.
80
XQuery
XQuery
81
File: QueryPhone.java
import
import
import
import
import
net.sf.saxon.Configuration;
net.sf.saxon.query.DynamicQueryContext;
net.sf.saxon.query.StaticQueryContext;
net.sf.saxon.query.XQueryExpression;
net.sf.saxon.trans.XPathException;
import javax.xml.transform.stream.StreamSource;
import java.util.*;
public class QueryPhone
{
public static void main(String [] args)
{
List<Entry> entries = new ArrayList<Entry>();
try
{
Configuration config = new Configuration();
StaticQueryContext sqc =
new StaticQueryContext(config);
DynamicQueryContext dqc =
new DynamicQueryContext(config);
dqc.setContextItem(
sqc.buildDocument(
new StreamSource("phoneA.xml")));
String query = "count(/phoneNumbers/entries/entry)";
XQueryExpression exp = sqc.compileQuery(query);
Long count = (Long)exp.evaluateSingle(dqc);
int num = count.intValue();
82
XQuery
XQuery
83
File: QueryBuilder.java
import
import
import
import
import
net.sf.saxon.Configuration;
net.sf.saxon.query.DynamicQueryContext;
net.sf.saxon.query.StaticQueryContext;
net.sf.saxon.query.XQueryExpression;
net.sf.saxon.trans.XPathException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.stream.StreamResult;
84
XQuery
import java.io.*;
import java.util.*;
public class QueryBuilder
{
static String mkQuery(List<Entry> entries)
{
StringBuffer query =
new StringBuffer("<phoneNumbers>");
query.append("{ ( element title { \"Phone Numbers\" }, ");
query.append("element entries { (");
boolean first = true;
for (Entry anEntry : entries)
{
if (!first) query.append(", ");
// put a comma
first = false;
// between entries
query.append("element entry { ( element name { (");
Name name = anEntry.getName();
String gender = anEntry.getGender();
if (gender != null)
query.append("attribute gender {\"" + gender + "\" },");
query.append("element first { \"" + name.getFirst() + "\" },");
String middle = name.getMiddle();
if (middle != null)
query.append("element middle {\"" + middle + "\" },");
query.append("element last { \""
+ name.getLast() + "\" } ) },");
query.append("element phone { \""
+ anEntry.getPhone() + "\" }");
String city = anEntry.getCity();
if (city != null && !city.equals(""))
query.append(", element city { \"" + city + "\" }");
query.append(") }");
}
XQuery
85
86
query.append(") } ) } </phoneNumbers>");
return query.toString();
public static void main(String [] args)
{
List<Entry> entries = new ArrayList<Entry>();
entries.add(new Entry(new Name("Robin", "Banks"),
"354-4455"));
entries.add(new Entry(new Name("Forrest", "Murmers"),
"male", "341-6152", "Solon"));
entries.add(new Entry(new Name("Barb", "A", "Wire"),
"337-8182", "Hills"));
entries.add(new Entry(new Name("Isabel", "Ringing"),
"female", "335-5985", null));
String query = mkQuery(entries);
System.out.println(query);
// for debugging query
try
{
Configuration config = new Configuration();
StaticQueryContext sqc =
new StaticQueryContext(config);
DynamicQueryContext dqc =
new DynamicQueryContext(config);
XQueryExpression exp = sqc.compileQuery(query);
Properties props = new Properties();
props.setProperty(OutputKeys.METHOD, "xml");
File file = new File("newPhone.xml");
exp.run(dqc, new StreamResult(file), props);
}
catch (Exception e)
{ System.out.println(e); }
}
XQuery
XQuery
87
File: newPhone.xml
<?xml version="1.0" encoding="UTF-8"?>
<phoneNumbers>
<title>Phone Numbers</title>
<entries>
<entry>
<name>
<first>Robin</first>
<last>Banks</last>
</name>
<phone>354-4455</phone>
</entry>
<entry>
<name gender="male">
<first>Forrest</first>
<last>Murmers</last>
</name>
<phone>341-6152</phone>
<city>Solon</city>
</entry>
<entry>
<name>
<first>Barb</first>
<middle>A</middle>
<last>Wire</last>
</name>
<phone>337-8182</phone>
<city>Hills</city>
</entry>
<entry>
<name gender="female">
<first>Isabel</first>
<last>Ringing</last>
</name>
<phone>335-5985</phone>
</entry>
</entries>
</phoneNumbers>
88
XQuery