Generating XML and HTML using XQuery
by Per Bothner
|
Pages: 1, 2, 3, 4, 5, 6
The Qexo (
http://www.gnu.org/software/qexo/)
implementation of XQuery includes a write-to built-in
function. It takes two parameters -- a value (normally an element node)
and a file name -- and writes the former to the latter, using
HTML-compatible XML (XHTML). It returns an empty sequence. Other XQuery
implementations may have similar functions.
The outermost expression of the new XQuery program writes both the
index.html overview file and each of the individual picture
pages, the latter in each of the 3 styles. Writing the picture pages is
done by two nested loops, the outer one looping over
<picture> elements and the inner one looping over the 3
styles. Each then calls the picture function to generate each
picture page and uses write-to to write the HTML to the
appropriate file. Each file name is generated by concatenating the
picture's id attribute, the style string, and the
.html extension.
let $group := document("index.xml")/group,
$group-date := $group/date,
$group-name := $group/title,
$pictures := $group//picture,
$count := count($pictures)
return (
write-to(make-group-page($group), "index.html"),
for $i in 1 to $count
for $style in ("", "info", "large")
let $picture := item-at($pictures,$i),
$prev := if ($i > 1) then item-at($pictures, $i - 1) else (),
$next := if ($i < $count) then item-at($pictures, $i + 1) else (),
$date := if ($picture/date) then $picture/date else $group-date
return
write-to(
picture($picture, $group, string($picture/@id),
$prev, $next, $date, $style),
concat(string($picture/@id), $style, ".html"))
)
The standard function item-at selects a value from a
sequence using an index (starting at one). We've already looked at
make-group-page. Listing 2 is the big picture
function, which returns a single picture page as an
<html> element.
Listing 2: The picture Function
{-- Generate a page picture image with links etc.
-- $picture: The <picture> node to use.
-- $group: The enclosing <group>.
-- $name: The string-value of the pictures's id attribute.
-- $prev: The previous <picture> or the empty sequence there is none.
-- $next: The next <picture> or the empty sequence there is none.
-- $date: The date the picture was taken, as a string, or the empty sequence.
-- $style: The style and size. Currently only "large" or "" (medium)
-- or "info". The "info" style show a thumbnail, plus EXIF information,
-- plus links to the raw JPGs.
--}
define function picture($picture, $group, $name, $prev, $next, $date, $style) {
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="up" href="index.html" />{
{-- Note how we pre-pend whitespace to properly indent the <links>s. --}
if (empty($prev)) then ()
else ("
",<link rel="prev" href="{$prev/@id}{$style}.html" />),
if (empty($next)) then ()
else ("
",<link rel="next" href="{$next/@id}{$style}.html" />)}
<title>{make-title($picture,$group)}</title>
<style type="text/css">
a {{ padding: 1 4; text-decoration: none; }}
td {{ padding-left: 0; border-style: none }}
span.button {{ border-width: thin; background-color: #FFFF99;
border-style: solid }}
</style>{( {-- (Note what we have to do to add an XQuery comment here!)
-- Next we generate a JavaScript handler, to handle pressing the
-- keys 'n' and 'p' for navigation. The indentation of the code
-- isn't "logical", but it makes the JavaScript code look ok. --} )}
<script language="JavaScript">
document.onkeypress = handler;
function handler(e) {{
var key = navigator.appName == 'Netscape' ? e.which
: window.event.keyCode;{
if (empty($next)) then ()
else {-- if 'n' was pressed, goto $next --}
concat('
if (key == 110) { location="',
string($next/@id), $style, '.html"; return true; }'),
if (empty($prev)) then ()
else {-- if 'p' was pressed, goto $prev --}
concat('
if (key == 112) { location="',
string($prev/@id), $style, '.html"; return true; }')}
return routeEvent(e); }}
</script>
</head>
<body bgcolor="#00AAAA">
{ nav-bar($picture, $name, $prev, $next, $style)}
{ make-header($picture, $group)}
{ picture-text($picture)}
{ if (empty($date)) then () else <p>Date taken: {$date}.</p>}
{ let $full-image := $picture/full-image,
$image := $picture/image
return
if ($style = "info") then (
<table><tr>
<td style="padding: 20 20 20 10">{make-thumbnail($picture)} </td>
<td>{document(concat($name,"-info.txt"))}</td>
</tr></table>,
<table><tr><td>Plain JPEG images:</td>
{raw-jpg-link($picture/full-image, "Original")}
{raw-jpg-link($picture/image,
if ($full-image) then "Scaled" else "Original")}
{raw-jpg-link($picture/small-image, "Thumbnail")}
</tr></table>
)
else if ($style="large" and $full-image) then
make-img($full-image, 1)
else if ($style="large" and $image
and number($image/@width) <= 640
and number($image/@height) <= 640) then
make-img($image, 2)
else if ($full-image) then
make-img($full-image, 0.5)
else
make-img($image, 1)
}
</body>
</html>
}
The basic structure should by now be familiar. The JavaScript function
handler (in the <script>) goes to the next
or previous page if the letter 'n' or 'p' on the
keyboard is pressed. (The strange layout of the code is to make the
generated HTML look nice.) Note how the XQuery comment before the
<script> section is nested inside parentheses (so we
get a syntactically valid empty sequence), which is nested inside the
curly brace escapes. Note how we check the $style parameter
to select which JPEG image to show and whether we need to scale it. The
"info" style generates a picture with information about the
picture itself, as in Figure 3. This so-called EXIF information is
generated by the camera and has been extracted from the JPEG file using
jhead.
Figure 3: A page giving detailed information about a single picture.
The navigation bar (i.e. the row of buttons on the top
of the page) is generated by the nav-bar function:
{-- Create a 1-row navigation-bar: next, prev etc --}
define function nav-bar($picture, $name, $prev, $next, $style) {
<table>
<tr>
<td><span class="button"><a href="index.html">Index</a></span></td>{
if ($style="info") then () else ("
",<td><span class="button">{make-link($name, "info", "Info")}</span></td>),
if ($style="large") then () else ("
",<td width="200" align="left"><span class="button">{
make-link($name, "large", "Large image")}</span></td>),
if ($style="") then () else ("
",<td width="200" align="left"><span class="button">{
make-link($name, "", "Medium image")}</span></td>)}
<td width="100" align="right">{
if ($prev) then
<span class="button">{make-link($prev/@id, $style, " < Previous ")
}</span> else ()}</td>
<td width="100" align="left">{
if ($next) then
<span class="button">{make-link($next/@id, $style, " Next > ")}</span>
else ()}</td>
</tr>
</table>
}
This is standard use of HTML tables to arrange the clickable buttons in
a row. The <span class="button"> acts with the
top-level <style>. Notice the use of conditionals to
only generate the buttons that make sense.
Finally, the last 5 small functions are as follows:
define function picture-text($picture) {
for $text in $picture/text return <p>{$text/node()}</p>
}
define function make-title($picture, $group) {
concat(string($group/title), " - ",
if (empty($picture/caption)) then string($picture/@id)
else string($picture/caption))
}
define function make-header($picture, $group) {
<h2>{if ($picture/caption) then $picture/caption/node()
else $group/title)/node()}</h2>
}
define function raw-jpg-link($image, $description) {
if (empty($image)) then () else
<td><span class="button"><a href="{$image}">{$description} ({
string($image/@width)}x{string($image/@height)})</a></span></td>
}
define function make-link($picture-name, $style, $text) {
<a href="{$picture-name}{$style}.html">{$text}</a>
}