XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

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

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

Pages: 1, 2, 3, 4, 5, 6

Next Pagearrow