Showing posts with label xml. Show all posts
Showing posts with label xml. Show all posts

Tuesday, 13 July 2010

MarkLogic cts:query serialization

If you work with MarkLogic you've surely come across cts functions (Built-In: Search), cts:query, and its closest counterparts.

One useful feature available in recent releases is the serialization of cts queries, which allows for the conversion of cts:query types to xml fragments, and the other way round.

If you take a look at the code below, you'll notice the local:mySearch() function which takes a cts:query as it's only parameter. You'll also note cts:search() is used inside this function and references the $query parameter abstracting the query executed by cts:search(). Most interestingly you'll also find a conditional statement invoking an xpath on the $query variable. "But the parameter is strongly typed to cts:query!" . . . you ask. Well that's because it is possible to serialize what would otherwise be "cts:element-value-query(xs:QName("filename"), "myFile", ("lang=en"), 1)" to xml, just by embedding the query in a parent element.

let $query as element(query) := element query {$query}


It is equally possible to do the reverse operation, simply by invoking an xpath returning cts elements in your fragment, wrapped in a cts:query() function.

cts:query($query/*)


This feature also allows for cts queries to be built dynamically as xml fragments rather than concatenated strings.


xquery version '1.0-ml';

declare function local:mySearch($query as cts:query) as element(response)
{
let $query as element(query) := element query {$query}
let $searchResults as element(record)* := cts:search(/record, cts:query($query/*))

return
element response
{
$query,
element results {
if($query/cts:element-value-query/cts:element/text() eq "filename")
then $searchResults//element1
else $searchResults//element2
}
}
};

declare function local:searchByFilename($filename as xs:string) as element(response)
{local:mySearch(cts:element-value-query(xs:QName("filename"), $filename))};

declare function local:searchByVolume($volume as xs:string) as element(response)
{local:mySearch(cts:element-value-query(xs:QName("volume"), $volume))};


local:searchByFilename("myFile")



returns

<response>
<query>
<cts:element-value-query xmlns:cts="http://marklogic.com/cts">
<cts:element>filename</cts:element>
<cts:text xml:lang="en">myfile</cts:text>
</cts:element-value-query>
</query>
<query>cts:element-value-query(xs:QName("filename"), "myfile", ("lang=en"), 1)</query>
<results>
<element1>some content</element1>
<element1>some other content</element1>
</results>
</response>

Friday, 13 February 2009

Basic Custom functions

The heart and soul of XQuery relies on an intercut network of functions distributed throughout library modules, offering reusable functionality, rivaling most procedural languages.

If you're familiar with XSLT 2, you probably had the chance to use functions in a very similar format to the way functions are implemented in XQuery.

Ultimately the main goal of functions is to group functionality in smaller portions, facilitating reusability of code. When you call a function you can pass values known as parameters to its local scope. Functions can either return something, or can return the equivalent of void in other languages in the format of an empty sequence.


declare function local:myFunction() as xs:string
{"myFunction"};

local:myfunction()


The above code will return the string "myFunction"

The declare function bit is self explanetory, local:myFunction is the namespace and function name respectively. The local namespace is a reserved namespace prefix built into XQuery, and traditionally used for locally declared functions (I'll probably write another blog explaining modules and external custom functions).

The (), represents an empty sequence of parameters. In this example no parameters are required by the function signature.

"as xs:string" is the returned value type, in this case a simple string. You don't need to strongly type the return type of a function, but in my opinion, it keeps the code clean and forces run time cast exceptions, if the values returned by a function aren't what you're expecting, making debugging much easier.

Ok, lets look at a more complex example:

XML Input

<?xml version="1.0" encoding="utf-8"?>
<geo:location xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
<geo:lat>51.667795</geo:lat>
<geo:long>0.040683</geo:long>
</geo:location>


XQuery

xquery version "1.0";

declare namespace geo="http://www.w3.org/2003/01/geo/wgs84_pos#";
declare namespace georss="http://www.georss.org/georss";

declare function local:get-georss($_latitude as xs:float, $_longitude as xs:float) as element(georss:point)
{
element georss:point {fn:concat($_latitude, " ", $_longitude)}
};


<rss version="2.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:georss="http://www.georss.org/georss">
<channel>
<title>Some RSS feed with geodata</title>
<link>http://www.foo.com/locations</link>
<description>Geo RSS location info</description>
<pubDate>Sat, 7 Feb 2009 22:24:09 -0800</pubDate>
<lastBuildDate>Sat, 7 Feb 2009 22:24:09 -0800</lastBuildDate>
<item>
<title>High Beach</title>
<link>http://www.foo.com/location?lat={fn:data(/geo:location/geo:lat)}&long={fn:data(/geo:location/geo:long)}</link>
<description>High Beach, Epping Forest, London, UK</description>
<pubDate>Sat, 7 Feb 2009 22:24:09 -0800</pubDate>
{
(
local:get-georss(
xs:float(/geo:location/geo:lat),
xs:float(/geo:location/geo:long)
),
/geo:location/geo:lat,
/geo:location/geo:long
)
}
</item>
</channel>
</rss>


returns

<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:georss="http://www.georss.org/georss"
xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
version="2.0">
<channel>
<title>Some RSS feed with geodata</title>
<link>http://www.foo.com/locations</link>
<description>Geo RSS location info</description>
<pubDate>Sat, 7 Feb 2009 22:24:09 -0800</pubDate>
<lastBuildDate>Sat, 7 Feb 2009 22:24:09 -0800</lastBuildDate>
<item>
<title>High Beach</title>
<link>http://www.foo.com/location?lat=51.667795&long=0.040683</link>
<description>High Beach, Epping Forest, London, UK</description>
<pubDate>Sat, 7 Feb 2009 22:24:09 -0800</pubDate>
<georss:point>51.667793 0.040683</georss:point>
<geo:lat>51.667795</geo:lat>
<geo:long>0.040683</geo:long>
</item>
</channel>
</rss>


This time the function local:get-georss() accepts two parameters, the first parameter an xs:float representing the latitude, and the second, again an xs:float representing the longitude. The function local:get-georss() must return a georss:point, element.
The body of the function first creates the element georss:point, then concatenates the latitude, and longitude values as it's text node separated by an empty space character.

To call local:get-georss(), you'll notice the values passed have been converted to the data types required for each parameter, in this case xs:float. When the query invokes the following function call to local:get-georss(), it returns <georss:point>51.667793 0.040683</georss:point>


local:get-georss(
xs:float(/geo:location/geo:lat),
xs:float(/geo:location/geo:long)
)