A Simple F# DSL to Mimic XQuery and XPath

System.Xml.Linq provides a nice, functional way of querying XML however with F# operator overloading we can create a nice mini DSL to have even cleaner code for XML querying.

With this DSL we can mimic XPath and XQuery quite closely.

Consider the following simple XML:

<Vehicle xmlns="urn:auto/v1">
<Engine>
<Part number="abc" type="piston" cost="4.0"/>
<Part number="def" type="camshaft" cost="25.0" />
</Engine>
<Body>
<Part number="234" type="fender" cost="32.47" />
<Part number="423" type="door" cost="45.0" />
</Body>
</Vehicle>

Say we want to find just the engine parts whose cost is greater than $5.00 then a typical XQuery would be:

xquery version "1.0";
declare namespace auto="urn:auto/v1";
for
$p in 
/auto:Vehicle
/auto:Engine
/auto:Part
where $p/@cost > 5.0
return $p

Now with some simple definitions, we can write F# code that mimics the above XQuery reasonably closely.

First we define three operators (“/-“ , “/+”, “@”) and then we define a simple function “auto” to give back a fully namespace qualified name. Finally we use list comprehension to perform the actual query:

let doc = XDocument.Load(file)
let inline (/-) (x : XContainer) n = x.Elements n
let inline (/+) (xs : XElement seq) n = xs |> Seq.collect(fun x -> x /- n)
let inline (@) (x:XElement) n = x.Attribute(XName.Get(n,"")).Value
let auto x = XName.Get(x,"urn:auto/v1")

[for p in
doc
/- auto"Vehicle"
/+ auto"Engine"
/+ auto"Part"
do
if p @ "cost" |> float > 5.0 then
yield p]

Once the operators are defined, the rest of the code is very clean and quite similar to the XQuery code.

Note another useful operation would be (“/*”) to get all the descendents of a node as in:

let inline (/*) (x : XContainer) n = x.Descendants n

Over the course of doing some work with actual XML data, I found the following helper functions to be useful:

let first xs = if Seq.isEmpty xs then None else Some(Seq.nth 0 xs)
let localName (x:XElement) = x.Name.LocalName
let value = function None->None | Some (y:XElement) -> Some(y.Value)

The “first” function can be used to get the first element of a sequence. It can be composed with any of “/…” operators defined above and with the “value” function. For example:

 someNode /- ns"Child1" |> first |> value
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s