# xqia.txt - UTF-8 # XQIA - XQuery in Action # # (c) 2015 Alexander Holupirek, Christian Grün # DBIS Group, U Konstanz # XML Technologies, Winter Term 2015 ################################################################################ ### Scope After being introduced to 'XML Basics' in the first lecture and having a glance at 'XPath' and the foundation of 'XQuery' in lecture two and three, we want to give you a 'hands-on' experience entitled 'XQuery in Action'. As XQuery processor we will use BaseX in its stand-alone GUI variant. ################################################################################ ################################################################################ ### BaseX XQuery processor • Java 7 is required. • Download XPath processor ( BaseX.jar ) $ wget http://files.basex.org/releases/BaseX.jar # Create database files in current directory. $ touch .basexhome # Create database from input file $ java -cp BaseX.jar org.basex.BaseX -c "create database foo foo.xml.gz" # Print some info about newly created database $ java -cp BaseX.jar org.basex.BaseX -c "open foo; info database; info index;" # Start BaseX GUI to formulate and evaluate XQuery interactively $ java -jar BaseX ################################################################################ ################################################################################ ### XML Data We will work with snippets from Discogs, short for discographies, a website and database of information about audio recordings. Original data can be downloaded at: -------------------------------------------------------------------------------- http://www.discogs.com/data/ Here you will find monthly dumps of Discogs Release, Artist, Label, and Master Release data. The data is in XML format and formatted according to the API spec: http://www.discogs.com/developers/ This data is made available under the CC0 No Rights Reserved license: http://creativecommons.org/about/cc0 -------------------------------------------------------------------------------- Snippets we are working with are contained in the xml directory (Nirvana is all we need right now). 145K MD5 (xml/Nirvana.xml) = 5fa1c3103fab433492aa2c841b20da62 9.9M MD5 (xml/ACDC.xml) = cb50164fb116462c2590158a02b8779f 20M MD5 (xml/BobDylan.xml) = efd0697cf049e002e0dbd6cd07c6d821 49M MD5 (xml/Beatles.xml) = dc4ecc43d39063c942b1330a6f153b98 ################################################################################ ################################################################################ ### Exploring the data with XQuery Let's start by loading the Nirvana document into BaseXGUI Screenshot: ================================================================================ Q: What's inside the document? -- xquery ---------------------------------------------------------------------- doc('xml/Nirvana.xml') -- result ---------------------------------------------------------------------- … the complete XML document … -------------------------------------------------------------------------------- • <release/> seems to be an important container. ================================================================================ ================================================================================ Q: How many releases do we have for Nirvana? -- xquery ---------------------------------------------------------------------- count(doc('./xml/Nirvana.xml')//release) -- result ---------------------------------------------------------------------- 30 -------------------------------------------------------------------------------- ================================================================================ ================================================================================ Q: How are they entitled? -- xquery ---------------------------------------------------------------------- doc('./xml/Nirvana.xml')//release/title -- 30 results ------------------------------------------------------------------ <title>Nevermind</title> <title>Nevermind</title> <title>In Utero</title> <title>MTV Unplugged In New York</title> <title>MTV Unplugged In New York</title> <title>MTV Unplugged In New York</title> <title>Nevermind</title> <title>Unplugged In New York</title> <title>Nevermind</title> <title>Nevermind</title> <title>Bleach + Bonus Tracks</title> <title>Nevermind</title> <title>Bleach</title> <title>From The Muddy Banks Of The Wishkah</title> <title>In Utero</title> <title>From The Muddy Banks Of The Wishkah</title> <title>MTV Unplugged In New York</title> <title>From The Muddy Banks Of The Wishkah</title> <title>Bleach</title> <title>Bleach</title> <title>Nevermind</title> <title>In Utero</title> <title>Nevermind</title> <title>Bleach</title> <title>Bleach</title> <title>From The Muddy Banks Of The Wishkah</title> <title>Bleach</title> <title>MTV Unplugged In New York</title> <title>Nevermind</title> <title>Nevermind</title> -------------------------------------------------------------------------------- • A lot of duplicates? Where do they come from? • Let's create something like a report about the releases. ================================================================================ ################################################################################ ### Task: Nirvana release report. The report shall give a compact overview about the Nirvana releases and their differences. -------------------------------------------------------------------------------- Step 1: Basic report setup -- xquery ---------------------------------------------------------------------- let $db := doc('./xml/Nirvana.xml') (: our 'database', i.e., the xml file :) let $releases := $db//release (: release nodes :) return (: constructing a result XML structure :) element report { attribute number-of-releases { count($releases) } } -- result ---------------------------------------------------------------------- <report number-of-releases="30"/> --- comments ------------------------------------------------------------------- • A first XQuery expression (not yet a FLWOR but still …) • XQuery comments • Two let clauses declare variables • Evaluation of an XPath step bound to a variable • XML construction -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 2: Augment report -- remark ---------------------------------------------------------------------- [xml/release.xml] shows a single <release/> element (for orientation). -- xquery ---------------------------------------------------------------------- let $db := doc('./xml/Nirvana.xml') (: our 'database', i.e., the xml file :) let $releases := $db//release (: sequence of releases :) return (: constructing a result XML structure :) element report { attribute number-of-releases { count($releases) }, for $rel in $releases return element release { attribute title { $rel/title }, attribute label { $rel/labels/label/@name }, attribute released { $rel/released || ' (' || $rel/country || ')'} } } -- result ---------------------------------------------------------------------- <report number-of-releases="30"> <release title="Nevermind" label="USM Japan" released="2007 (Japan)"/> <release title="Nevermind" label="DGC" released="1991 (Argentina)"/> <release title="In Utero" label="Geffen Records" released="1996-10-23 (Japan)"/> <release title="MTV Unplugged In New York" label="Geffen Records" released="1994-11-02 (Japan)"/> <release title="MTV Unplugged In New York" label="DGC" released="1994 (US)"/> <release title="MTV Unplugged In New York" label="Geffen Records" released="1994 (Brazil)"/> <release title="Nevermind" label="Geffen Records" released="1992 (South Korea)"/> <release title="Unplugged In New York" label="Geffen Records" released="1994 (Hungary)"/> <release title="Nevermind" label="DGC" released="1991 (US)"/> <release title="Nevermind" label="DGC" released="1991 (US)"/> <release title="Bleach + Bonus Tracks" label="Future Records (18)" released="2002 (Italy)"/> <release title="Nevermind" label="DGC" released="1991 (Canada)"/> <release title="Bleach" label="Waterfront Records" released="1992 (Australia)"/> <release title="From The Muddy Banks Of The Wishkah" label="Geffen Records" released="1996 (Germany)"/> <release title="In Utero" label="ЗАО "Юниверсал Мьюзик"" released=" (Russia)"/> <release title="From The Muddy Banks Of The Wishkah" label="ЗАО "Юниверсал Мьюзик"" released="1996 (Russia)"/> <release title="MTV Unplugged In New York" label="Geffen Records" released="1994 (Canada)"/> <release title="From The Muddy Banks Of The Wishkah" label="Geffen Records" released="1996 (Brazil)"/> <release title="Bleach" label="MCA Music Entertainment S.A." released="1996 (Argentina)"/> <release title="Bleach" label="BMG Music Group" released="1992 (Brazil)"/> <release title="Nevermind" label="DGC" released="2012 (Europe)"/> <release title="In Utero" label="Geffen Records" released="1993 (Europe)"/> <release title="Nevermind" label="DGC DGC DGC Sub Pop" released="1991 (Spain)"/> <release title="Bleach" label="Никитин" released="2007 (Russia)"/> <release title="Bleach" label="Geffen Records" released=" (South Korea)"/> <release title="From The Muddy Banks Of The Wishkah" label="Geffen Records" released="1996 (US)"/> <release title="Bleach" label="Universal Music Ukrainian Records" released="2001 (Ukraine)"/> <release title="MTV Unplugged In New York" label="Geffen Records" released="1994 (UK)"/> <release title="Nevermind" label="Geffen Records (2) EMI (2)" released="1991-09-24 (UK)"/> <release title="Nevermind" label="DGC Sub Pop" released=" (Europe)"/> </report> -------------------------------------------------------------------------------- • Added FLWR expression in XML construction • Used '||' operator, a shortcut for fn:concat(…) -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 3: Group releases for better overview -- xquery ---------------------------------------------------------------------- let $db := doc('./xml/Nirvana.xml') (: our 'database', ie the xml file :) let $releases := $db//release (: sequence of releases :) return (: constructing a result XML structure :) element report { attribute number-of-releases { count($releases) }, for $rel in $releases group by $title := $rel/title return element release-group { attribute title { $title }, attribute group-count { count($rel) }, (: the 'grouped-together' releases :) (: Now process each group :) for $r in $rel return element release { attribute label { $r/labels/label/@name }, attribute released { $r/released || ' (' || $r/country || ')'}, attribute format { $r//format//description } } } } -- result ---------------------------------------------------------------------- <report number-of-releases="30"> <release-group title="Nevermind" group-count="10"> <release label="USM Japan" released="2007 (Japan)" format="Album Limited Edition"/> <release label="DGC" released="1991 (Argentina)" format="LP Album"/> <release label="Geffen Records" released="1992 (South Korea)" format="LP Album"/> <release label="DGC" released="1991 (US)" format="Album"/> <release label="DGC" released="1991 (US)" format="Album Limited Edition"/> <release label="DGC" released="1991 (Canada)" format="LP Album"/> <release label="DGC" released="2012 (Europe)" format="LP Album"/> <release label="DGC DGC DGC Sub Pop" released="1991 (Spain)" format="Album"/> <release label="Geffen Records (2) EMI (2)" released="1991-09-24 (UK)" format="LP Album"/> <release label="DGC Sub Pop" released=" (Europe)" format="Album Limited Edition"/> </release-group> <release-group title="In Utero" group-count="3"> <release label="Geffen Records" released="1996-10-23 (Japan)" format="LP Album"/> <release label="ЗАО "Юниверсал Мьюзик"" released=" (Russia)" format="Album"/> <release label="Geffen Records" released="1993 (Europe)" format="Album"/> </release-group> <release-group title="MTV Unplugged In New York" group-count="5"> <release label="Geffen Records" released="1994-11-02 (Japan)" format="Album"/> <release label="DGC" released="1994 (US)" format="LP Album"/> <release label="Geffen Records" released="1994 (Brazil)" format="LP Album"/> <release label="Geffen Records" released="1994 (Canada)" format="LP Album"/> <release label="Geffen Records" released="1994 (UK)" format="LP Album"/> </release-group> <release-group title="Unplugged In New York" group-count="1"> <release label="Geffen Records" released="1994 (Hungary)" format="Album"/> </release-group> <release-group title="Bleach + Bonus Tracks" group-count="1"> <release label="Future Records (18)" released="2002 (Italy)" format="Album"/> </release-group> <release-group title="Bleach" group-count="6"> <release label="Waterfront Records" released="1992 (Australia)" format="LP Album Repress Limited Edition"/> <release label="MCA Music Entertainment S.A." released="1996 (Argentina)" format="Album"/> <release label="BMG Music Group" released="1992 (Brazil)" format="Album"/> <release label="Никитин" released="2007 (Russia)" format="Album"/> <release label="Geffen Records" released=" (South Korea)" format="LP Album"/> <release label="Universal Music Ukrainian Records" released="2001 (Ukraine)" format="Album"/> </release-group> <release-group title="From The Muddy Banks Of The Wishkah" group-count="4"> <release label="Geffen Records" released="1996 (Germany)" format="Album"/> <release label="ЗАО "Юниверсал Мьюзик"" released="1996 (Russia)" format="Album"/> <release label="Geffen Records" released="1996 (Brazil)" format="Album"/> <release label="Geffen Records" released="1996 (US)" format="Album"/> </release-group> </report> -------------------------------------------------------------------------------- • group-by operator -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 4: As a last step, let's add the tracks the release report. -- xquery ---------------------------------------------------------------------- let $db := doc('./xml/Nirvana.xml') (: our 'database', ie the xml file :) let $releases := $db//release (: sequence of releases :) return (: constructing a result XML structure :) element report { attribute number-of-releases { count($releases) }, for $rel in $releases group by $title := $rel/title return element release-group { attribute title { $title }, attribute group-count { count($rel) }, (: the 'grouped-together' releases :) (: Now process each group :) for $r in $rel order by $r//released return element release { attribute label { $r/labels/label/@name }, attribute released { $r/released || ' (' || $r/country || ')'}, attribute format { $r//format//description }, attribute videos { count($r//video) }, element tracks { attribute count { count($r//track) }, for $t at $pos in $r//track return attribute { 't' || $pos } { substring($t//title, 1, 9) || '…' } } } } } -- result ---------------------------------------------------------------------- <report number-of-releases="30"> <release-group title="Nevermind" group-count="10"> <release label="DGC Sub Pop" released=" (Europe)" format="Album Limited Edition" videos="4"> <tracks count="12" t1="Smells Li…" t2="In Bloom…" t3="Come As Y…" t4="Breed…" t5="Lithium…" t6="Polly…" t7="Territori…" t8="Drain You…" t9="Lounge Ac…" t10="Stay Away…" t11="On A Plai…" t12="Something…"/> </release> <release label="DGC" released="1991 (Argentina)" format="LP Album" videos="4"> <tracks count="12" t1="Smells Li…" t2="In Bloom…" t3="Come As Y…" t4="Breed…" t5="Lithium…" t6="Polly…" t7="Territori…" t8="Drain You…" t9="Lounge Ac…" t10="Stay Away…" t11="On A Plai…" t12="Something…"/> </release> <release label="DGC" released="1991 (US)" format="Album" videos="4"> <tracks count="12" t1="Smells Li…" t2="In Bloom…" t3="Come As Y…" t4="Breed…" t5="Lithium…" t6="Polly…" t7="Territori…" t8="Drain You…" t9="Lounge Ac…" t10="Stay Away…" t11="On A Plai…" t12="Something…"/> </release> <release label="DGC" released="1991 (US)" format="Album Limited Edition" videos="4"> <tracks count="12" t1="Smells Li…" t2="In Bloom…" t3="Come As Y…" t4="Breed…" t5="Lithium…" t6="Polly…" t7="Territori…" t8="Drain You…" t9="Lounge Ac…" t10="Stay Away…" t11="On A Plai…" t12="Something…"/> </release> … </release-group> </report> -------------------------------------------------------------------------------- • FLWR at $pos construct • dynamic element/attribute name construction • order by construct -------------------------------------------------------------------------------- ################################################################################ ### Website generation Now that we explored the data and have an overview, we want to get productive. The task will be to produce a webpage. We already produced content, namely the report in XML. Now we produce HTML from the data using XQuery. On-the-fly we learn about XQuery functions and code separation. ================================================================================ ### Task: Nirvana release report as webpage. Instead of an XML report, use XQuery to construct a webpage. The page shall display information to be found in the discogs XML data. Here is a mockup (a screenshot of the expected result) and a HTML template. The html directory contains some additional stuff: html ├── css ..................................... Cascading style sheets │ └── bootstrap.css ├── img ..................................... graphics, etc. │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png ├── (nirvana.html ........................... the file we shall create) └── mockup.html ............................. a static mockup of the page we want to create Bootstrap is a 'Sleek, intuitive, and powerful front-end framework for faster and easier web development.' It allows you to style webpages with ease. For some details have a look at: http://getbootstrap.com/ -------------------------------------------------------------------------------- Step 1: To actually produce an HTML file, we have to write a file to disk -- xquery ---------------------------------------------------------------------- (:~ : Constructs basic HTML webpage. : @returns HTML :) declare function local:html() as element(html) { <html lang="en"> <head> <meta charset="utf-8"/> <title>Discogs XML Data</title> <link href="css/bootstrap.css" rel="stylesheet"/> </head> <body> <h1>Hello, nothing to see, yet ;-)</h1> </body> </html> }; (: --------- Main entry point (could be another query file ) ------- :) let $html := local:html() return file:write('./html/nirvana.html', $html) -- result ---------------------------------------------------------------------- A file has been created. html ├── nirvana.html ............................ the file we just created └── mockup.html Screenshot gfx/html-step1.png: -------------------------------------------------------------------------------- • XQuery function declaration • Comments to functions (XQDoc style, http://xqdoc.org/) • XQuery extensions (such as file operations) • BaseX (and other XQuery processors) come with a rich set of functions that extend the XQuery core language. • For BaseX documentation can be found at - http://docs.basex.org ................... overall documentation - http://docs.basex.org/wiki/XQuery ........ XQuery specific documentation - http://docs.basex.org/wiki/File_Module .. the file module we have just used - http://expath.org/spec/file ............. File module specification -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 2: Feeding content into HTML template -- xquery ---------------------------------------------------------------------- (:~ : Constructs basic HTML webpage. : : @param $releases sequence of discogs release data container : @returns HTML :) declare function local:html( $releases as element(release)* ) as element(html) { <html lang="en"> <head> <meta charset="utf-8"/> <title>Discogs XML Data</title> <link href="css/bootstrap.css" rel="stylesheet"/> </head> <body> <div class="container"> <div class="row"> <div class="well"><h2>Nirvana @ Discogs <small>( { count($releases) } releases )</small></h2></div> </div> </div> </body> </html> }; (: --------- Main entry point (could be another query file ) ------- :) let $db := doc('./xml/Nirvana.xml') (: our 'database', ie the xml file :) let $releases := $db//release (: sequence of releases :) let $html := local:html($releases) return file:write('./html/nirvana.html', $html) -- result ---------------------------------------------------------------------- Screenshot gfx/html-step2.png: -------------------------------------------------------------------------------- • typed function parameters, return type • { } XQuery evaluation in static content -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 3: Produce 'correct' (acc. to mockup) headline, separate it to a function -- xquery ---------------------------------------------------------------------- (:~ : Constructs basic HTML webpage. : : @param $releases sequence of discogs release data container : @returns HTML :) declare function local:html( $releases as element(release)* ) as element(html) { <html lang="en"> <head> <meta charset="utf-8"/> <title>Discogs XML Data</title> <link href="css/bootstrap.css" rel="stylesheet"/> </head> <body> <div class="container"> { local:headline($releases), for $rel in $releases group by $title := $rel/title return (: release-group :) local:render-release-group( $title, $rel ) } </div> </body> </html> }; (:~ : Constructs headline part of release webpage. : [./gfx/html-step3.png] : : @param $releases sequence of discogs release data container : @return a sequence of <div/> nodes constructing the headline :) declare function local:headline( $releases as element(release)* ) as element(div)+ { ( <div class="row"> <h2>Discover Music <small>(display <a href="http://www.discogs.com/data/">Discogs</a> XML data)</small> </h2> </div> , <div class="row"><hr/></div> , <div class="row"> <div class="well"><h2>Nirvana @ Discogs <small>( { count($releases) } releases )</small></h2></div> </div> ) }; (: --------- Main entry point (could be another query file ) ------- :) let $db := doc('./xml/Nirvana.xml') (: our 'database', ie the xml file :) let $releases := $db//release (: sequence of releases :) let $html := local:html($releases) return file:write('./html/nirvana.html', $html) -- result ---------------------------------------------------------------------- Screenshot gfx/html-step3.png: -------------------------------------------------------------------------------- • Returning a sequence of <div/> nodes -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 4: Render release-group -- xquery ---------------------------------------------------------------------- (:~ : Constructs basic HTML webpage. : : @param $releases sequence of discogs release data container : @returns HTML :) declare function local:html( $releases as element(release)* ) as element(html) { <html lang="en"> <head> <meta charset="utf-8"/> <title>Discogs XML Data</title> <link href="css/bootstrap.css" rel="stylesheet"/> </head> <body> <div class="container"> { local:headline($releases), for $rel in $releases group by $title := $rel/title return (: release-group :) local:render-release-group( $title, $rel ) } </div> </body> </html> }; (:~ : Constructs headline part of release webpage. : : @param $releases sequence of discogs release data container : @return a sequence of <div/> nodes constructing the headline :) declare function local:headline( $releases as element(release)* ) as element(div)+ { ( <div class="row"> <h2>Discover Music <small>(display <a href="http://www.discogs.com/data/">Discogs</a> XML data)</small> </h2> </div> , <div class="row"><hr/></div> , <div class="row"> <div class="well"><h2>Nirvana @ Discogs <small>( { count($releases) } releases )</small></h2></div> </div> ) }; (:~ : Constructs release group rendering. : [./gfx/html-step4.png] : : @param $title of release group (grouping condition) : @param $grouped-releases the, by $title, grouped-together releases : @return a sequence of <div/> nodes constructing a single release group :) declare function local:render-release-group( $title as xs:string, $grouped-releases as element(release)* ) as element(div)+ { <div class="row"> <!-- Release group header --> <div class="alert alert-success"> <h3>{ $title } <small> ( { count($grouped-releases) } releases )</small></h3> </div> </div> }; (: --------- Main entry point (could be another query file ) ------- :) let $db := doc('./xml/Nirvana.xml') (: our 'database', ie the xml file :) let $releases := $db//release (: sequence of releases :) let $html := local:html($releases) return file:write('./html/nirvana.html', $html) -- result ---------------------------------------------------------------------- Screenshot gfx/html-step4.png: -------------------------------------------------------------------------------- • Nothing really new • Used entity ( ^=  ), look up in xhtml-lat1.ent -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 5: Render individual release -- xquery ---------------------------------------------------------------------- (:~ : Constructs basic HTML webpage. : [./gfx/html-step2.png] : : @param $releases sequence of discogs release data container : @returns HTML :) declare function local:html( $releases as element(release)* ) as element(html) { <html lang="en"> <head> <meta charset="utf-8"/> <title>Discogs XML Data</title> <link href="css/bootstrap.css" rel="stylesheet"/> </head> <body> <div class="container"> { local:headline($releases), for $rel in $releases group by $title := $rel/title return (: release-group :) local:render-release-group( $title, $rel ) } </div> </body> </html> }; (:~ : Constructs headline part of release webpage. : [./gfx/html-step3.png] : : @param $releases sequence of discogs release data container : @return a sequence of <div/> nodes constructing the headline :) declare function local:headline( $releases as element(release)* ) as element(div)+ { ( <div class="row"> <h2>Discover Music <small>(display <a href="http://www.discogs.com/data/">Discogs</a> XML data)</small> </h2> </div> , <div class="row"><hr/></div> , <div class="row"> <div class="well"><h2>Nirvana @ Discogs <small>( { count($releases) } releases )</small></h2></div> </div> ) }; (:~ : Constructs release group rendering. : [./gfx/html-step4.png] : : @param $title of release group (grouping condition) : @param $grouped-releases the, by $title, grouped-together releases : @return a sequence of <div/> nodes constructing a single release group :) declare function local:render-release-group( $title as xs:string, $grouped-releases as element(release)* ) as element(div)+ { ( <div class="row"> <!-- Release group header --> <div class="alert alert-success"> <h3>{ $title } <small> ( { count($grouped-releases) } releases )</small></h3> </div> </div> , (: Process release group. :) for $release in $grouped-releases order by $release//released (: contains release date, eg. '2007' :) return local:render-release($release) ) }; (:~ : Renders a single release. : [./gfx/html-step5.png] : : @param $release a single release to be rendered : @return a sequence of <div/> nodes constructing a single release :) declare function local:render-release( $release as element(release) ) as element(div)+ { ( <div class="row"> <!-- Release Headline --> <pre>Release Headline</pre> </div> , <div class="row"> <div class="span2"> <!-- Cover area --> <pre>Cover area</pre> </div> <div class="span4"> <!-- Track Table --> <pre>Track Table</pre> </div> <div class="span4"> <!-- Metadata Table --> <pre>Metadata Table</pre> <!-- Video Table --> <pre>Video Table</pre> </div> <div class="span2"> <!-- Labels area --> <pre>Labels area</pre> </div> </div> ) }; (: --------- Main entry point (could be another query file ) ------- :) let $db := doc('./xml/Nirvana.xml') (: our 'database', ie the xml file :) let $releases := $db//release (: sequence of releases :) let $html := local:html($releases) return file:write('./html/nirvana.html', $html) -- result ---------------------------------------------------------------------- Screenshot gfx/html-step5.png: -------------------------------------------------------------------------------- • A new function to render individual releases has been added with stubs for the sections of a release: a) Release Headline b) Cover area c) Track Table d) Metadata Table e) Video Table -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 6a: Render sections of individual release - Release Headline -- xquery ---------------------------------------------------------------------- (:~ : Renders a single release. : [./gfx/html-step5.png] : : @param $release a single release to be rendered : @return a sequence of <div/> nodes constructing a single release :) declare function local:render-release( $release as element(release) ) as element(div)+ { ( local:render-release-headline($release) , <div class="row"> <div class="span2"> <!-- Cover area --> <pre>Cover area</pre> </div> <div class="span4"> <!-- Track Table --> <pre>Track Table</pre> </div> <div class="span4"> <!-- Metadata Table --> <pre>Metadata Table</pre> <!-- Video Table --> <pre>Video Table</pre> </div> <div class="span2"> <!-- Labels area --> <pre>Labels area</pre> </div> </div> ) }; (:~ : Renders release headline. : [./gfx/html-step6a.png] : : @param $release the single release for which the headline should be rendered : @return a <div/> node constructing the release headline :) declare function local:render-release-headline( $release as element(release) ) as element(div) { <div class="row"> <!-- Release Title --> <h4>{ $release/title/text() } <small>( { let $vals := ($release/released, $release/country, $release//label/@name/data()) for $v at $pos in $vals return if ($v) then concat($v, if ($pos != count($vals)) then ',' else ()) else () } )</small> </h4> </div> }; -- result ---------------------------------------------------------------------- Screenshot gfx/html-step6a.png: -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 6b: Render sections of individual release - Cover area -- xquery ---------------------------------------------------------------------- (:~ : Renders a single release. : [./gfx/html-step5.png] : : @param $release a single release to be rendered : @return a sequence of <div/> nodes constructing a single release :) declare function local:render-release( $release as element(release) ) as element(div)+ { ( local:render-release-headline($release) , <div class="row"> { local:render-cover-area($release) } <div class="span4"> <!-- Track Table --> <pre>Track Table</pre> </div> <div class="span4"> <!-- Metadata Table --> <pre>Metadata Table</pre> <!-- Video Table --> <pre>Video Table</pre> </div> <div class="span2"> <!-- Labels area --> <pre>Labels area</pre> </div> </div> ) }; ... (:~ : Renders cover area. : [./gfx/html-step6b.png] : : @param $release the single release for which the cover area should be rendered : @return a sequence of <div/> nodes constructing the cover area :) declare function local:render-cover-area( $release as element(release) ) as element(div) { <div class="span2"> <!-- Cover area --> { for $img in $release/images/image return <p><img src="{ $img/@uri150 }"/></p> } </div> }; -- result ---------------------------------------------------------------------- Screenshot gfx/html-step6b.png: -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 6c: Render sections of individual release - Track table -- xquery ---------------------------------------------------------------------- ... declare function local:render-release( $release as element(release) ) as element(div)+ { ( local:render-release-headline($release) , <div class="row"> { local:render-cover-area($release), local:render-track-area($release) } ... (:~ : Renders track area. : [./gfx/html-step6c.png] : : @param $release the single release for which the track table should be rendered : @return a sequence of <div/> nodes constructing the track table :) declare function local:render-track-area( $release as element(release) ) as element(div) { <div class="span4"> <!-- Track Table --> <table class="table table-bordered table-striped"> <tbody> { for $track in $release/tracklist/track return <tr> <th>{ $track/position }</th> <td>{ $track/title/data() }</td> </tr> } </tbody> </table> </div> }; -- result ---------------------------------------------------------------------- Screenshot gfx/html-step6c.png: -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 6d: Render sections of individual release - Metadata/Video table -- xquery ---------------------------------------------------------------------- ... (:~ : Renders a single release. : [./gfx/html-step5.png] : : @param $release a single release to be rendered : @return a sequence of <div/> nodes constructing a single release :) declare function local:render-release( $release as element(release) ) as element(div)+ { ( local:render-release-headline($release) , <div class="row"> { local:render-cover-area($release), local:render-track-area($release), local:render-metadata-video-area($release) } <div class="span2"> <!-- Labels area --> <pre>Labels area</pre> </div> </div> ) }; ... (:~ : Renders metadata and video area. : [./gfx/html-step6d.png] : : @param $release the single release for which the metadata/video table should be rendered : @return a <div/> node constructing the track table :) declare function local:render-metadata-video-area( $release as element(release) ) as element(div) { <div class="span4"> <!-- Metadata Table --> <table class="table table-bordered table-striped"> <tbody> <tr> <th>Format</th> <td>{ local:comma-separated-values($release//formats//description) }</td> </tr> <tr> <th>Genre</th> <td>{ local:comma-separated-values($release//genres/genre) }</td> </tr> <tr> <th>Styles</th> <td>{ local:comma-separated-values($release//styles/style) }</td> </tr> <tr> <th>Notes</th> <td>{ substring(local:comma-separated-values($release//notes), 1, 350) || '…' }</td> </tr> </tbody> </table> <!-- Video Table --> <table class="table table-bordered table-striped"> <thead> <tr> <th>Video</th> <th>Duration</th> </tr> </thead> <tbody> { for $v in $release/videos//video return <tr> <th><a href="{ $v/@src/data() }">{ $v/title/data() }</a></th> <td>{ $v/@duration/data() }</td> </tr> } </tbody> </table> </div> }; (:~ : Constructs a comma separated values string from a sequence of input nodes. : : @param $values to be concatenated : @return CSV string :) declare function local:comma-separated-values( $values as item()* ) as xs:string* { let $vals := $values for $v at $pos in $vals return if ($v) then concat($v, if ($pos != count($vals)) then ',' else ()) else () }; ... -- result ---------------------------------------------------------------------- Screenshot gfx/html-step6d.png: -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Step 6e: Render sections of individual release - Labels area -- xquery ---------------------------------------------------------------------- (:~ : Renders a single release. : [./gfx/html-step5.png] : : @param $release a single release to be rendered : @return a sequence of <div/> nodes constructing a single release :) declare function local:render-release( $release as element(release) ) as element(div)+ { ( local:render-release-headline($release) , <div class="row"> { local:render-cover-area($release), local:render-track-area($release), local:render-metadata-video-area($release), local:render-labels-area($release) } </div> ) }; ... (:~ : Renders labels area. : [./gfx/html-step6e.png] : : @param $release the single release for which the labels area should be rendered : @return a <div/> node constructing the labels area :) declare function local:render-labels-area( $release as element(release) ) as element(div) { <div class="span2"> <!-- Labels area --> <p><span class="badge badge-warning">{ $release/formats/format/@name/data() }</span></p> <p><span class="label label-info">{ $release/formats/format/@text/data() }</span></p> <p><span class="label label-important">{ $release/labels/label/@catno/data() }</span></p> <p><span class="label label-success">{ $release/labels/label/@name/data() }</span></p> </div> }; -- result ---------------------------------------------------------------------- Screenshot gfx/html-step6e.png: -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Complete XQuery code for website generation: -- xquery ---------------------------------------------------------------------- (:~ : XQuery code to render discogs release data (XML) as webpage (HTML). : : (c) 2013 DBIS group, U Konstanz, XML Technologies, Winter Term 2013/14 :) (:~ : Constructs basic HTML webpage. : [./gfx/html-step2.png] : : @param $releases sequence of discogs release data container : @returns HTML :) declare function local:html( $releases as element(release)* ) as element(html) { <html lang="en"> <head> <meta charset="utf-8"/> <title>Discogs XML Data</title> <link href="css/bootstrap.css" rel="stylesheet"/> </head> <body> <div class="container"> { local:headline($releases), for $rel in $releases group by $title := $rel/title return (: release-group :) local:render-release-group( $title, $rel ) } </div> </body> </html> }; (:~ : Constructs headline part of release webpage. : [./gfx/html-step3.png] : : @param $releases sequence of discogs release data container : @return a sequence of <div/> nodes constructing the headline :) declare function local:headline( $releases as element(release)* ) as element(div)+ { ( <div class="row"> <h2>Discover Music <small>(display <a href="http://www.discogs.com/data/">Discogs</a> XML data)</small> </h2> </div> , <div class="row"><hr/></div> , <div class="row"> <div class="well"><h2>Nirvana @ Discogs <small>( { count($releases) } releases )</small></h2></div> </div> ) }; (:~ : Constructs release group rendering. : [./gfx/html-step4.png] : : @param $title of release group (grouping condition) : @param $grouped-releases the, by $title, grouped-together releases : @return a sequence of <div/> nodes constructing a single release group :) declare function local:render-release-group( $title as xs:string, $grouped-releases as element(release)* ) as element(div)+ { ( <div class="row"> <!-- Release group header --> <div class="alert alert-success"> <h3>{ $title } <small> ( { count($grouped-releases) } releases )</small></h3> </div> </div> , (: Process release group. :) for $release in $grouped-releases order by $release//released (: contains release date, eg. '2007' :) return local:render-release($release) ) }; (:~ : Renders a single release. : [./gfx/html-step5.png] : : @param $release a single release to be rendered : @return a sequence of <div/> nodes constructing a single release :) declare function local:render-release( $release as element(release) ) as element(div)+ { ( local:render-release-headline($release) , <div class="row"> { local:render-cover-area($release), local:render-track-area($release), local:render-metadata-video-area($release), local:render-labels-area($release) } </div> ) }; (:~ : Renders release headline. : [./gfx/html-step6a.png] : : @param $release the single release for which the headline should be rendered : @return a <div/> node constructing the release headline :) declare function local:render-release-headline( $release as element(release) ) as element(div) { <div class="row"> <!-- Release Title --> <h4>{ $release/title/text() } <small>( { local:comma-separated-values(($release/released, $release/country, $release//label/@name/data())) } )</small> </h4> </div> }; (:~ : Renders cover area. : [./gfx/html-step6b.png] : : @param $release the single release for which the cover area should be rendered : @return a <div/> node constructing the cover area :) declare function local:render-cover-area( $release as element(release) ) as element(div) { <div class="span2"> <!-- Cover area --> { for $img in $release/images/image return <p><img src="{ $img/@uri150 }"/></p> } </div> }; (:~ : Renders track area. : [./gfx/html-step6c.png] : : @param $release the single release for which the track table should be rendered : @return a <div/> node constructing the track table :) declare function local:render-track-area( $release as element(release) ) as element(div) { <div class="span4"> <!-- Track Table --> <table class="table table-bordered table-striped"> <tbody> { for $track in $release/tracklist/track return <tr> <th>{ $track/position }</th> <td>{ $track/title/data() }</td> </tr> } </tbody> </table> </div> }; (:~ : Renders metadata and video area. : [./gfx/html-step6d.png] : : @param $release the single release for which the metadata/video table should be rendered : @return a <div/> node constructing the track table :) declare function local:render-metadata-video-area( $release as element(release) ) as element(div) { <div class="span4"> <!-- Metadata Table --> <table class="table table-bordered table-striped"> <tbody> <tr> <th>Format</th> <td>{ local:comma-separated-values($release//formats//description) }</td> </tr> <tr> <th>Genre</th> <td>{ local:comma-separated-values($release//genres/genre) }</td> </tr> <tr> <th>Styles</th> <td>{ local:comma-separated-values($release//styles/style) }</td> </tr> <tr> <th>Notes</th> <td>{ substring(local:comma-separated-values($release//notes), 1, 350) || '…' }</td> </tr> </tbody> </table> <!-- Video Table --> <table class="table table-bordered table-striped"> <thead> <tr> <th>Video</th> <th>Duration</th> </tr> </thead> <tbody> { for $v in $release/videos//video return <tr> <th><a href="{ $v/@src/data() }">{ $v/title/data() }</a></th> <td>{ $v/@duration/data() }</td> </tr> } </tbody> </table> </div> }; (:~ : Renders labels area. : [./gfx/html-step6e.png] : : @param $release the single release for which the labels area should be rendered : @return a <div/> node constructing the labels area :) declare function local:render-labels-area( $release as element(release) ) as element(div) { <div class="span2"> <!-- Labels area --> <p><span class="badge badge-warning">{ $release/formats/format/@name/data() }</span></p> <p><span class="label label-info">{ $release/formats/format/@text/data() }</span></p> <p><span class="label label-important">{ $release/labels/label/@catno/data() }</span></p> <p><span class="label label-success">{ $release/labels/label/@name/data() }</span></p> </div> }; (:~ : Constructs a comma separated values string from a sequence of input nodes. : : @param $values to be concatenated : @return CSV string :) declare function local:comma-separated-values( $values as item()* ) as xs:string* { let $vals := $values for $v at $pos in $vals return if ($v) then concat($v, if ($pos != count($vals)) then ',' else ()) else () }; (: --------- Main entry point (could be another query file ) ------- :) let $db := doc('./xml/Nirvana.xml') (: our 'database', ie the xml file :) let $releases := $db//release (: sequence of releases :) let $html := local:html($releases) return file:write('./html/nirvana.html', $html) -- result ---------------------------------------------------------------------- Screenshot gfx/html-step6e.png: -------------------------------------------------------------------------------- ================================================================================ ### Final remarks • XQuery as information processor (analyse, report, restructure, …) • XQuery as cross-media output generator Have fun using XML technologies, Alexander Holupirek, Christian Grün ================================================================================