Thursday, January 24, 2008

Modify an XML Fragment Retrieved by MarkLogic Server

Last week I was experimenting with a scenario where I wanted to retrieve a section of a larger XML document from MarkLogic Server, but modify it before passing it up to the web application layer (we're using MarkLogic -> XCC -> ASP.NET). This XQuery sample recurses through all of the element and text nodes, modifies what's needed, and passes the rest back unchanged. In the end we decided on a different approach, so this query is not tuned but the premise is interesting. I think there's probably a better way to structure the default handler, but for now ...

define variable $myId as xs:string external

define function recurse($node as node()) as node()*
{
for $i in $node/node() return modify-fragment($i)
}

(: Here we define which elements we want to modify and declare
a handler for them. :)
define function modify-fragment($node as node()) as node()*
{
typeswitch ($node)
case text() return text-handler($node)
case element(ref) return ref($node)
case element(see) return see($node)
case element(see-also) return see-also($node)
default return default-handler($node)
}

define function text-handler($node as node()?) as node()*
{
if(empty($node)) then ()
else (text {$node})
}

define function default-handler($node as node()?) as element()*
{
element { local-name($node) }
{ $node/@*, recurse($node) }
}

define function see($element as element(see)) as element()
{
let $destination-node := $element/ancestor::book/
descendant::node()[@local-id=$element/@seeref] return
if(not(empty($destination-node/ancestor-or-self::node()
[@fragment='true'][1]))) then
< see>
{ $element/@* }
{
attribute {"destination-node"}
{local-name($destination-node)},
attribute {"fragment-local-id"}
{$destination-node/ancestor-or-self::node()
[@fragment='true'][1]/@local-id}
}
{ recurse($element) }
< /see>
else recurse($element)
}

define function see-also($element as element(see-also)) as element()
{
let $destination-node := $element/ancestor::book/
descendant::node()[@local-id=$element/@seeref] return
if(not(empty($destination-node/ancestor-or-self::node()
[@fragment='true'][1]))) then
< see-also>
{ $element/@* }
{
attribute {"destination-node"}
{local-name($destination-node)},
attribute {"fragment-local-id"}
{$destination-node/ancestor-or-self::node()
[@fragment='true'][1]/@local-id}
}
{ recurse($element) }
< /see-also>
else recurse($element)
}

define function ref($element as element(ref)) as element()
{
if($element/@type='local-id') then
let $destination-node := $element/ancestor::book/
descendant::node()[@local-id=$element/@value] return
if(not(empty($destination-node/ancestor-or-self::node()
[@fragment='true'][1]))) then
< ref>
{ $element/@* }
{
attribute {"destination-node"}
{local-name($destination-node)},
attribute {"fragment-local-id"}
{$destination-node/ancestor-or-self::node()
[@fragment='true'][1]/@local-id}
}
{ recurse($element) }
< /ref>
else recurse($element)
else recurse($element)
}

(:let $myId := 'ID1234':)

for $i in (//chapter[@local-id = $myId] | //entry[@local-id = $myId])[1]
return

< fragment imagepath="{$i/property::imagepath/text()}">
{
recurse($i)
}
< /fragment>

No comments: