Interactive Session #9: Processing XML

Reading and interpreting XML data is performed through the System.Xml package. You need to create a new XmlDocument value and load the XML text into the XmlDocument.

> open System.Xml;;
> let txt = "<drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx>
  <dy>50.0</dy></line><circle><x>50.0</x><y>50.0</y><r>20.0</r>
  </circle></drawing>";;

val txt : string =
 "<drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy>
  </line><circle><x>50.0</x><y>50.0</y><r>20.0</r></circle>
  </drawing>"

> let doc = new XmlDocument();;

val doc : XmlDocument

> doc.LoadXml(txt);;
val it : unit = ()

Now you may explore the XmlDocument structure with the Item method. There are two different ways of accessing the elements

  1. identifiying the elements by their name, e.g. doc.Item(“drawing”)
  2. identifiying the elements by their position in the document, e.g. doc.ChildNodes.Item(0)

In advance we need to register a printer function in the interactive for XmlNode values.

> fsi.AddPrinter(fun (x:XmlNode) -> x.OuterXml);;
val it : unit = ()
> doc.Item("drawing").Value;;
val it : string = null
> doc.Item("drawing").OuterXml;;
val it : string =
 "<drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line>
  <circle><x>50.0</x><y>50.0</y><r>20.0</r></circle></drawing>"
> doc.Item("drawing");;
val it : XmlElement =
 <drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line>
<circle><x>50.0</x><y>50.0</y><r>20.0</r></circle></drawing>
> doc.Item("drawing").Item("line");;
val it : XmlElement =
 <line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line>
> doc.Item("drawing").Item("line").Item("x");;
val it : XmlElement = 10.0
> doc.Item("drawing").Item("line").Item("x").ChildNodes.Item(0);;
val it : XmlNode = 10.0
> doc.ChildNodes.Item(0);;
val it : XmlNode =
 <drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line>
  <circle><x>50.0</x><y>50.0</y><r>20.0</r></circle></drawing>

Now let’s parse this xml document into a data structure for drawing elements. First we need a data type for our data structure:

> type element =
-   | Drawing of element list
-   | Line of (float * float * float * float)
-   | Circle of (float * float * float);;

type element =
 | Drawing of element list
 | Line of (float * float * float * float)
 | Circle of (float * float * float)

Now we need a parser function which translates a XmlDocument into our drawing data structure. This parser function traverses recursively the XmlDocument structure and returns a structure of type element.

> exception UnknownDrawingElement of string;;

exception UnknownDrawingElement of string

> let rec parse_element (item:XmlNode) =
-   match item.Name with
-     | "drawing" -> Drawing [for i in item.ChildNodes do yield parse_element i]
-     | "line" -> Line (float(item.Item("x").ChildNodes.Item(0).Value),
-                       float(item.Item("y").ChildNodes.Item(0).Value),
-                       float(item.Item("dx").ChildNodes.Item(0).Value),
-                       float(item.Item("dy").ChildNodes.Item(0).Value))
-     | "circle" -> Circle (float(item.Item("x").ChildNodes.Item(0).Value),
-                           float(item.Item("y").ChildNodes.Item(0).Value),
-                           float(item.Item("r").ChildNodes.Item(0).Value))
-     | s -> raise (UnknownDrawingElement ("unkown element " + s));;

val parse_element : XmlNode -> element
> let drg = parse_element(doc.ChildNodes.Item(0));;

val drg : element =
 Drawing [Line (10.0, 20.0, 50.0, 50.0); Circle (50.0, 50.0, 20.0)]

draw_element takes an element data structure and paints it onto a Graphics canvas with a pen.

> open System.Drawing
- open System.Windows.Forms
-
- let rec draw_element (gfx:Graphics) (pen:Pen) (e:element) =
-   match e with
-     | Drawing d -> List.iter (fun i -> (draw_element gfx pen i)) d
-     | Line (x, y, dx, dy) -> gfx.DrawLine(pen, int(x), int(y),
-                                                int(x+dx), int(y+dy))
-     | Circle (x, y, r) -> gfx.DrawEllipse(pen, int(x), int(y),
-                                                int(r), int(r));;

val draw_element : Graphics -> Pen -> element -> unit

Now we need a form with a canvas to draw our elements onto.

> let form = new Form(Visible=true, Width=300, Height=300)
- let gfx = form.CreateGraphics()
- let pen = new Pen(Color.Black, 2.0f);;

val form : Form = System.Windows.Forms.Form, Text:
val gfx : Graphics
val pen : Pen

Finally lets draw the drawing onto the form.

> draw_element gfx pen drg;;
val it : unit = ()

Alternatively you may load the complete source as a script into the interactive

open System.Xml
let txt = "<drawing><line><x>10.0</x><y>20.0</y><dx>50.0</dx><dy>50.0</dy></line><circle><x>50.0</x><y>50.0</y><r>20.0</r></circle></drawing>"

let doc = new XmlDocument()
doc.LoadXml(txt);;

type element =
 | Drawing of element list
 | Line of (float * float * float * float)
 | Circle of (float * float * float)

exception UnknownDrawingElement of string

let rec parse_element (item:XmlNode) =
 match item.Name with
 | "drawing" -> Drawing [for i in item.ChildNodes do yield parse_element i]
 | "line" -> Line (float(item.Item("x").ChildNodes.Item(0).Value),
 float(item.Item("y").ChildNodes.Item(0).Value),
 float(item.Item("dx").ChildNodes.Item(0).Value),
 float(item.Item("dy").ChildNodes.Item(0).Value))
 | "circle" -> Circle (float(item.Item("x").ChildNodes.Item(0).Value),
 float(item.Item("y").ChildNodes.Item(0).Value),
 float(item.Item("r").ChildNodes.Item(0).Value))
 | s -> raise (UnknownDrawingElement ("unkown element " + s))

open System.Drawing
open System.Windows.Forms

let rec draw_element (gfx:Graphics) (pen:Pen) (e:element) =
 match e with
 | Drawing d -> List.iter (fun i -> (draw_element gfx pen i)) d
 | Line (x, y, dx, dy) -> gfx.DrawLine(pen, int(x), int(y),
 int(x+dx), int(y+dy))
 | Circle (x, y, r) -> gfx.DrawEllipse(pen, int(x), int(y),
 int(r), int(r))

let form = new Form(Visible=true, Width=300, Height=300)
let gfx = form.CreateGraphics()
let pen = new Pen(Color.Black, 2.0f)

let drg = parse_element(doc.ChildNodes.Item(0))
draw_element gfx pen drg

Now load this script into the interactive and draw the result into the canvas.

> #load "xml1.fsx";;
[Loading ~/Projekte/Fsharp/xml1.fsx]
...
> open Xml1;;
> draw_element gfx pen drg;;
val it : unit = ()

Further reading:

Advertisements
This entry was posted in Getting Started, GUI, IO, Text. Bookmark the permalink.

One Response to Interactive Session #9: Processing XML

  1. Pingback: Rick Minerich's Development Wonderland : F# Discoveries This Week 10/03/2010

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