Hoping to capitalize on marketing for the upcoming Transformer movie, I thought I would take a look at what makes XQuery the ultimate XML transformer. (What? You haven't heard? It's actually not bad looking looking.)
Just like the nutty car and truck toys, XML has always been made to be transformed into something else. And while the end formats of HTML, mobile devices and print ready PDFs aren't as much fun as 5 story tall robots, the idea is pretty much the same: your content is in a presentation free format (just like Bumblebee's Camaro) and can instantaneously be transformed into something else (lets just say . . . a huge yellow robot). Everything needed to do the transformation is actually in the content . . . you just need an engine and some rules to make it happen.
A couple of weeks back I posted about making PDFs from the Shakespeare XML in the tutorial that looked a bit like this:
for $speech in //SPEECH
return
<formatting-for-speech>
{fn:string($speech)}
</formatting-for-speech>
While this worked, it's more of a report type of XQuery. What we want to do is feed in some XML and get out the new format.
To do this, we'll use a design pattern that is very similar to what an XSLT engine does behind the scenes: we'll recurse through the XML and then perform formatting as we hit certain elements.
The first part is to set up the recursion:
define function recursion($x, $options) {
for $z in $x/node() return mapping($z, $options)
}
Then we'll make its counterpart - the mapping:
define function mapping($x, $options)
{
typeswitch ($x)
case element(PLAY) return play($x, $options)
case element(TITLE) return title($x, $options)
case element(ACT) return section($x, $options)
case element(SPEECH) return p($x, $options)
case element(LINE) return p($x, $options)
case element(PERSONAE) return section($x, $options)
case element(PERSONA) return p($x, $options)
case element(STAGEDIR) return i($x, $options)
case text() return output-text($x, $options)
case element(FM) return ()
default return p($x, $options)
}
While it's similar to an XSL transformation, the important thing is that we control the recursion. If we wanted to add some (admittedly silly) logic to recursion() we can just go ahead and do it:
define function recursion($x, $options) {
if (fn:contains($x, "blood")) then
<p>warning: blood coming up: {for $z in $x/node() return mapping($z, $options)}</p>
else
for $z in $x/node() return mapping($z, $options)
}
We now have the list of parts (just like the Transformers wheels, door handles, etc) and we now need to make our 'template' functions to make them into something new. Instead of a fancy robot, we'll just make some HTML:
define function p($x, $options) {
<p>{recursion($x, $options)}</p>
}
define function i($x, $options) {
<i>{recursion($x, $options)}</i>
}
define function output-text($x, $options) {
if (fn:empty($x)) then () else text {$x}
}
These basic transformations work with the mapping --> if we have a <LINE> it is now a <p> because it went through the p() function.
These are, after all, XQuery functions so we can also do some tests and complex formatting as well:
define function title($x, $options) {
if ($x/parent::PLAY) then
<h1>{recursion($x, $options)}</h1>
else
<h3>{recursion($x, $options)}</h3>
}
define function section($x, $options){
<div style="margin-top: 25px;">{recursion($x, $options)}<hr/></div>
}
The super cool thing is that $x node we are passing around carries it's context. Here we are doing a test of it's parent element. We could reach up to the root node and get the play title or anything else . . . the possibilities are endless and since transformations always have a catch, this flexibility comes in very handy. Also, I'm pretty sure this is how the Transformers can do such cool tricks :).
Finally, we'll do the equivalent of a Transformer Powerlink (sure to be the climax of the movie). This is where two Transformers bend and twist and make a new, bigger, more powerful robot.
To make our more powerful XML transformation, we'll use that $options node that we've been passing around:
define function play($x, $options) {
<html><head>
<title>{fn:string($options/desc)}: {fn:string($x/TITLE)}</title>
<meta name="keywords" content="{$options/meta-info}"/>
</head><body>
{recursion($x, $options)}
</body></html>
}
(: main function :)
let $meta-info :=
let $personae := //PERSONA/text()
return
fn:string-join($personae, ",")
let $options := <options><desc>XQuery
Transformers!</desc>
<meta-info>{$meta-info}</meta-info></options>
for $play in (/PLAY)[1]
return
mapping($play, $options)
We are pulling all the Shakespeare characters for extra large meta-data and passing along my new tag line: XQuery Transformers! In the transformation for the PLAY element we stick this in the head of the document (a common PowerLink move) to make an HTML wrapper that packs some extra power.
Putting this all together in a single stored XQuery module will give you the building blocks for powerful , fully programmable transformations.
XQuery Transformers: more than meets the eye!
Hi Trasnformers is the world's best no doubt, I'm looking forward to the second part, wowow transformers Xquery rulez!
Posted by: Trasformers Passion | May 07, 2008 at 07:06 PM
i love transformers! now i can be like them using your program.
Posted by: Zoneaire | April 13, 2009 at 08:56 AM