Every day, the European Central Bank publishes “official” reference FX (foreign exchange) rates where currencies are quoted against the Euro (expressed as EUR/XXX), or 1 EUR = xxx XXX.

The exchange rates of the last thirty sessions are available in xml format which we shall try to parse.

In order to do so, we use F# and active patterns, based on Don Syme’s draft paper.

Note : the ampersand operator ( & ) is used in active patterns to combine two patterns. That is : p1 & p2 returns true is both patterns are matched. They are the equivalent of the ( && ) operator for boolean tests.

/*

The basic structure of the xml node we are interested in looks like :

<Cube>
  <Cube time="YYYY-MM-DD">
    <Cube currency="..." rate="..." />
    <Cube currency="..." rate="..." />
    <Cube currency="..." rate="..." />
  </Cube>
</Cube>

*/

open System
open System.Net
open System.Text
open System.Xml

//FxRate of (currency * official rate)
type t = FxRate of string * float

let (|Elem|_|) name (inp:XmlNode) =
  if inp.Name = name then Some inp else None

//The root cube has no attributes
let (|NoAttr|_|) (inp:XmlNode) =
  if inp.Attributes.Count = 0 then Some inp else None

let (|RootCube|_|) inp =
  match inp with
  | (Elem "Cube" (NoAttr c)) -> Some c
  | _ -> None

//But its children have...
let (|Attributes|)  (inp:XmlNode) = inp.Attributes

let (|Attr|) attr (inp: XmlAttributeCollection) =
  match inp.GetNamedItem(attr) with
  | null -> failwithf "%s not found" attr
  | node -> node.Value  

//We want to convert attributes to the types we expect in programming
let (|Float|) (s:string) = float s
let (|Timestamp|) (s:string) = DateTime.Parse s  

let rec (|FxCube|_|) = function
  | Elem "Cube" (Attributes (Attr "currency" currency & Attr "rate" (Float rate))) -> Some <| FxRate (currency, rate)
  | _ -> None
and (|FxCubes|) (inp:XmlNode) =
  [ for x in inp.ChildNodes do
      match x with | FxCube c -> yield c | _ -> ()
  ]

let rec (|DateCube|_|) = function
  | Elem "Cube" (Attributes (Attr "time" (Timestamp dt)) & FxCubes xs) -> Some (dt, xs)
  | _ -> None
and (|DateCubes|) (inp:XmlNode) =
  [ for x in inp.ChildNodes do
      match x with DateCube c -> yield c | _ -> ()
  ]

let parse inp =
  match inp with
  | RootCube (DateCubes elems) -> elems
  | _ -> failwith "not an interesting cube"

Once we have defined the helpers, we download the xml file from the ECB, and we parse it. We can then whatever we like with the data, such as printing it (a mighty interesting application indeed…)


let url = @"http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml"

let extractCube (root:XmlElement) =
  let rootCube = ref <| Unchecked.defaultof<XmlNode>
  for node in root.ChildNodes do
    match node with
    | RootCube c -> rootCube := c
    | _ -> ()
  !rootCube

let rootCube =
  async {
    use webClient = new WebClient(Encoding=Encoding.UTF8)
    let uri = Uri(url)
    let! xml = webClient.AsyncDownloadString(uri)
    let doc = new XmlDocument()
    doc.LoadXml(xml)
    return extractCube doc.DocumentElement
  } |> Async.RunSynchronously

let ecbFxRates = parse rootCube

for (dt, fxRates) in ecbFxRates do
  printfn "On date %A :" dt
  for FxRate(currency, rate) in fxRates do
    printfn "    EUR/%s @ %0.4f" currency rate

Comments are closed.