21 October 2006

Working with XML - Part 1 - Using the DOM

XML (Extensible Markup Language) is a way of storing information as text by applying structure and meaning to data using a system of nested elements and attributes. HTML is a loose form of XML because it consists of elements, attributes and text although it doesn't always obey the strict rules necesary for valid XML.

The basic rules are:

  • Element and attribute names are case sensitive i.e. derek != Derek
  • Each element must be closed
  • All an element's child elements must be closed before it can be
  • An XML document must have one element only as its root node

Document Type Definitions (DTD) and XML Schema Definition (XSD) are methods for defining the structure of an XML document and ensuring it adheres to your specification. Although i'm not going to cover them in this post they're very important particularly if you're letting other people write XML for your system.

Examples are in ASP/VBScript

Example 1 - Some XML

<?xml version="1.0" ?>
      <author id="12345">
         <name>Charles Dickens</name>
      <author id="23456">   
         <name>Rudyard Kipling</name>
         <title>Great Expectations</title>
         <title>The Jungle Book</title>

In this example we have a library element as the root node of the XML document. Inside that we have an authors element containing author elements and a books element containing book elements. The author elements have an id attribute and a name child element whereas the book elements have a title element and an author element containing the id of the associated author.

You can see from this example how it's easy to denote quite complex relationships in a simple, readable manner. I'll base the other examples in this post around this bit of XML

Example 2 - Loading XML in to a DOM

Set xmlDoc = Server.CreateObject("Microsoft.XMLDOM")
xmlDoc.async = False
Response.Write xmlDoc.xml

This snipet loads and XML document from a file into a DOM object. There's also a LoadXML function on the Microsoft DOM object for loading a string containing XML.

Once we've got our XML loaded we can traverse the tree, read data, change properties and save it back to a file.

Set xmlDoc = Server.CreateObject("Microsoft.XMLDOM")
xmlDoc.async = False
Set ndRoot = xmlDoc.documentElement

'retrieving element names
Response.Write ndRoot.tagName & "<br />"

'looping though nodes
Set ndAuthors = ndRoot.firstChild
For Each ndAuthor In ndAuthors.childNodes
   Response.Write ndAuthor.getAttribute("id") & "<br />"

'setting attributes
Set ndSecondAuthor = ndAuthors.childNodes(1)
ndSecondAuthor.setAttribute "id", 99999
Response.Write ndSecondAuthor.getAttribute("id") & "<br />"

'retrieving and setting node text
Response.Write ndSecondAuthor.firstChild.text & "<br />"
ndSecondAuthor.firstChild.text = "Joe Bloggs"
Response.Write ndSecondAuthor.firstChild.text & "<br />"


18 October 2006

Simple event driven programming using VBScript's GetRef

VBScript doesn't have an event implementation so if you fancy having features like attaching handlers which will respond to specific events on your object you can do it simply by using the GetRef function and a bit of "syntactic sugar".

I'm using ASP in these examples cos it's easy.

Example 1 - Simple Events

'Create a handler
Function MyHandler()
   Response.Write "Hello from the handler!"
End Function

'Create an event
Dim OnLoad
Set OnLoad = GetRef("MyHandler")

'Fire the event

Here we've created a simple event which takes one handler function and fired the event which in turn has called the function we attached.

To turn this in to a more useful event system we can use an array for the OnLoad event variable thus...

'Create some handlers
Function MyHandler1()
   Response.Write "Hello from handler 1!"
End Function

Function MyHandler2()
   Response.Write "Hello from handler 2!"
End Function

'Create an event
Dim OnLoad
OnLoad = Array(GetRef("MyHandler1"), GetRef("MyHandler2"))

'Fire the event
For Each handler In OnLoad

Example 2 - Event Arguments

In most event implementations the event handlers take one argument, passed to them by the fired event, which contains things like the type of event and a reference to the object on which it was fired etc.

'Create a handler which takes one argument
Function MyHandler(e)
   Response.Write "Hello from the handler - i was called by " & e
End Function

'Create two events
Dim OnLoad
Set OnLoad = GetRef("MyHandler")

Dim OnUnload
Set OnUnload = GetRef("MyHandler")

'Fire the events

Wrapping it up

We've established we can do all the basics of events, now all we need to do is wrap it up in a few classes to make it usable.

First we need an Event class that we can instantiate for each event we want. This will have to expose an event arguments property and methods for attaching handlers and firing the event. It will also have to keep track internally of the attached handlers. Lets have a go...

Class clsEvent

   'An array to keep track of our handlers
   Private aryHandlers()

   'Our event arguments object to be passed 
   'to the handlers
   Public EventArgs

   Private Sub Class_Initialize()
      ReDim aryHandlers(-1)
      Set EventArgs = New clsEventArgs
   End Sub

   Private Sub Class_Terminate()
      Set EventArgs = Nothing
      Erase aryHandlers
   End Sub

   'Method for adding a handler
   Public Function AddHandler(strFunctionName)
      ReDim Preserve aryHandlers(UBound(aryHandlers) + 1)
      Set aryHandlers(UBound(aryHandlers)) = _
   End Function

   'Method for firing the event
   Public Function Fire(strType, objCaller)
      EventArgs.EventType = strType
      Set EventArgs.Caller = objCaller
      For Each f In aryHandlers
   End Function

End Class

Next we need an EventArgs class for passing data about the event to the handlers. This just needs three properties; event type, caller and an arguments collection for event type specific things.

Class clsEventArgs

   Public EventType, Caller, Args

   Private Sub Class_Initialize()
      Set Args = CreateObject("Scripting.Dictionary")
   End Sub

   Private Sub Class_Terminate()
      Set Args = Nothing
   End Sub

End Class

Next our class that has an event, in this case an OnLoad which fires after the object's Load method is called. We'll also create a few handlers and do a trial run.

Class MyClass

   Public OnLoad

   Private Sub Class_Initialize()
      'Setting up our event
      Set OnLoad = New clsEvent

      'Adding an argument
      OnLoad.EventArgs.Args.Add "arg1", "Hello"
   End Sub

   Public Function Load()
      Response.Write "loading the object here!<br />"
      'Firing the event
      OnLoad.Fire "load", Me
   End Function

End Class

'A couple of handling function for the events
Function EventHandler(e)
   Response.Write "<h2>EventHandler</h2>"
   Response.Write "<p>Event """ & e.EventType & """ fired by object
of type " & TypeName(e.Caller) & ".</p>"
End Function

Function EventHandler2(e)
   Response.Write "<h2>EventHandler2</h2>"
   For Each x In e.Args
      Response.Write x & ": " & e.Args(x) & "<br />"
End Function

'instantiate the object, attach the handlers and call the load
Set myObj = New MyClass

Event based programming reverses the responsibility for code execution within your program. In conventional procedural programming it would be the responsibility of the myObj class to make sure the two event handlers were fired when it's Load method was called. By using an OnLoad event instead myObj doesn't have to know anything about the environment in which its executing, it just blindly fires the event and any attached handlers will be called. In this way you can add additional functions which run when myObj's Load method is called without modifying MyClass.

In more complex systems being able to add functionality with a minimum of intrusion into other parts of the system is a big bonus and event based programming is an easy way of achieving it.