changeset 640:901803e1305f

First instance of audioDB browser code.
author mas01mj
date Thu, 08 Oct 2009 11:19:11 +0000
parents 2eaea1afd6b3
children 1f70abc1ba32
files examples/browser/export/cat2rdf.py examples/browser/ontology/audiodb.owl examples/browser/web/css/data_table.css examples/browser/web/css/jOWL.css examples/browser/web/css/jq/custom-theme/images/ui-bg_flat_0_aaaaaa_40x100.png examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_55_fbf9ee_1x400.png examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_65_ffffff_1x400.png examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_75_dadada_1x400.png examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_75_e6e6e6_1x400.png examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_75_ffffff_1x400.png examples/browser/web/css/jq/custom-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png examples/browser/web/css/jq/custom-theme/images/ui-bg_inset-soft_95_fef1ec_1x100.png examples/browser/web/css/jq/custom-theme/images/ui-icons_222222_256x240.png examples/browser/web/css/jq/custom-theme/images/ui-icons_2e83ff_256x240.png examples/browser/web/css/jq/custom-theme/images/ui-icons_454545_256x240.png examples/browser/web/css/jq/custom-theme/images/ui-icons_888888_256x240.png examples/browser/web/css/jq/custom-theme/images/ui-icons_cd0a0a_256x240.png examples/browser/web/css/jq/custom-theme/jquery-ui-1.7.custom.css examples/browser/web/img/back_disabled.jpg examples/browser/web/img/back_enabled.jpg examples/browser/web/img/forward_disabled.jpg examples/browser/web/img/forward_enabled.jpg examples/browser/web/img/sort_asc.jpg examples/browser/web/img/sort_both.jpg examples/browser/web/img/sort_desc.jpg examples/browser/web/img/spinner.gif examples/browser/web/img/tooltip/Thumbs.db examples/browser/web/img/tooltip/arrow_left.gif examples/browser/web/img/tooltip/arrow_right.gif examples/browser/web/img/tooltip/loader.gif examples/browser/web/img/treeView/Thumbs.db examples/browser/web/img/treeView/dotted/Thumbs.db examples/browser/web/img/treeView/dotted/tvi.gif examples/browser/web/img/treeView/dotted/tvic.gif examples/browser/web/img/treeView/dotted/tvie.gif examples/browser/web/img/treeView/dotted/tvil.gif examples/browser/web/img/treeView/dotted/tvilc.gif examples/browser/web/img/treeView/dotted/tvile.gif examples/browser/web/img/treeView/dotted/tviload.gif examples/browser/web/index.html examples/browser/web/js/browser.js examples/browser/web/js/jOWL.js examples/browser/web/js/jOWL_UI.js examples/browser/web/js/jquery.dataTables.js examples/browser/web/js/sparql.js
diffstat 45 files changed, 9263 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/export/cat2rdf.py	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,152 @@
+#!/usr/bin/python
+
+import sys
+import psycopg2
+import psycopg2.extras
+
+from rdflib.Graph import ConjunctiveGraph as Graph
+from rdflib import Namespace, Literal, URIRef, BNode, RDF
+
+catalogue = sys.argv[1]
+
+foaf = Namespace("http://xmlns.com/foaf/0.1/")
+mo = Namespace("http://purl.org/ontology/mo/")
+mb_artist = Namespace("http://dbtune.org/musicbrainz/resource/artist/")
+dc = Namespace("http://purl.org/dc/elements/1.1/")
+default_graph_uri = "http://omras2.gold.ac.uk/catalogue/"+catalogue.lower()
+
+username = "USERNAME"
+host = "HOST"
+database = "DATABASE"
+
+counters = {}
+namespaces = {}
+
+def loadCatalogue(catalogue):
+	try:
+		conn = psycopg2.connect("dbname='"+database+"' user='"+username+"' host='"+host+"'");
+	except:
+		print "Unable to connect to the database"
+
+	cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
+
+	cursor.execute("""SELECT * from media WHERE catalogue LIKE '"""+catalogue+"""'""")
+
+	rows = cursor.fetchall()
+
+	return rows
+
+def createGraph(rows):
+	albums = {}
+
+	artists = {
+		'Madonna': mb_artist['79239441-bfd5-4981-a70c-55c3f15c1287'], 
+		'John Coltrane': mb_artist['b625448e-bf4a-41c3-a421-72ad46cdb831'], 
+		'Miles Davis' : mb_artist['561d854a-6a28-4aa7-8c99-323e6ce46c2a']}
+
+	counter = 1
+	for row in rows:
+		graph = Graph(identifier = URIRef(default_graph_uri))
+		
+		# Create all the relevant nodes (with the correct IDs)
+
+		work = getNewNode('work')
+		composition = getNewNode('composition')
+		track = getNewNode('track')
+		signal = getNewNode('signal')
+		record = getNewNode('record')
+		performance = getNewNode('performance')
+
+		# If we don't have an artist url, make a foaf Agent instead.
+		if row['artist']:
+			try:
+				artist = artists[row['artist']]
+			except KeyError:
+				artist = getNewNode('artist')
+				graph.add((artist, RDF.type, foaf['Agent']))
+				graph.add((artist, foaf['name'], Literal(row['artist'].strip())))
+				artists[row['artist']] = artist;	
+
+		if row['composer']:
+			try:
+				composer = artists[row['composer']]
+			except KeyError:
+				composer = getNewNode('artist')
+				graph.add((composer, RDF.type, foaf['Agent']))
+				graph.add((composer, foaf['name'], Literal(row['composer'].strip())))
+				artists[row['composer']] = composer;	
+		else:
+			composer = artist
+
+
+		# Work
+		graph.add((work, RDF.type, mo['MusicalWork']))
+		
+		# Composition
+		graph.add((composition, RDF.type, mo['Composition']))
+		if composer:
+			graph.add((composition, mo['composer'], composer)) 
+		graph.add((composition, mo['produced_work'], work))
+
+		# Track
+		graph.add((track, RDF.type, mo['Track']))
+		if row['artist']:
+			graph.add((track, foaf['maker'], artist))
+		if row['tracknum']:
+			graph.add((track, mo['track_number'], Literal(row['tracknum'])))
+
+		# Album
+		try:
+			album = albums[row['album']]
+		except KeyError:
+			album = getNewNode('album')
+			graph.add((album, RDF.type, mo['Record']))
+			graph.add((album, dc['title'], Literal(row['album'].strip())))
+			graph.add((album, mo['release_type'], mo['album']))
+			albums[row['album']] = album
+		graph.add((album, mo['track'], track))
+
+		# Signal
+		graph.add((signal, RDF.type, mo['Signal']))
+		graph.add((signal, mo['published_as'], record))
+		
+		if row['track']:
+			graph.add((signal, dc['title'], Literal(row['track'].strip())))
+		if row['isrc']:
+			graph.add((signal, mo['isrc'], Literal(row['isrc'].strip())))
+
+		# Record
+		graph.add((record, RDF.type, mo['Record']))
+		graph.add((record, mo['publication_of'], signal))
+		graph.add((record, mo['track'], track))
+
+		# Performance
+		graph.add((performance, RDF.type, mo['Performance']))
+		graph.add((performance, mo['performance_of'], work))
+		if row['artist']:
+			graph.add((performance, mo['performer'], artist))
+		graph.add((performance, mo['recorded_as'], signal))
+
+		#graph.close()
+		
+		graph.serialize(format='xml',destination="output/"+catalogue.lower()+"_"+str(counter)+".rdf")
+		counter += 1
+ 
+def getNewNode(type):
+	try:
+		count = counters[type]
+	except KeyError:
+		counters[type] = 1
+		count = counters[type]
+
+	try:
+		namespace = namespaces[type]
+	except KeyError:
+		namespaces[type] = Namespace(default_graph_uri+"/"+type+"/")
+		namespace = namespaces[type]
+
+	node = namespace[str(count)]
+	counters[type] += 1
+	return node
+
+createGraph(loadCatalogue(catalogue))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/ontology/audiodb.owl	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE owl [
+  <!ENTITY xsd          "http://www.w3.org/2001/XMLSchema#">
+  <!ENTITY rdf          "http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+  <!ENTITY rdfs         "http://www.w3.org/2000/01/rdf-schema#">
+  <!ENTITY owl          "http://www.w3.org/2002/07/owl#">
+  <!ENTITY base         "http://omras2.gold.ac.uk/ontology/audiodb#">
+  <!ENTITY dc           "http://purl.org/dc/elements/1.1/">
+  <!ENTITY dct          "http://purl.org/dc/terms/">
+]>
+
+
+
+<rdf:RDF xmlns:rdf="&rdf;"
+         xmlns:rdfs="&rdfs;"
+         xmlns:owl="&owl;"
+         xmlns:xsd="&xsd;"
+         xmlns:dat="&dat;"
+         xml:base="&base;"
+         xmlns:dc="&dc;"
+         xmlns:dct="&dct;">
+
+
+  <owl:Ontology rdf:about="&base;">
+    <rdfs:label>AudioDB Ontology</rdfs:label>
+    <dc:title xml:lang="en">AudioDB Ontology</dc:title>
+    <dc:description xml:lang="en">Describes the contents of an AudioDB instance</dc:description>
+    <dc:creator>Michael O. Jewell (mailto:mas01mj@gold.ac.uk)</dc:creator>
+    <dct:created>2009-10-07</dct:created>
+    <owl:versionInfo>0.1</owl:versionInfo>
+  </owl:Ontology>
+
+  <owl:Class rdf:ID="Database">
+    <rdfs:label>AudioDB</rdfs:label>
+    <rdfs:comment>Represents a collection of extracted features and information about their extraction.</rdfs:comment>
+    <rdfs:subClassOf rdf:resource="foaf:Document" />
+  </owl:Class>
+  
+  <owl:Class rdf:ID="Feature">
+    <rdfs:label>Feature</rdfs:label>
+    <rdfs:comment>Information about an audio feature</rdfs:comment>
+  </owl:Class>
+
+  <owl:ObjectProperty rdf:ID="window-type">
+    <rdfs:label>Window Type</rdfs:label>
+    <rdfs:comment>This property indicates that there is some no link between the named Expressions</rdfs:comment>
+    <owl:inverseOf rdf:resource="#is-not-linked-to"/>
+    <rdfs:domain rdf:resource="#Expression"/>
+    <rdfs:range rdf:resource="#Expression"/>
+  </owl:ObjectProperty>
+
+  <owl:Class rdf:ID="ChromogramFeature">
+    <rdfs:label>Chromogram Feature</rdfs:label>
+    <rdfs:comment>Information about an audio feature</rdfs:comment>
+    <rdfs:subClassOf rdf:resource="audiodb:Feature" />
+  </owl:Class>
+  
+  <owl:Class rdf:ID="MFCCFeature">
+    <rdfs:label>MFCC Feature</rdfs:label>
+    <rdfs:comment>Information about an audio feature</rdfs:comment>
+    <rdfs:subClassOf rdf:resource="audiodb:Feature" />
+  </owl:Class>
+  
+  <owl:Class rdf:ID="CQTFeature">
+    <rdfs:label>CQT Feature</rdfs:label>
+    <rdfs:comment>Information about an audio feature</rdfs:comment>
+    <rdfs:subClassOf rdf:resource="audiodb:Feature" />
+  </owl:Class>
+
+</rdf:RDF> 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/css/data_table.css	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,447 @@
+/*
+ *  File:         demo_table.css
+ *  CVS:          $Id$
+ *  Description:  CSS descriptions for DataTables demo pages
+ *  Author:       Allan Jardine
+ *  Created:      Tue May 12 06:47:22 BST 2009
+ *  Modified:     $Date$ by $Author$
+ *  Language:     CSS
+ *  Project:      DataTables
+ *
+ *  Copyright 2009 Allan Jardine. All Rights Reserved.
+ *
+ * ***************************************************************************
+ * DESCRIPTION
+ *
+ * The styles given here are suitable for the demos that are used with the standard DataTables
+ * distribution (see www.datatables.net). You will most likely wish to modify these styles to
+ * meet the layout requirements of your site.
+ *
+ * Common issues:
+ *   'full_numbers' pagination - I use an extra selector on the body tag to ensure that there is
+ *     no conflict between the two pagination types. If you want to use full_numbers pagination
+ *     ensure that you either have "example_alt_pagination" as a body class name, or better yet,
+ *     modify that selector.
+ *   Note that the path used for Images is relative. All images are by default located in
+ *     ../images/ - relative to this CSS file.
+ */
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables features
+ */
+
+.dataTables_wrapper {
+	position: relative;
+	min-height: 302px;
+	_height: 302px;
+	clear: both;
+}
+
+.dataTables_processing {
+	position: absolute;
+	top: 0px;
+	left: 50%;
+	width: 250px;
+	margin-left: -125px;
+	border: 1px solid #ddd;
+	text-align: center;
+	color: #999;
+	font-size: 11px;
+	padding: 2px 0;
+}
+
+.dataTables_length {
+	width: 40%;
+	float: left;
+}
+
+.dataTables_filter {
+	width: 50%;
+	float: right;
+	text-align: right;
+}
+
+.dataTables_info {
+	width: 60%;
+	float: left;
+}
+
+.dataTables_paginate {
+	width: 44px;
+	* width: 50px;
+	float: right;
+	text-align: right;
+}
+
+/* Pagination nested */
+.paginate_disabled_previous, .paginate_enabled_previous, .paginate_disabled_next, .paginate_enabled_next {
+	height: 19px;
+	width: 19px;
+	margin-left: 3px;
+	float: left;
+}
+
+.paginate_disabled_previous {
+	background-image: url('../img/back_disabled.jpg');
+}
+
+.paginate_enabled_previous {
+	background-image: url('../img/back_enabled.jpg');
+}
+
+.paginate_disabled_next {
+	background-image: url('../img/forward_disabled.jpg');
+}
+
+.paginate_enabled_next {
+	background-image: url('../img/forward_enabled.jpg');
+}
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables display
+ */
+table.display {
+	margin: 0 auto;
+	width: 100%;
+	clear: both;
+}
+
+table.display thead th {
+	padding: 3px 18px 3px 10px;
+	border-bottom: 1px solid black;
+	font-weight: bold;
+	cursor: pointer;
+	* cursor: hand;
+}
+
+table.display tfoot th {
+	padding: 3px 10px;
+	border-top: 1px solid black;
+	font-weight: bold;
+}
+
+table.display tr.heading2 td {
+	border-bottom: 1px solid #aaa;
+}
+
+table.display td {
+	padding: 3px 10px;
+}
+
+table.display td.center {
+	text-align: center;
+}
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables sorting
+ */
+
+.sorting_asc {
+	background: url('../img/sort_asc.jpg') no-repeat center right;
+}
+
+.sorting_desc {
+	background: url('../img/sort_desc.jpg') no-repeat center right;
+}
+
+.sorting {
+	background: url('../img/sort_both.jpg') no-repeat center right;
+}
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables row classes
+ */
+table.display tr.odd.gradeA {
+	background-color: #ddffdd;
+}
+
+table.display tr.even.gradeA {
+	background-color: #eeffee;
+}
+
+
+
+
+table.display tr.odd.gradeA {
+	background-color: #ddffdd;
+}
+
+table.display tr.even.gradeA {
+	background-color: #eeffee;
+}
+
+table.display tr.odd.gradeC {
+	background-color: #ddddff;
+}
+
+table.display tr.even.gradeC {
+	background-color: #eeeeff;
+}
+
+table.display tr.odd.gradeX {
+	background-color: #ffdddd;
+}
+
+table.display tr.even.gradeX {
+	background-color: #ffeeee;
+}
+
+table.display tr.odd.gradeU {
+	background-color: #ddd;
+}
+
+table.display tr.even.gradeU {
+	background-color: #eee;
+}
+
+
+tr.odd {
+	background-color: #E2E4FF;
+}
+
+tr.even {
+	background-color: white;
+}
+
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Misc
+ */
+.top, .bottom {
+	padding: 15px;
+	background-color: #F5F5F5;
+	border: 1px solid #CCCCCC;
+}
+
+.top .dataTables_info {
+	float: none;
+}
+
+.clear {
+	clear: both;
+}
+
+.dataTables_empty {
+	text-align: center;
+}
+
+tfoot input {
+	margin: 0.5em 0;
+	width: 100%;
+	color: #444;
+}
+
+tfoot input.search_init {
+	color: #999;
+}
+
+td.group {
+	background-color: #d1cfd0;
+	border-bottom: 2px solid #A19B9E;
+	border-top: 2px solid #A19B9E;
+}
+
+td.details {
+	background-color: #d1cfd0;
+	border: 2px solid #A19B9E;
+}
+
+
+.example_alt_pagination div.dataTables_info {
+	width: 40%;
+}
+
+.paging_full_numbers {
+	width: 400px;
+	height: 22px;
+	line-height: 22px;
+}
+
+.paging_full_numbers span.paginate_button,
+ 	.paging_full_numbers span.paginate_active {
+	border: 1px solid #aaa;
+	-webkit-border-radius: 5px;
+	-moz-border-radius: 5px;
+	padding: 2px 5px;
+	margin: 0 3px;
+	cursor: pointer;
+	*cursor: hand;
+}
+
+.paging_full_numbers span.paginate_button {
+	background-color: #ddd;
+}
+
+.paging_full_numbers span.paginate_button:hover {
+	background-color: #ccc;
+}
+
+.paging_full_numbers span.paginate_active {
+	background-color: #99B3FF;
+}
+
+table.display tr.even.row_selected td {
+	background-color: #B0BED9;
+}
+
+table.display tr.odd.row_selected td {
+	background-color: #9FAFD1;
+}
+
+
+/*
+ * Sorting classes for columns
+ */
+/* For the standard odd/even */
+tr.odd td.sorting_1 {
+	background-color: #D3D6FF;
+}
+
+tr.odd td.sorting_2 {
+	background-color: #DADCFF;
+}
+
+tr.odd td.sorting_3 {
+	background-color: #E0E2FF;
+}
+
+tr.even td.sorting_1 {
+	background-color: #EAEBFF;
+}
+
+tr.even td.sorting_2 {
+	background-color: #F2F3FF;
+}
+
+tr.even td.sorting_3 {
+	background-color: #F9F9FF;
+}
+
+
+/* For the Conditional-CSS grading rows */
+/*
+ 	Colour calculations (based off the main row colours)
+  Level 1:
+		dd > c4
+		ee > d5
+	Level 2:
+	  dd > d1
+	  ee > e2
+ */
+tr.odd.gradeA td.sorting_1 {
+	background-color: #c4ffc4;
+}
+
+tr.odd.gradeA td.sorting_2 {
+	background-color: #d1ffd1;
+}
+
+tr.odd.gradeA td.sorting_3 {
+	background-color: #d1ffd1;
+}
+
+tr.even.gradeA td.sorting_1 {
+	background-color: #d5ffd5;
+}
+
+tr.even.gradeA td.sorting_2 {
+	background-color: #e2ffe2;
+}
+
+tr.even.gradeA td.sorting_3 {
+	background-color: #e2ffe2;
+}
+
+tr.odd.gradeC td.sorting_1 {
+	background-color: #c4c4ff;
+}
+
+tr.odd.gradeC td.sorting_2 {
+	background-color: #d1d1ff;
+}
+
+tr.odd.gradeC td.sorting_3 {
+	background-color: #d1d1ff;
+}
+
+tr.even.gradeC td.sorting_1 {
+	background-color: #d5d5ff;
+}
+
+tr.even.gradeC td.sorting_2 {
+	background-color: #e2e2ff;
+}
+
+tr.even.gradeC td.sorting_3 {
+	background-color: #e2e2ff;
+}
+
+tr.odd.gradeX td.sorting_1 {
+	background-color: #ffc4c4;
+}
+
+tr.odd.gradeX td.sorting_2 {
+	background-color: #ffd1d1;
+}
+
+tr.odd.gradeX td.sorting_3 {
+	background-color: #ffd1d1;
+}
+
+tr.even.gradeX td.sorting_1 {
+	background-color: #ffd5d5;
+}
+
+tr.even.gradeX td.sorting_2 {
+	background-color: #ffe2e2;
+}
+
+tr.even.gradeX td.sorting_3 {
+	background-color: #ffe2e2;
+}
+
+tr.odd.gradeU td.sorting_1 {
+	background-color: #c4c4c4;
+}
+
+tr.odd.gradeU td.sorting_2 {
+	background-color: #d1d1d1;
+}
+
+tr.odd.gradeU td.sorting_3 {
+	background-color: #d1d1d1;
+}
+
+tr.even.gradeU td.sorting_1 {
+	background-color: #d5d5d5;
+}
+
+tr.even.gradeU td.sorting_2 {
+	background-color: #e2e2e2;
+}
+
+tr.even.gradeU td.sorting_3 {
+	background-color: #e2e2e2;
+}
+
+
+/*
+ * Row highlighting example
+ */
+.ex_highlight #example tbody tr.even:hover, #example tbody tr.even td.highlighted {
+	background-color: #ECFFB3;
+}
+
+.ex_highlight #example tbody tr.odd:hover, #example tbody tr.odd td.highlighted {
+	background-color: #E6FF99;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/css/jOWL.css	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,47 @@
+/**
+* jOWL CSS Generic Style Settings.
+* Creator - David Decraene
+* Version 1.0
+* http://jowl.ontologyonline.org
+*/
+
+/** bordered box */
+.ui-widget-content {border:1px solid steelblue;text-align:center;}
+/** shaded titlebar */
+.ui-dialog-titlebar {margin:0px;padding:3px;text-align:center;background-color:#F8F8FF}
+
+.jowl-content {padding:5px;min-height:200px;}
+
+/** jowl-navbar */
+.jowl-navbar span {margin-right:5px;cursor:pointer;}
+.jowl-navbar div {margin-bottom:5px;}
+.jowl-navbar .external {font-weight:bold;color:red;font-style:italic;}
+
+/** jowl_autocomplete */
+.jowl_autocomplete_results{
+	display:none;min-height:20px;text-align:left;width:250px;z-index:20;position:absolute;
+	border: 1px solid WindowFrame; background-color: Window;
+	list-style-position: outside; list-style: none; margin: 0px;padding-left:0px;
+	}
+.jowl_autocomplete_results li {margin: 0px;cursor: pointer;display: block;font: menu;font-size: 12px;padding: 2px 5px;margin-bottom:2px;}
+.jowl_autocomplete_results .type{font-style:italic;float:right}
+.jowl_autocomplete_results .termlabel{font-size:8px}
+.jowl_autocomplete_results .name{font-weight:bold}
+
+/*Treeview*/
+.jowl-tree {text-align: left}
+.jowl-treeview{list-style-type: none;}
+.jowl-treeview .root > .name{ font-weight:bold;}
+.jowl-treeview .focus>.name{font-weight:bold;color:steelblue}
+.jowl-treeview ul.tv, .jowl-treeview .tv ul { list-style-image:none; list-style-position:outside; list-style-type:none; margin:0pt; padding:0pt; }
+.jowl-treeview .tv li { margin:0pt; padding:1px 0pt 3px 20px; position:relative; z-index:10; }
+.jowl-treeview .tvie .name, .jowl-treeview .tvic .name, .jowl-treeview .tvilc .name, .jowl-treeview .tvile .name, .jowl-treeview .root .name { cursor:pointer; position:relative;}
+.jowl-treeview .tv li, .jowl-treeview .tv .tvi { background:transparent url(../img/treeView/dotted/tvi.gif) no-repeat scroll top left;}
+.jowl-treeview .tv .tvic { background-image:url(../img/treeView/dotted/tvic.gif) ;}
+.jowl-treeview .tv .tvie { background-image:url(../img/treeView/dotted/tvie.gif); }
+.jowl-treeview .tv .tvil  { background-image:url(../img/treeView/dotted/tvil.gif); }
+.jowl-treeview .tv .tvilc  { background-image:url(../img/treeView/dotted/tvilc.gif); }
+.jowl-treeview .tv .tvile   { background-image:url(../img/treeView/dotted/tvile.gif); }
+.jowl-treeview .tvload { background-image:url(../img/treeView/dotted/tviload.gif);}
+
+
Binary file examples/browser/web/css/jq/custom-theme/images/ui-bg_flat_0_aaaaaa_40x100.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_55_fbf9ee_1x400.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_65_ffffff_1x400.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_75_dadada_1x400.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_75_e6e6e6_1x400.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-bg_glass_75_ffffff_1x400.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-bg_inset-soft_95_fef1ec_1x100.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-icons_222222_256x240.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-icons_2e83ff_256x240.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-icons_454545_256x240.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-icons_888888_256x240.png has changed
Binary file examples/browser/web/css/jq/custom-theme/images/ui-icons_cd0a0a_256x240.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/css/jq/custom-theme/jquery-ui-1.7.custom.css	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,408 @@
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+*/
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ctl=themeroller
+*/
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_75_ffffff_1x400.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; outline: none; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; outline: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; outline: none; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; outline: none; }
+.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; outline: none; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; outline: none; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_inset-soft_95_fef1ec_1x100.png) 50% bottom repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #cd0a0a; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-right {  -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; }/* Resizable
+----------------------------------*/
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Accordion
+----------------------------------*/
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; }
+.ui-accordion .ui-accordion-content-active { display: block; }/* Dialog
+----------------------------------*/
+.ui-dialog { position: relative; padding: .2em; width: 300px; }
+.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative;  }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; } 
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/* Slider
+----------------------------------*/
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
+----------------------------------*/
+.ui-tabs { padding: .2em; zoom: 1; }
+.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
+.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/* Datepicker
+----------------------------------*/
+.ui-datepicker { width: 17em; padding: .2em .2em 0; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month, 
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+    display: none; /*sorry for IE5*/
+    display/**/: block; /*sorry for IE5*/
+    position: absolute; /*must have*/
+    z-index: -1; /*must have*/
+    filter: mask(); /*must have*/
+    top: -4px; /*must have*/
+    left: -4px; /*must have*/
+    width: 200px; /*must have*/
+    height: 200px; /*must have*/
+}/* Progressbar
+----------------------------------*/
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
+
+#search_options ul { display:table}
+#search_options li { display:table-cell; padding: 5px}
Binary file examples/browser/web/img/back_disabled.jpg has changed
Binary file examples/browser/web/img/back_enabled.jpg has changed
Binary file examples/browser/web/img/forward_disabled.jpg has changed
Binary file examples/browser/web/img/forward_enabled.jpg has changed
Binary file examples/browser/web/img/sort_asc.jpg has changed
Binary file examples/browser/web/img/sort_both.jpg has changed
Binary file examples/browser/web/img/sort_desc.jpg has changed
Binary file examples/browser/web/img/spinner.gif has changed
Binary file examples/browser/web/img/tooltip/Thumbs.db has changed
Binary file examples/browser/web/img/tooltip/arrow_left.gif has changed
Binary file examples/browser/web/img/tooltip/arrow_right.gif has changed
Binary file examples/browser/web/img/tooltip/loader.gif has changed
Binary file examples/browser/web/img/treeView/Thumbs.db has changed
Binary file examples/browser/web/img/treeView/dotted/Thumbs.db has changed
Binary file examples/browser/web/img/treeView/dotted/tvi.gif has changed
Binary file examples/browser/web/img/treeView/dotted/tvic.gif has changed
Binary file examples/browser/web/img/treeView/dotted/tvie.gif has changed
Binary file examples/browser/web/img/treeView/dotted/tvil.gif has changed
Binary file examples/browser/web/img/treeView/dotted/tvilc.gif has changed
Binary file examples/browser/web/img/treeView/dotted/tvile.gif has changed
Binary file examples/browser/web/img/treeView/dotted/tviload.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/index.html	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,56 @@
+<html>
+<head>
+
+<link rel="stylesheet" href="http://ontologyonline.org/css/blueprint/screen.css" type="text/css" media="screen, projection"/>
+<link rel="stylesheet" href="css/jOWL.css" type="text/css"/>
+<link rel="stylesheet" href="css/data_table.css" type="text/css"/>
+<link rel="stylesheet" href="css/jq/custom-theme/jquery-ui-1.7.custom.css" type="text/css"/>
+
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>      
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.0/jquery-ui.min.js"></script>
+<script type="text/javascript" src="js/jquery.dataTables.js"></script>
+<script type="text/javascript" src="js/sparql.js"></script>
+<script type="text/javascript" src="js/jOWL.js"></script>
+<script type="text/javascript" src="js/jOWL_UI.js"></script>
+<script type="text/javascript" src="js/browser.js"></script>
+</head>
+<body>
+
+<div class="ui-widget">
+	<div class="ui-widget-header">Search Options</div>
+	<div class="ui-widget-content">
+		<div align="center">
+		<ul id="search_options">
+			<li>Artist: <input type="text" id="artistsearch" /></li>
+			<li>Track: <input type="text" id="tracksearch" /></li>
+			<li><input type="button" id="search" value="Search" /></li>
+		</ul>
+		</div>
+		<div align="center"><span id="spinner">Searching...</span></div>
+	</div>
+</div>
+
+<div class="ui-widget">
+<div class="ui-widget-header">Search Results</div>
+<div class="ui-widget-content">
+<div align="center">
+<table class="display" id="results" width="50%">
+    <thead>
+        <tr>
+            <th>Artist Name</th>
+            <th>Track Name</th>
+            <th>Track #</th>
+            <th>Album Name</th>
+        </tr>
+    </thead>
+    <tbody>
+    </tbody>
+</table>
+</div>
+</div>
+</div>
+<div style="clear:both" id="query">Current Query</div>
+
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/js/browser.js	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,130 @@
+var sparqler = new SPARQL.Service("http://harrison/sparql/");
+
+sparqler.setPrefix("mo", "http://purl.org/ontology/mo/");
+sparqler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/");
+sparqler.setPrefix("dc", "http://purl.org/dc/elements/1.1/");
+
+sparqler.setRequestHeader("Accept", "application/json");
+
+var resultsTable;
+
+$(document).ready(function() {
+	$("#search").click(search)
+	$("#spinner").hide();
+	resultsTable = $('#results').dataTable({"bFilter":false,"bLengthChange":false,"bPaginate":true, 
+		"fnRowCallback": function(nRow, aData, iDisplayIndex)
+		{
+			$(nRow).attr("typeof", "mo:Track");
+			return nRow;
+		},
+		"fnDrawCallback": function ()
+		{
+			$(".artist_name").click(function(event) { searchArtist($(this).attr("href")); return false; });
+			$(".album_name").click(function(event) { searchAlbum($(this).attr("href")); return false; });
+		}
+	});
+
+	$("#results tbody").click(function(event) {
+		$(resultsTable.fnSettings().aoData).each(function (){
+			$(this.nTr).removeClass('row_selected');
+		});
+		$(event.target.parentNode).addClass('row_selected');
+	});
+
+});
+
+function search(event) {
+
+	var trackSearchString = $("#tracksearch").val();
+	var artistSearchString = $("#artistsearch").val();
+
+	if(trackSearchString.length == 0 && artistSearchString.length == 0)
+	{
+		resultsTable.fnClearTable();
+		return;
+	}
+
+	var queryString = "SELECT ?maker ?album ?album_title ?tracknum ?artist_name ?track_title WHERE {";
+
+	queryString += " ?track a mo:Track; mo:track_number ?tracknum; foaf:maker ?maker. ?album mo:track ?track; dc:title ?album_title. ?maker foaf:name ?artist_name";
+	
+	if(artistSearchString.length > 0)
+	{
+		queryString += ' FILTER regex(?artist_name, "'+artistSearchString+'", "i")';
+	}
+	else
+	{
+		queryString += ".";
+	}
+	
+	queryString += " ?record mo:track ?track; mo:publication_of ?signal. ?signal dc:title ?track_title";
+
+	if(trackSearchString.length > 0)
+	{
+		queryString += ' FILTER regex(?track_title, "'+trackSearchString+'", "i")';
+	}
+	else
+	{
+		queryString += ".";
+	}
+
+	queryString += " }";
+
+	performSearch(queryString);
+}
+
+function searchArtist(id) {
+	var queryString = "SELECT ?maker ?album ?album_title ?tracknum ?artist_name ?track_title WHERE {";
+	queryString += " ?track a mo:Track; mo:track_number ?tracknum; foaf:maker ?maker. ?album mo:track ?track; dc:title ?album_title. ?maker foaf:name ?artist_name";
+	queryString += " ?record mo:track ?track; mo:publication_of ?signal. ?signal dc:title ?track_title.";
+	queryString += " FILTER(sameTerm(?maker, <"+id+">))";
+	queryString += " }";
+	performSearch(queryString);
+}
+
+function searchAlbum(id) {
+	var queryString = "SELECT ?maker ?album ?album_title ?tracknum ?artist_name ?track_title WHERE {";
+	queryString += " ?track a mo:Track; mo:track_number ?tracknum; foaf:maker ?maker. ?album mo:track ?track; dc:title ?album_title. ?maker foaf:name ?artist_name";
+	queryString += " ?record mo:track ?track; mo:publication_of ?signal. ?signal dc:title ?track_title.";
+	queryString += " FILTER(sameTerm(?album, <"+id+">))";
+	queryString += " }";
+	performSearch(queryString);
+}
+
+
+function performSearch(queryString) {
+	$("#spinner").show();
+	$("#query").text(queryString);
+	var query = sparqler.createQuery();
+	query.query(queryString, {failure: function(xhr) { alert("Bad response! "+xhr.responseText) }, success: displayResults});
+}
+
+function displayResults(json) {
+	resultsTable.fnClearTable();
+	if(json) {
+		
+		var bindings = json.results.bindings;
+		for(var i=0; i<bindings.length; i++)
+		{
+			var artistEl = $('<div />');
+			var artistLink = $('<a/>');
+			artistEl.append(artistLink);
+			artistLink.attr("href", bindings[i].maker.value);
+			artistLink.attr("rel", "foaf:maker");
+			artistLink.addClass("artist_name");
+			artistLink.append(bindings[i].artist_name.value);
+			
+			var albumEl = $('<div />');
+			var albumLink = $('<a/>');
+			albumEl.append(albumLink);
+			albumLink.attr("href", bindings[i].album.value);
+			albumLink.attr("rel", "dc:title");
+			albumLink.addClass("album_name");
+			albumLink.append(bindings[i].album_title.value);
+			
+			resultsTable.fnAddData([artistEl.html(), bindings[i].track_title.value, bindings[i].tracknum.value, albumEl.html()]);
+		}
+
+	}
+	$("#spinner").hide();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/js/jOWL.js	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,2242 @@
+/**
+* jOWL - a jQuery plugin for traversing and visualizing OWL-DL documents.
+* Creator - David Decraene
+* Version 1.0
+* Website: 
+*	http://Ontologyonline.org
+* Licensed under the MIT license 
+*	http://www.opensource.org/licenses/mit-license.php
+* Verified with JSLint 
+*	http://www.jslint.com/
+*/
+
+jOWL = window.jOWL = function( resource, options ){ return jOWL.getResource( resource, options );  };
+jOWL.version = "1.0";
+
+/** for debugging compatibility */
+	try { console.log('...'); } catch(e) { console = window.console = { log: function() {} } }
+	if ($.browser.opera && opera.postError) { console = window.console = { log : function(){opera.postError(arguments); } }; }
+
+
+
+(function($){
+
+/** 
+* if no param: @return string of main namespaces
+* if 1 param: assume a documentElement, parse namespaces
+* if prefix & URI: Bind prefix to namespace URI 
+*/
+jOWL.NS = function(prefix, URI){
+	if(!arguments.length)
+	{ return "xmlns:"+jOWL.NS.owl.prefix+"='"+jOWL.NS.owl()+"' xmlns:"+jOWL.NS.rdf.prefix+"='"+jOWL.NS.rdf()+"' xmlns:"+jOWL.NS.rdfs.prefix+"='"+jOWL.NS.rdfs()+"' xmlns:"+jOWL.NS.xsd.prefix+" ='"+jOWL.NS.xsd()+"'";}
+
+	if(arguments.length == 1){
+		var attr = prefix.get(0).attributes;
+		for(var i=0;i<attr.length;i++){
+			var nn = attr[i].nodeName.split(':');
+			if(nn.length == 2){
+				if(attr[i].nodeValue == jOWL.NS.owl.URI){ jOWL.NS.owl.prefix = nn[1];}
+				else if(attr[i].nodeValue == jOWL.NS.rdf.URI){ jOWL.NS.rdf.prefix = nn[1];}
+				else if(attr[i].nodeValue == jOWL.NS.rdfs.URI){ jOWL.NS.rdfs.prefix = nn[1];}
+				else if(attr[i].nodeValue == jOWL.NS.xsd.URI){ jOWL.NS.xsd.prefix = nn[1];}
+				else { jOWL.NS(nn[1], attr[i].nodeValue);}
+			}
+		}
+		jOWL.namespace =  prefix.xmlAttr('xml:base') || prefix.xmlAttr('xmlns');
+		return;
+	}
+	jOWL.NS[prefix] = function(element){
+		if(element){
+			return (arguments.callee.prefix == 'base') ? element : arguments.callee.prefix + ":" + element;
+			}
+		return arguments.callee.URI;
+		};
+	jOWL.NS[prefix].prefix = prefix;
+	jOWL.NS[prefix].URI = URI;	
+};
+
+var __ = jOWL.NS;
+
+/** set Main namespaces */
+__("owl", "http://www.w3.org/2002/07/owl#");
+__("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+__("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
+__("xsd", "http://www.w3.org/2001/XMLSchema#");
+
+/** jQuery function additions for easy parsing of identities */
+$.fn.extend({
+	/** Used for Opera compatibility when parsing xml attributes, nodeName must be checked, in contrast to native jquery call attr() */
+	xmlAttr : function(nodeName){
+		var t = this[0].attributes; if(!t){ return;}
+		for(var i =0;i<t.length;i++){
+			if(t[i].nodeName == nodeName){ return t[i].nodeValue;}
+		}
+	},
+	RDF_ID : function(match){
+		var res = this.xmlAttr(__.rdf('ID'));
+		if(!res){ return false;}
+		res = jOWL.resolveURI(res);
+		if(match){
+			return res.toLowerCase() == (jOWL.resolveURI(match.toString())).toLowerCase();}
+		return res;
+		},
+	RDF_Resource : function(match){
+		function getClassName(dom){
+			var cl = jOWL.Xpath(__.owl("Class"), dom);
+			if(cl.length == 1){ return new jOWL.Ontology.Class(cl).URI;}
+			return false;
+		}
+		if(!this.length){ return false;}
+		var rsrc = this.xmlAttr(__.rdf('resource'));
+		if(!rsrc){
+			var dom = this.get(0);
+			switch(dom.nodeName){
+				case __.rdfs("subClassOf"): rsrc = getClassName(dom); break;
+				case __.owl("disjointWith"): rsrc = getClassName(dom); break;
+				case __.owl("allValuesFrom"): rsrc = getClassName(dom); break;
+				case __.owl("someValuesFrom"): rsrc = getClassName(dom); break;
+				case __.owl("onProperty"):
+					var t = jOWL.Xpath(__.owl("ObjectProperty"), dom);
+					if(t.length === 0){ t = jOWL.Xpath(__.owl("DatatypeProperty"), dom);}
+					if(t.length === 0){ t = jOWL.Xpath(__.owl("FunctionalProperty"), dom);}
+					rsrc = t.xmlAttr(__.rdf('about')); break;
+				default: return false;
+			}
+		}
+		if(!rsrc){ return false;}
+		rsrc = jOWL.resolveURI(rsrc);
+		if(match){ return rsrc.toLowerCase() == (jOWL.resolveURI(match.toString())).toLowerCase();}
+		return rsrc;
+		},
+	RDF_About : function(match){
+		var res = this.xmlAttr(__.rdf('about'));
+		if(!res){ return false;}
+		res = jOWL.resolveURI(res);
+		if(match){
+			return res.toLowerCase() == (jOWL.resolveURI(match.toString())).toLowerCase();}
+		return res;
+		}
+});
+
+/** Check XPath implementation */
+if( document.implementation.hasFeature("XPath", "3.0") ){
+	XMLDocument.prototype.selectNodes = function(cXPathString, xNode){
+		if( !xNode ){ xNode = this;}
+		var oNSResolver = this.createNSResolver(this.documentElement);
+		var aItems = this.evaluate(cXPathString, xNode, oNSResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var aResult = []; for( var i = 0; i < aItems.snapshotLength; i++){ aResult[i] = aItems.snapshotItem(i);}  
+		return aResult; 
+		};
+	Element.prototype.selectNodes = function(cXPathString){  
+		if(this.ownerDocument.selectNodes)  {  return this.ownerDocument.selectNodes(cXPathString, this);}
+		else{throw "For XML Elements Only";} 
+		}; 
+	XMLDocument.prototype.selectSingleNode = function(cXPathString, xNode){ if( !xNode ){ xNode = this;}
+		var xItems = this.selectNodes(cXPathString, xNode); if( xItems.length > 0 ){  return xItems[0];} else {  return null;}
+		};
+	Element.prototype.selectSingleNode = function(cXPathString){
+		if(this.ownerDocument.selectSingleNode)  {  return this.ownerDocument.selectSingleNode(cXPathString, this);}
+		else{throw "For XML Elements Only";} 
+		};  
+}
+
+/** @return A jQuery array of xml elements */
+jOWL.Xpath = function(selector, elem){
+	var node = null;
+	if(elem){ if(elem.each){ node = elem.get(0);} else { node = elem;} }
+	var arr = node ? node.selectNodes(selector) : jOWL.document.selectNodes(selector);
+	if($.browser.msie){ return $($.makeArray(arr));} return $(arr); //this is needed for IE, it returns a length of 1 on empty node array
+};
+
+/** @return a String array of class references */
+jOWL.Xpath.classes = function(jnode){
+	var cl = [];
+	jOWL.Xpath(__.rdfs("subClassOf"), jnode)
+		.each(function(){
+			var res = $(this).RDF_Resource();
+			if(res){ cl.push(res);}
+		});
+
+	jOWL.Xpath(__.owl("intersectionOf")+"/"+__.owl("Class"), jnode)
+		.each(function(){
+			var p = $(this).RDF_About(); if(p){ cl.push(p);}
+		});
+	return cl;
+};
+
+/** Functions stored in jOWL.priv are intended for local access only, to avoid a closure function */
+jOWL.priv = {
+	/** Arrray functions */
+	Array : {
+		isArray : function(array){
+			return Object.prototype.toString.call(array) === '[object Array]';
+		},
+		pushUnique : function(array, item){
+			if(jOWL.priv.Array.getIndex(array, item) === -1){	array.push(item); return true;}
+			return false;
+		},
+		getIndex : function(array, item){
+			for (var i=0; i<array.length; i++){ if(item == array[i]){ return i;} }
+			return -1;
+		},
+		/** Sorted array as input, returns the same array without duplicates. */
+		unique : function(array){
+			var result = []; var lastValue="";
+			for (var i=0; i<array.length; i++)
+			{
+				var curValue=array[i];
+				if(curValue != lastValue){ result[result.length] = curValue;}
+				lastValue=curValue;
+			}
+			return result;
+		}
+	}
+};
+
+/** Make values work with jOWL.Ontology.Array */
+jOWL.Literal = function(value){
+	this.name = value;
+};
+
+/** Access to the owl:Ontology element, also the main coding namespace for ontology objects */
+jOWL.Ontology = function(){
+	if(!(this instanceof arguments.callee)){ return new jOWL.Ontology();}
+	this.parse(jOWL.Xpath("/"+__.rdf("RDF")+"/"+__.owl("Ontology")));
+	return this;
+};
+
+/** 'superclass' for referencable ontology objects */
+jOWL.Ontology.Thing = function(jnode){
+	this.parse(jnode);
+};
+
+jOWL.Ontology.Thing.prototype = {
+	jOWL : jOWL.version,
+	equals : function(id){
+		var URI = (typeof id == "string") ? jOWL.resolveURI(id) : id.URI;
+		return URI === this.URI;
+	},
+	/** Initialization */
+	parse : function(jnode){
+		if(!jnode.length){ return;}
+		var identifier;
+		if(typeof jnode == 'string'){
+			identifier = jnode;
+			jnode = $();
+			}
+		else {
+			identifier = jnode.RDF_ID() || jnode.RDF_About();
+			if(!identifier){identifier = "anonymousOntologyObject";
+			this.isAnonymous = true;
+			}
+		}
+		identifier = jOWL.resolveURI(identifier);
+		this.isExternal = jOWL.isExternal(identifier);
+		if(this.isExternal){this.baseURI = this.isExternal[0]; this.name = this.isExternal[1]; this.URI = this.baseURI+this.name;}
+		else { this.baseURI = jOWL.namespace; this.name = identifier; this.URI = this.name;}
+		this.jnode = jnode;
+		this.type = jnode.get(0).nodeName;
+	},
+	/** @return A jQuery array of elements matching the annotation (qualified name or annotation Property) */
+	annotations : function(annotation){
+		return jOWL.Xpath(annotation, this.jnode);
+	},
+	/** @return rdfs:comment annotations */
+	description : function(){
+		return $.map(this.annotations(__.rdfs('comment')), function(n){ return $(n).text();});
+	},
+	/**
+	@return Array of Arrays, where secondary array is of form: [0] = term (rdfs:label) , [1] = identifier, [2] = language; [3] = type of object
+	example:
+	[
+		["bleu", "blue", "fr", "owl:Class"]
+	]
+	*/
+	terms : function(){
+		var terms = [], self = this;
+		if(jOWL.options.dictionary.addID && this.name != "anonymousOntologyObject"){ terms.push([this.name.beautify(), this.URI, jOWL.options.defaultlocale, this.type]);}
+		this.annotations(__.rdfs('label')).each(function(){
+			var lbl = $(this);
+			var locale = lbl.xmlAttr("xml:lang") || jOWL.options.defaultlocale;
+			var txt = lbl.text();
+			var match = false;
+			for(var i =0;i<terms.length;i++){
+			if(terms[i][0].toUpperCase() == txt.toUpperCase() && terms[i][2] == locale){ match = true;}
+			}
+			if(!match){ terms.push([lbl.text(), self.URI, locale, self.type]);}
+		});
+		return terms;
+	},
+	/** @return A representation name */
+	label : function(){
+		var label = false;
+		this.annotations(__.rdfs('label')).each(function(){
+			var $label = $(this);
+			if(jOWL.options.locale){
+				var lang = $label.xmlAttr('xml:lang') || jOWL.options.defaultlocale;
+				if(lang == jOWL.options.locale){ label = $label.text(); return false;}
+			} else { label = $label.text(); return false;}
+		});
+		if(label){ return label;}
+		if(this.name == "anonymousOntologyObject"){ return jOWL.Manchester(this) || "anonymous Object";}
+		if(jOWL.options.niceClassLabels && (this.isClass || this.isThing)){
+			return this.name.beautify();
+		}
+		return this.name;
+	},
+	/** Binds the Ontology element to the jQuery element for visual representation 
+	* @return jQuery Element
+	*/
+	bind : function(jqelem){
+		return jqelem.text(this.label()).attr('typeof', this.type).attr('title', this.URI);
+	}
+};
+
+jOWL.Ontology.prototype = jOWL.Ontology.Thing.prototype;
+
+/** used for jOWL.Ontology.Individual.sourceof */
+jOWL.priv.testObjectTarget = function(target, matchtarget){
+	if(target.isArray){
+		for(var i=0;i<target.length;i++){
+			if(jOWL.priv.testObjectTarget(target.get(i), matchtarget)){ return true;}
+		}
+		return false;
+	}
+	//if the target is a class, fetch individuals instead.
+	else if(target.isClass){
+		 var a = target.individuals();
+		 for(var i=0;i<a.length;i++){
+			if(a.get(i).URI == matchtarget){ return true;}
+		}
+	}
+	else if(target.URI == matchtarget){ return true;}
+	return false;
+};
+
+/** access to Individuals of the ontology*/
+jOWL.Ontology.Individual = function(jnode, owlclass){	
+	this.parse(jnode);
+	if(this.type == __.owl("Thing")){
+	var t = jOWL.Xpath(__.rdf('type'), this.jnode);
+		if(!t.length){ throw "unable to find a Class for the Individual "+this.name;}
+		this.Class = $(t[0]).RDF_Resource();
+	}
+	else {
+		this.Class = jOWL.resolveURI(jnode.get(0));
+	}
+	this.type = __.owl("Thing");
+	if(owlclass){ this.owlClass(owlclass);}
+};
+
+jOWL.Ontology.Individual.prototype = $.extend({}, jOWL.Ontology.Thing.prototype, {
+	isThing : true,
+	/** @return The owl:Class */
+	owlClass : function(owlclass){
+		if(owlclass){ jOWL.data(this.name, "class", owlclass);}
+		else {
+			var cl = jOWL.data(this.name, "class");
+			if(!cl){ cl = jOWL(this.Class); if(cl){ this.owlClass(cl);} }
+			return cl;
+		}
+	},
+	/** Access to restrictions */
+	sourceof : function(property, target, options){
+		options = $.extend({
+			inherited : true, // add restrictions specified on parents as well
+			transitive : true,
+			ignoreGenerics : false, //if a parent has an identical property, with another target 'Thing', skip that restriction
+			ignoreClasses : true,
+			valuesOnly : true
+		}, options);
+
+		var results = new jOWL.Ontology.Array();
+
+		this.jnode.children().filter(function(){return (this.prefix != __.rdfs.prefix && this.prefix != __.rdf.prefix && this.prefix != __.owl.prefix);})
+			.each(function(){
+			var restriction = new jOWL.Ontology.Restriction($(this));
+			var propertyMatch = property ? false : true;
+			var targetMatch = target ? false : true;
+
+			if(!propertyMatch){
+				if( property.isArray){ propertyMatch = property.contains(restriction.property);}
+				else { propertyMatch = (property.URI == restriction.property.URI);}
+				if(!propertyMatch){ return;}
+			}
+
+			if(!target){
+				if(options.transitive && restriction.property.isTransitive && !options.ignoreGenerics){
+					var rTarget = restriction.getTarget();
+					var transitives = rTarget.sourceof(restriction.property, null, options);
+					results.concat(transitives);
+				}
+			}
+			else {
+				if(restriction.property.isObjectProperty){
+					targetMatch = jOWL.priv.testObjectTarget(target, restriction.target);
+					if(!targetMatch && options.transitive && restriction.property.isTransitive){
+						var rTransitives = restriction.getTarget().sourceof(restriction.property, target, options);
+						if(rTransitives.length > 0){ targetMatch = true;}
+					}
+				}
+				else if(restriction.property.isDatatypeProperty){
+					targetMatch = restriction.property.assert(restriction.target, target);
+				}
+				else { targetMatch = (target == restriction.target);}
+			}
+			if(propertyMatch && targetMatch){ results.pushUnique(restriction);}
+
+		});
+		if(options.inherited){ 
+			var clRestrictions = this.owlClass().sourceof(property, target, options)
+				.each(function(){
+				//target can be a class, null, a duplicate individual...
+				var clRestr = this;
+				if(options.valuesOnly && clRestr.target === null){return;}
+				var clTarget = this.getTarget();
+				if(clTarget.isClass && options.ignoreClasses){ return;}
+
+				var containsProperty = false;
+				for(var i = 0;i<results.length;i++){
+					var restr = results.get(i);
+					if(restr.property.URI == clRestr.property.URI){
+						containsProperty = true;
+						if(!options.ignoreGenerics){
+							if(clRestr.target != restr.target){ results.pushUnique(clRestr);}
+						}
+					}
+				}
+				if(!containsProperty){ results.pushUnique(clRestr);}
+			});
+		}
+		return results;
+
+	},
+	localRestrictions : function(property, target){
+		return this.sourceof(property, target, {inherited : false, transitive : false });
+	},
+	/** Include generic will add transitivity reasoning */
+	valueRestrictions : function(includeGeneric){
+		return this.sourceof(null, null, {ignoreGenerics : !includeGeneric, valuesOnly : true });
+	}
+});
+
+/** jNode is of type owl:Restriction */
+jOWL.Ontology.Restriction = function(jnode){
+
+	var jprop, prop, op, restrtype;
+
+	this.cachedTarget = null;
+
+	if(jnode.get(0).nodeName != __.owl("Restriction")){
+		this.property = jOWL(jOWL.resolveURI(jnode.get(0)), {type: "property"});
+		this.target = jnode.RDF_Resource() || jnode.text();
+		restrtype = "Individual";
+	}
+	else
+	{
+		jprop = jOWL.Xpath(__.owl("onProperty"), jnode);
+		prop = jprop.RDF_Resource(); if(!prop){ throw "no property found for the given owl:restriction";}
+		op = jprop.siblings(); 
+		restrtype = op.get(0).nodeName;
+		this.property = jOWL(prop, {type: "property"});
+		this.target = null; //string only
+	}
+
+	this.restriction = { minCard: false, maxCard : false, some: [], all : [], value : false };
+	this.type = jnode.get(0).nodeName;
+	this.isAnonymous = true;
+	this.isValueRestriction = (restrtype == __.owl('someValuesFrom') || restrtype == __.owl('allValuesFrom') || restrtype == __.owl('hasValue'));
+	this.isCardinalityRestriction = (restrtype == __.owl('cardinality') || restrtype == __.owl('maxCardinality') || restrtype == __.owl('minCardinality'));
+
+	if(!this.property || !restrtype){ throw "badly formed owl:restriction";}
+	switch(restrtype){
+		case __.owl('cardinality'): this.restriction.minCard = this.restriction.maxCard = parseInt(op.text(), 10); break;
+		case __.owl('maxCardinality'): this.restriction.maxCard = parseInt(op.text(), 10); break;
+		case __.owl('minCardinality'): this.restriction.minCard = parseInt(op.text(), 10); break;
+		case __.owl('hasValue'): var res = op.RDF_Resource(); if(res){ this.target = res;} break;
+	}
+	if(this.property.isObjectProperty){
+		if(this.isCardinalityRestriction && this.property.range){ this.target = this.property.range;}
+		else if(this.isValueRestriction){
+			var t = op.RDF_Resource(); 
+			if(t == "anonymousOntologyObject"){//nested groupings, anonymous classes
+				this.cachedTarget = new jOWL.Ontology.Class(jOWL.Xpath(__.owl("Class"), op));
+			}
+			this.target = t;
+		}
+	}
+
+	var suffix = this.target || this.restrtype;
+	this.name = this.property.name+'#'+suffix;
+	return this;
+};
+
+jOWL.Ontology.Restriction.prototype = {
+	jOWL : jOWL.version,
+	isRestriction : true,
+	bind : function(){return null;},
+	merge : function(crit){
+		if(this.isCardinalityRestriction && crit.isValueRestriction ){ this.target = crit.target; return true;}
+		else if(this.isValueRestriction && crit.isCardinalityRestriction){
+			switch(crit.restrtype){
+			case __.owl('cardinality'): this.restriction.minCard = this.restriction.maxCard = crit.restriction.minCard; return true;
+			case __.owl('minCardinality'): this.restriction.minCard = crit.restriction.minCard; return true;
+			case __.owl('maxCardinality'): this.restriction.maxCard = crit.restriction.maxCard; return true;
+			}
+		}
+		return false;
+	},
+	getTarget : function(){
+		if(!this.target){ return jOWL('Thing');}
+		if(this.cachedTarget){ return this.cachedTarget;}
+		this.cachedTarget = (this.property.isObjectProperty) ? jOWL(this.target) : new jOWL.Literal(this.target);
+		return this.cachedTarget;	
+	},
+	equals : function(restr){
+		if(!restr.isRestriction){ return false;}
+		if(this.property.URI == restr.property.URI){
+			if(this.target == 'anonymousOntologyObject'){return false;}//oneof lists etc unsupported right now
+			if(this.target && this.target === restr.target){ return true;}
+		}
+		return false;
+	}
+};
+
+/** Datatype Logic, local functions */
+jOWL.priv.Dt = function(options){
+	this.settings = $.extend({base: null, pattern : null, assert: function(b){return true;}, match: function(a, b){return true;}}, options);
+	this.base = jOWL.Ontology.Datatype[this.settings.base];
+};
+
+jOWL.priv.Dt.prototype = {
+	sanitize : function(b){
+		if(this.settings.sanitize){ return this.settings.sanitize(b);}
+		if(this.base && this.base.sanitize){ return this.base.sanitize(b);}
+	},
+	assert : function(b){
+		var v = this.sanitize(b); if(v !== undefined){ b = v;}
+		if(this.base && !this.base.assert(b)){ return false;}
+		if(this.settings.pattern && !this.settings.pattern.test(b)){ return false;}
+		return this.settings.assert(b);
+	},
+	match : function(a, b){
+		var v = this.sanitize(b); if(v !== undefined){ b = v;}
+		if(!this.assert(b)){ return false;}
+		if(this.base && !this.base.match(a, b)){ return false;}
+		return this.settings.match(a, b);
+	}
+};
+
+jOWL.Ontology.Datatype = function(URI, options){
+	jOWL.Ontology.Datatype[URI] = new jOWL.priv.Dt(options);
+};
+
+/** Datatype Definitions */
+jOWL.Ontology.Datatype(__.xsd()+"integer", {sanitize : function(x){return  parseInt(x, 10);}, assert : function(x){ return Math.round(x) == x;}, match : function(a, b){
+	var check = parseInt(a, 10);
+	if(!isNaN(check)){ return check == b;}
+	var arr = a.split('&&');
+	for(var i=0;i<arr.length;i++){ arr[i] = b+arr[i];}
+	try {
+		return eval(arr.join(' && '));
+	} catch(e){ return false;}
+} });
+jOWL.Ontology.Datatype(__.xsd()+"positiveInteger", {base: __.xsd()+"integer", assert : function(x){ return x > 0;} });
+jOWL.Ontology.Datatype(__.xsd()+"decimal", {base: __.xsd()+"integer" });
+jOWL.Ontology.Datatype(__.xsd()+"float", {base: __.xsd()+"integer" });
+jOWL.Ontology.Datatype(__.xsd()+"double", {base: __.xsd()+"integer" });
+jOWL.Ontology.Datatype(__.xsd()+"negativeInteger", {base: __.xsd()+"integer", assert : function(x){ return x < 0;} });
+jOWL.Ontology.Datatype(__.xsd()+"nonNegativeInteger", {base: __.xsd()+"integer", assert : function(x){ return x >= 0;} });
+jOWL.Ontology.Datatype(__.xsd()+"nonPositiveInteger", {base: __.xsd()+"integer", assert : function(x){ return x <= 0;} });
+jOWL.Ontology.Datatype(__.xsd()+"string");
+
+var URIPattern = /^([a-z0-9+.\-]+):(?:\/\/(?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(?::(\d*))?(\/(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?|(\/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?)(?:\?((?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*))?$/i;    
+
+jOWL.Ontology.Datatype(__.xsd()+"anyURI", {base: __.xsd()+"string", pattern : URIPattern });
+jOWL.Ontology.Datatype(__.xsd()+"boolean", {sanitize : function(x){
+		if(typeof x == 'boolean'){ return x;}
+		if(x == 'true'){ return true;}
+		if(x == 'false'){ return false;}
+	}, assert : function(x){
+		return typeof x == 'boolean';
+	}, match: function(a, b){
+		if(a === "false"){ a = false;}
+		if(a === "true"){ a = true;}
+		return (a === b);
+}});
+
+/** 'superclass' for Properties */
+jOWL.Ontology.Property = function(jnode){
+	var r = this.parseProperty(jnode);
+	if(r){ return r;}
+};
+
+jOWL.Ontology.Property.prototype = $.extend({}, jOWL.Ontology.Thing.prototype,{
+	isProperty : true,
+	parseProperty : function(jnode){
+		if(!jnode || typeof jnode == 'string'){
+			this.domain = this.range = null;
+			this.parse(jnode);
+			return;
+		}
+		if(jOWL.options.cacheProperties && jOWL.indices.IDs){
+			var res = jnode.RDF_ID() || jnode.RDF_About();
+			var c = jOWL.index('property').get(res);
+			if(c){ return c;}
+		}
+		this.parse(jnode);
+		this.domain= $(this.jnode.get(0).selectSingleNode(__.rdfs('domain'))).RDF_Resource();
+		this.range = $(this.jnode.get(0).selectSingleNode(__.rdfs('range'))).RDF_Resource();	
+	}
+});
+
+/** access to Datatype properties */
+jOWL.Ontology.DatatypeProperty = function(jnode){
+	var r = this.parseProperty(jnode);
+	if(r){ return r;}
+	if(this.type == __.owl("AnnotationProperty")){ this.range = __.xsd()+"string";}
+};
+
+jOWL.Ontology.DatatypeProperty.prototype = $.extend({}, jOWL.Ontology.Thing.prototype, jOWL.Ontology.Property.prototype, {
+	isDatatypeProperty : true,
+	/** check datatype values against this */
+	assert : function(targetValue, value){
+		var self = this;
+		var dt = jOWL.Ontology.Datatype[this.range];
+		if(!dt){
+			console.log(this.range+" datatype reasoning not implemented");
+			return true;
+		}
+		if(value === undefined){ return dt.assert(targetValue);}
+		else {return dt.match(value, targetValue);}
+	}
+});
+
+/** access to Object properties */
+jOWL.Ontology.ObjectProperty = function(jnode){
+	var r = this.parseProperty(jnode);	
+	if(r){ return r;}
+	var self = this;
+	jOWL.Xpath(__.rdf('type'), this.jnode).each(function(){
+		if($(this).RDF_Resource() == __.owl()+"TransitiveProperty"){ self.isTransitive = true;}
+	});
+	if(this.jnode.get(0).nodeName == __.owl("TransitiveProperty")){ self.isTransitive = true;}
+};
+
+jOWL.Ontology.ObjectProperty.prototype = $.extend({}, jOWL.Ontology.Thing.prototype, jOWL.Ontology.Property.prototype, {
+	isObjectProperty : true
+});
+
+/** access to an owl:Class */
+jOWL.Ontology.Class = function(jnode){
+	this.parse(jnode);
+};
+
+/** @return jOWL Array of Restrictions */
+jOWL.Xpath.restrictions = function(jnode){
+	var result = new jOWL.Ontology.Array();
+	jOWL.Xpath(__.rdfs("subClassOf")+"/"+__.owl("Restriction"), jnode)
+		.add(jOWL.Xpath(__.owl("intersectionOf")+"/"+__.owl("Restriction"), jnode))
+		.each(function(){
+			result.push(new jOWL.Ontology.Restriction($(this)));
+		});
+	return result;
+};
+
+/** Internal Use */
+jOWL.Ontology.Intersection = function(jnode){
+	var self = this;
+	this.jnode = jnode;
+	this._arr = [];
+	this.URI = this.jnode.parent().RDF_ID();
+	this.matches = {};
+	jOWL.Xpath(__.owl("Restriction"), jnode).each(function(){
+		var restr = new jOWL.Ontology.Restriction($(this));
+		if(restr.isValueRestriction){self._arr.push(restr);}
+	});
+	jOWL.Xpath(__.owl('Class'), jnode).each(function(){
+		var uri = $(this).RDF_About();
+		if(uri){ self._arr.push(jOWL(uri));}
+	});
+};
+
+jOWL.Ontology.Intersection.prototype = {
+	isIntersection : true,
+	jOWL : jOWL.version,
+	match : function(id, cls, clRestr){
+		if(id == this.URI){ return false;}
+		if(this.matches[id] !== undefined){ return this.matches[id]; }//local cache
+
+		for(var i =0;i<this._arr.length;i++){
+			var entry = this._arr[i];
+			var m = false;
+			if(entry.isRestriction){
+				clRestr.each(function(){
+					if(this.equals(entry)){ m = true; return false;}
+				});
+				if(!m) {
+					this.matches[id] = false;
+					return false;
+				}
+			} else if(entry.isClass){
+				for(var j = 0;j<cls.length;j++){
+					if(entry.equals(cls[j])){m = true; break;}
+					var it = jOWL.index('ID')[cls[j]];
+					if(it){
+						var narr = jOWL.Xpath.classes(jOWL.index('ID')[cls[j]].jnode);
+						for (var z=0;z<narr.length ; z++){
+							if(entry.equals(narr[z])){m = true; break;}
+						}
+					}
+				}
+				if(!m){
+					this.matches[id] = false;
+					return false;
+				}
+			}
+		}
+		this.matches[id] = true;
+		return this.matches[id];
+	},
+	equals : function(isect){
+		if(!isect.isIntersection){ return false;}
+			for(var i =0;i<this._arr.length;i++){
+				var match = false;
+				for(var j = 0;j<isect._arr.length;j++){
+					if(isect._arr[j].equals(this._arr[i])){ match = true;}
+				}
+				if(!match){ return false;}
+			}
+		return true;
+	}
+};
+
+jOWL.Ontology.Class.prototype = $.extend({}, jOWL.Ontology.Thing.prototype, {
+	isClass : true,
+	/** @return A jOWL.Ontology.Array of individuals for this class & its subclasses */
+	individuals : function(){
+		var arr = new jOWL.Ontology.Array();
+		var q = new jOWL.SPARQL_DL("Type(?x, "+this.name+")").execute({async: false, onComplete: function(r){ arr = r.jOWLArray("?x");}  });
+		return arr;
+	},
+	/** @return A jOWL.Ontology.Array of individuals (if oneOf list) */
+	oneOf : function(){
+		var arr = new jOWL.Ontology.Array();
+		var oneOf = this.jnode.children().filter(function(){return this.tagName == __.owl("oneOf");});
+		oneOf.children().each(function(){
+			arr.push(jOWL($(this).RDF_About()));
+		});
+		return arr;
+	},
+	/** @return A jOWL.Ontology.Array of direct children */
+	children : function(){
+		var that = this;
+		var oChildren = jOWL.data(this.name, "children");
+		if(oChildren){ return oChildren;}
+		oChildren = new jOWL.Ontology.Array();
+		if(this.oneOf().length){return oChildren;}
+		var URI = this.URI;
+
+		for(x in jOWL.index('ID')){
+			if(x === this.URI){ continue;}
+			var node = jOWL.index('ID')[x]; 
+			if(!node.isClass){continue;}
+			var cls = jOWL.Xpath.classes(node.jnode); //direct subClasses
+			for(var i=0;i<cls.length;i++){
+				if(this.equals(cls[i])){
+					oChildren.push(node);
+				}
+			}
+			var clRestr = jOWL.Xpath.restrictions(node.jnode);
+			var intersections = jOWL.index("intersection")[URI];
+			if(intersections){
+				intersections.each(function(){//fully defined Subclasses
+					if(this.match(x, cls, clRestr)){oChildren.push(node);}
+				});
+			}
+		}
+		//an ObjectProperty mentions this as domain
+		jOWL.index("property").each(function(){
+		if(this.domain == that.name){
+			var nodes = jOWL.Xpath('//'+__.owl('onProperty')+'[@'+__.rdf('resource')+'="#'+this.name+'"]/parent::'+__.owl('Restriction')+'/..');
+			nodes.filter(function(){ return (this.nodeName == __.owl('intersectionOf') || this.nodeName == __.rdfs('subClassOf'));
+			}).each(function(){
+				var cl = jOWL($(this.selectSingleNode('parent::'+__.owl('Class'))));
+				if(!oChildren.contains(cl) && cl.name != that.name && cl.name !== undefined){ oChildren.push(cl);}
+				});
+			}
+			});
+		//filter out redundancies
+		oChildren.filter(function(){
+			this.hierarchy(false);
+			return (this.parents().contains(URI));
+		});
+		jOWL.data(this.name, "children", oChildren);
+		return oChildren;
+	},
+	setParents : function(parents){
+		jOWL.data(this.name, "parents", parents); return parents;
+	},
+	/** @return A jOWL.Ontology.Array of parents, includes redundancies, to exclude do a hierarchy search first.*/
+	parents : function(){
+		var self = this;
+		var oParents = jOWL.data(this.name, "parents");
+		if(oParents){ return oParents;}
+
+		var temp = [];
+
+		var cls = jOWL.Xpath.classes(this.jnode);
+			for(var i=0;i<cls.length;i++){ jOWL.priv.Array.pushUnique(temp, cls[i]);}
+
+		var restr = jOWL.Xpath.restrictions(this.jnode);
+			restr.each(function(){
+				if(this.property.domain && this.property.domain != self.name){ jOWL.priv.Array.pushUnique(temp, this.property.domain);
+			}
+		});
+
+		var iSectLoop = function(){
+			if(this.match(self.URI, cls, restr)){
+				jOWL.priv.Array.pushUnique(temp, this.URI);
+			}
+		
+		};
+
+		if(jOWL.options.reason){
+			for(resource in jOWL.index('intersection')){
+				jOWL.index('intersection')[resource].each(iSectLoop);
+			}
+		}
+
+		oParents = new jOWL.Ontology.Array( jOWL.getXML(temp), true);
+		if(!oParents.length){ oParents.push(jOWL('Thing'));}
+		else if(oParents.length > 1){ oParents.filter(function(){return this.name != ('Thing');});} //Remove Thing reference if other parents exist
+		jOWL.data(this.name, "parents", oParents);
+		return oParents;
+	},
+/** @return ancestors to the class in a jOWL.Ontology.Array */
+	ancestors : function(){
+		return this.hierarchy(false).flatindex;
+	},
+/**
+Constructs the entire (parent) hierarchy for a class
+@return a jOWL.Ontology.Array containing top nodes (classes directly subsumed by 'owl:Thing')
+@param addInverse add a variable invParents (jOWL.Ontology.Array of child references) to each node with exception of the leaves (original concept)
+*/
+	hierarchy : function(addInverse){
+		var endNodes = new jOWL.Ontology.Array();
+		var self = this;
+		endNodes.flatindex  = new jOWL.Ontology.Array();
+
+		function URIARR(p_arr, obj){
+			var add = true;
+			if(!obj){ obj = {}; add = false;}
+			if(p_arr.each){
+				p_arr.each(function(){
+					if(obj[this.URI]){return;}
+					if(this.URI == __.owl()+'Thing'){ return;}
+					if(add){ obj[this.URI] = true;}
+					if(this.parents){ URIARR(this.parents(), obj);}
+				});
+			}
+			return obj;
+		}
+
+		function traverse(concept){
+			var parents = concept.parents();
+			if(parents.length == 1 && parents.contains(__.owl()+'Thing')){ endNodes.pushUnique(concept); return;}
+			else
+			{
+				var asso = jOWL.options.reason ? URIARR(parents) : {};
+				parents.filter(function(){ return (!asso[this.URI]);}); //throw out redundancies
+				parents.each(function(){
+						var item = endNodes.flatindex.pushUnique(this);
+						if(addInverse){
+							if(!item.invParents){ item.invParents = new jOWL.Ontology.Array();}
+							item.invParents.pushUnique(concept);
+							}
+						traverse(item);
+					});
+				concept.setParents(parents);
+			}
+		}
+
+		traverse(this);
+		return endNodes;
+
+	},
+	/**
+	@param level depth to fetch children, Default 5 
+	@return jOWL array of classes that are descendant
+	*/
+	descendants : function(level){
+		level = (typeof level == 'number') ? level : 5;
+		var oDescendants = jOWL.data(this.name, "descendants");
+		if(oDescendants && oDescendants.level >= level){ return oDescendants;}
+		oDescendants = new jOWL.Ontology.Array();
+		oDescendants.level = level;
+
+		function descend(concept, i){
+			if(i <= level){
+				i++;
+				var ch = concept.children();
+				oDescendants.concat(ch);
+				ch.each(function(item){ descend(item, i);});
+			}
+		}
+
+		descend(this, 1);
+		jOWL.data(this.name, "descendants", oDescendants);
+		return oDescendants;
+	},
+	/** @return jOWL.Array of Restrictions, target is an individual, not a class or undefined (unless includeAll is specified) - deprecated */
+	valueRestrictions : function(includeAll, array){
+		return this.sourceof(null, null, {ignoreClasses : !includeAll});
+	},
+	/**
+	get all restrictions that satisfy the arguments
+	@param property property or array of properties, or null
+	@param target class, individuals of array of them, or null
+	@return jOWL.Array of Restrictions
+	*/
+	sourceof : function(property, target, options){
+		options = $.extend({
+			inherited : true, // add restrictions specified on parents as well
+			transitive : true, //expand on transitive relations too
+			ignoreGenerics : true, //if a parent has an identical property, with another target 'Thing', skip that restriction
+			ignoreClasses : false, //only individuals should return
+			valuesOnly : true //do not return valueless criteria
+		}, options);
+		var self = this;
+		var crit = jOWL.data(this.name, "sourceof");
+		var jnode = this.jnode;
+
+		if(!crit){
+			crit = new jOWL.Ontology.Array();
+			var arr = jOWL.Xpath(__.rdfs("subClassOf")+"/"+__.owl("Restriction"), jnode)
+				.add(jOWL.Xpath(__.owl("intersectionOf")+"/"+__.owl("Restriction"), jnode));
+			arr.each(function(index, entry){
+			var cr = new jOWL.Ontology.Restriction($(entry));
+				var dupe = false;
+				crit.each(function(item, i){
+						if(this.property.name == cr.property.name){ dupe = item;}
+				});
+				if(dupe){ if(!dupe.merge(cr)){ crit.push(cr);} }
+				else { crit.push(cr);}
+			});
+			jOWL.data(self.name, "sourceof", crit);
+		}
+		var results = new jOWL.Ontology.Array();
+
+		crit.each(function(){
+
+			var propertyMatch = property ? false : true;
+			var targetMatch = target ? false : true;
+
+			if(!propertyMatch){
+				if(property.isArray){	propertyMatch = property.contains(this.property);}
+				else { propertyMatch = (property.URI == this.property.URI);}
+			}
+
+			if(!target){
+				if(options.transitive && this.property.isTransitive){ 
+					var rTarget = this.getTarget();
+					var transitives = rTarget.sourceof(this.property, null, options);
+					results.concat(transitives);
+				}
+			}
+
+			if(!targetMatch && !this.target){
+				targetMatch = !options.valuesOnly;
+			} 
+
+			if(!targetMatch){
+				var targ = this.getTarget();
+				if(targ.isClass && options.ignoreClasses){ return;}
+				targetMatch = jOWL.priv.testObjectTarget(target, this.target);
+				if(!targetMatch && options.transitive && propertyMatch && this.property.isTransitive){
+					if(targ.isThing){
+						if(targ.sourceof(property, target).length){ targetMatch = true;}
+					}
+				}
+			}
+
+			if(propertyMatch && targetMatch){ results.pushUnique(this);}
+		});
+
+		if(!options.inherited){ return results;}
+
+		this.parents().each(function(){ 
+			if(this.sourceof){
+				this.sourceof(property, target, options).each(function(parentsource){
+					var ptarget = this.getTarget();
+					var containsProperty = false; 
+					var tempArray = new jOWL.Ontology.Array();
+					results.filter(function(){
+						var restr = this, keep = true;
+						if(restr.property.URI == parentsource.property.URI){
+							containsProperty = true;
+							if(!options.ignoreGenerics){
+								if(parentsource.target != restr.target){ tempArray.push(parentsource);}
+							} else {
+								if(ptarget.isThing){
+									keep = restr.getTarget().isThing && parentsource.target != restr.target;
+									tempArray.push(parentsource);
+								}
+							}
+						}
+						return keep;
+					});
+					if(!containsProperty){ results.push(parentsource);}
+					results.concat(tempArray);
+				});
+			}
+		});
+		return results;
+	}
+});
+
+/** Utility object */
+jOWL.Ontology.Array = function(arr, isXML){
+	var self = this;
+	this.items = [];
+	if(arr){
+		if(isXML){ $.each(arr, function(){
+			var entry = this.jOWL ? this : jOWL($(this));
+			self.items.push(entry);}); 
+			}
+		else { this.items = arr;}
+	}
+	this.length = this.items.length;
+};
+
+jOWL.Ontology.Array.prototype = {
+	jOWL : jOWL.version,
+	isArray : true,
+	bind : function(listitem, fn){
+		return this.map(function(){
+			var syntax = listitem ? listitem.clone(true) : $('<span/>');
+			var html  = this.bind(syntax).append(document.createTextNode(' '));
+			if(fn){ fn.call(html, html, this);}
+			return html.get(0);
+		});
+	},
+	concat : function(arr, ignoreUnique){
+		var self = this;
+		if(arr.each){ arr.each(function(){
+			if(ignoreUnique){ self.push(this); }
+			else { self.pushUnique(this); }
+			});
+			}
+		else { self.items = self.items.concat(arr.items); this.length = self.items.length;}
+		return this;
+	},
+	contains : function(o){
+		return this.get(o) ? true: false;
+	},
+	each : function(fn, reverse){
+		var i, self = this;
+		var stop = false;
+		if(reverse){
+			for(i=this.items.length - 1; i>=0;i--){ 
+				if(stop){ break;}
+				(function(){
+					var item = self.eq(i); 
+					if(fn.call(item, item, i) === false){ stop = true;}
+				})();
+			}
+		}
+		else {
+			for(i=0;i<this.items.length;i++){
+				if(stop){ break;}
+				(function(){
+					var item = self.eq(i); 
+					if(fn.call(item, item, i) === false){ stop = true;}
+				})();} 
+		}
+		return this;
+	},
+	eq : function(index){
+		if(index < 0 || index > this.items.length -1){ return null;}
+		return this.items[index];
+	},
+	filter : function(fn){
+		var self = this;
+		this.each(function(item, i){
+			var q = fn.call(item, item, i);
+			if(!q){ self.items.splice(i, 1);}
+			}, true);
+		this.length = this.items.length;
+		return this;
+	},
+	getIndex : function(o){
+		var found  = -1;
+		if(o.equals){
+			this.each(function(a, i){
+				if(this.equals && this.equals(o)){ found = i; return false;}
+			});
+		}
+		else {
+			if(typeof o == 'number'){ return o;}
+			var name = typeof o == "string" ? o : o.name;
+			var URI = o.URI || name;
+
+			this.each(function(a, i){
+				if(this.URI){ if(this.URI == URI){ found = i;}}
+				else if(this.name == name){ found = i;}
+			});
+		}
+		return found;
+	},
+	get : function(o){
+		return this.eq(this.getIndex(o));
+	},
+	map : function(fn){
+		var arr = [];
+		this.each(function(){ arr.push(fn.call(this, this));});
+		return arr;
+	},
+	push : function(o){
+		this.items.push(o);
+		this.length = this.items.length;
+		return this;
+	},
+	pushUnique : function(o){
+		return this.get(o) || this.push(o).get(o);
+	},
+	toString : function(){
+		return this.map(function(){return this.URI;}).join(', ');
+	},
+	/** Convert this array into an associative array with key = URI */
+	associative : function(){
+		var arr = {};
+		this.each(function(){
+			if(this.URI){ arr[this.URI] = this;}
+		});
+		return arr;
+	}
+};
+
+
+jOWL.options = {reason: true, locale:false, defaultlocale: 'en',
+	dictionary : { create: true, addID : true },
+	onParseError : function(msg){alert("jOWL parseError: "+msg);}, cacheProperties : true, niceClassLabels : true};
+jOWL.document = null;
+jOWL.namespace = null;
+jOWL.indices = { //internal indices
+	P : null, //jOWL array
+	data : {},
+	IDs : null,
+	I : null, //Intersection
+	T : null, //Thing
+	D : null, //dictionary
+	reset : function(){var i = jOWL.indices; i.data = {}; i.P = null; i.T = null; i.IDs = null; i.I = null;i.D = null;}
+};
+
+jOWL.index = function(type, wipe){
+		var i = jOWL.indices;
+		switch (type)
+		{
+		/**jOWL indexes all elements with rdf:ID, and first order ontology elements specified with rdf:about 
+		@return Associative array with key = URI and value = jOWL object.
+		*/
+		case "ID":
+			if(i.IDs === null || wipe){
+				if(wipe){ i.reset();}
+				i.IDs = {};
+				i.T = {};
+				var start = new Date();
+
+				var rID = jOWL.Xpath("//*[@"+__.rdf("ID")+"]").each(function(){
+					var jowl = jOWL.getResource($(this)); 
+					if(jowl){
+						i.IDs[jowl.URI] = jowl;
+						if(jowl.isThing){
+							if(!i.T[jowl.Class]){ i.T[jowl.Class] = new jOWL.Ontology.Array();}
+							i.T[jowl.Class].push(jowl);
+						}
+					}
+				});
+
+				var rAbout = jOWL.Xpath("/"+__.rdf("RDF")+"/*[@"+__.rdf("about")+"]").each(function(){
+					var jnode = $(this);
+					var jowl = jOWL.getResource($(this));
+					if(!jowl){ return;}
+						if(jowl.isClass || jowl.isProperty || jowl.isThing){
+							if(i.IDs[jowl.URI]){ jnode.children().appendTo(i.IDs[jowl.URI].jnode); return;}
+							i.IDs[jowl.URI] = jowl; 
+							if(jowl.isThing){
+								if(!i.T[jowl.Class]){ i.T[jowl.Class] = new jOWL.Ontology.Array();}
+								i.T[jowl.Class].push(jowl);
+							}
+							return; 
+						}
+				});
+				console.log("Loaded in "+(new Date().getTime() - start.getTime())+"ms");
+				}
+			return i.IDs;
+		/** Generated together with ID index.
+		* @return Associative Array, key = class, value = jOWL Array of individuals.
+		*/
+		case "Thing":
+			return i.T;
+		case "intersection":
+			if(i.I === null || wipe){
+				var temp =  new jOWL.Ontology.Array();
+				i.I  = {};
+				jOWL.Xpath("//"+__.owl("intersectionOf")).each(function(){
+					var isect = new jOWL.Ontology.Intersection($(this));
+					if(!isect.URI){return;}
+					var dupe = temp.get(isect);
+					if(dupe){
+						console.log("duplicate intersection found between : (Ignoring) "+isect.URI+"  and "+dupe.URI);
+					} else {
+						if(!i.I[isect.URI]){i.I[isect.URI] = new jOWL.Ontology.Array();}
+						temp.push(isect);
+						i.I[isect.URI].push(isect);
+					}
+				});
+				}
+			return i.I;
+		case "property":
+			if(i.P === null || wipe)
+			{
+			jOWL.options.cacheProperties = false;
+			i.P = new jOWL.Ontology.Array();
+			for(x in i.IDs){
+				var jowl = i.IDs[x];
+				if(jowl.isProperty){ i.P.push(jowl);}
+			}
+			jOWL.options.cacheProperties = true;
+			}
+			return i.P;
+		case "dictionary":
+			/**Dictionary: Array of Arrays, where secondary array is of form: [0] = term, [1] = rdfID, [2] = locale */
+			if(i.D === null || wipe)
+			{
+				i.D = [];
+				for(x in i.IDs){
+					var entry = i.IDs[x];
+					i.D = i.D.concat(entry.terms());
+				}
+			}
+			return i.D;
+		}
+};
+
+/** Internal Function, storing data in associative array (JSON),
+jquery data function cannot be used as expando data does not work in IE for ActiveX XMLhttprequest*/
+jOWL.data = function(rdfID, dtype, data){
+	var d = jOWL.indices.data;
+	if(!d[rdfID]){ d[rdfID] = {};}
+	if(!data){ return d[rdfID][dtype];}
+	d[rdfID][dtype] = data;
+};
+
+/**
+* Initialize jOWL with an OWL-RDFS document.
+* @param path relative path to xml document
+* @param callback callback function to be called when loaded.
+* @options : optional settings:
+*	onParseError : function(msg){} function to ba called when parsing fails
+*	reason : true/false, turns on additional reasoning at the expense of performance
+*	locale: set preferred language (if available), examples en, fr...
+*/
+jOWL.load = function(path, callback, options){
+	var that = this;
+	if($.browser.msie && location.toString().indexOf('file') === 0){ //IE won't load local xml files otherwise
+		var xml = document.createElement("xml");
+		xml.validateOnParse = false; //IE throws DTD errors (for 'rdf:') on perfectly defined OWL files otherwise
+		xml.src = path;
+		xml.onreadystatechange = function(){
+			if(xml.readyState == "interactive"){ var xmldoc = xml.XMLDocument; document.body.removeChild(xml);callback(that.parse(xmldoc, options));}
+			};
+		document.body.appendChild(xml);
+		}
+	else {
+		$.get(path, function(xml){callback(that.parse(xml, options));});
+	}
+};
+
+/**
+* initialize jOWL with some OWL-RDFS syntax
+* @param doc Either an xmlString or an xmlDocument
+* @param options optional, onParseError(msg) : function to execute when parse fails
+* @returns false on failure, or the jOWL object
+*/
+jOWL.parse = function(doc, options){
+	jOWL.document = null;
+	this.options = $.extend(jOWL.options, options);
+	if(typeof doc == 'string'){ doc = jOWL.fromString(doc);}
+	jOWL.document = doc;
+	if($.browser.msie){
+		if(doc.parseError.errorCode !== 0){ jOWL.options.onParseError(doc.parseError.reason); return false;}
+		}
+	else if(doc.documentElement.nodeName == 'parsererror'){jOWL.options.onParseError(doc.documentElement.firstChild.nodeValue); return false;}
+	var root = $(doc.documentElement);
+	jOWL.NS(root);
+	if($.browser.msie){
+		jOWL.document.setProperty("SelectionLanguage", "XPath");
+		jOWL.document.setProperty("SelectionNamespaces", __());
+	}
+	this.index('ID', true);
+	if(jOWL.options.cacheProperties){ this.index('property', true);}
+	if(jOWL.options.dictionary.create){ jOWL.index("dictionary");}
+	jOWL.Thing = new jOWL.Ontology.Thing($(jOWL.create(__.owl, "Class").attr(__.rdf, 'about', __.owl()+'Thing').node)); 
+	jOWL.Thing.type = false;	
+	return this;
+};
+
+/**
+* A String representation of the OWL-RDFS document
+* @param xmlNode optional, node to generate a string from, when unspecified the entire document
+*/
+jOWL.toString = function(xmlNode){
+	if(!xmlNode){ return jOWL.toString(jOWL.document);}
+	if($.browser.msie){ return xmlNode.xml;}
+	return new XMLSerializer().serializeToString(xmlNode);// Gecko-based browsers, Safari, Opera.
+};
+
+/** create a document from string */
+jOWL.fromString = function(doc){
+	var owldoc;
+	if(document.implementation.createDocument){ owldoc = new DOMParser().parseFromString(doc, "text/xml");} // Mozilla and Netscape browsers
+	else if(window.ActiveXObject){ // MSIE
+		var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
+		xmldoc.async="false";
+		xmldoc.validateOnParse = false;
+		xmldoc.loadXML(doc);
+		owldoc = xmldoc;
+		}
+	return owldoc;
+};
+
+/** @return false if belongs to this namespace, or an array with length two, arr[0] == url, arr[1] == id */
+jOWL.isExternal = function(resource){
+	var r = jOWL.resolveURI(resource, true);
+	return r[0] != jOWL.namespace ? r : false;
+};
+
+/** 
+if a URI belongs to the loaded namespace, then strips the prefix url of, else preserves URI 
+also able to parse and reference html (or jquery) elements for their URI.
+*/
+jOWL.resolveURI = function(URI, array){
+	if(typeof URI != "string"){
+		var node = URI.jquery ? URI.get(0) : URI;
+		URI = node.localName || node.baseName;
+		if(node.namespaceURI){ URI = node.namespaceURI + URI;}
+		return jOWL.resolveURI(URI, array);
+	}
+	var rs = URI, ns = jOWL.namespace;
+	if(URI.indexOf('http') === 0){
+		var tr = URI.indexOf('#');
+		if(tr <= 0){ tr = URI.lastIndexOf('/');}
+		if(tr > 0)
+		{
+			ns = URI.substring(0, tr+1);
+			rs = URI.substring(tr+1);
+		}
+	} else if(URI.charAt(0) == '#'){ return URI.substring(1);}
+	if(array){ return [ns, rs];}
+	if(ns == jOWL.namespace){ return rs;}
+	return URI;
+};
+
+/**
+Main method to get an Ontology Object, access via jOWL(>String>, options);
+resource: rdfID/rdfResource<String> or jQuery node.
+*/
+jOWL.getResource = function(resource, options){
+	if(!jOWL.document){ throw "You must successfully load an ontology before you can find anything";}
+	if(!resource){ throw "No resource specified";}
+	var node;
+	var opts = $.extend({}, options);
+	if(typeof resource == 'string'){
+		resource = jOWL.resolveURI(resource);
+		if(resource == 'Thing' || resource == __.owl()+'Thing'){ return jOWL.Thing;}
+		if(opts.type == 'property' && jOWL.options.cacheProperties){
+			var c = jOWL.index('property').get(resource);
+			if(c){ return c;}
+			if(jOWL.isExternal(resource)){ console.log("undeclared resource: "+resource); return new jOWL.Ontology.Property(resource);}
+			}
+		var match = jOWL.index("ID")[resource];
+		if(!match){ //try case insensitive
+			for(caseIns in jOWL.index("ID")){
+				if(caseIns.toLowerCase() == resource.replace(/ /g, "").toLowerCase()){ match = jOWL.index("ID")[caseIns]; break;}
+			}
+		}
+		if(!match){
+			if(jOWL.isExternal(resource)){
+				console.log("undeclared resource: "+resource);
+				return new jOWL.Ontology.Thing(resource);
+			}
+			console.log(resource+" not found"); 
+			return null; 
+		}
+		return match;
+	}
+	node = resource.jquery ? resource : $(resource);
+	var jj = jOWL.type(node); if(!jj){ return null;}
+	return new (jj)(node);
+};
+
+/** 
+* @param node jquery or html element.
+* @return the ontology type of the object.
+*/
+jOWL.type = function(node){
+	var xmlNode = node.jquery ? node.get(0) : node;
+	switch(xmlNode.nodeName){
+		case __.owl("Class") : return jOWL.Ontology.Class;
+		case __.rdfs("Class") : return jOWL.Ontology.Class; //test
+		case __.owl("Ontology") : return jOWL.Ontology;
+		case __.owl("ObjectProperty") : return jOWL.Ontology.ObjectProperty;
+		case __.owl("DatatypeProperty") : return jOWL.Ontology.DatatypeProperty;
+		case __.owl("FunctionalProperty") : return jOWL.Ontology.Property;
+		case __.rdf("Property") : return jOWL.Ontology.Property;
+		case __.owl("InverseFunctionalProperty") : return jOWL.Ontology.ObjectProperty;
+		case __.owl("TransitiveProperty") : return jOWL.Ontology.ObjectProperty;
+		case __.owl("SymmetricProperty") : return jOWL.Ontology.ObjectProperty;
+		//jOWL currently treats annotationproperties as string datatypeproperties.
+		case __.owl("AnnotationProperty") : return jOWL.Ontology.DatatypeProperty;
+		default :
+			switch(xmlNode.namespaceURI){
+				case __.owl(): if(xmlNode.nodeName == __.owl("Thing") ){ return jOWL.Ontology.Individual;} return false;
+				case __.rdf(): return false;
+				case __.rdfs(): return false;
+				default : return jOWL.Ontology.Individual;
+			}
+	}
+};
+
+/**
+@param rdfID <String> or Array<String>
+@return Array of DOM (xml) Nodes
+*/
+jOWL.getXML = function(rdfID){
+	var node = [];
+	function fetchFromIndex(rdfID){ 
+		var el = jOWL.index("ID")[rdfID];
+		return el ? el : null;
+	}
+
+	if(typeof rdfID == 'string'){ var q = fetchFromIndex(rdfID); if(q){ node.push(q);} }
+	else if(jOWL.priv.Array.isArray(rdfID)){ //assume an array of string rdfIDs
+		$.each(rdfID, function(){  
+			var el = fetchFromIndex(this); if(el){ node.push(el);}
+			});
+	}
+	return node;
+};
+
+/** Create new ontology elements */
+jOWL.create = function(namespace, name, document){
+	var doc = document ? document : jOWL.document;
+
+	var el = {
+		attr : function(namespace, name, value){
+			if($.browser.msie){
+				var attribute = doc.createNode(2, namespace(name), namespace());
+				attribute.nodeValue = value;
+				this.node.setAttributeNode(attribute);
+			}
+			else { this.node.setAttributeNS(namespace(), namespace(name), value);}
+			return this;
+		},
+		appendTo : function(node){
+			var n = node.node ? node.node : node;
+			n.appendChild(this.node);
+			return this;
+		},
+		text : function(text, cdata){
+			var txt = cdata ? doc.createCDATASection(text) : doc.createTextNode(text);
+			this.node.appendChild(txt);
+			return this;
+		}
+	};
+
+	if($.browser.msie){ el.node = doc.createNode(1, namespace(name), namespace());}
+	else { el.node = doc.createElementNS(namespace(), namespace(name));}
+	return el;
+};
+
+/** Create a blank ontology document */
+jOWL.create.document = function(href){
+	var owl = [];
+	var base = href || window.location.href+"#";
+	owl.push('<?xml version="1.0"?>');
+	owl.push('<'+__.rdf('RDF')+' xml:base="'+base+'" xmlns="'+base+'" '+__()+'>');
+	owl.push('   <'+__.owl('Ontology')+' '+__.rdf('about')+'=""/>');
+	owl.push('</'+__.rdf('RDF')+'>');
+	return jOWL.fromString(owl.join('\n'));
+};
+
+/** Extracts RDFa syntax from current page and feeds it to jOWL, simple implementation, only classes for the time being */
+jOWL.parseRDFa = function(fn, options){
+	var entries = options.node ? $("[typeof]", options.node) : $("[typeof]");
+	var doc = jOWL.create.document();
+
+	 function property(p, node){
+		var arr = [];
+		$("[property="+p+"]", node).each(function(){ arr.push($(this).attr('content') || $(this).html());});
+		if(node.attr('property') === p){ arr.push(node.attr('content') || node.html());}
+		return arr;
+	}
+
+	function rel(p, node){
+		var arr = [];
+		$("[rel="+p+"]", node).each(function(){ arr.push($(this).attr('resource'));});
+		if(node.attr("rel") === p){ arr.push(node.attr('resource'));}
+		return arr;
+	}
+
+	function makeClass(node, ID){
+		var cl = jOWL.create(__.owl, "Class", doc).attr(__.rdf, 'about', ID).appendTo(doc.documentElement);
+
+		var parents = property(__.rdfs("subClassOf"), node).concat(rel(__.rdfs("subClassOf"), node));
+		for(var i = 0;i<parents.length;i++){
+			var p = jOWL.create(__.rdfs, "subClassOf", doc).attr(__.rdf, "resource", parents[i]).appendTo(cl);
+		}
+		return cl;
+	}
+
+	entries.each(function(){
+		var node = $(this);
+		var type = node.attr("typeof"), el;
+
+		if(type == __.owl("Class")){ el = makeClass(node, jOWL.resolveURI(node.attr("about")));}
+
+		$.each(property(__.rdfs('comment'), node), function(){
+			jOWL.create(__.rdfs, "comment", doc).appendTo(el).text(this, true);
+		});
+
+		$.each(property(__.rdfs('label'), node), function(){
+			jOWL.create(__.rdfs, "label", doc).appendTo(el).text(this);
+		});
+	});
+	jOWL.parse(doc, options);
+	fn();
+};
+
+/**
+Match part or whole of the rdfResource<String>
+Used for term searches, intend to (partially) replace it by a sparql-dl query later on
+options:
+    filter: filter on a specific type, possible values: Class, Thing, ObjectProperty, DatatypeProperty
+    exclude: exclude specific types, not fully implemented
+*/
+jOWL.query = function(match, options){
+	options = $.extend({exclude : false}, options);
+	if(options.filter == 'Class'){ options.filter = __.owl("Class");}
+	var that = this;
+	//filter : [], exclude : false
+	var items = new jOWL.Ontology.Array();
+	var jsonobj = {};
+	var test = jOWL.index("dictionary");
+
+	function store(item){
+			var include = false, i = 0;
+			if(options.filter){
+				if(typeof options.filter == 'string'){ include = (options.filter == item[3]);}
+				else { for(i = 0;i<options.filter.length;i++){ if(options.filter[i] == item[3]){ include = true;} } }
+				}
+			else if(options.exclude){
+				include = true;
+				if(typeof options.exclude == 'string'){ include = (options.exclude !== item[3]);}
+				else { for(i = 0;i<options.exclude.length;i++){ if(options.exclude[i] == item[3]){ include = false;} } }
+			}
+			else { include = true;}
+			if(!include){ return;}
+			if(!jsonobj[item[1]]){ jsonobj[item[1]] = [];}
+			jsonobj[item[1]].push( { term : item[0], locale: item[2], type: item[3] });
+	}
+
+	for(var y = 0;y<test.length;y++){
+		var item = test[y];
+		var bool = options.exclude;
+		var r = item[0].searchMatch(match);
+		if(r > -1){
+			if(options.locale){ if(options.locale == item[2]){ store(item);} }
+			else { store(item);}
+		}
+	}
+	return jsonobj;
+};
+
+/**
+allows asynchronous looping over arrays (prevent bowser freezing).
+arr the array to loop asynchonrously over.
+options.modify(item) things to do with each item of the array
+options.onUpdate array the size of chewsize or smaller, containing processed entries
+options.onComplete(array of results) function triggered when looping has completed
+*/
+jOWL.throttle =function(array, options){
+	options = $.extend({
+		modify : function(result){},
+		//onUpdate : function(arr){},
+		onComplete : function(arr){},
+		async : true,
+		chewsize : 5,
+		startIndex : 0,
+		timing : 5
+		}, options);
+	var temp = array.jOWL ? array.items : (array.jquery) ? $.makeArray(array) : array;
+	var items = options.startIndex ? temp.slice(startIndex) : temp.concat(); //clone the array
+	var results = [];
+
+	(function(){
+		var count = options.chewsize;
+		var a = [];
+		while (count > 0 && items.length > 0)
+		{
+			var item = items.shift(); count--;
+			var result = options.modify.call(item, item);
+			if(result){ results.push(result); a.push(result);}
+		}
+		if(options.onUpdate){ options.onUpdate(a);}
+
+		if(items.length> 0){
+			if(options.async){ setTimeout(arguments.callee, options.timing);}
+			else {arguments.callee();}
+		}
+		else{ options.onComplete(results);}
+	})();
+};
+
+/** Creates a new resultobj for the SPARQL-DL functionality */
+jOWL.SPARQL_DL_Result = function(){
+	this.assert = undefined;
+	this.head = {}; //associative array of query parameters, with value jOWL Array of results
+	this.results = []; //sparql-dl bindings
+	this.isBound = false;
+};
+
+jOWL.SPARQL_DL_Result.prototype = {
+	sort : function(param){
+		if(!param){ throw "parameter must be defined for sort function";}
+		function sortResults(a, b){
+			var o = a[param].name || a[param];
+			var p = b[param].name || b[param];
+			return (o < p) ? -1 : 1;
+		}
+		if(this.results){ this.results.sort(sortResults); }
+	},
+	jOWLArray : function(param){
+		if(!param){ throw "parameter must be defined for jOWLArray function";}
+		var arr = new jOWL.Ontology.Array();
+		for(var i=0;i<this.results.length;i++){
+		if(this.results[i][param]){ arr.pushUnique(this.results[i][param]);}
+		}
+		return arr;
+	},
+	/** Filter head Parameters */
+	filter : function(param, arr){
+		if(this.head[param] === undefined){this.head[param] = arr;}
+		else {
+			var self = this; 
+			this.head[param].filter(function(){ return (arr.contains(this));});
+			arr.filter(function(){ return (self.head[param].contains(this));});
+		}
+	},
+	/** Update result section, results = SPARQL_DL_Array */
+	bind : function(results){
+		if(!this.isBound){//new results
+			this.results = this.results.concat(results.arr);
+			this.isBound = true;
+			return;
+		}
+		var multimapping = -1;
+		for(x in results.mappings){ multimapping++; }
+		var toAdd = [];
+
+		for(x in results.mappings){
+			var otherKeys;
+			if(multimapping){
+				otherKeys = results.keyCentric(x);
+			}
+			for(var i = this.results.length-1;i>=0;i--){
+				var valueX = this.results[i][x];
+				if(valueX){
+					if(!results.mappings[x].contains(valueX)){
+						this.results.splice(i, 1);
+						continue;
+					}
+					if(multimapping){
+						var keyArr= otherKeys[valueX.URI];
+						//ignoring the opposite for now (assuming original key x is unique (limits statements))
+						//TODO: improve these result merging methods/flexibility
+						for(var oK = 0; oK < keyArr.length;oK++){
+							var obj = (oK === 0) ? this.results[i] : {};
+							var valueY = keyArr[oK];
+							obj[x] = valueX;
+							for(yK in valueY){ obj[yK] = valueY[yK]; }
+							toAdd.push(obj);
+						}
+						this.results.splice(i, 1);
+					}
+				}
+			}
+		}
+		this.results = this.results.concat(toAdd);
+	}
+};
+/** Creates a new query for the SPARQL-DL functionality */
+jOWL.SPARQL_DL_Query = function(syntax, parameters){
+		this.parse(syntax);
+		this.fill(parameters);
+		this.entries = this.entries.sort(this.sort);
+};
+
+jOWL.SPARQL_DL_Query.prototype = {
+	parse : function(syntax){
+		 var r2 = /(\w+)[(]([^)]+)[)]/;
+		 var entries = syntax.match(/(\w+[(][^)]+[)])/g);
+		 if(!entries){ this.error =  "invalid abstract sparql-dl syntax"; return;}
+		 entries = jOWL.priv.Array.unique(entries);
+		 for(var i = 0;i<entries.length;i++){
+			var y = entries[i].match(r2);
+			if(y.length != 3){ this.error = "invalid abstract sparql-dl syntax"; return;}
+			entries[i] = [y[1], y[2].replace(/ /g, "").split(',')];
+		 }
+		 this.entries = entries;
+	},
+	fill : function(parameters){
+		for(var i = 0;i<this.entries.length;i++){
+			for(var j =0; j<this.entries[i][1].length; j++){
+				var p = parameters[this.entries[i][1][j]];
+				if(p !== undefined)  { this.entries[i][1][j] = p;}
+				else {
+					p = this.entries[i][1][j];
+					if(p.charAt(0) != '?')
+					{
+						if(this.entries[i][0] == "PropertyValue" && j == 2)
+						{
+						var m = p.match(/^["'](.+)["']$/);
+						if(m && m.length == 2){ this.entries[i][1][j] = {test: m[1]}; break;}
+						}
+					this.entries[i][1][j] = jOWL(p);
+					if(this.entries[i][1][j] === null){this.entries.error = "a parameter in the query was not found"; return;}
+					}
+				}
+			}
+		}
+	},
+	sort : function(a, b){
+		var i;
+		if(a[1].length == 1){ return (b[0] == 'PropertyValue') ? 1 : -1;}
+		if(b[1].length == 1){ return (a[0] == 'PropertyValue') ? -1 : 1;}
+		var avar = 0; for(i = 0;i<a[1].length;i++){ if(typeof a[1][i] == 'string'){ avar++;} }
+		var bvar = 0; for(i = 0;i<a[1].length;i++){ if(typeof b[1][i] == 'string'){ bvar++;} }
+		if(avar != bvar){ return avar - bvar;}
+		if(a[0] == 'Type' && b[0] != 'Type'){ return -1;}
+		if(a[0] != 'Type' && b[0] == 'Type'){ return 1;}
+		return 0;
+	}
+};
+
+/** Private function */
+function _Binding(bindingarray){
+	this.value = {};
+	this.arr = bindingarray;
+}
+
+_Binding.prototype = {
+	bind : function(key, value){
+		this.value[key] = value;
+		if(!this.arr.mappings[key]){ this.arr.mappings[key] = new jOWL.Ontology.Array();}
+		this.arr.mappings[key].push(value);
+		return this;
+	}
+};
+
+/** Local Function, private access, Temp results */
+function SPARQL_DL_Array(keys){	
+	this.arr = [];
+	this.mappings = {};
+
+	if(keys){
+		for(var i =0;i<keys.length;i++){
+			if(keys[i]){this.mappings[keys[i]] = new jOWL.Ontology.Array();}
+		}
+	}
+}
+
+SPARQL_DL_Array.prototype = {
+	add : function(binding){
+		this.arr.push(binding.value);
+		return binding;
+	},
+	push : function(key, value){
+		var binding = new _Binding(this);
+		binding.bind(key, value);
+		this.arr.push(binding.value);
+		return binding;
+	},
+	keyCentric : function(keyX){
+		var arr = {};
+		for(var i = this.arr.length-1;i>=0;i--){
+			if(this.arr[i][keyX]){
+				if(!arr[this.arr[i][keyX].URI]){ arr[this.arr[i][keyX].URI] = []; }
+				arr[this.arr[i][keyX].URI].push(this.arr[i]);
+			}
+		}
+		return arr;
+	},
+	get : function(key)
+	{
+		return (this.mappings[key]) ? this.mappings[key] : new jOWL.Ontology.Array();
+	},
+	getArray : function(){
+		//check mappings for presence, discard arr entries based on that, return remainder.
+		for(var i = this.arr.length - 1;i>=0;i--){
+			var binding = this.arr[i], splice = false;
+			for(key in binding){
+				if(!splice){
+					splice = (!this.mappings[key] || !this.mappings[key].contains(binding[key]));
+				}
+			}
+			if(splice){
+				this.arr.splice(i, 1);
+			}
+		}
+		return this;
+	}
+};
+
+/**
+Support for abstract SPARQl-DL syntax
+options.onComplete: function triggered when all individuals have been looped over
+options.childDepth: depth to fetch children, default 5, impacts performance
+options.chewsize: arrays will be processed in smaller chunks (asynchronous), with size indicated by chewsize, default 10
+options.async: default true, query asynchronously
+parameters: prefill some sparql-dl parameters with jOWL objects
+execute: start query, results are passed through options.onComplete
+*/
+jOWL.SPARQL_DL = function(syntax, parameters, options){
+	if(!(this instanceof arguments.callee)){ return new jOWL.SPARQL_DL(syntax, parameters, options);}
+	var self = this;
+	this.parameters = $.extend({}, parameters);
+	this.query = new jOWL.SPARQL_DL_Query(syntax, this.parameters).entries;
+	this.result = new jOWL.SPARQL_DL_Result();
+	this.options = $.extend({onComplete: function(results){}}, options);
+};
+
+jOWL.SPARQL_DL.prototype = {
+	error: function(msg){ this.result.error = msg; return this.options.onComplete(this.result);},
+	/**
+	if(options.async == false) then this method returns the result of options.onComplete,
+	no matter what, result is always passed in options.onComplete
+	*/
+	execute : function(options){
+		var self = this;
+		this.options = $.extend(this.options, options);
+		if(this.query.error){ return this.error(this.query.error);}
+		
+		var resultobj = this.result;
+		var i = 0;  
+		var loopoptions = $.extend({}, this.options);
+		loopoptions.onComplete = function(results){ i++; resultobj = results; loop(i);};
+		
+		if(!this.query.length){
+			resultobj.error = "no query found or query did not parse properly";
+			return self.options.onComplete(resultobj);
+			}  
+			
+		function loop(i){
+			if(i < self.query.length){
+				self.process(self.query[i], resultobj, loopoptions );
+				}
+			else {
+				for(var j =0;j<resultobj.results.length;j++){ //Convert Literals into strings
+					var b = resultobj.results[j];
+					for(x in b){
+						if(b[x] instanceof jOWL.Literal){b[x] = b[x].name;}
+					}
+				}
+				return self.options.onComplete(resultobj);
+			}
+		} 
+		loop(i);
+	},
+	/** results are passed in the options.onComplete function */
+	process: function(entry, resultobj, options){
+		var self = this;
+		options = $.extend({chewsize: 10, async : true, onComplete : function(results){}}, options);
+		var q = entry[0];
+		var sizes = {
+			"Type": [__.owl('Thing'), __.owl('Class')],
+			"DirectType": [__.owl('Thing'), __.owl('Class')],
+			"PropertyValue" : [false, false, false],
+			"Class": [false],
+			"Thing": [false],
+			"ObjectProperty": [false],
+			"DatatypeProperty": [false],
+			"SubClassOf" : [__.owl('Class'), __.owl('Class')],
+			"DirectSubClassOf" : [__.owl('Class'), __.owl('Class')]
+			};
+		
+		if(!sizes[q]){ return self.error("'"+q+"' queries are not implemented");}
+		if(sizes[q].length != entry[1].length){ return self.error("invalid SPARQL-DL "+q+" specifications, "+sizes[q].length+" parameters required");}
+		for(var i = 0;i<entry[1].length;i++){
+			var v = sizes[q][i];
+			if(v){
+				var m = entry[1][i];
+				if(typeof m != 'string' && m.type != v){ return self.error("Parameter "+i+" in SPARQL-DL Query for "+q+" must be of the type: "+v);}
+			}
+		}
+		if(q == "DirectType"){ options.childDepth = 0; return self.fn.Type.call(self, entry[1], resultobj, options);}
+		else if(q == "DirectSubClassOf"){ options.childDepth = 1; return self.fn.SubClassOf.call(self, entry[1], resultobj, options);}
+		return self.fn[q].call(self, entry[1], resultobj, options);
+	},
+	fn : {
+			"SubClassOf" : function(syntax, resultobj, options){
+				var atom = new jOWL.SPARQL_DL.DoubleAtom(syntax, resultobj.head);
+				var results = new SPARQL_DL_Array();
+
+				if(atom.source.isURI() && atom.target.isURI()){//assert
+					if(resultobj.assert !== false){
+						var parents = atom.source.value.ancestors();
+						resultobj.assert = parents.contains(atom.target.value);
+					}
+					return options.onComplete(resultobj);
+				}
+				else if(atom.source.isURI()){//get parents
+					atom.source.value.ancestors().each(function(){
+						results.push(atom.target.value, this);
+						});
+					resultobj.filter(atom.target.value, results.get(atom.target.value));
+					resultobj.bind(results.getArray());
+					return options.onComplete(resultobj);
+				}
+				else if(atom.target.isURI()){//get children
+					atom.target.value.descendants(options.childDepth).each(function(){
+						results.push(atom.source.value, this);
+						});
+					resultobj.filter(atom.source.value, results.get(atom.source.value));
+					resultobj.bind(results.getArray());
+					return options.onComplete(resultobj);
+				}
+				else{//both undefined
+					return this.error('Unsupported SubClassOf query');
+				}
+			},
+			"Type" : function(syntax, resultobj, options){
+				var atom = new jOWL.SPARQL_DL.DoubleAtom(syntax, resultobj.head);
+			
+			function addIndividual(cl){
+				if(indivs[this.URI]){ return;}
+				var b = results.push(atom.source.value, this);
+				if(addTarget){  b.bind(atom.target.value, cl);}
+				indivs[this.URI] = true;
+			}
+
+			function traverse(node, match){
+					var a = node.parents();
+					var found = false;
+					if(a.contains(match)){ found = true;}
+					else { 
+						a.each(function(){
+							if(this == jOWL.Thing){ return;}
+							if(!found && traverse(this, match)){ found = true;} });
+						}
+					return found;
+				}
+				
+				if(atom.source.isURI() && atom.target.isURI()){//assert
+					return jOWL.SPARQL_DL.priv.assert(resultobj, function(){
+						var cl = atom.source.value.owlClass();
+						if(cl.URI == atom.target.value.URI){ return true;}
+						return traverse(cl, atom.target.value); 
+					}, options.onComplete);
+				}
+				else if(atom.source.getURIs() && !atom.target.getURIs()){//get class
+					var results = new SPARQL_DL_Array();
+					var addSource = !atom.source.isURI();
+					var addTarget = !atom.target.isURI();
+					 atom.source.getURIs().each(function(){
+						var b;
+						if(addTarget){ b = results.push(atom.target.value, this.owlClass());}
+						if(addSource){ 
+							if(addTarget){ b.bind(atom.source.value, this);}
+							else {results.push(atom.source.value, this);}
+						}
+					 });
+					if(addSource){  resultobj.filter(atom.source.value, results.get(atom.source.value));}
+					if(addTarget){  resultobj.filter(atom.target.value, results.get(atom.target.value));}
+					resultobj.bind(results.getArray());
+					return options.onComplete(resultobj);
+				}
+				else if(atom.target.getURIs()){//get Individuals, slow
+					var addTarget = !atom.target.isURI();
+					var classlist = atom.target.getURIs(),
+						classes = {}, indivs = {};
+
+						var results = new SPARQL_DL_Array();
+
+
+						classlist.each(function(){ //expand list of classes, not very fast!
+							if(classes[this.URI]){ return;}
+							var oneOf = this.oneOf(), cl = this;
+							if(oneOf.length){ oneOf.each(function(){ addIndividual.call(this, cl);});}
+							else{ this.descendants(options.childDepth).each(function(){ //this is the slower call
+								classes[this.URI] = true;
+							}); }
+							classes[this.URI] = true;
+						});
+
+						for(x in classes){
+							var individuals = jOWL.index("Thing")[x];
+							if(individuals){
+								var cl = jOWL.index('ID')[x];
+								if(options.onUpdate){ options.onUpdate(individuals);}
+								individuals.each(function(){
+									addIndividual.call(this, cl);
+								});
+							}
+						}
+						resultobj.filter(atom.source.value, results.get(atom.source.value));
+						resultobj.bind(results.getArray());
+						return options.onComplete(resultobj);
+				}
+				return this.error('Unsupported Type query');
+			},
+			"Thing" : function(syntax, resultobj, options){
+				jOWL.SPARQL_DL.priv.IDQuery(syntax[0], "isThing", resultobj, options);
+			},
+			"Class" : function(syntax, resultobj, options){ console.log('cl');
+				jOWL.SPARQL_DL.priv.IDQuery(syntax[0], "isClass", resultobj, options);
+			},
+			"ObjectProperty" : function(syntax, resultobj, options){
+				jOWL.SPARQL_DL.priv.PropertyQuery(syntax[0], jOWL.index("property").items, "isObjectProperty", resultobj, options);
+			},
+			"DatatypeProperty" : function(syntax, resultobj, options){
+				jOWL.SPARQL_DL.priv.PropertyQuery(syntax[0], jOWL.index("property").items, "isDatatypeProperty", resultobj, options);
+			},
+			"PropertyValue" : function(syntax, resultobj, options){
+				var atom = new jOWL.SPARQL_DL.TripleAtom(syntax, resultobj.head);
+				
+				if(atom.source.isURI() && atom.property.isURI() && atom.target.isURI()){//assert
+					if(resultobj.assert !== false){
+						jOWL.SPARQL_DL.priv.PropertyValuegetSourceInfo(atom.source.value, atom.property.value, atom.target.value, resultobj, { assert : true });
+					}
+					return options.onComplete(resultobj);
+				}
+
+				if(!atom.source.getURIs()){
+					jOWL.SPARQL_DL.priv.IDQuery(atom.source.value, ["isClass", "isThing"], resultobj, options);
+					return;
+				}
+				var filterTarget = atom.target.isVar() ? atom.target.value : false;
+				var filterProperty = atom.property.isVar() ? atom.property.value : false;
+				var filterSource = atom.source.isVar() ? atom.source.value : false;
+				jOWL.SPARQL_DL.priv.PropertyValuegetSourceInfo(atom.source.getURIs(), atom.property.getURIs(), atom.target.getURIs(), resultobj,
+					{
+						filterTarget : filterTarget, filterProperty : filterProperty, filterSource : filterSource
+					});
+				return options.onComplete(resultobj);
+			}
+		}
+};
+
+jOWL.SPARQL_DL.priv = {
+	assert : function(resultobj, fn, onComplete){
+		if(resultobj.assert !== false){
+			resultobj.assert = fn();
+		}
+		onComplete(resultobj);	
+	},
+	//reusable function
+	PropertyValuegetSourceInfo : function(jSource, property, target, resultobj, options){
+		if(!(jSource.isArray)){
+			return jOWL.SPARQL_DL.priv.PropertyValuegetSourceInfo(new jOWL.Ontology.Array([jSource]), property, target, resultobj, options);
+		}
+		
+		options = $.extend({}, options);
+		var results = new SPARQL_DL_Array([options.filterSource, options.filterProperty, options.filterTarget]), 
+			match = false;
+		jSource.each(function(){
+			var source = this;
+			if(target && target.isArray && target.length == 1){
+				var literal = target.get(0).test;
+				if(literal){ target = literal;}//unwrap literal expressions
+			}
+			var restrictions = source.sourceof(property, target);
+			if(options.assert){
+				if(restrictions.length > 0){ match = true;}
+				return;
+			}
+			if(!restrictions.length){ return;}
+			restrictions.each(function(){
+				var binding = new _Binding(results);
+				if(options.filterSource){
+					binding.bind(options.filterSource, source);
+					if(!options.filterProperty && !options.filterTarget){ results.add(binding); return false;}
+				}
+				if(options.filterProperty){
+					binding.bind(options.filterProperty, this.property);
+				}
+				if(options.filterTarget){
+					binding.bind(options.filterTarget, this.getTarget());
+				}
+				results.add(binding);
+			});
+			return true;
+		});
+		if(options.assert){
+			resultobj.assert = match;
+			return resultobj.assert;
+		}
+		if(options.filterSource){ resultobj.filter(options.filterSource, results.get(options.filterSource));}
+		if(options.filterProperty){ resultobj.filter(options.filterProperty, results.get(options.filterProperty));}
+		if(options.filterTarget)  { resultobj.filter(options.filterTarget, results.get(options.filterTarget));}
+		resultobj.bind(results.getArray());
+	},
+	hasClassID: function(match, classID){
+		if(Object.prototype.toString.call(classID) === '[object Array]'){
+			for(var i =0;i<classID.length;i++){
+				if(match[classID]){ return true;}
+			}
+		} else if(match[classID]){  return true;}
+		return false;	
+	},
+	IDQuery : function(parameter, classID, resultobj, options){
+		var atom = new jOWL.SPARQL_DL.Atom(parameter, resultobj.head);
+		if(atom.isURI()){
+			return jOWL.SPARQL_DL.priv.assert(resultobj, function(){
+				return jOWL.SPARQL_DL.priv.hasClassID(atom.getURIs().get(0), classID);
+			}, options.onComplete);
+		}
+		var results = new SPARQL_DL_Array();
+		for(x in jOWL.index("ID")){
+			var match = jOWL.index("ID")[x];
+			if(jOWL.SPARQL_DL.priv.hasClassID(match, classID)){ results.push(parameter, match);}
+		}
+		resultobj.filter(parameter, results.get(parameter));
+		resultobj.bind(results.getArray());
+		options.onComplete(resultobj);
+	},
+	PropertyQuery : function(parameter, index, className, resultobj, options){
+		var atom = new jOWL.SPARQL_DL.Atom(parameter, resultobj.head);
+		if(atom.isURI()){
+			return jOWL.SPARQL_DL.priv.assert(resultobj, function(){
+				return jOWL.SPARQL_DL.priv.hasClassID(atom.getURIs().get(0), className);
+			}, options.onComplete);
+		}
+		var results = new SPARQL_DL_Array();
+		var tr = new jOWL.throttle(index, $.extend({}, options, {
+			modify : function(result){
+				if(!result.jOWL){ result = jOWL(result);}
+				if(jOWL.SPARQL_DL.priv.hasClassID(result, className)){results.push(parameter, result);}
+				return false;
+			},
+			onComplete : function(){
+				resultobj.filter(parameter, results.get(parameter));
+				resultobj.bind(results.getArray());
+				options.onComplete(resultobj);
+			}
+		}));
+	}
+};
+
+jOWL.SPARQL_DL.TripleAtom = function(syntax, store){
+	this.source = new jOWL.SPARQL_DL.Atom(syntax[0], store);
+	this.property = new jOWL.SPARQL_DL.Atom(syntax[1], store);
+	this.target = new jOWL.SPARQL_DL.Atom(syntax[2], store);
+};
+
+jOWL.SPARQL_DL.DoubleAtom = function(syntax, store){
+	this.source = new jOWL.SPARQL_DL.Atom(syntax[0], store);
+	this.target = new jOWL.SPARQL_DL.Atom(syntax[1], store);
+};
+
+
+jOWL.SPARQL_DL.Atom = function(syntax, store){
+	this.value = syntax;
+	this.type = 0;
+	if(typeof syntax == 'string'){
+		if(syntax.indexOf('?') === 0){
+			this.type = this.VAR;
+			if(store && store[syntax]){ this.mappings = store[syntax];}
+		} else {
+			this.type = this.LITERAL;
+		}
+	} else {
+		this.type = this.URI;
+	}
+};
+
+jOWL.SPARQL_DL.Atom.prototype = {
+	URI : 1, LITERAL : 2, VAR : 3,
+	getURIs : function(){
+		if(this.isURI()){return new jOWL.Ontology.Array([this.value]);}
+		return this.mappings;
+	},
+	isVar : function(){return this.type == this.VAR;},
+	isLiteral :  function(){return this.type == this.LITERAL;},
+	isURI : function(){ return this.type == this.URI;}
+};
+
+/**
+* @return Associative array of parameters in the current documents URL 
+*/
+jOWL.getURLParameters = function(){
+	var href = window.location.href.split("?", 2), param = {};
+	if(href.length == 1){ return {};}
+	var qstr = href[1].split('&');
+	for(var i =0;i<qstr.length;i++){
+		var arr = qstr[i].split("=");
+		if(arr.length == 2){ param[arr[0]] = arr[1];}
+	}
+	return param;
+};
+
+/**
+Without arguments this function will parse the current url and see if any parameters are defined, returns a JOWL object
+@return With argument it will return a string that identifies the potential permalink fr the given entry
+*/
+jOWL.permalink = function(entry){
+	if(!entry){
+		var param = jOWL.getURLParameters();
+		if(param.owlClass){ return jOWL(unescape(param.owlClass));}
+	}
+	else {
+		if(!entry.URI){ return false;}
+		var href = window.location.href.split("?", 2);
+		if(window.location.search){ href = href[0];}
+		if(entry.isClass){ return href+'?owlClass='+escape(entry.URI);}
+	}
+	return false;
+};
+
+/** Convert an item into Manchester syntax, currently only for oneOf 
+* @return String
+*/
+jOWL.Manchester = function(owlElement){
+	var syntax = [];
+	if(owlElement.isClass){
+		var oneOf = owlElement.oneOf().map(function(){ return this.label();});
+		if(oneOf.length){ syntax.push("{ "+oneOf.join(", ")+" }");}
+	}
+	return syntax.join(", ");
+};
+
+})(jQuery);
+
+/**
+* @return 1 for exact match, 0 for partial match, -1 for no match.
+*/
+String.prototype.searchMatch = function(matchstring, exact){
+	if(this.search(new RegExp(matchstring, "i")) > -1){ return 1;} //contained within
+	var c = 0; var arr = matchstring.match(new RegExp("\\w+", "ig"));
+	for(var i = 0;i<arr.length;i++){ if(this.search(arr[i]) > -1){ c++;} }
+	if(c == arr.length){ return 0;} //word shift
+	return -1; //nomatch
+};
+/**
+* @return Modified String.
+*/
+String.prototype.beautify = function(){
+	var e1 = new RegExp("([a-z0-9])([A-Z])", "g");
+	var e2 = new RegExp("([A-Z])([A-Z0-9])([a-z])", "g");
+	var e3 = new RegExp("_", "g");
+	return this.replace(e1, "$1 $2").replace(e2, "$1 $2$3").replace(e3, " ");
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/js/jOWL_UI.js	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,698 @@
+/*
+ * jOWL_UI, User Interface Elements for jOWL, semantic javascript library
+ * Creator - David Decraene
+ * Version 1.0
+ * Website: http://Ontologyonline.org
+ * Licensed under the MIT license
+ * Verified with JSLint http://www.jslint.com/
+ */
+(function($){
+
+jOWL.UI = {
+	broadcaster : function(){
+		var listeners = [];
+		this.addListener = function(obj){
+			function add(obj){if(obj.propertyChange) { listeners.push(obj); } }
+			if(obj.constructor == Array){for(var i=0;i<obj.length;i++){ add(obj[i]); } }
+			else { add(obj); }
+			return this; 
+		};
+		this.broadcast = function(item){ for(var i=0;i<listeners.length;i++){ listeners[i].propertyChange.call(item, item); } };
+		if(!this.propertyChange){ this.propertyChange = function(item){}; }
+	},
+	asBroadcaster : function(ui_elem){ ui_elem.broadcaster = jOWL.UI.broadcaster; ui_elem.broadcaster(); },
+	defaults : {
+		contentClass: "jowl-content",
+		focusClass: "ui-state-hover",
+		wrapperClass : "ui-widget-content"
+	}
+};
+
+/** 
+WIDGETS 
+all widgets: 
+	addListener : add an object with a propertyChange function, will be triggered on select
+	propertyChange: update the widget with a new jowl object
+Events:
+	onSelect: (look into propertylens click), return false to suppress the event, this = jquery element, first argument = jOWL object
+CSS:
+	wrapperClass: css class(es) for main element, the el itself
+	contentClass: css class(es) for main content element, accessible by el.content
+	focusClass: css class(es) for element in focus,
+*/
+$.fn.extend({
+/* 
+owl_navbar
+options:
+	onSelect : this element refers to jquery node, first argument = jOWL object
+*/
+	owl_navbar: function(options){
+		options = $.extend({
+			contentClass : jOWL.UI.defaults.contentClass,
+			focusClass : jOWL.UI.defaults.focusClass,
+			onSelect : function(item){},
+			onPropertyChange : function(item){}
+		}, options);
+		var self = this;
+		this.addClass("jowl-navbar");
+		this.content = $("."+options.contentClass, this).empty();
+			if(!this.content.length) { this.content = $("<div/>").addClass(options.contentClass).appendTo(this); }
+		this.parents =  $('<div/>').appendTo(this.content);
+		this.focus = $('<div/>').addClass(options.focusClass).appendTo(this.content);
+		this.children = $('<div/>').appendTo(this.content);
+		var listnode = $('<span/>').click(function(){
+			var node = $(this);
+			var res = jOWL(node.attr('title'));
+			if(options.onSelect.call(node, res) === false) { return; }
+			if(res && res.isClass) { self.propertyChange.call(res, res); self.broadcast(res); }
+		});
+
+		jOWL.UI.asBroadcaster(this);
+
+		this.propertyChange = function(item){
+			if(options.onPropertyChange.call(this, item) === false) { return; }
+			if(item.isClass){
+				item.bind(self.focus);
+				if(jOWL.options.reason) { item.hierarchy();}
+				self.parents.empty().append(item.parents().bind(listnode));
+				self.children.empty().append(item.children().bind(listnode));
+			}
+		};
+		return this;
+	},
+/** 
+autocomplete field.
+*/
+	owl_autocomplete : function(options){
+		options = $.extend({
+			time:500, //responsetime to check for new keystrokes, default 500
+			chars:3, //number of characters needed before autocomplete starts searching
+			focus:false, //put cursor on the input field when loading
+			limit:10, //limit size of result list to given amount
+			contentClass : "ui-widget-content",
+			focusClass : jOWL.UI.defaults.focusClass,
+			hintClass : "ui-autocomplete-hint",
+			hint: false, //Message (if any) to show when unfocused.
+			onSelect : function(item){}, //function that can be overridden
+			formatListItem : function(listitem, type, identifier, termarray){ //formatting of results, can be overridden
+				if(type){ listitem.append($('<div class="type"/>').text(type)); }
+				listitem.append($('<div class="name"/>').text(identifier));
+				if(termarray.length) { listitem.append($('<div class="terms"/>').text(termarray.join(', '))
+					.prepend($('<span/>').addClass('termlabel').text("Terms: ")));
+			}
+		}}, options);
+		jOWL.UI.asBroadcaster(this);
+
+		this.showHint = function(){
+			this.hinted = true;
+			if(options.hint){
+				this.addClass(options.hintClass).val(options.hint);
+			}
+			else {this.val(''); }
+		};
+		this.showHint();
+
+		var self = this; var old = ''; var open = false; self.val('');
+		var results = $('<ul/>').addClass(options.contentClass).addClass("jowl_autocomplete_results");
+		var div = $("<div/>").addClass(options.wrapperClass).append(results); this.after(div);
+		results.cache = {};
+		results.isEmpty = function(){ for(x in results.cache) { return false; } return true; };
+		results.close = function(){this.hide();};
+		results.open = function(q, cache){
+			this.show(); 
+			if(q){
+				if(!cache || results.isEmpty()) { results.cache = jOWL.query(q, options); }
+				else { 
+					var newcache = {};
+					for(x in results.cache){
+						var entry = results.cache[x]; 
+						var found = false;
+						var newentries = [];
+						if(x.searchMatch(q) > -1) { found = true; }
+						for(var i = 0;i<entry.length;i++){
+							if(entry[i].term.searchMatch(q) > -1) { found = true; newentries.push(entry[i]); }
+						}
+						if(found) { newcache[x] = newentries; }
+						}
+					results.cache = newcache;
+					}
+				this.populate(results.cache);
+				}
+		};
+
+		results.populate = function(data){
+			var res = this; this.empty(); var count =0;
+			var clickFunction = function(){
+				var node = $(this), res = jOWL(node.data("jowltype"));
+				if(options.onSelect.call(node, res) === false) { return; }
+				self.broadcast(res);
+			};
+
+			var onHover = function(){ $(this).addClass(options.focusClass); };
+			var offHover = function(){$(this).removeClass(options.focusClass);};
+
+			for(x in data){
+				if(count < options.limit){
+					var item = data[x];
+					var v = jOWL.isExternal(x);
+					v = v ? v[1] : x;
+					var list = $('<li/>').data("jowltype", x)
+					.click(clickFunction).hover(onHover, offHover)
+					.appendTo(res);
+					var terms = [];
+					for(var l = 0;l<item.length;l++){ 
+						var found = false; var newterm = item[l].term;
+						for(var y=0; y < terms.length;y++){ if(terms[y].toLowerCase() == newterm.toLowerCase()) { found = true; } }
+						if(!found) { terms.push(newterm); }
+						}
+					options.formatListItem.call(list, list, item[0].type, v, terms);
+
+				}
+				count++;
+			}
+		};
+
+		setInterval(function(){
+			var newvalue = self.val();
+			var cache = true;
+			if(old != newvalue){
+				var longervalue = newvalue.length > old.length && newvalue.indexOf(old) === 0;
+				if(!old) { cache = false; }
+				old = newvalue; 
+				if(newvalue.length < options.chars && open){ results.close();open = false;}
+				else if(newvalue.length >=options.chars && newvalue.length > 0){
+					if(cache) { cache = longervalue && newvalue.length > options.chars; }
+					results.open(newvalue, cache);
+					open = true;
+					}
+				
+			}
+		}, options.time);
+
+		self.bind('keyup', function(){ if(!this.value.length){ results.close(); open = false; } });
+		self.bind('blur', function(){
+			if(open){setTimeout(function(){results.close();}, 200);open = false;}
+			if(!self.val().length){self.showHint();}
+			});
+		//timeout for registering clicks on results.
+		self.bind('focus', function(){
+			if(self.hinted){
+				self.hinted = false;
+				$(this).removeClass(options.hintClass).val('');
+			}
+			if(self.val().length && !open){results.open('', open);open = true;}});
+		//reopen, but do not get results
+		return this;
+	},
+/** 
+Tree View
+*/
+	owl_treeview : function(options){
+		options = $.extend({
+			contentClass : jOWL.UI.defaults.contentClass,
+			focusClass: "focus",
+			nameClass: "name",
+			treeClass: "jowl-treeview",
+			rootClass: "root",
+			onSelect : function(item){}, //function that can be overwritten to specfy behavior when something is selected
+			rootThing : false, //if true then topnode is (owl) 'Thing'
+			isStatic : false, // if static then selections will refresh the entire tree
+			addChildren : false //add a given objects children to the treeview as well
+		}, options);
+
+		/** construct the hierarchy & make a tree of it */
+		function TreeModel(owlobject){
+			
+			function clear(el){ //reset invParents, for later use.
+				if(el.parents) { el.parents().each(function(){
+					this.invParents = null; clear(this);
+				}); }
+			}
+
+			function leaf(node){
+				node.jnode.addClass(options.focusClass);
+				if(options.addChildren){
+					var entry = jOWL(node.$name.attr('title'));
+					if(entry && entry.children){ entry.children().each(function(){ node.add(this); }); } }
+			}
+
+			function traverse(itemarray, appendto){
+				if(!itemarray) { return; }
+				itemarray.each(function(){
+					var node = appendto.add(this);
+					if(this.invParents){ traverse(this.invParents, node); }
+					else { leaf(node); }
+				});
+
+			}	
+
+			var h = owlobject.hierarchy(true);
+			if(options.rootThing) { traverse(h, tree.root(jOWL("Thing"))); }
+			else { 
+				var root = tree.root(h); 
+				for(var i=0;i<root.length;i++){ 
+						traverse(root[i].invParents, root[i]);
+						if(!root[i].invParents) { leaf(root[i]); }
+					}
+
+				}
+			clear(owlobject);
+
+		}
+
+		/**
+		var tree = $(selector).owl_treeview();
+		var root = tree.root("node");
+		root.add("node2").add("child");
+		*/
+		function Tree(node, treemodel, options){
+			var rack = $('<ul/>').addClass(options.treeClass).appendTo(node);
+			var tree = this;
+			/**item can be text, a jOWL object, or a jOWL array */
+			this.root = function(item){
+				var rt = null; //root
+				rack.empty();  
+				if(item && item.each) {
+					rt = [];
+					item.each(function(it){
+						var x =  new fn.node(it, true); 
+						x.wrapper.addClass("tv");
+						x.jnode.appendTo(rack);
+						x.invParents = it.invParents; it.invParents = null;	//reset for later use
+						rt.push(x);
+					}); 
+					return rt;
+				}
+				rt = new fn.node(item, true);
+				rt.wrapper.addClass("tv"); 
+				rt.jnode.appendTo(rack);
+				return rt;
+			};
+
+			var fn = {};
+			fn.node = function(text, isRoot){ //creates a new node
+				this.jnode = isRoot ? $('<li/>').addClass(options.rootClass) : $('<li class="tvi"/>');
+				this.$name = null;
+				if(text){
+					this.$name = $('<span/>').addClass(options.nameClass);
+					if(typeof text == "string") { this.$name.html(text); }
+					else if(text.bind) { text.bind(this.$name); }
+					var n = this.$name; 
+					this.$name.appendTo(this.jnode).click(function(){
+						var entry = jOWL(n.attr('title'));
+						if(entry && options.onSelect.call(n, entry) === false) { return; }
+						tree.broadcast(entry); 
+						if(options.isStatic) { tree.propertyChange(entry); }
+						return false;});
+				}
+				
+				this.wrapper = $('<ul/>').appendTo(this.jnode);
+				var self = this;
+					self.jnode.click(function(){toggle(); return false;});
+
+				this.add = function(text){
+					var nn = new fn.node(text);
+					if(!self.wrapper.children().length) { toNode();	}
+					else { 
+						var lastchild = self.wrapper.children(':last'); 
+						lastchild.swapClass("tvilc", "tvic");
+						lastchild.swapClass("tvile", "tvie");
+						lastchild.swapClass("tvil", "tvi");
+						
+						}//children - change end of list
+					self.wrapper.append(nn.jnode.swapClass('tvi', 'tvil'));
+					return nn;
+					};
+
+				function toggle(){ 
+					var t = self.jnode.hasClass("tvic") || self.jnode.hasClass("tvie") || self.jnode.hasClass("tvilc") || self.jnode.hasClass("tvile");
+					if(!t) { return; }
+					self.jnode.swapClass('tvic', 'tvie'); self.jnode.swapClass('tvilc', 'tvile');
+					self.wrapper.slideToggle();
+					}
+
+				function toNode(){ 
+					self.jnode.swapClass('tvil', 'tvilc');
+					self.jnode.swapClass('tvi', 'tvic');
+					}
+				};
+				return this;
+		}// end Tree
+
+		this.addClass("jowl-tree");
+		this.content = $("."+options.contentClass, this).empty();
+		if(!this.content.length){ this.content = $('<div/>').addClass(options.contentClass).appendTo(this); }
+		var tree = new Tree(this.content, null, options);
+		jOWL.UI.asBroadcaster(tree);
+		tree.propertyChange = function(item){ if(item.isClass) { var m = new TreeModel(item); } };
+		return tree;
+	},
+/** Uses templating 	
+options: 
+onChange: owl:Class, owl:Thing, etc..., tell the widget what to do with the different kinds of Ontology Objects.
+"data-jowl" : {split: ",  ", "somevariable" : function_triggered_for_each_result } 
+   example: "rdfs:label" : {split: ",  ", "rdfs:label" : function(){ //'this' keyword refers to HTML element}} )
+   example: "sparql-dl:PropertyValue(owl:Class, ?p, ?x)" : {"?p": function(){ //'this' keyword refers to HTML element }}
+   //prefil: for sparql-dl queries
+   //onComplete: function to trigger when the specific propertybox query is completed, this refers to the HTML element for propertybox
+   //sort: sort results on specified parameter, for sparql-dl results.
+onUpdate: called when the widget updates itself
+*/
+		owl_propertyLens : function(options){ 
+			var self = this;
+			self.options = $.extend({
+				backlinkClass : "backlink",
+				split: {},
+				disable : {},
+				click : {}},
+				options);
+			self.resourcetype = this.attr('data-jowl') || "owl:Class";
+			var propertyboxes = [];
+			$('.propertybox', this).each(function(){
+				var node = new jOWL.UI.PropertyBox($(this), self);
+				propertyboxes.push(node);
+				node.el.hide();
+			});
+			var backlink = $('.backlink', this).hide();
+			if(!backlink.length) { backlink = $('<div class="jowl_link"/>').addClass(self.options.backlinkClass).text("Back").hide().appendTo(this); }
+			jOWL.UI.asBroadcaster(this);
+
+			/** fn: optional function to execute*/
+			this.link = function(source, target, htmlel, fn){
+				htmlel.addClass("jowl_link").click(function(){
+				if(fn) { fn(); }
+				self.broadcast(target);
+				self.propertyChange(target);
+				backlink.source = source.name;
+				backlink.show().unbind('click').click(function(){
+					self.broadcast(source); self.propertyChange(source); backlink.hide();
+				});
+
+				});
+
+			};
+
+			var action = {
+				"rdfs:label": function(item){ return [{"rdfs:label": item.label() }]; },
+				"rdf:ID" : function(item){ return [{"rdf:ID": [item.name, item] }]; },
+				"rdfs:comment" : function(item){
+					return $.map(item.description(), function(n){return {"rdfs:comment":n }; });
+					},
+				"rdf:type" : function(item){
+					if(item.owlClass) { return [{"rdf:type": item.owlClass() }]; }
+					return [{"rdf:type": item.type }];
+				},
+				"term" : function(item){
+					return $.map(item.terms(), function(n, i){ return { "term" : n[0] }; });
+				},
+				"rdfs:range": function(item){if(item.range) { return [{"rdfs:range": item.range }]; } },
+				"rdfs:domain": function(item){if(item.domain) { return [{"rdfs:domain": item.domain }]; } },
+				"permalink": function(item){
+					var href = jOWL.permalink(item);
+					return [{"permalink": "<a href='"+href+"'>Permalink</a>" }];
+				},
+				"owl:disjointWith": function(item){
+					if(!(item.isClass)) { return; }
+					return $.map(
+							jOWL.Xpath('*', item.jnode)
+								.filter(function(){return this.nodeName == "owl:disjointWith"; }), 
+							function(n, i){ return {"owl:disjointWith": jOWL($(n).RDF_Resource())};
+							});	
+				},
+				"default" : function(item){
+					var type = this.attr("data-jowl");
+					return $.map(
+								jOWL.Xpath('*', item.jnode).filter(function(){return this.nodeName == type; }),
+								function(n, i){ var x = {}; x[type] = $(n).text(); return x; }
+								);	
+				}
+			};
+
+			this.propertyChange = function(item){ 
+				if(!item) { return; }
+				self.property = item;
+				if(backlink.source != item.name) { backlink.hide(); } else { backlink.source = false; }
+				
+				if(item.type != self.resourcetype){
+					if(item.isDatatypeProperty && self.resourcetype == "rdf:Property") {}
+					else if(item.isObjectProperty && self.resourcetype == "rdf:Property"){}
+					else { return; }
+				}
+
+				for(var i = 0;i<propertyboxes.length;i++){
+					var pbox = propertyboxes[i];
+					pbox.clear();
+					if(!pbox.actiontype){return; }
+					var actiontype = pbox.actiontype;
+					if(self.options.disable[actiontype]) { return; }
+
+					if(!self.options[actiontype]) { self.options[actiontype] = {}; }
+
+					if(actiontype.indexOf("sparql-dl:") === 0){ 
+						var query = actiontype.split("sparql-dl:", 2)[1];
+						var fill = {}; fill[self.resourcetype] = item;
+						if(self.options[actiontype].prefill) { $.extend(fill, self.options[actiontype].prefill); }
+						var qr = new jOWL.SPARQL_DL(query, fill).execute({onComplete : function(r){
+							if(self.options[actiontype].sort) { r.sort(self.options[actiontype].sort); }
+							pbox.setResults(r.results, item);
+							}});
+					}
+					else {
+						var choice = (action[actiontype]) ? actiontype : "default";
+						var results = action[choice].call(pbox.valuebox, item);
+						pbox.setResults(results, item);
+					}
+				}
+					
+					if(self.options.onUpdate) { self.options.onUpdate.call(this, item); }
+			}; //end property change
+		
+		if(self.options.tooltip){
+			var lens = this.remove();
+			this.display = function(element, htmlel){
+				htmlel.tooltip({
+					title: element.label(), 
+					html: function(){	lens.propertyChange(element); backlink.hide(); return lens.get(0); }
+				}); 
+			};
+		}
+		return this;
+		},
+
+/**
+Use propertyChange to set the class
+Use addField to add property refinements
+Use serialize to serialize input
+*/
+	owl_datafield: function(options){
+		options = $.extend({
+			selectorClass : "jowl-datafield-selector",
+			messageClass : "jowl-datafield-message",
+			labelClass : "jowl-datafield-property-label"
+		}, options);
+		var self = this;
+		var pArray = {}; //associative array for properties.
+		this.messages = {};
+		this.messages[jOWL.NS.xsd()+"positiveInteger"] = "Allowed values: positive numbers or comparisons like  '>5 && <15' ";
+
+		this.addClass("owl_UI");
+		jOWL.UI.asBroadcaster(this);
+
+		this.property = null;
+
+		this.propertyChange = function(item){
+			if(item.isClass){
+				this.property = item;
+					for(x in pArray){//reset all properties
+						if(pArray[x].remove){ pArray[x].remove(); delete pArray[x]; }
+					}
+			}
+		};
+
+		/** Sets up a new field */
+        this.addField = function(property){
+            if(pArray[property.URI]){
+                //allow for multiple fields?
+				return;
+            }
+
+			var $content = $("<div/>");
+				 pArray[property.URI] = $content;
+
+			var $title = property.bind($("<div/>")).addClass(options.labelClass).appendTo($content).click(function(){ $content.remove(); delete pArray[property.URI]; });
+
+            if(property.isObjectProperty){
+
+				var sp = new jOWL.SPARQL_DL("Type(?t, ?c),PropertyValue(concept, property, ?c)", {concept : self.property, property : property }).execute({ 
+					onComplete : function(obj){
+						if(!obj.results.length){ return; } //modify to deal with non value results
+						obj.sort("?t");
+						
+						$select = $("<select class='"+options.selectorClass+"'/>").appendTo($content);
+
+						for(var i=0;i<obj.results.length;i++){
+							obj.results[i]['?t'].bind($("<option/>")).appendTo($select);
+						}
+
+						$content.appendTo(self);
+					}});
+
+            }
+            else if(property.isDatatypeProperty){
+				var msg ="";
+				if(self.messages[property.range]){ msg = self.messages[property.range];	}
+
+				var $input = $('<div/>').addClass(options.selectorClass).attr("title", property.range).append($('<input type="text" style="font-size:11px;width:100px;"/>'));
+				var $message = $('<div/>').addClass(options.messageClass).text(msg).appendTo($input);
+
+				$content.append($input).appendTo(self);
+				$('input', $content).focus(function(){
+					$message.animate({opacity: 1.0}, 1500).fadeOut();
+				});
+
+				
+			}
+
+		};
+
+		this.serialize = function(){
+			var q = { "Type": self.property, "PropertyValue" : [] };
+
+			$('.'+options.selectorClass, self).each(function(){
+				var $this = $(this);
+				var $prop = $this.siblings('.'+options.labelClass);
+				var prop = $prop.attr('title');
+				if( $this.is("select")){
+						var s = $this.get(0);
+						var thing = $(s[s.selectedIndex]).attr('title');
+						q.PropertyValue.push([prop, thing]);
+					}
+				else {
+					var $input = $this.find("input");
+					var datatype = $this.attr('title');
+					var entry = $input.get(0).value;
+					if(entry) { q.PropertyValue.push([prop, '"'+entry+'"']); }
+				}
+			});
+			return q;
+		};
+
+		return this;
+	}
+});
+
+/** Used by owl_PropertyLens */
+jOWL.UI.PropertyBox = function($el, resourcebox){
+	var v = $('[data-jowl]', $el);
+	if(v.length){	this.descendant = true;}
+
+	this.el = $el;
+	this.resourcebox = resourcebox;
+	this.valuebox = v.length ? v : $el;
+	this.actiontype = this.valuebox.attr('data-jowl'); 
+};
+
+jOWL.UI.PropertyBox.prototype = {
+	setResults : function(results, item){
+		var nodes = jOWL.UI.Template(results, this.valuebox, this.resourcebox.options[this.actiontype].split);
+		this.complete(nodes, item);
+		if(nodes && nodes.length && this.descendant) { this.el.show(); this.valuebox.hide(); } 
+		if(this.resourcebox.options[this.actiontype].onComplete) { this.resourcebox.options[this.actiontype].onComplete.call(this.el.get(0)); }	
+	},
+	complete : function(nodes, item){
+		var res = this.resourcebox;
+		if(!nodes || !nodes.length) { return; }
+		var v = $.data(nodes, "parameters"); 
+		for(x in v){ 
+			if(v[x].length && typeof res.options[this.actiontype][x] == "function") {
+				v[x].each(res.options[this.actiontype][x]);
+			}}
+		for(x in res.options.onChange){
+			var data = $('[typeof='+x+']', nodes).add(nodes.filter('[typeof='+x+']'));
+			if(x.charAt(0) == "." || x.charAt(0) == "#"){ data = data.add($(x, nodes));}
+			data.each(function(){
+				var node = $(this);
+				$.data(node, 'data-jowl', x);
+				var id = node.attr('title');
+				if(id != "anonymousOntologyObject") { res.options.onChange[$.data(node, 'data-jowl')].call(node, item, jOWL(id), res); }
+			});
+		}
+	},
+	clear : function(){
+		var prev = this.valuebox.prev('.jowl-template-result');
+		if(!prev.length){ prev = this.valuebox.prev('.jowl-template-splitter');}
+		if(prev.length) { prev.remove(); this.clear(this.valuebox); }
+	}
+};
+
+/**arr: associative array of variablrd, jqel: node for which variables need to be substituted,  */
+jOWL.UI.Template = function(arr, jqel, splitter){
+	var options = {
+		resultClass : "jowl-template-result",
+		splitterClass : "jowl-template-splitter"
+	};
+	if(!arr) { return; }
+
+	function bindObject(value, jnode){
+		var bound = false;
+		if(!value) { return false; }
+		if(typeof value == 'string') { jnode.html(value); bound = true;}
+		else if(value.constructor == Array){ 
+			if(value.length == 2) { value[1].bind(jnode).text(value[0]); bound = true;	} 
+			}
+		else if(value.bind){ value.bind(jnode); bound = true; }
+		return bound;
+	}
+	var count = 0, a = [], b = {};
+	var remnantFn = function(){
+		var txt = $(this).text(); 
+		if(txt.indexOf('${') === 0 && txt.lastIndexOf('}') == txt.length-1 ) { $(this).hide(); }
+	};
+	for(var i=0;i<arr.length;i++){ 
+		var x = jqel.clone(true).wrapInner("<"+jqel.get(0).nodeName+" class='"+options.resultClass+"'/>").children();
+		/** copy style settings */
+			x.addClass(jqel.attr('class')).removeClass('propertybox');
+		/** accepted obj types= string, array["string", "jowlobject"], jowlobject*/
+		for(obj in arr[i]){
+			if(!b[obj]) { b[obj] = []; }
+			var occurrences = $(':contains(${'+obj+'})', x);
+			if(!occurrences.length){
+				if(x.text() == "${"+obj+"}") { if(bindObject(arr[i][obj], x)) {
+					count++; b[obj].push(x.get(0));
+				}}
+			}
+			else { 
+				occurrences.each(function(){	
+					if(this.innerHTML == "${"+obj+"}") { var node = $(this); if(bindObject(arr[i][obj], node)) { count++;  b[obj].push(this); }	}
+				});
+			}
+		}
+		var remnants = $(':contains(${)', x); //hide parameters that weren't substituted
+			remnants.each(remnantFn);
+		if(count){
+			x.insertBefore(jqel);
+			a.push(x.get(0));
+			if(count > 1 && splitter) { 
+				$splitter = (splitter.indexOf('<') === 0) ? $(splitter) : $("<span/>").text(splitter);
+				$splitter.addClass(options.splitterClass).insertBefore(x);
+				}
+		}
+	}
+	for(x in b){ if(b[x].length) { b[x] = $(b[x]); } }
+	var nodes = $(a);
+	$.data(nodes, "parameters", b);
+	return nodes;
+};
+
+/** 
+Supporting functionality
+*/
+
+$.fn.swapClass = function(c1,c2) {
+	return this.each(function() {
+		if ($(this).hasClass(c1)) { $(this).removeClass(c1); $(this).addClass(c2);} 
+		else if ($(this).hasClass(c2)) {$(this).removeClass(c2);$(this).addClass(c1);}
+		});
+};
+
+})(jQuery);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/js/jquery.dataTables.js	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,4522 @@
+/*
+ * File:        jquery.dataTables.js
+ * Version:     1.5.2
+ * CVS:         $Id$
+ * Description: Paginate, search and sort HTML tables
+ * Author:      Allan Jardine (www.sprymedia.co.uk)
+ * Created:     28/3/2008
+ * Modified:    $Date$ by $Author$
+ * Language:    Javascript
+ * License:     GPL v2 or BSD 3 point style
+ * Project:     Mtaala
+ * Contact:     allan.jardine@sprymedia.co.uk
+ * 
+ * Copyright 2008-2009 Allan Jardine, all rights reserved.
+ *
+ * This source file is free software, under either the GPL v2 license or a
+ * BSD style license, as supplied with this software.
+ * 
+ * This source file is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ * 
+ * For details pleease refer to: http://www.datatables.net
+ */
+
+/*
+ * When considering jsLint, we need to allow eval() as it it is used for reading cookies and 
+ * building the dynamic multi-column sort functions.
+ */
+/*jslint evil: true, undef: true, browser: true */
+/*globals $, jQuery,_fnReadCookie,_fnProcessingDisplay,_fnDraw,_fnSort,_fnReDraw,_fnDetectType,_fnSortingClasses,_fnSettingsFromNode,_fnBuildSearchArray,_fnCalculateEnd,_fnFeatureHtmlProcessing,_fnFeatureHtmlPaginate,_fnFeatureHtmlInfo,_fnFeatureHtmlFilter,_fnFilter,_fnSaveState,_fnFilterColumn,_fnEscapeRegex,_fnFilterComplete,_fnFeatureHtmlLength,_fnGetDataMaster,_fnVisibleToColumnIndex,_fnDrawHead,_fnAddData,_fnGetTrNodes,_fnColumnIndexToVisible,_fnCreateCookie,_fnAddOptionsHtml,_fnMap,_fnClearTable,_fnDataToSearch,_fnReOrderIndex,_fnFilterCustom,_fnVisbleColumns,_fnAjaxUpdate,_fnAjaxUpdateDraw,_fnColumnOrdering,fnGetMaxLenString*/
+
+(function($) {
+	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+	 * DataTables variables
+	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+	
+	/*
+	 * Variable: dataTableSettings
+	 * Purpose:  Store the settings for each dataTables instance
+	 * Scope:    jQuery.fn
+	 */
+	$.fn.dataTableSettings = [];
+	
+	/*
+	 * Variable: dataTableExt
+	 * Purpose:  Container for customisable parts of DataTables
+	 * Scope:    jQuery.fn
+	 */
+	$.fn.dataTableExt = {};
+	var _oExt = $.fn.dataTableExt; /* Short reference for fast internal lookup */
+	
+	
+	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+	 * DataTables extensible objects
+	 * 
+	 * The _oExt object is used to provide an area where user dfined plugins can be 
+	 * added to DataTables. The following properties of the object are used:
+	 *   oApi - Plug-in API functions
+	 *   aTypes - Auto-detection of types
+	 *   oSort - Sorting functions used by DataTables (based on the type)
+	 *   oPagination - Pagination functions for different input styles
+	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+	
+	/*
+	 * Variable: sVersion
+	 * Purpose:  Version string for plug-ins to check compatibility
+	 * Scope:    jQuery.fn.dataTableExt
+	 */
+	_oExt.sVersion = "1.5.2";
+	
+	/*
+	 * Variable: iApiIndex
+	 * Purpose:  Index for what 'this' index API functions should use
+	 * Scope:    jQuery.fn.dataTableExt
+	 */
+	_oExt.iApiIndex = 0;
+	
+	/*
+	 * Variable: oApi
+	 * Purpose:  Container for plugin API functions
+	 * Scope:    jQuery.fn.dataTableExt
+	 */
+	_oExt.oApi = { };
+	
+	/*
+	 * Variable: aFiltering
+	 * Purpose:  Container for plugin filtering functions
+	 * Scope:    jQuery.fn.dataTableExt
+	 */
+	_oExt.afnFiltering = [ ];
+	
+	/*
+	 * Variable: aoFeatures
+	 * Purpose:  Container for plugin function functions
+	 * Scope:    jQuery.fn.dataTableExt
+	 * Notes:    Array of objects with the following parameters:
+	 *   fnInit: Function for initialisation of Feature. Takes oSettings and returns node
+	 *   cFeature: Character that will be matched in sDom - case sensitive
+	 *   sFeature: Feature name - just for completeness :-)
+	 */
+	_oExt.aoFeatures = [ ];
+	
+	/*
+	 * Variable: ofnSearch
+	 * Purpose:  Container for custom filtering functions
+	 * Scope:    jQuery.fn.dataTableExt
+	 * Notes:    This is an object (the name should match the type) for custom filtering function,
+	 *   which can be used for live DOM checking or formatted text filtering
+	 */
+	_oExt.ofnSearch = { };
+	
+	/*
+	 * Variable: oStdClasses
+	 * Purpose:  Storage for the various classes that DataTables uses
+	 * Scope:    jQuery.fn.dataTableExt
+	 */
+	_oExt.oStdClasses = {
+		/* Two buttons buttons */
+		"sPagePrevEnabled": "paginate_enabled_previous",
+		"sPagePrevDisabled": "paginate_disabled_previous",
+		"sPageNextEnabled": "paginate_enabled_next",
+		"sPageNextDisabled": "paginate_disabled_next",
+		"sPageJUINext": "",
+		"sPageJUIPrev": "",
+		
+		/* Full numbers paging buttons */
+		"sPageButton": "paginate_button",
+		"sPageButtonActive": "paginate_active",
+		"sPageButtonStaticActive": "paginate_button",
+		"sPageFirst": "first",
+		"sPagePrevious": "previous",
+		"sPageNext": "next",
+		"sPageLast": "last",
+		
+		/* Stripping classes */
+		"sStripOdd": "odd",
+		"sStripEven": "even",
+		
+		/* Empty row */
+		"sRowEmpty": "dataTables_empty",
+		
+		/* Features */
+		"sWrapper": "dataTables_wrapper",
+		"sFilter": "dataTables_filter",
+		"sInfo": "dataTables_info",
+		"sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
+		"sLength": "dataTables_length",
+		"sProcessing": "dataTables_processing",
+		
+		/* Sorting */
+		"sSortAsc": "sorting_asc",
+		"sSortDesc": "sorting_desc",
+		"sSortable": "sorting",
+		"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
+		"sSortJUIAsc": "",
+		"sSortJUIDesc": "",
+		"sSortJUI": ""
+	};
+	
+	/*
+	 * Variable: oJUIClasses
+	 * Purpose:  Storage for the various classes that DataTables uses - jQuery UI suitable
+	 * Scope:    jQuery.fn.dataTableExt
+	 */
+	_oExt.oJUIClasses = {
+		/* Two buttons buttons */
+		"sPagePrevEnabled": "fg-button ui-state-default ui-corner-left",
+		"sPagePrevDisabled": "fg-button ui-state-default ui-corner-left ui-state-disabled",
+		"sPageNextEnabled": "fg-button ui-state-default ui-corner-right",
+		"sPageNextDisabled": "fg-button ui-state-default ui-corner-right ui-state-disabled",
+		"sPageJUINext": "ui-icon ui-icon-circle-arrow-e",
+		"sPageJUIPrev": "ui-icon ui-icon-circle-arrow-w",
+		
+		/* Full numbers paging buttons */
+		"sPageButton": "fg-button ui-state-default",
+		"sPageButtonActive": "fg-button ui-state-default ui-state-disabled",
+		"sPageButtonStaticActive": "fg-button ui-state-default ui-state-disabled",
+		"sPageFirst": "first ui-corner-tl ui-corner-bl",
+		"sPagePrevious": "previous",
+		"sPageNext": "next",
+		"sPageLast": "last ui-corner-tr ui-corner-br",
+		
+		/* Stripping classes */
+		"sStripOdd": "odd",
+		"sStripEven": "even",
+		
+		/* Empty row */
+		"sRowEmpty": "dataTables_empty",
+		
+		/* Features */
+		"sWrapper": "dataTables_wrapper",
+		"sFilter": "dataTables_filter",
+		"sInfo": "dataTables_info",
+		"sPaging": "dataTables_paginate fg-buttonset fg-buttonset-multi paging_", /* Note that the type is postfixed */
+		"sLength": "dataTables_length",
+		"sProcessing": "dataTables_processing",
+		
+		/* Sorting */
+		"sSortAsc": "ui-state-default",
+		"sSortDesc": "ui-state-default",
+		"sSortable": "ui-state-default",
+		"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
+		"sSortJUIAsc": "css_right ui-icon ui-icon-triangle-1-n",
+		"sSortJUIDesc": "css_right ui-icon ui-icon-triangle-1-s",
+		"sSortJUI": "css_right ui-icon ui-icon-triangle-2-n-s"
+	};
+	
+	/*
+	 * Variable: oPagination
+	 * Purpose:  Container for the various type of pagination that dataTables supports
+	 * Scope:    jQuery.fn.dataTableExt
+	 */
+	_oExt.oPagination = {
+		/*
+		 * Variable: two_button
+		 * Purpose:  Standard two button (forward/back) pagination
+	 	 * Scope:    jQuery.fn.dataTableExt.oPagination
+		 */
+		"two_button": {
+			/*
+			 * Function: oPagination.two_button.fnInit
+			 * Purpose:  Initalise dom elements required for pagination with forward/back buttons only
+			 * Returns:  -
+	 		 * Inputs:   object:oSettings - dataTables settings object
+			 *           function:fnCallbackDraw - draw function which must be called on update
+			 */
+			"fnInit": function ( oSettings, fnCallbackDraw )
+			{
+				var nPaging = oSettings.anFeatures.p;
+				
+				/* Store the next and previous elements in the oSettings object as they can be very
+				 * usful for automation - particularly testing
+				 */
+				if ( !oSettings.bJUI )
+				{
+					oSettings.nPrevious = document.createElement( 'div' );
+					oSettings.nNext = document.createElement( 'div' );
+				}
+				else
+				{
+					oSettings.nPrevious = document.createElement( 'a' );
+					oSettings.nNext = document.createElement( 'a' );
+					
+					var nNextInner = document.createElement('span');
+					nNextInner.className = oSettings.oClasses.sPageJUINext;
+					oSettings.nNext.appendChild( nNextInner );
+					
+					var nPreviousInner = document.createElement('span');
+					nPreviousInner.className = oSettings.oClasses.sPageJUIPrev;
+					oSettings.nPrevious.appendChild( nPreviousInner );
+				}
+				
+				if ( oSettings.sTableId !== '' )
+				{
+					nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
+					oSettings.nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
+					oSettings.nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
+				}
+				
+				oSettings.nPrevious.className = oSettings.oClasses.sPagePrevDisabled;
+				oSettings.nNext.className = oSettings.oClasses.sPageNextDisabled;
+				
+				oSettings.nPrevious.title = oSettings.oLanguage.oPaginate.sPrevious;
+				oSettings.nNext.title = oSettings.oLanguage.oPaginate.sNext;
+				
+				nPaging.appendChild( oSettings.nPrevious );
+				nPaging.appendChild( oSettings.nNext );
+				$(nPaging).insertAfter( oSettings.nTable );
+				
+				$(oSettings.nPrevious).click( function() {
+					oSettings._iDisplayStart -= oSettings._iDisplayLength;
+					
+					/* Correct for underrun */
+					if ( oSettings._iDisplayStart < 0 )
+					{
+					  oSettings._iDisplayStart = 0;
+					}
+					
+					fnCallbackDraw( oSettings );
+				} );
+				
+				$(oSettings.nNext).click( function() {
+					/* Make sure we are not over running the display array */
+					if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
+					{
+						oSettings._iDisplayStart += oSettings._iDisplayLength;
+					}
+					
+					fnCallbackDraw( oSettings );
+				} );
+				
+				/* Take the brutal approach to cancelling text selection */
+				$(oSettings.nPrevious).bind( 'selectstart', function () { return false; } );
+				$(oSettings.nNext).bind( 'selectstart', function () { return false; } );
+			},
+			
+			/*
+			 * Function: oPagination.two_button.fnUpdate
+			 * Purpose:  Update the two button pagination at the end of the draw
+			 * Returns:  -
+	 		 * Inputs:   object:oSettings - dataTables settings object
+			 *           function:fnCallbackDraw - draw function which must be called on update
+			 */
+			"fnUpdate": function ( oSettings, fnCallbackDraw )
+			{
+				if ( !oSettings.anFeatures.p )
+				{
+					return;
+				}
+				
+				oSettings.nPrevious.className = 
+					( oSettings._iDisplayStart === 0 ) ? 
+					oSettings.oClasses.sPagePrevDisabled : oSettings.oClasses.sPagePrevEnabled;
+				
+				oSettings.nNext.className = 
+					( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) ? 
+					oSettings.oClasses.sPageNextDisabled : oSettings.oClasses.sPageNextEnabled;
+			}
+		},
+		
+		
+		/*
+		 * Variable: iFullNumbersShowPages
+		 * Purpose:  Change the number of pages which can be seen
+	 	 * Scope:    jQuery.fn.dataTableExt.oPagination
+		 */
+		"iFullNumbersShowPages": 5,
+		
+		/*
+		 * Variable: full_numbers
+		 * Purpose:  Full numbers pagination
+	 	 * Scope:    jQuery.fn.dataTableExt.oPagination
+		 */
+		"full_numbers": {
+			/*
+			 * Function: oPagination.full_numbers.fnInit
+			 * Purpose:  Initalise dom elements required for pagination with a list of the pages
+			 * Returns:  -
+	 		 * Inputs:   object:oSettings - dataTables settings object
+			 *           function:fnCallbackDraw - draw function which must be called on update
+			 */
+			"fnInit": function ( oSettings, fnCallbackDraw )
+			{
+				var nPaging = oSettings.anFeatures.p;
+				var nFirst = document.createElement( 'span' );
+				var nPrevious = document.createElement( 'span' );
+				var nList = document.createElement( 'span' );
+				var nNext = document.createElement( 'span' );
+				var nLast = document.createElement( 'span' );
+				
+				nFirst.innerHTML = oSettings.oLanguage.oPaginate.sFirst;
+				nPrevious.innerHTML = oSettings.oLanguage.oPaginate.sPrevious;
+				nNext.innerHTML = oSettings.oLanguage.oPaginate.sNext;
+				nLast.innerHTML = oSettings.oLanguage.oPaginate.sLast;
+				
+				var oClasses = oSettings.oClasses;
+				nFirst.className = oClasses.sPageButton+" "+oClasses.sPageFirst;
+				nPrevious.className = oClasses.sPageButton+" "+oClasses.sPagePrevious;
+				nNext.className= oClasses.sPageButton+" "+oClasses.sPageNext;
+				nLast.className = oClasses.sPageButton+" "+oClasses.sPageLast;
+				
+				if ( oSettings.sTableId !== '' )
+				{
+					nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
+					nFirst.setAttribute( 'id', oSettings.sTableId+'_first' );
+					nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
+					nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
+					nLast.setAttribute( 'id', oSettings.sTableId+'_last' );
+				}
+				
+				nPaging.appendChild( nFirst );
+				nPaging.appendChild( nPrevious );
+				nPaging.appendChild( nList );
+				nPaging.appendChild( nNext );
+				nPaging.appendChild( nLast );
+				
+				$(nFirst).click( function () {
+					oSettings._iDisplayStart = 0;
+					fnCallbackDraw( oSettings );
+				} );
+				
+				$(nPrevious).click( function() {
+					oSettings._iDisplayStart -= oSettings._iDisplayLength;
+					
+					/* Correct for underrun */
+					if ( oSettings._iDisplayStart < 0 )
+					{
+					  oSettings._iDisplayStart = 0;
+					}
+					
+					fnCallbackDraw( oSettings );
+				} );
+				
+				$(nNext).click( function() {
+					/* Make sure we are not over running the display array */
+					if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
+					{
+						oSettings._iDisplayStart += oSettings._iDisplayLength;
+					}
+					
+					fnCallbackDraw( oSettings );
+				} );
+				
+				$(nLast).click( function() {
+					var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
+					oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength;
+					
+					fnCallbackDraw( oSettings );
+				} );
+				
+				/* Take the brutal approach to cancelling text selection */
+				$('span', nPaging).bind( 'mousedown', function () { return false; } );
+				$('span', nPaging).bind( 'selectstart', function () { return false; } );
+				
+				oSettings.nPaginateList = nList;
+			},
+			
+			/*
+			 * Function: oPagination.full_numbers.fnUpdate
+			 * Purpose:  Update the list of page buttons shows
+			 * Returns:  -
+	 		 * Inputs:   object:oSettings - dataTables settings object
+			 *           function:fnCallbackDraw - draw function which must be called on update
+			 */
+			"fnUpdate": function ( oSettings, fnCallbackDraw )
+			{
+				if ( !oSettings.anFeatures.p )
+				{
+					return;
+				}
+				
+				var iPageCount = jQuery.fn.dataTableExt.oPagination.iFullNumbersShowPages;
+				var iPageCountHalf = Math.floor(iPageCount / 2);
+				var iPages = Math.ceil((oSettings.fnRecordsDisplay()) / oSettings._iDisplayLength);
+				var iCurrentPage = Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength) + 1;
+				var sList = "";
+				var iStartButton;
+				var iEndButton;
+				var oClasses = oSettings.oClasses;
+				
+				if (iPages < iPageCount)
+				{
+					iStartButton = 1;
+					iEndButton = iPages;
+				}
+				else
+				{
+					if (iCurrentPage <= iPageCountHalf)
+					{
+						iStartButton = 1;
+						iEndButton = iPageCount;
+					}
+					else
+					{
+						if (iCurrentPage >= (iPages - iPageCountHalf))
+						{
+							iStartButton = iPages - iPageCount + 1;
+							iEndButton = iPages;
+						}
+						else
+						{
+							iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1;
+							iEndButton = iStartButton + iPageCount - 1;
+						}
+					}
+				}
+				
+				for ( var i=iStartButton ; i<=iEndButton ; i++ )
+				{
+					if ( iCurrentPage != i )
+					{
+						sList += '<span class="'+oClasses.sPageButton+'">'+i+'</span>';
+					}
+					else
+					{
+						sList += '<span class="'+oClasses.sPageButtonActive+'">'+i+'</span>';
+					}
+				}
+				
+				oSettings.nPaginateList.innerHTML = sList;
+				
+				/* Take the brutal approach to cancelling text selection */
+				$('span', oSettings.nPaginateList).bind( 'mousedown', function () { return false; } );
+				$('span', oSettings.nPaginateList).bind( 'selectstart', function () { return false; } );
+				
+				$('span', oSettings.nPaginateList).click( function() {
+					var iTarget = (this.innerHTML * 1) - 1;
+					oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength;
+					
+					fnCallbackDraw( oSettings );
+					return false;
+				} );
+				
+				/* Update the 'premanent botton's classes */
+				var nButtons = $('span', oSettings.anFeatures.p);
+				var nStatic = [ nButtons[0], nButtons[1], nButtons[nButtons.length-2], nButtons[nButtons.length-1] ];
+				$(nStatic).removeClass( oClasses.sPageButton+" "+oClasses.sPageButtonActive );
+				if ( iCurrentPage == 1 )
+				{
+					nStatic[0].className += " "+oClasses.sPageButtonStaticActive;
+					nStatic[1].className += " "+oClasses.sPageButtonStaticActive;
+				}
+				else
+				{
+					nStatic[0].className += " "+oClasses.sPageButton;
+					nStatic[1].className += " "+oClasses.sPageButton;
+				}
+				
+				if ( iCurrentPage == iPages )
+				{
+					nStatic[2].className += " "+oClasses.sPageButtonStaticActive;
+					nStatic[3].className += " "+oClasses.sPageButtonStaticActive;
+				}
+				else
+				{
+					nStatic[2].className += " "+oClasses.sPageButton;
+					nStatic[3].className += " "+oClasses.sPageButton;
+				}
+			}
+		}
+	};
+	
+	/*
+	 * Variable: oSort
+	 * Purpose:  Wrapper for the sorting functions that can be used in DataTables
+	 * Scope:    jQuery.fn.dataTableExt
+	 * Notes:    The functions provided in this object are basically standard javascript sort
+	 *   functions - they expect two inputs which they then compare and then return a priority
+	 *   result. For each sort method added, two functions need to be defined, an ascending sort and
+	 *   a descending sort.
+	 */
+	_oExt.oSort = {
+		/*
+		 * text sorting
+		 */
+		"string-asc": function ( a, b )
+		{
+			var x = a.toLowerCase();
+			var y = b.toLowerCase();
+			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+		},
+		
+		"string-desc": function ( a, b )
+		{
+			var x = a.toLowerCase();
+			var y = b.toLowerCase();
+			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+		},
+		
+		
+		/*
+		 * html sorting (ignore html tags)
+		 */
+		"html-asc": function ( a, b )
+		{
+			var x = a.replace( /<.*?>/g, "" ).toLowerCase();
+			var y = b.replace( /<.*?>/g, "" ).toLowerCase();
+			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+		},
+		
+		"html-desc": function ( a, b )
+		{
+			var x = a.replace( /<.*?>/g, "" ).toLowerCase();
+			var y = b.replace( /<.*?>/g, "" ).toLowerCase();
+			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+		},
+		
+		
+		/*
+		 * date sorting
+		 */
+		"date-asc": function ( a, b )
+		{
+			var x = Date.parse( a );
+			var y = Date.parse( b );
+			
+			if ( isNaN( x ) )
+			{
+    		x = Date.parse( "01/01/1970 00:00:00" );
+			}
+			if ( isNaN( y ) )
+			{
+				y =	Date.parse( "01/01/1970 00:00:00" );
+			}
+			
+			return x - y;
+		},
+		
+		"date-desc": function ( a, b )
+		{
+			var x = Date.parse( a );
+			var y = Date.parse( b );
+			
+			if ( isNaN( x ) )
+			{
+    		x = Date.parse( "01/01/1970 00:00:00" );
+			}
+			if ( isNaN( y ) )
+			{
+				y =	Date.parse( "01/01/1970 00:00:00" );
+			}
+			
+			return y - x;
+		},
+		
+		
+		/*
+		 * numerical sorting
+		 */
+		"numeric-asc": function ( a, b )
+		{
+			var x = a == "-" ? 0 : a;
+			var y = b == "-" ? 0 : b;
+			return x - y;
+		},
+		
+		"numeric-desc": function ( a, b )
+		{
+			var x = a == "-" ? 0 : a;
+			var y = b == "-" ? 0 : b;
+			return y - x;
+		}
+	};
+	
+	
+	/*
+	 * Variable: aTypes
+	 * Purpose:  Container for the various type of type detection that dataTables supports
+	 * Scope:    jQuery.fn.dataTableExt
+	 * Notes:    The functions in this array are expected to parse a string to see if it is a data
+	 *   type that it recognises. If so then the function should return the name of the type (a
+	 *   corresponding sort function should be defined!), if the type is not recognised then the
+	 *   function should return null such that the parser and move on to check the next type.
+	 *   Note that ordering is important in this array - the functions are processed linearly,
+	 *   starting at index 0.
+	 */
+	_oExt.aTypes = [
+		/*
+		 * Function: -
+		 * Purpose:  Check to see if a string is numeric
+		 * Returns:  string:'numeric' or null
+		 * Inputs:   string:sText - string to check
+		 */
+		function ( sData )
+		{
+			/* Snaity check that we are dealing with a string or quick return for a number */
+			if ( typeof sData == 'number' )
+			{
+				return 'numeric';
+			}
+			else if ( typeof sData.charAt != 'function' )
+			{
+				return null;
+			}
+			
+			var sValidFirstChars = "0123456789-";
+			var sValidChars = "0123456789.";
+			var Char;
+			var bDecimal = false;
+			
+			/* Check for a valid first char (no period and allow negatives) */
+			Char = sData.charAt(0); 
+			if (sValidFirstChars.indexOf(Char) == -1) 
+			{
+				return null;
+			}
+			
+			/* Check all the other characters are valid */
+			for ( var i=1 ; i<sData.length ; i++ ) 
+			{
+				Char = sData.charAt(i); 
+				if (sValidChars.indexOf(Char) == -1) 
+				{
+					return null;
+				}
+				
+				/* Only allowed one decimal place... */
+				if ( Char == "." )
+				{
+					if ( bDecimal )
+					{
+						return null;
+					}
+					bDecimal = true;
+				}
+			}
+			
+			return 'numeric';
+		},
+		
+		/*
+		 * Function: -
+		 * Purpose:  Check to see if a string is actually a formatted date
+		 * Returns:  string:'date' or null
+		 * Inputs:   string:sText - string to check
+		 */
+		function ( sData )
+		{
+			var iParse = Date.parse(sData);
+			if ( iParse !== null && !isNaN(iParse) )
+			{
+				return 'date';
+			}
+			return null;
+		}
+	];
+	
+	
+	/*
+	 * Variable: _oExternConfig
+	 * Purpose:  Store information for DataTables to access globally about other instances
+	 * Scope:    jQuery.fn.dataTableExt
+	 */
+	_oExt._oExternConfig = {
+		/* int:iNextUnique - next unique number for an instance */
+		"iNextUnique": 0
+	};
+	
+	
+	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+	 * DataTables prototype
+	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+	
+	/*
+	 * Function: dataTable
+	 * Purpose:  DataTables information
+	 * Returns:  -
+	 * Inputs:   object:oInit - initalisation options for the table
+	 */
+	$.fn.dataTable = function( oInit )
+	{
+		/*
+		 * Variable: _aoSettings
+		 * Purpose:  Easy reference to data table settings
+		 * Scope:    jQuery.dataTable
+		 */
+		var _aoSettings = $.fn.dataTableSettings;
+		
+		/*
+		 * Function: classSettings
+		 * Purpose:  Settings container function for all 'class' properties which are required
+		 *   by dataTables
+		 * Returns:  -
+		 * Inputs:   -
+		 */
+		function classSettings ()
+		{
+			this.fnRecordsTotal = function ()
+			{
+				if ( this.oFeatures.bServerSide ) {
+					return this._iRecordsTotal;
+				} else {
+					return this.aiDisplayMaster.length;
+				}
+			};
+			
+			this.fnRecordsDisplay = function ()
+			{
+				if ( this.oFeatures.bServerSide ) {
+					return this._iRecordsDisplay;
+				} else {
+					return this.aiDisplay.length;
+				}
+			};
+			
+			this.fnDisplayEnd = function ()
+			{
+				if ( this.oFeatures.bServerSide ) {
+					return this._iDisplayStart + this.aiDisplay.length;
+				} else {
+					return this._iDisplayEnd;
+				}
+			};
+			
+			/*
+			 * Variable: sInstance
+			 * Purpose:  Unique idendifier for each instance of the DataTables object
+			 * Scope:    jQuery.dataTable.classSettings 
+			 */
+			this.sInstance = null;
+			
+			/*
+			 * Variable: oFeatures
+			 * Purpose:  Indicate the enablement of key dataTable features
+			 * Scope:    jQuery.dataTable.classSettings 
+			 */
+			this.oFeatures = {
+				"bPaginate": true,
+				"bLengthChange": true,
+				"bFilter": true,
+				"bSort": true,
+				"bInfo": true,
+				"bAutoWidth": true,
+				"bProcessing": false,
+				"bSortClasses": true,
+				"bStateSave": false,
+				"bServerSide": false
+			};
+			
+			/*
+			 * Variable: anFeatures
+			 * Purpose:  Array referencing the nodes which are used for the features
+			 * Scope:    jQuery.dataTable.classSettings 
+			 * Notes:    The parameters of this object match what is allowed by sDom - i.e.
+			 *   'l' - Length changing
+			 *   'f' - Filtering input
+			 *   't' - The table!
+			 *   'i' - Information
+			 *   'p' - Pagination
+			 *   'r' - pRocessing
+			 */
+			this.anFeatures = [];
+			
+			/*
+			 * Variable: oLanguage
+			 * Purpose:  Store the language strings used by dataTables
+			 * Scope:    jQuery.dataTable.classSettings
+			 * Notes:    The words in the format _VAR_ are variables which are dynamically replaced
+			 *   by javascript
+			 */
+			this.oLanguage = {
+				"sProcessing": "Processing...",
+				"sLengthMenu": "Show _MENU_ entries",
+				"sZeroRecords": "No matching records found",
+				"sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
+				"sInfoEmpty": "Showing 0 to 0 of 0 entries",
+				"sInfoFiltered": "(filtered from _MAX_ total entries)",
+				"sInfoPostFix": "",
+				"sSearch": "Search:",
+				"sUrl": "",
+				"oPaginate": {
+					"sFirst":    "First",
+					"sPrevious": "Previous",
+					"sNext":     "Next",
+					"sLast":     "Last"
+				}
+			};
+			
+			/*
+			 * Variable: aoData
+			 * Purpose:  Store data information
+			 * Scope:    jQuery.dataTable.classSettings 
+			 * Notes:    This is an array of objects with the following parameters:
+			 *   int: _iId - internal id for tracking
+			 *   array: _aData - internal data - used for sorting / filtering etc
+			 *   node: nTr - display node
+			 *   array node: _anHidden - hidden TD nodes
+			 */
+			this.aoData = [];
+			
+			/*
+			 * Variable: aiDisplay
+			 * Purpose:  Array of indexes which are in the current display (after filtering etc)
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.aiDisplay = [];
+			
+			/*
+			 * Variable: aiDisplayMaster
+			 * Purpose:  Array of indexes for display - no filtering
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.aiDisplayMaster = [];
+							
+			/*
+			 * Variable: aoColumns
+			 * Purpose:  Store information about each column that is in use
+			 * Scope:    jQuery.dataTable.classSettings 
+			 */
+			this.aoColumns = [];
+			
+			/*
+			 * Variable: iNextId
+			 * Purpose:  Store the next unique id to be used for a new row
+			 * Scope:    jQuery.dataTable.classSettings 
+			 */
+			this.iNextId = 0;
+			
+			/*
+			 * Variable: asDataSearch
+			 * Purpose:  Search data array for regular expression searching
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.asDataSearch = [];
+			
+			/*
+			 * Variable: oPreviousSearch
+			 * Purpose:  Store the previous search incase we want to force a re-search
+			 *   or compare the old search to a new one
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.oPreviousSearch = {
+				"sSearch": "",
+				"bEscapeRegex": true
+			};
+			
+			/*
+			 * Variable: aoPreSearchCols
+			 * Purpose:  Store the previous search for each column
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.aoPreSearchCols = [];
+			
+			/*
+			 * Variable: aaSorting
+			 * Purpose:  Sorting information
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.aaSorting = [ [0, 'asc'] ];
+			
+			/*
+			 * Variable: aaSortingFixed
+			 * Purpose:  Sorting information that is always applied
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.aaSortingFixed = null;
+			
+			/*
+			 * Variable: asStripClasses
+			 * Purpose:  Classes to use for the striping of a table
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.asStripClasses = [];
+			
+			/*
+			 * Variable: fnRowCallback
+			 * Purpose:  Call this function every time a row is inserted (draw)
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.fnRowCallback = null;
+			
+			/*
+			 * Variable: fnHeaderCallback
+			 * Purpose:  Callback function for the header on each draw
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.fnHeaderCallback = null;
+			
+			/*
+			 * Variable: fnFooterCallback
+			 * Purpose:  Callback function for the footer on each draw
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.fnFooterCallback = null;
+			
+			/*
+			 * Variable: fnDrawCallback
+			 * Purpose:  Callback function for the whole table on each draw
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.fnDrawCallback = null;
+			
+			/*
+			 * Variable: fnInitComplete
+			 * Purpose:  Callback function for when the table has been initalised
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.fnInitComplete = null;
+			
+			/*
+			 * Variable: sTableId
+			 * Purpose:  Cache the table ID for quick access
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.sTableId = "";
+			
+			/*
+			 * Variable: nTable
+			 * Purpose:  Cache the table node for quick access
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.nTable = null;
+			
+			/*
+			 * Variable: iDefaultSortIndex
+			 * Purpose:  Sorting index which will be used by default
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.iDefaultSortIndex = 0;
+			
+			/*
+			 * Variable: bInitialised
+			 * Purpose:  Indicate if all required information has been read in
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.bInitialised = false;
+			
+			/*
+			 * Variable: aoOpenRows
+			 * Purpose:  Information about open rows
+			 * Scope:    jQuery.dataTable.classSettings
+			 * Notes:    Has the parameters 'nTr' and 'nParent'
+			 */
+			this.aoOpenRows = [];
+			
+			/*
+			 * Variable: sDomPositioning
+			 * Purpose:  Dictate the positioning that the created elements will take
+			 * Scope:    jQuery.dataTable.classSettings
+			 * Notes:    The following syntax is expected:
+			 *   'l' - Length changing
+			 *   'f' - Filtering input
+			 *   't' - The table!
+			 *   'i' - Information
+			 *   'p' - Pagination
+			 *   'r' - pRocessing
+			 *   '<' and '>' - div elements
+			 *   '<"class" and '>' - div with a class
+			 *    Examples: '<"wrapper"flipt>', '<lf<t>ip>'
+			 */
+			this.sDomPositioning = 'lfrtip';
+			
+			/*
+			 * Variable: sPaginationType
+			 * Purpose:  Note which type of sorting should be used
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.sPaginationType = "two_button";
+			
+			/*
+			 * Variable: iCookieDuration
+			 * Purpose:  The cookie duration (for bStateSave) in seconds - default 2 hours
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.iCookieDuration = 60 * 60 * 2;
+			
+			/*
+			 * Variable: sAjaxSource
+			 * Purpose:  Source url for AJAX data for the table
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.sAjaxSource = null;
+			
+			/*
+			 * Variable: bAjaxDataGet
+			 * Purpose:  Note if draw should be blocked while getting data
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.bAjaxDataGet = true;
+			
+			/*
+			 * Variable: fnServerData
+			 * Purpose:  Function to get the server-side data - can be overruled by the developer
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.fnServerData = $.getJSON;
+			
+			/*
+			 * Variable: iServerDraw
+			 * Purpose:  Counter and tracker for server-side processing draws
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.iServerDraw = 0;
+			
+			/*
+			 * Variable: _iDisplayLength, _iDisplayStart, _iDisplayEnd
+			 * Purpose:  Display length variables
+			 * Scope:    jQuery.dataTable.classSettings
+			 * Notes:    These variable must NOT be used externally to get the data length. Rather, use
+			 *   the fnRecordsTotal() (etc) functions.
+			 */
+			this._iDisplayLength = 10;
+			this._iDisplayStart = 0;
+			this._iDisplayEnd = 10;
+			
+			/*
+			 * Variable: _iRecordsTotal, _iRecordsDisplay
+			 * Purpose:  Display length variables used for server side processing
+			 * Scope:    jQuery.dataTable.classSettings
+			 * Notes:    These variable must NOT be used externally to get the data length. Rather, use
+			 *   the fnRecordsTotal() (etc) functions.
+			 */
+			this._iRecordsTotal = 0;
+			this._iRecordsDisplay = 0;
+			
+			/*
+			 * Variable: bJUI
+			 * Purpose:  Should we add the markup needed for jQuery UI theming?
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.bJUI = false;
+			
+			/*
+			 * Variable: bJUI
+			 * Purpose:  Should we add the markup needed for jQuery UI theming?
+			 * Scope:    jQuery.dataTable.classSettings
+			 */
+			this.oClasses = _oExt.oStdClasses;
+		}
+		
+		/*
+		 * Variable: oApi
+		 * Purpose:  Container for publicly exposed 'private' functions
+		 * Scope:    jQuery.dataTable
+		 */
+		this.oApi = {};
+		
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * API functions
+		 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+		
+		/*
+		 * Function: fnDraw
+		 * Purpose:  Redraw the table
+		 * Returns:  -
+		 * Inputs:   -
+		 */
+		this.fnDraw = function()
+		{
+			_fnReDraw( _fnSettingsFromNode( this[_oExt.iApiIndex] ) );
+		};
+		
+		/*
+		 * Function: fnFilter
+		 * Purpose:  Filter the input based on data
+		 * Returns:  -
+		 * Inputs:   string:sInput - string to filter the table on
+		 *           int:iColumn - optional - column to limit filtering to
+		 *           bool:bEscapeRegex - optional - escape regex characters or not - default true
+		 */
+		this.fnFilter = function( sInput, iColumn, bEscapeRegex )
+		{
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			
+			if ( typeof bEscapeRegex == 'undefined' )
+			{
+				bEscapeRegex = true;
+			}
+			
+			if ( typeof iColumn == "undefined" || iColumn === null )
+			{
+				/* Global filter */
+				_fnFilterComplete( oSettings, {"sSearch":sInput, "bEscapeRegex": bEscapeRegex}, 1 );
+			}
+			else
+			{
+				/* Single column filter */
+				oSettings.aoPreSearchCols[ iColumn ].sSearch = sInput;
+				oSettings.aoPreSearchCols[ iColumn ].bEscapeRegex = bEscapeRegex;
+				_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
+			}
+		};
+		
+		/*
+		 * Function: fnSettings
+		 * Purpose:  Get the settings for a particular table for extern. manipulation
+		 * Returns:  -
+		 * Inputs:   -
+		 */
+		this.fnSettings = function( nNode  )
+		{
+			return _fnSettingsFromNode( this[_oExt.iApiIndex] );
+		};
+		
+		/*
+		 * Function: fnSort
+		 * Purpose:  Sort the table by a particular row
+		 * Returns:  -
+		 * Inputs:   int:iCol - the data index to sort on. Note that this will
+		 *   not match the 'display index' if you have hidden data entries
+		 */
+		this.fnSort = function( aaSort )
+		{
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			oSettings.aaSorting = aaSort;
+			_fnSort( oSettings );
+		};
+		
+		/*
+		 * Function: fnAddData
+		 * Purpose:  Add new row(s) into the table
+		 * Returns:  array int: array of indexes (aoData) which have been added (zero length on error)
+		 * Inputs:   array:mData - the data to be added. The length must match
+		 *               the original data from the DOM
+		 *             or
+		 *             array array:mData - 2D array of data to be added
+		 *           bool:bRedraw - redraw the table or not - default true
+		 * Notes:    Warning - the refilter here will cause the table to redraw
+		 *             starting at zero
+		 * Notes:    Thanks to Yekimov Denis for contributing the basis for this function!
+		 */
+		this.fnAddData = function( mData, bRedraw )
+		{
+			var aiReturn = [];
+			var iTest;
+			if ( typeof bRedraw == 'undefined' )
+			{
+				bRedraw = true;
+			}
+			
+			/* Find settings from table node */
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			
+			/* Check if we want to add multiple rows or not */
+			if ( typeof mData[0] == "object" )
+			{
+				for ( var i=0 ; i<mData.length ; i++ )
+				{
+					iTest = _fnAddData( oSettings, mData[i] );
+					if ( iTest == -1 )
+					{
+						return aiReturn;
+					}
+					aiReturn.push( iTest );
+				}
+			}
+			else
+			{
+				iTest = _fnAddData( oSettings, mData );
+				if ( iTest == -1 )
+				{
+					return aiReturn;
+				}
+				aiReturn.push( iTest );
+			}
+			
+			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+			
+			/* Rebuild the search */
+			_fnBuildSearchArray( oSettings, 1 );
+			
+			if ( bRedraw )
+			{
+				_fnReDraw( oSettings );
+			}
+			return aiReturn;
+		};
+		
+		/*
+		 * Function: fnDeleteRow
+		 * Purpose:  Remove a row for the table
+		 * Returns:  array:aReturn - the row that was deleted
+		 * Inputs:   int:iIndex - index of aoData to be deleted
+		 *           function:fnCallBack - callback function - default null
+		 *           bool:bNullRow - remove the row information from aoData by setting the value to
+		 *             null - default false
+		 * Notes:    This function requires a little explanation - we don't actually delete the data
+		 *   from aoData - rather we remove it's references from aiDisplayMastr and aiDisplay. This
+		 *   in effect prevnts DataTables from drawing it (hence deleting it) - it could be restored
+		 *   if you really wanted. The reason for this is that actually removing the aoData object
+		 *   would mess up all the subsequent indexes in the display arrays (they could be ajusted - 
+		 *   but this appears to do what is required).
+		 */
+		this.fnDeleteRow = function( iAODataIndex, fnCallBack, bNullRow )
+		{
+			/* Find settings from table node */
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			var i;
+			
+			/* Delete from the display master */
+			for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
+			{
+				if ( oSettings.aiDisplayMaster[i] == iAODataIndex )
+				{
+					oSettings.aiDisplayMaster.splice( i, 1 );
+					break;
+				}
+			}
+			
+			/* Delete from the current display index */
+			for ( i=0 ; i<oSettings.aiDisplay.length ; i++ )
+			{
+				if ( oSettings.aiDisplay[i] == iAODataIndex )
+				{
+					oSettings.aiDisplay.splice( i, 1 );
+					break;
+				}
+			}
+			
+			/* Rebuild the search */
+			_fnBuildSearchArray( oSettings, 1 );
+			
+			/* If there is a user callback function - call it */
+			if ( typeof fnCallBack == "function" )
+			{
+				fnCallBack.call( this );
+			}
+			
+			/* Check for an 'overflow' they case for dislaying the table */
+			if ( oSettings._iDisplayStart >= oSettings.aiDisplay.length )
+			{
+				oSettings._iDisplayStart -= oSettings._iDisplayLength;
+				if ( oSettings._iDisplayStart < 0 )
+				{
+					oSettings._iDisplayStart = 0;
+				}
+			}
+			
+			_fnCalculateEnd( oSettings );
+			_fnDraw( oSettings );
+			
+			/* Return the data array from this row */
+			var aData = oSettings.aoData[iAODataIndex]._aData.slice();
+			
+			if ( typeof bNullRow != "undefined" && bNullRow === true )
+			{
+				oSettings.aoData[iAODataIndex] = null;
+			}
+			
+			return aData;
+		};
+		
+		/*
+		 * Function: fnClearTable
+		 * Purpose:  Quickly and simply clear a table
+		 * Returns:  -
+		 * Inputs:   bool:bRedraw - redraw the table or not - default true
+		 * Notes:    Thanks to Yekimov Denis for contributing the basis for this function!
+		 */
+		this.fnClearTable = function( bRedraw )
+		{
+			/* Find settings from table node */
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			_fnClearTable( oSettings );
+			
+			if ( typeof bRedraw == 'undefined' || bRedraw )
+			{
+				_fnDraw( oSettings );
+			}
+		};
+		
+		/*
+		 * Function: fnOpen
+		 * Purpose:  Open a display row (append a row after the row in question)
+		 * Returns:  -
+		 * Inputs:   node:nTr - the table row to 'open'
+		 *           string:sHtml - the HTML to put into the row
+		 *           string:sClass - class to give the new cell
+		 */
+		this.fnOpen = function( nTr, sHtml, sClass )
+		{
+			/* Find settings from table node */
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			
+			/* the old open one if there is one */
+			this.fnClose( nTr );
+			
+			
+			var nNewRow = document.createElement("tr");
+			var nNewCell = document.createElement("td");
+			nNewRow.appendChild( nNewCell );
+			nNewCell.className = sClass;
+			nNewCell.colSpan = _fnVisbleColumns( oSettings );
+			nNewCell.innerHTML = sHtml;
+			
+			$(nNewRow).insertAfter(nTr);
+			
+			/* No point in storing the row if using server-side processing since the nParent will be
+			 * nuked on a re-draw anyway
+			 */
+			if ( !oSettings.oFeatures.bServerSide )
+			{
+				oSettings.aoOpenRows.push( {
+					"nTr": nNewRow,
+					"nParent": nTr
+				} );
+			}
+		};
+		
+		/*
+		 * Function: fnClose
+		 * Purpose:  Close a display row
+		 * Returns:  int: 0 (success) or 1 (failed)
+		 * Inputs:   node:nTr - the table row to 'close'
+		 */
+		this.fnClose = function( nTr )
+		{
+			/* Find settings from table node */
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			
+			for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ )
+			{
+				if ( oSettings.aoOpenRows[i].nParent == nTr )
+				{
+					var nTrParent = oSettings.aoOpenRows[i].nTr.parentNode;
+					if ( nTrParent )
+					{
+						/* Remove it if it is currently on display */
+						nTrParent.removeChild( oSettings.aoOpenRows[i].nTr );
+					}
+					oSettings.aoOpenRows.splice( i, 1 );
+					return 0;
+				}
+			}
+			return 1;
+		};
+		
+		/*
+		 * Function: fnGetData
+		 * Purpose:  Return an array with the data which is used to make up the table
+		 * Returns:  array array string: 2d data array ([row][column]) or array string: 1d data array
+		 *           or
+		 *           array string (if iRow specified)
+		 * Inputs:   int:iRow - optional - if present then the array returned will be the data for
+		 *             the row with the index 'iRow'
+		 */
+		this.fnGetData = function( iRow )
+		{
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			
+			if ( typeof iRow != 'undefined' )
+			{
+				return oSettings.aoData[iRow]._aData;
+			}
+			return _fnGetDataMaster( oSettings );
+		};
+		
+		/*
+		 * Function: fnGetNodes
+		 * Purpose:  Return an array with the TR nodes used for drawing the table
+		 * Returns:  array node: TR elements
+		 *           or
+		 *           node (if iRow specified)
+		 * Inputs:   int:iRow - optional - if present then the array returned will be the node for
+		 *             the row with the index 'iRow'
+		 */
+		this.fnGetNodes = function( iRow )
+		{
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			
+			if ( typeof iRow != 'undefined' )
+			{
+				return oSettings.aoData[iRow].nTr;
+			}
+			return _fnGetTrNodes( oSettings );
+		};
+		
+		/*
+		 * Function: fnGetPosition
+		 * Purpose:  Get the array indexes of a particular cell from it's DOM element
+		 * Returns:  int: - row index, or array[ int, int ]: - row index and column index
+		 * Inputs:   node:nNode - this can either be a TR or a TD in the table, the return is
+		 *             dependent on this input
+		 */
+		this.fnGetPosition = function( nNode )
+		{
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			var i;
+			
+			if ( nNode.nodeName == "TR" )
+			{
+				for ( i=0 ; i<oSettings.aoData.length ; i++ )
+				{
+					if ( oSettings.aoData[i] !== null && oSettings.aoData[i].nTr == nNode )
+					{
+						return i;
+					}
+				}
+			}
+			else if ( nNode.nodeName == "TD" )
+			{
+				for ( i=0 ; i<oSettings.aoData.length ; i++ )
+				{
+					var iCorrector = 0;
+					for ( var j=0 ; j<oSettings.aoColumns.length ; j++ )
+					{
+						if ( oSettings.aoColumns[j].bVisible )
+						{
+							//$('>td', oSettings.aoData[i].nTr)[j-iCorrector] == nNode )
+							if ( oSettings.aoData[i] !== null &&
+								oSettings.aoData[i].nTr.getElementsByTagName('td')[j-iCorrector] == nNode )
+							{
+								return [ i, j-iCorrector, j ];
+							}
+						}
+						else
+						{
+							iCorrector++;
+						}
+					}
+				}
+			}
+			return null;
+		};
+		
+		/*
+		 * Function: fnUpdate
+		 * Purpose:  Update a table cell or row
+		 * Returns:  int: 0 okay, 1 error
+		 * Inputs:   array string 'or' string:mData - data to update the cell/row with
+		 *           int:iRow - the row (from aoData) to update
+		 *           int:iColumn - the column to update
+		 *           bool:bRedraw - redraw the table or not - default true
+		 */
+		this.fnUpdate = function( mData, iRow, iColumn, bRedraw )
+		{
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			var iVisibleColumn;
+			var sDisplay;
+			if ( typeof bRedraw == 'undefined' )
+			{
+				bRedraw = true;
+			}
+			
+			if ( typeof mData != 'object' )
+			{
+				sDisplay = mData;
+				oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
+				
+				if ( oSettings.aoColumns[iColumn].fnRender !== null )
+				{
+					sDisplay = oSettings.aoColumns[iColumn].fnRender( {
+						"iDataRow": iRow,
+						"iDataColumn": iColumn,
+						"aData": oSettings.aoData[iRow]._aData
+					} );
+					
+					if ( oSettings.aoColumns[iColumn].bUseRendered )
+					{
+						oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
+					}
+				}
+				
+				iVisibleColumn = _fnColumnIndexToVisible( oSettings, iColumn );
+				if ( iVisibleColumn !== null )
+				{
+					oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML = 
+						sDisplay;
+				}
+			}
+			else
+			{
+				if ( mData.length != oSettings.aoColumns.length )
+				{
+					alert( 'Warning: An array passed to fnUpdate must have the same number of columns as '+
+						'the table in question - in this case '+oSettings.aoColumns.length );
+					return 1;
+				}
+				
+				for ( var i=0 ; i<mData.length ; i++ )
+				{
+					sDisplay = mData[i];
+					oSettings.aoData[iRow]._aData[i] = sDisplay;
+					
+					if ( oSettings.aoColumns[i].fnRender !== null )
+					{
+						sDisplay = oSettings.aoColumns[i].fnRender( {
+							"iDataRow": iRow,
+							"iDataColumn": i,
+							"aData": oSettings.aoData[iRow]._aData
+						} );
+						
+						if ( oSettings.aoColumns[i].bUseRendered )
+						{
+							oSettings.aoData[iRow]._aData[i] = sDisplay;
+						}
+					}
+					
+					iVisibleColumn = _fnColumnIndexToVisible( oSettings, i );
+					if ( iVisibleColumn !== null )
+					{
+						oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML = 
+							sDisplay;
+					}
+				}
+			}
+			
+			/* Update the search array */
+			_fnBuildSearchArray( oSettings, 1 );
+			
+			/* Redraw the table */
+			if ( bRedraw )
+			{
+				_fnReDraw( oSettings );
+			}
+			return 0;
+		};
+		
+		
+		/*
+		 * Function: fnShowColoumn
+		 * Purpose:  Show a particular column
+		 * Returns:  -
+		 * Inputs:   int:iCol - the column whose display should be changed
+		 *           bool:bShow - show (true) or hide (false) the column
+		 */
+		this.fnSetColumnVis = function ( iCol, bShow )
+		{
+			var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
+			var i, iLen;
+			var iColumns = oSettings.aoColumns.length;
+			var nTd;
+			
+			/* No point in doing anything if we are requesting what is already true */
+			if ( oSettings.aoColumns[iCol].bVisible == bShow )
+			{
+				return;
+			}
+			
+			var nTrHead = $('thead tr', oSettings.nTable)[0];
+			var nTrFoot = $('tfoot tr', oSettings.nTable)[0];
+			var anTheadTh = [];
+			var anTfootTh = [];
+			for ( i=0 ; i<iColumns ; i++ )
+			{
+				anTheadTh.push( oSettings.aoColumns[i].nTh );
+				anTfootTh.push( oSettings.aoColumns[i].nTf );
+			}
+			
+			/* Show the column */
+			if ( bShow )
+			{
+				var iInsert = 0;
+				for ( i=0 ; i<iCol ; i++ )
+				{
+					if ( oSettings.aoColumns[i].bVisible )
+					{
+						iInsert++;
+					}
+				}
+				
+				/* Need to decide if we should use appendChild or insertBefore */
+				if ( iInsert >= _fnVisbleColumns( oSettings ) )
+				{
+					nTrHead.appendChild( anTheadTh[iCol] );
+					if ( nTrFoot )
+					{
+						nTrFoot.appendChild( anTfootTh[iCol] );
+					}
+					
+					for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
+					{
+						nTd = oSettings.aoData[i]._anHidden[iCol];
+						oSettings.aoData[i].nTr.appendChild( nTd );
+					}
+				}
+				else
+				{
+					/* Which coloumn should we be inserting before? */
+					var iBefore;
+					for ( i=iCol ; i<iColumns ; i++ )
+					{
+						iBefore = _fnColumnIndexToVisible( oSettings, i );
+						if ( iBefore !== null )
+						{
+							break;
+						}
+					}
+					
+					nTrHead.insertBefore( anTheadTh[iCol], nTrHead.getElementsByTagName('th')[iBefore] );
+					if ( nTrFoot )
+					{
+						nTrFoot.insertBefore( anTfootTh[iCol], nTrFoot.getElementsByTagName('th')[iBefore] );
+					}
+					
+					for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
+					{
+						nTd = oSettings.aoData[i]._anHidden[iCol];
+						oSettings.aoData[i].nTr.insertBefore( nTd, oSettings.aoData[i].nTr.getElementsByTagName('td')[iBefore] );
+					}
+				}
+				
+				oSettings.aoColumns[iCol].bVisible = true;
+			}
+			else
+			{
+				/* Remove a column from display */
+				nTrHead.removeChild( anTheadTh[iCol] );
+				if ( nTrFoot )
+				{
+					nTrFoot.removeChild( anTfootTh[iCol] );
+				}
+				
+				var iVisCol = _fnColumnIndexToVisible(oSettings, iCol);
+				for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
+				{
+					nTd = oSettings.aoData[i].nTr.getElementsByTagName('td')[ iVisCol ];
+					oSettings.aoData[i]._anHidden[iCol] = nTd;
+					nTd.parentNode.removeChild( nTd );
+				}
+				
+				oSettings.aoColumns[iCol].bVisible = false;
+			}
+			
+			/* If there are any 'open' rows, then we need to alter the colspan for this col change */
+			for ( i=0, iLen=oSettings.aoOpenRows.length ; i<iLen ; i++ )
+			{
+				oSettings.aoOpenRows[i].nTr.colSpan = _fnVisbleColumns( oSettings );
+			}
+			
+			/* Since there is no redraw done here, we need to save the state manually */
+			_fnSaveState( oSettings );
+		};
+		
+		
+		/*
+		 * Plugin API functions
+		 * 
+		 * This call will add the functions which are defined in _oExt.oApi to the
+		 * DataTables object, providing a rather nice way to allow plug-in API functions. Note that
+		 * this is done here, so that API function can actually override the built in API functions if
+		 * required for a particular purpose.
+		 */
+		
+		/*
+		 * Function: _fnExternApiFunc
+		 * Purpose:  Create a wrapper function for exporting an internal func to an external API func
+		 * Returns:  function: - wrapped function
+		 * Inputs:   string:sFunc - API function name
+		 */
+		function _fnExternApiFunc (sFunc)
+		{
+			return function() {
+					var aArgs = [_fnSettingsFromNode(this[_oExt.iApiIndex])].concat( 
+						Array.prototype.slice.call(arguments) );
+					return _oExt.oApi[sFunc].apply( this, aArgs );
+				};
+		}
+		
+		for ( var sFunc in _oExt.oApi )
+		{
+			if ( sFunc )
+			{
+				/*
+				 * Function: anon
+				 * Purpose:  Wrap the plug-in API functions in order to provide the settings as 1st arg 
+				 *   and execute in this scope
+				 * Returns:  -
+				 * Inputs:   -
+				 */
+				this[sFunc] = _fnExternApiFunc(sFunc);
+			}
+		}
+		
+		
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * Local functions
+		 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * Initalisation
+		 */
+		
+		/*
+		 * Function: _fnInitalise
+		 * Purpose:  Draw the table for the first time, adding all required features
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnInitalise ( oSettings )
+		{
+			/* Ensure that the table data is fully initialised */
+			if ( oSettings.bInitialised === false )
+			{
+				setTimeout( function(){ _fnInitalise( oSettings ); }, 200 );
+				return;
+			}
+			
+			/* Show the display HTML options */
+			_fnAddOptionsHtml( oSettings );
+			
+			/* Draw the headers for the table */
+			_fnDrawHead( oSettings );
+			
+			/* If there is default sorting required - let's do it. The sort function
+			 * will do the drawing for us. Otherwise we draw the table
+			 */
+			if ( oSettings.oFeatures.bSort )
+			{
+				_fnSort( oSettings, false );
+				/*
+				 * Add the sorting classes to the header and the body (if needed).
+				 * Reason for doing it here after the first draw is to stop classes being applied to the
+				 * 'static' table.
+				 */
+				_fnSortingClasses( oSettings );
+			}
+			else
+			{
+				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+				_fnCalculateEnd( oSettings );
+				_fnDraw( oSettings );
+			}
+			
+			/* if there is an ajax source */
+			if ( oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
+			{
+				_fnProcessingDisplay( oSettings, true );
+				
+				$.getJSON( oSettings.sAjaxSource, null, function(json) {
+					
+					/* Got the data - add it to the table */
+					for ( var i=0 ; i<json.aaData.length ; i++ )
+					{
+						_fnAddData( oSettings, json.aaData[i] );
+					}
+					
+					/* Reset the init display for cookie saving. We've already done a filter, and
+					 * therefore cleared it before. So we need to make it appear 'fresh'
+					 */
+					oSettings.iInitDisplayStart = oSettings._iDisplayStart;
+					
+					if ( oSettings.oFeatures.bSort )
+					{
+						_fnSort( oSettings );
+					}
+					else
+					{
+						oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+						_fnCalculateEnd( oSettings );
+						_fnDraw( oSettings );
+					}
+					_fnProcessingDisplay( oSettings, false );
+					
+					/* Run the init callback if there is one */
+					if ( typeof oSettings.fnInitComplete == 'function' )
+					{
+						oSettings.fnInitComplete( oSettings, json );
+					}
+				} );
+				return;
+			}
+			
+			/* Run the init callback if there is one */
+			if ( typeof oSettings.fnInitComplete == 'function' )
+			{
+				oSettings.fnInitComplete( oSettings );
+			}
+			_fnProcessingDisplay( oSettings, false );
+		}
+		
+		/*
+		 * Function: _fnLanguageProcess
+		 * Purpose:  Copy language variables from remote object to a local one
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           object:oLanguage - Language information
+		 *           bool:bInit - init once complete
+		 */
+		function _fnLanguageProcess( oSettings, oLanguage, bInit )
+		{
+			_fnMap( oSettings.oLanguage, oLanguage, 'sProcessing' );
+			_fnMap( oSettings.oLanguage, oLanguage, 'sLengthMenu' );
+			_fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords' );
+			_fnMap( oSettings.oLanguage, oLanguage, 'sInfo' );
+			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoEmpty' );
+			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoFiltered' );
+			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoPostFix' );
+			_fnMap( oSettings.oLanguage, oLanguage, 'sSearch' );
+			
+			if ( typeof oLanguage.oPaginate != 'undefined' )
+			{
+				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sFirst' );
+				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sPrevious' );
+				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sNext' );
+				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sLast' );
+			}
+			
+			if ( bInit )
+			{
+				_fnInitalise( oSettings );
+			}
+		}
+		
+		/*
+		 * Function: _fnAddColumn
+		 * Purpose:  Add a column to the list used for the table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           object:oOptions - object with sType, bVisible and bSearchable
+		 *           node:nTh - the th element for this column
+		 * Notes:    All options in enter column can be over-ridden by the user
+		 *   initialisation of dataTables
+		 */
+		function _fnAddColumn( oSettings, oOptions, nTh )
+		{
+			oSettings.aoColumns[ oSettings.aoColumns.length++ ] = {
+				"sType": null,
+				"_bAutoType": true,
+				"bVisible": true,
+				"bSearchable": true,
+				"bSortable": true,
+				"sTitle": nTh ? nTh.innerHTML : '',
+				"sName": '',
+				"sWidth": null,
+				"sClass": null,
+				"fnRender": null,
+				"bUseRendered": true,
+				"iDataSort": oSettings.aoColumns.length-1,
+				"nTh": nTh ? nTh : document.createElement('th'),
+				"nTf": null
+			};
+			
+			/* User specified column options */
+			var iLength = oSettings.aoColumns.length-1;
+			if ( typeof oOptions != 'undefined' && oOptions !== null )
+			{
+				var oCol = oSettings.aoColumns[ iLength ];
+				
+				if ( typeof oOptions.sType != 'undefined' )
+				{
+					oCol.sType = oOptions.sType;
+					oCol._bAutoType = false;
+				}
+				
+				_fnMap( oCol, oOptions, "bVisible" );
+				_fnMap( oCol, oOptions, "bSearchable" );
+				_fnMap( oCol, oOptions, "bSortable" );
+				_fnMap( oCol, oOptions, "sTitle" );
+				_fnMap( oCol, oOptions, "sName" );
+				_fnMap( oCol, oOptions, "sWidth" );
+				_fnMap( oCol, oOptions, "sClass" );
+				_fnMap( oCol, oOptions, "fnRender" );
+				_fnMap( oCol, oOptions, "bUseRendered" );
+				_fnMap( oCol, oOptions, "iDataSort" );
+			}
+			
+			/* Add a column specific filter */
+			if ( typeof oSettings.aoPreSearchCols[ iLength ] == 'undefined' ||
+			     oSettings.aoPreSearchCols[ iLength ] === null )
+			{
+				oSettings.aoPreSearchCols[ iLength ] = {
+					"sSearch": "",
+					"bEscapeRegex": true
+				};
+			}
+			else if ( typeof oSettings.aoPreSearchCols[ iLength ].bEscapeRegex == 'undefined' )
+			{
+				/* Don't require that the user must specify bEscapeRegex */
+				oSettings.aoPreSearchCols[ iLength ].bEscapeRegex = true;
+			}
+		}
+		
+		/*
+		 * Function: _fnAddData
+		 * Purpose:  Add a data array to the table, creating DOM node etc
+		 * Returns:  int: - >=0 if successful (index of new aoData entry), -1 if failed
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           array:aData - data array to be added
+		 */
+		function _fnAddData ( oSettings, aData )
+		{
+			/* Sanity check the length of the new array */
+			if ( aData.length != oSettings.aoColumns.length )
+			{
+				alert( "Warning - added data does not match known number of columns" );
+				return -1;
+			}
+			
+			/* Create the object for storing information about this new row */
+			var iThisIndex = oSettings.aoData.length;
+			oSettings.aoData.push( {
+				"_iId": oSettings.iNextId++,
+				"_aData": aData.slice(),
+				"nTr": document.createElement('tr'),
+				"_anHidden": []
+			} );
+			
+			/* Create the cells */
+			var nTd;
+			for ( var i=0 ; i<aData.length ; i++ )
+			{
+				nTd = document.createElement('td');
+				
+				if ( typeof oSettings.aoColumns[i].fnRender == 'function' )
+				{
+					var sRendered = oSettings.aoColumns[i].fnRender( {
+							"iDataRow": iThisIndex,
+							"iDataColumn": i,
+							"aData": aData
+						} );
+					nTd.innerHTML = sRendered;
+					if ( oSettings.aoColumns[i].bUseRendered )
+					{
+						/* Use the rendered data for filtering/sorting */
+						oSettings.aoData[iThisIndex]._aData[i] = sRendered;
+					}
+				}
+				else
+				{
+					nTd.innerHTML = aData[i];
+				}
+				
+				if ( oSettings.aoColumns[i].sClass !== null )
+				{
+					nTd.className = oSettings.aoColumns[i].sClass;
+				}
+				
+				/* See if we should auto-detect the column type */
+				if ( oSettings.aoColumns[i]._bAutoType && oSettings.aoColumns[i].sType != 'string' )
+				{
+					/* Attempt to auto detect the type - same as _fnGatherData() */
+					if ( oSettings.aoColumns[i].sType === null )
+					{
+						oSettings.aoColumns[i].sType = _fnDetectType( aData[i] );
+					}
+					else if ( oSettings.aoColumns[i].sType == "date" || 
+					          oSettings.aoColumns[i].sType == "numeric" )
+					{
+						oSettings.aoColumns[i].sType = _fnDetectType( aData[i] );
+					}
+				}
+					
+				if ( oSettings.aoColumns[i].bVisible )
+				{
+					oSettings.aoData[iThisIndex].nTr.appendChild( nTd );
+				}
+				else
+				{
+					oSettings.aoData[iThisIndex]._anHidden[i] = nTd;
+				}
+			}
+			
+			/* Add to the display array */
+			oSettings.aiDisplayMaster.push( iThisIndex );
+			return iThisIndex;
+		}
+		
+		/*
+		 * Function: _fnGatherData
+		 * Purpose:  Read in the data from the target table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnGatherData( oSettings )
+		{
+			var iLoop;
+			var i, j;
+			
+			/*
+			 * Process by row first
+			 * Add the data object for the whole table - storing the tr node. Note - no point in getting
+			 * DOM based data if we are going to go and replace it with Ajax source data.
+			 */
+			if ( oSettings.sAjaxSource === null )
+			{
+				$('tbody:eq(0)>tr', oSettings.nTable).each( function() {
+					var iThisIndex = oSettings.aoData.length;
+					oSettings.aoData.push( {
+						"_iId": oSettings.iNextId++,
+						"_aData": [],
+						"nTr": this,
+						"_anHidden": []
+					} );
+					
+					oSettings.aiDisplayMaster.push( iThisIndex );
+					
+					/* Add the data for this column */
+					var aLocalData = oSettings.aoData[iThisIndex]._aData;
+					$('td', this).each( function( i ) {
+						aLocalData[i] = this.innerHTML;
+					} );
+				} );
+			}
+			
+			/*
+			 * Now process by column
+			 */
+			var iCorrector = 0;
+			for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+			{
+				/* Get the title of the column - unless there is a user set one */
+				if ( oSettings.aoColumns[i].sTitle === null )
+				{
+					oSettings.aoColumns[i].sTitle = oSettings.aoColumns[i].nTh.innerHTML;
+				}
+				
+				var bAutoType = oSettings.aoColumns[i]._bAutoType;
+				var bRender = typeof oSettings.aoColumns[i].fnRender == 'function';
+				var bClass = oSettings.aoColumns[i].sClass !== null;
+				var bVisible = oSettings.aoColumns[i].bVisible;
+				
+				/* A single loop to rule them all (and be more efficient) */
+				if ( bAutoType || bRender || bClass || !bVisible )
+				{
+					iLoop = oSettings.aoData.length;
+					for ( j=0 ; j<iLoop ; j++ )
+					{
+						var nCellNode = oSettings.aoData[j].nTr.getElementsByTagName('td')[ i-iCorrector ];
+						
+						if ( bAutoType )
+						{
+							if ( oSettings.aoColumns[i].sType === null )
+							{
+								oSettings.aoColumns[i].sType = _fnDetectType( oSettings.aoData[j]._aData[i] );
+							}
+							else if ( oSettings.aoColumns[i].sType == "date" || 
+							          oSettings.aoColumns[i].sType == "numeric" )
+							{
+								/* If type is date or numeric - ensure that all collected data
+								 * in the column is of the same type
+								 */
+								oSettings.aoColumns[i].sType = _fnDetectType( oSettings.aoData[j]._aData[i] );
+							}
+							/* The else would be 'type = string' we don't want to do anything
+							 * if that is the case
+							 */
+						}
+						
+						if ( bRender )
+						{
+							var sRendered = oSettings.aoColumns[i].fnRender( {
+									"iDataRow": j,
+									"iDataColumn": i,
+									"aData": oSettings.aoData[j]._aData
+								} );
+							nCellNode.innerHTML = sRendered;
+							if ( oSettings.aoColumns[i].bUseRendered )
+							{
+								/* Use the rendered data for filtering/sorting */
+								oSettings.aoData[j]._aData[i] = sRendered;
+							}
+						}
+						
+						if ( bClass )
+						{
+							nCellNode.className += ' '+oSettings.aoColumns[i].sClass;
+						}
+						
+						if ( !bVisible )
+						{
+							oSettings.aoData[j]._anHidden[i] = nCellNode;
+							nCellNode.parentNode.removeChild( nCellNode );
+						}
+					}
+					
+					/* Keep an index corrector for the next loop */
+					if ( !bVisible )
+					{
+						iCorrector++;
+					}
+				}
+			}	
+		}
+		
+		
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * Drawing functions
+		 */
+		
+		/*
+		 * Function: _fnDrawHead
+		 * Purpose:  Create the HTML header for the table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnDrawHead( oSettings )
+		{
+			var i, nTh, iLen;
+			var iThs = oSettings.nTable.getElementsByTagName('thead')[0].getElementsByTagName('th').length;
+			var iCorrector = 0;
+			
+			/* If there is a header in place - then use it - otherwise it's going to get nuked... */
+			if ( iThs !== 0 )
+			{
+				/* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */
+				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+				{
+					//oSettings.aoColumns[i].nTh = nThs[i];
+					nTh = oSettings.aoColumns[i].nTh;
+					
+					if ( oSettings.aoColumns[i].bVisible )
+					{
+						/* Set width */
+						if ( oSettings.aoColumns[i].sWidth !== null )
+						{
+							nTh.style.width = oSettings.aoColumns[i].sWidth;
+						}
+						
+						/* Set the title of the column if it is user defined (not what was auto detected) */
+						if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML )
+						{
+							nTh.innerHTML = oSettings.aoColumns[i].sTitle;
+						}
+					}
+					else
+					{
+						nTh.parentNode.removeChild( nTh );
+						iCorrector++;
+					}
+				}
+			}
+			else
+			{
+				/* We don't have a header in the DOM - so we are going to have to create one */
+				var nTr = document.createElement( "tr" );
+				
+				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+				{
+					if ( oSettings.aoColumns[i].bVisible )
+					{
+						nTh = oSettings.aoColumns[i].nTh;
+						
+						if ( oSettings.aoColumns[i].sClass !== null )
+						{
+							nTh.className = oSettings.aoColumns[i].sClass;
+						}
+						
+						if ( oSettings.aoColumns[i].sWidth !== null )
+						{
+							nTh.style.width = oSettings.aoColumns[i].sWidth;
+						}
+						
+						nTh.innerHTML = oSettings.aoColumns[i].sTitle;
+						nTr.appendChild( nTh );
+					}
+				}
+				$('thead', oSettings.nTable).html( '' )[0].appendChild( nTr );
+			}
+			
+			/* Add the extra markup needed by jQuery UI's themes */
+			if ( oSettings.bJUI )
+			{
+				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+				{
+					var nSpan = document.createElement('span');
+					oSettings.aoColumns[i].nTh.appendChild( nSpan );
+				}
+			}
+			
+			/* Add sort listener */
+			if ( oSettings.oFeatures.bSort )
+			{
+				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+				{
+					if ( oSettings.aoColumns[i].bSortable === false )
+					{
+						continue;
+					}
+					
+					$(oSettings.aoColumns[i].nTh).click( function (e) {
+						var iDataIndex;
+						/* Find which column we are sorting on - can't use index() due to colspan etc */
+						for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
+						{
+							if ( oSettings.aoColumns[i].nTh == this )
+							{
+								iDataIndex = i;
+								break;
+							}
+						}
+						
+						/* If the column is not sortable - don't to anything */
+						if ( oSettings.aoColumns[iDataIndex].bSortable === false )
+						{
+							return;
+						}
+						
+						/*
+						 * This is a little bit odd I admit... I declare a temporary function inside the scope of
+						 * _fnDrawHead and the click handler in order that the code presented here can be used 
+						 * twice - once for when bProcessing is enabled, and another time for when it is 
+						 * disabled, as we need to perform slightly different actions.
+						 *   Basically the issue here is that the Javascript engine in modern browsers don't 
+						 * appear to allow the rendering engine to update the display while it is still excuting
+						 * it's thread (well - it does but only after long intervals). This means that the 
+						 * 'processing' display doesn't appear for a table sort. To break the js thread up a bit
+						 * I force an execution break by using setTimeout - but this breaks the expected 
+						 * thread continuation for the end-developer's point of view (their code would execute
+						 * too early), so we on;y do it when we absolutely have to.
+						 */
+						var fnInnerSorting = function () {
+							if ( e.shiftKey )
+							{
+								/* If the shift key is pressed then we are multipe column sorting */
+								var bFound = false;
+								for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
+								{
+									if ( oSettings.aaSorting[i][0] == iDataIndex )
+									{
+										if ( oSettings.aaSorting[i][1] == "asc" )
+										{
+											oSettings.aaSorting[i][1] = "desc";
+										}
+										else
+										{
+											oSettings.aaSorting.splice( i, 1 );
+										}
+										bFound = true;
+										break;
+									}
+								}
+								
+								if ( bFound === false )
+								{
+									oSettings.aaSorting.push( [ iDataIndex, "asc" ] );
+								}
+							}
+							else
+							{
+								/* If no shift key then single column sort */
+								if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex )
+								{
+									oSettings.aaSorting[0][1] = oSettings.aaSorting[0][1]=="asc" ? "desc" : "asc";
+								}
+								else
+								{
+									oSettings.aaSorting.splice( 0, oSettings.aaSorting.length );
+									oSettings.aaSorting.push( [ iDataIndex, "asc" ] );
+								}
+							}
+							
+							/* Run the sort */
+							_fnSort( oSettings );
+						}; /* /fnInnerSorting */
+						
+						if ( !oSettings.oFeatures.bProcessing )
+						{
+							fnInnerSorting();
+						}
+						else
+						{
+							_fnProcessingDisplay( oSettings, true );
+							setTimeout( function() {
+								fnInnerSorting();
+								if ( !oSettings.oFeatures.bServerSide )
+								{
+									_fnProcessingDisplay( oSettings, false );
+								}
+							}, 0 );
+						}
+					} ); /* /click */
+				} /* For each column */
+				
+				/* Take the brutal approach to cancelling text selection due to the shift key */
+				$('thead th', oSettings.nTable).mousedown( function (e) {
+					if ( e.shiftKey )
+					{
+						this.onselectstart = function() { return false; };
+						return false;
+					}
+				} );
+			} /* /if feature sort */
+			
+			/* Set an absolute width for the table such that pagination doesn't
+			 * cause the table to resize
+			 */
+			if ( oSettings.oFeatures.bAutoWidth )
+			{
+				oSettings.nTable.style.width = oSettings.nTable.offsetWidth+"px";
+			}
+			
+			/* Cache the footer elements */
+			var nTfoot = oSettings.nTable.getElementsByTagName('tfoot');
+			if ( nTfoot.length !== 0 )
+			{
+				iCorrector = 0;
+				var nTfs = nTfoot[0].getElementsByTagName('th');
+				for ( i=0, iLen=nTfs.length ; i<iLen ; i++ )
+				{
+					oSettings.aoColumns[i].nTf = nTfs[i-iCorrector];
+					if ( !oSettings.aoColumns[i].bVisible )
+					{
+						nTfs[i-iCorrector].parentNode.removeChild( nTfs[i-iCorrector] );
+						iCorrector++;
+					}
+				}
+			}
+		}
+		
+		/*
+		 * Function: _fnDraw
+		 * Purpose:  Insert the required TR nodes into the table for display
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnDraw( oSettings )
+		{
+			var i;
+			var anRows = [];
+			var iRowCount = 0;
+			var bRowError = false;
+			var iStrips = oSettings.asStripClasses.length;
+			var iOpenRows = oSettings.aoOpenRows.length;
+			
+			/* If we are dealing with Ajax - do it here */
+			if ( oSettings.oFeatures.bServerSide && 
+			     !_fnAjaxUpdate( oSettings ) )
+			{
+				return;
+			}
+			
+			if ( oSettings.aiDisplay.length !== 0 )
+			{
+				var iStart = oSettings._iDisplayStart;
+				var iEnd = oSettings._iDisplayEnd;
+				
+				if ( oSettings.oFeatures.bServerSide )
+				{
+					iStart = 0;
+					iEnd = oSettings.aoData.length;
+				}
+				
+				for ( var j=iStart ; j<iEnd ; j++ )
+				{
+					var nRow = oSettings.aoData[ oSettings.aiDisplay[j] ].nTr;
+					
+					/* Remove any old stripping classes and then add the new one */
+					if ( iStrips !== 0 )
+					{
+						$(nRow).removeClass( oSettings.asStripClasses.join(' ') );
+						$(nRow).addClass( oSettings.asStripClasses[ iRowCount % iStrips ] );
+					}
+					
+					/* Custom row callback function - might want to manipule the row */
+					if ( typeof oSettings.fnRowCallback == "function" )
+					{
+						nRow = oSettings.fnRowCallback( nRow, 
+							oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j );
+						if ( !nRow && !bRowError )
+						{
+							alert( "Error: A node was not returned by fnRowCallback" );
+							bRowError = true;
+						}
+					}
+					
+					anRows.push( nRow );
+					iRowCount++;
+					
+					/* If there is an open row - and it is attached to this parent - attach it on redraw */
+					if ( iOpenRows !== 0 )
+					{
+						for ( var k=0 ; k<iOpenRows ; k++ )
+						{
+							if ( nRow == oSettings.aoOpenRows[k].nParent )
+							{
+								anRows.push( oSettings.aoOpenRows[k].nTr );
+							}
+						}
+					}
+				}
+			}
+			else
+			{
+				/* Table is empty - create a row with an empty message in it */
+				anRows[ 0 ] = document.createElement( 'tr' );
+				
+				if ( typeof oSettings.asStripClasses[0] != 'undefined' )
+				{
+					anRows[ 0 ].className = oSettings.asStripClasses[0];
+				}
+				
+				var nTd = document.createElement( 'td' );
+				nTd.setAttribute( 'valign', "top" );
+				nTd.colSpan = oSettings.aoColumns.length;
+				nTd.className = oSettings.oClasses.sRowEmpty;
+				nTd.innerHTML = oSettings.oLanguage.sZeroRecords;
+				
+				anRows[ iRowCount ].appendChild( nTd );
+			}
+			
+			/* Callback the header and footer custom funcation if there is one */
+			if ( typeof oSettings.fnHeaderCallback == 'function' )
+			{
+				oSettings.fnHeaderCallback( $('thead tr', oSettings.nTable)[0], 
+					_fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
+					oSettings.aiDisplay );
+			}
+			
+			if ( typeof oSettings.fnFooterCallback == 'function' )
+			{
+				oSettings.fnFooterCallback( $('tfoot tr', oSettings.nTable)[0], 
+					_fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
+					oSettings.aiDisplay );
+			}
+			
+			/* 
+			 * Need to remove any old row from the display - note we can't just empty the tbody using
+			 * .html('') since this will unbind the jQuery event handlers (even although the node still
+			 * exists!) - note the initially odd ':eq(0)>tr' expression. This basically ensures that we
+			 * only get tr elements of the tbody that the data table has been initialised on. If there
+			 * are nested tables then we don't want to remove those elements.
+			 */
+			var nTrs = $('tbody:eq(0)>tr', oSettings.nTable);
+			for ( i=0 ; i<nTrs.length ; i++ )
+			{
+				nTrs[i].parentNode.removeChild( nTrs[i] );
+			}
+			
+			/* Put the draw table into the dom */
+			var nBody = $('tbody:eq(0)', oSettings.nTable);
+			if ( nBody[0] )
+			{
+				for ( i=0 ; i<anRows.length ; i++ )
+				{
+					nBody[0].appendChild( anRows[i] );
+				}
+			}
+			
+			/* Update the pagination display buttons */
+			if ( oSettings.oFeatures.bPaginate )
+			{
+				_oExt.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) {
+					_fnCalculateEnd( oSettings );
+					_fnDraw( oSettings );
+				} );
+			}
+			
+			/* Show information about the table */
+			if ( oSettings.oFeatures.bInfo && oSettings.anFeatures.i )
+			{
+				/* Update the information */
+				if ( oSettings.fnRecordsDisplay() === 0 && 
+					   oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
+				{
+					oSettings.anFeatures.i.innerHTML = 
+						oSettings.oLanguage.sInfoEmpty+ oSettings.oLanguage.sInfoPostFix;
+				}
+				else if ( oSettings.fnRecordsDisplay() === 0 )
+				{
+					oSettings.anFeatures.i.innerHTML = oSettings.oLanguage.sInfoEmpty +' '+ 
+						oSettings.oLanguage.sInfoFiltered.replace('_MAX_', 
+							oSettings.fnRecordsTotal())+ oSettings.oLanguage.sInfoPostFix;
+				}
+				else if ( oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
+				{
+					oSettings.anFeatures.i.innerHTML = 
+						oSettings.oLanguage.sInfo.
+							replace('_START_',oSettings._iDisplayStart+1).
+							replace('_END_',oSettings.fnDisplayEnd()).
+							replace('_TOTAL_',oSettings.fnRecordsDisplay())+ 
+						oSettings.oLanguage.sInfoPostFix;
+				}
+				else
+				{
+					oSettings.anFeatures.i.innerHTML = 
+						oSettings.oLanguage.sInfo.
+							replace('_START_',oSettings._iDisplayStart+1).
+							replace('_END_',oSettings.fnDisplayEnd()).
+							replace('_TOTAL_',oSettings.fnRecordsDisplay()) +' '+ 
+						oSettings.oLanguage.sInfoFiltered.replace('_MAX_', oSettings.fnRecordsTotal())+ 
+						oSettings.oLanguage.sInfoPostFix;
+				}
+			}
+			
+			/* Alter the sorting classes to take account of the changes */
+			if ( oSettings.oFeatures.bServerSide && oSettings.oFeatures.bSort )
+			{
+				_fnSortingClasses( oSettings );
+			}
+			
+			/* Save the table state on each draw */
+			_fnSaveState( oSettings );
+			
+			/* Drawing is finished - call the callback if there is one */
+			if ( typeof oSettings.fnDrawCallback == 'function' )
+			{
+				oSettings.fnDrawCallback( oSettings );
+			}
+		}
+		
+		/*
+		 * Function: _fnReDraw
+		 * Purpose:  Redraw the table - taking account of the various features which are enabled
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnReDraw( oSettings )
+		{
+			if ( oSettings.oFeatures.bSort )
+			{
+				/* Sorting will refilter and draw for us */
+				_fnSort( oSettings, oSettings.oPreviousSearch );
+			}
+			else if ( oSettings.oFeatures.bFilter )
+			{
+				/* Filtering will redraw for us */
+				_fnFilterComplete( oSettings, oSettings.oPreviousSearch );
+			}
+			else
+			{
+				_fnCalculateEnd( oSettings );
+				_fnDraw( oSettings );
+			}
+		}
+		
+		/*
+		 * Function: _fnAjaxUpdate
+		 * Purpose:  Update the table using an Ajax call
+		 * Returns:  bool: block the table drawing or not
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnAjaxUpdate( oSettings )
+		{
+			if ( oSettings.bAjaxDataGet )
+			{
+				_fnProcessingDisplay( oSettings, true );
+				var iColumns = oSettings.aoColumns.length;
+				var aoData = [];
+				var i;
+				
+				/* Paging and general */
+				oSettings.iServerDraw++;
+				aoData.push( { "name": "sEcho",          "value": oSettings.iServerDraw } );
+				aoData.push( { "name": "iColumns",       "value": iColumns } );
+				aoData.push( { "name": "sColumns",       "value": _fnColumnOrdering(oSettings) } );
+				aoData.push( { "name": "iDisplayStart",  "value": oSettings._iDisplayStart } );
+				aoData.push( { "name": "iDisplayLength", "value": oSettings.oFeatures.bPaginate !== false ?
+					oSettings._iDisplayLength : -1 } );
+				
+				/* Filtering */
+				if ( oSettings.oFeatures.bFilter !== false )
+				{
+					aoData.push( { "name": "sSearch",        "value": oSettings.oPreviousSearch.sSearch } );
+					aoData.push( { "name": "bEscapeRegex",   "value": oSettings.oPreviousSearch.bEscapeRegex } );
+					for ( i=0 ; i<iColumns ; i++ )
+					{
+						aoData.push( { "name": "sSearch_"+i,      "value": oSettings.aoPreSearchCols[i].sSearch } );
+						aoData.push( { "name": "bEscapeRegex_"+i, "value": oSettings.aoPreSearchCols[i].bEscapeRegex } );
+					}
+				}
+				
+				/* Sorting */
+				if ( oSettings.oFeatures.bSort !== false )
+				{
+					var iFixed = oSettings.aaSortingFixed !== null ? oSettings.aaSortingFixed.length : 0;
+					var iUser = oSettings.aaSorting.length;
+					aoData.push( { "name": "iSortingCols",   "value": iFixed+iUser } );
+					for ( i=0 ; i<iFixed ; i++ )
+					{
+						aoData.push( { "name": "iSortCol_"+i,  "value": oSettings.aaSortingFixed[i][0] } );
+						aoData.push( { "name": "iSortDir_"+i,  "value": oSettings.aaSortingFixed[i][1] } );
+					}
+					
+					for ( i=0 ; i<iUser ; i++ )
+					{
+						aoData.push( { "name": "iSortCol_"+(i+iFixed),  "value": oSettings.aaSorting[i][0] } );
+						aoData.push( { "name": "iSortDir_"+(i+iFixed),  "value": oSettings.aaSorting[i][1] } );
+					}
+				}
+				
+				oSettings.fnServerData( oSettings.sAjaxSource, aoData, function(json) {
+					_fnAjaxUpdateDraw( oSettings, json );
+				} );
+				return false;
+			}
+			else
+			{
+				return true;
+			}
+		}
+		
+		/*
+		 * Function: _fnAjaxUpdateDraw
+		 * Purpose:  Data the data from the server (nuking the old) and redraw the table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           object:json - json data return from the server.
+		 *             The following must be defined:
+		 *               iTotalRecords, iTotalDisplayRecords, aaData
+		 *             The following may be defined:
+		 *               sColumns
+		 */
+		function _fnAjaxUpdateDraw ( oSettings, json )
+		{
+			if ( typeof json.sEcho != 'undefined' )
+			{
+				/* Protect against old returns over-writing a new one. Possible when you get
+				 * very fast interaction, and later queires are completed much faster
+				 */
+				if ( json.sEcho*1 < oSettings.iServerDraw )
+				{
+					return;
+				}
+				else
+				{
+					oSettings.iServerDraw = json.sEcho * 1;
+				}
+			}
+			
+			_fnClearTable( oSettings );
+			oSettings._iRecordsTotal = json.iTotalRecords;
+			oSettings._iRecordsDisplay = json.iTotalDisplayRecords;
+			
+			/* Determine if reordering is required */
+			var sOrdering = _fnColumnOrdering(oSettings);
+			var bReOrder = (json.sColumns != 'undefined' && sOrdering !== "" && json.sColumns != sOrdering );
+			if ( bReOrder )
+			{
+				var aiIndex = _fnReOrderIndex( oSettings, json.sColumns );
+			}
+			
+			for ( var i=0, iLen=json.aaData.length ; i<iLen ; i++ )
+			{
+				if ( bReOrder )
+				{
+					/* If we need to re-order, then create a new array with the correct order and add it */
+					var aData = [];
+					for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
+					{
+						aData.push( json.aaData[i][ aiIndex[j] ] );
+					}
+					_fnAddData( oSettings, aData );
+				}
+				else
+				{
+					/* No re-order required, sever got it "right" - just straight add */
+					_fnAddData( oSettings, json.aaData[i] );
+				}
+			}
+			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+			
+			oSettings.bAjaxDataGet = false;
+			_fnDraw( oSettings );
+			oSettings.bAjaxDataGet = true;
+			_fnProcessingDisplay( oSettings, false );
+		}
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * Options (features) HTML
+		 */
+		
+		/*
+		 * Function: _fnAddOptionsHtml
+		 * Purpose:  Add the options to the page HTML for the table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnAddOptionsHtml ( oSettings )
+		{
+			/*
+			 * Create a temporary, empty, div which we can later on replace with what we have generated
+			 * we do it this way to rendering the 'options' html offline - speed :-)
+			 */
+			var nHolding = document.createElement( 'div' );
+			oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable );
+			
+			/* 
+			 * All DataTables are wrapped in a div - this is not currently optional - backwards 
+			 * compatability. It can be removed if you don't want it.
+			 */
+			var nWrapper = document.createElement( 'div' );
+			nWrapper.className = oSettings.oClasses.sWrapper;
+			if ( oSettings.sTableId !== '' )
+			{
+				nWrapper.setAttribute( 'id', oSettings.sTableId+'_wrapper' );
+			}
+			
+			/* Track where we want to insert the option */
+			var nInsertNode = nWrapper;
+			
+			/* IE don't treat strings as arrays */
+			var sDom = oSettings.sDomPositioning.split('');
+			
+			/* Loop over the user set positioning and place the elements as needed */
+			var nTmp;
+			for ( var i=0 ; i<sDom.length ; i++ )
+			{
+				var cOption = sDom[i];
+				
+				if ( cOption == '<' )
+				{
+					/* New container div */
+					var nNewNode = document.createElement( 'div' );
+					
+					/* Check to see if we should append a class name to the container */
+					var cNext = sDom[i+1];
+					if ( cNext == "'" || cNext == '"' )
+					{
+						var sClass = "";
+						var j = 2;
+						while ( sDom[i+j] != cNext )
+						{
+							sClass += sDom[i+j];
+							j++;
+						}
+						nNewNode.className = sClass;
+						i += j; /* Move along the position array */
+					}
+					
+					nInsertNode.appendChild( nNewNode );
+					nInsertNode = nNewNode;
+				}
+				else if ( cOption == '>' )
+				{
+					/* End container div */
+					nInsertNode = nInsertNode.parentNode;
+				}
+				else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
+				{
+					/* Length */
+					nTmp = _fnFeatureHtmlLength( oSettings );
+					oSettings.anFeatures[cOption] = nTmp;
+					nInsertNode.appendChild( nTmp );
+				}
+				else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
+				{
+					/* Filter */
+					nTmp = _fnFeatureHtmlFilter( oSettings );
+					oSettings.anFeatures[cOption] = nTmp;
+					nInsertNode.appendChild( nTmp );
+				}
+				else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
+				{
+					/* pRocessing */
+					nTmp = _fnFeatureHtmlProcessing( oSettings );
+					oSettings.anFeatures[cOption] = nTmp;
+					nInsertNode.appendChild( nTmp );
+				}
+				else if ( cOption == 't' )
+				{
+					/* Table */
+					oSettings.anFeatures[cOption] = oSettings.nTable;
+					nInsertNode.appendChild( oSettings.nTable );
+				}
+				else if ( cOption ==  'i' && oSettings.oFeatures.bInfo )
+				{
+					/* Info */
+					nTmp = _fnFeatureHtmlInfo( oSettings );
+					oSettings.anFeatures[cOption] = nTmp;
+					nInsertNode.appendChild( nTmp );
+				}
+				else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
+				{
+					/* Pagination */
+					nTmp = _fnFeatureHtmlPaginate( oSettings );
+					oSettings.anFeatures[cOption] = nTmp;
+					nInsertNode.appendChild( nTmp );
+				}
+				else if ( _oExt.aoFeatures.length !== 0 )
+				{
+					var aoFeatures = _oExt.aoFeatures;
+					for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
+					{
+						if ( cOption == aoFeatures[k].cFeature )
+						{
+							nTmp = aoFeatures[k].fnInit( oSettings );
+							oSettings.anFeatures[cOption] = nTmp;
+							nInsertNode.appendChild( nTmp );
+							break;
+						}
+					}
+				}
+			}
+			
+			/* Built our DOM structure - replace the holding div with what we want */
+			nHolding.parentNode.replaceChild( nWrapper, nHolding );
+		}
+		
+		/*
+		 * Function: _fnFeatureHtmlFilter
+		 * Purpose:  Generate the node required for filtering text
+		 * Returns:  node
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnFeatureHtmlFilter ( oSettings )
+		{
+			var nFilter = document.createElement( 'div' );
+			if ( oSettings.sTableId !== '' )
+			{
+				nFilter.setAttribute( 'id', oSettings.sTableId+'_filter' );
+			}
+			nFilter.className = oSettings.oClasses.sFilter;
+			var sSpace = oSettings.oLanguage.sSearch==="" ? "" : " ";
+			nFilter.innerHTML = oSettings.oLanguage.sSearch+sSpace+'<input type="text" />';
+			
+			var jqFilter = $("input", nFilter);
+			jqFilter.val( oSettings.oPreviousSearch.sSearch.replace('"','&quot;') );
+			jqFilter.keyup( function(e) {
+				_fnFilterComplete( oSettings, { 
+					"sSearch": this.value, 
+					"bEscapeRegex": oSettings.oPreviousSearch.bEscapeRegex 
+				} );
+				
+				/* Prevent default */
+				return false;
+			} );
+			
+			return nFilter;
+		}
+		
+		/*
+		 * Function: _fnFeatureHtmlInfo
+		 * Purpose:  Generate the node required for the info display
+		 * Returns:  node
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnFeatureHtmlInfo ( oSettings )
+		{
+			var nInfo = document.createElement( 'div' );
+			if ( oSettings.sTableId !== '' )
+			{
+				nInfo.setAttribute( 'id', oSettings.sTableId+'_info' );
+			}
+			nInfo.className = oSettings.oClasses.sInfo;
+			return nInfo;
+		}
+		
+		/*
+		 * Function: _fnFeatureHtmlPaginate
+		 * Purpose:  Generate the node required for default pagination
+		 * Returns:  node
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnFeatureHtmlPaginate ( oSettings )
+		{
+			var nPaginate = document.createElement( 'div' );
+			nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType;
+			oSettings.anFeatures.p = nPaginate; /* Need this stored in order to call paging plug-ins */
+			
+			_oExt.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, function( oSettings ) {
+				_fnCalculateEnd( oSettings );
+				_fnDraw( oSettings );
+			} );
+			return nPaginate;
+		}
+		
+		/*
+		 * Function: _fnFeatureHtmlLength
+		 * Purpose:  Generate the node required for user display length changing
+		 * Returns:  node
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnFeatureHtmlLength ( oSettings )
+		{
+			/* This can be overruled by not using the _MENU_ var/macro in the language variable */
+			var sName = (oSettings.sTableId === "") ? "" : 'name="'+oSettings.sTableId+'_length"';
+			var sStdMenu = 
+				'<select size="1" '+sName+'>'+
+					'<option value="10">10</option>'+
+					'<option value="25">25</option>'+
+					'<option value="50">50</option>'+
+					'<option value="100">100</option>'+
+				'</select>';
+			
+			var nLength = document.createElement( 'div' );
+			if ( oSettings.sTableId !== '' )
+			{
+				nLength.setAttribute( 'id', oSettings.sTableId+'_length' );
+			}
+			nLength.className = oSettings.oClasses.sLength;
+			nLength.innerHTML = oSettings.oLanguage.sLengthMenu.replace( '_MENU_', sStdMenu );
+			
+			/*
+			 * Set the length to the current display length - thanks to Andrea Pavlovic for this fix,
+			 * and Stefan Skopnik for fixing the fix!
+			 */
+			$('select option[value="'+oSettings._iDisplayLength+'"]',nLength).attr("selected",true);
+			
+			$('select', nLength).change( function(e) {
+				oSettings._iDisplayLength = parseInt($(this).val(), 10);
+				
+				_fnCalculateEnd( oSettings );
+				
+				/* If we have space to show extra rows (backing up from the end point - then do so */
+				if ( oSettings._iDisplayEnd == oSettings.aiDisplay.length )
+				{
+					oSettings._iDisplayStart = oSettings._iDisplayEnd - oSettings._iDisplayLength;
+					if ( oSettings._iDisplayStart < 0 )
+					{
+						oSettings._iDisplayStart = 0;
+					}
+				}
+				
+				if ( oSettings._iDisplayLength == -1 )
+				{
+					oSettings._iDisplayStart = 0;
+				}
+				
+				_fnDraw( oSettings );
+			} );
+			
+			return nLength;
+		}
+		
+		/*
+		 * Function: _fnFeatureHtmlProcessing
+		 * Purpose:  Generate the node required for the processing node
+		 * Returns:  node
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnFeatureHtmlProcessing ( oSettings )
+		{
+			var nProcessing = document.createElement( 'div' );
+			
+			if ( oSettings.sTableId !== '' )
+			{
+				nProcessing.setAttribute( 'id', oSettings.sTableId+'_processing' );
+			}
+			nProcessing.innerHTML = oSettings.oLanguage.sProcessing;
+			nProcessing.className = oSettings.oClasses.sProcessing;
+			oSettings.nTable.parentNode.insertBefore( nProcessing, oSettings.nTable );
+			
+			return nProcessing;
+		}
+		
+		/*
+		 * Function: _fnProcessingDisplay
+		 * Purpose:  Display or hide the processing indicator
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           bool:
+		 *   true - show the processing indicator
+		 *   false - don't show
+		 */
+		function _fnProcessingDisplay ( oSettings, bShow )
+		{
+			if ( oSettings.oFeatures.bProcessing )
+			{
+				oSettings.anFeatures.r.style.visibility = bShow ? "visible" : "hidden";
+			}
+		}
+		
+		
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * Filtering
+		 */
+		
+		/*
+		 * Function: _fnFilterComplete
+		 * Purpose:  Filter the table using both the global filter and column based filtering
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           object:oSearch: search information
+		 *           int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
+		 */
+		function _fnFilterComplete ( oSettings, oInput, iForce )
+		{
+			/* Filter on everything */
+			_fnFilter( oSettings, oInput.sSearch, iForce, oInput.bEscapeRegex );
+			
+			/* Now do the individual column filter */
+			for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
+			{
+				_fnFilterColumn( oSettings, oSettings.aoPreSearchCols[i].sSearch, i, 
+					oSettings.aoPreSearchCols[i].bEscapeRegex );
+			}
+			
+			/* Custom filtering */
+			if ( _oExt.afnFiltering.length !== 0 )
+			{
+				_fnFilterCustom( oSettings );
+			}
+			
+			/* Redraw the table */
+			if ( typeof oSettings.iInitDisplayStart != 'undefined' && oSettings.iInitDisplayStart != -1 )
+			{
+				oSettings._iDisplayStart = oSettings.iInitDisplayStart;
+				oSettings.iInitDisplayStart = -1;
+			}
+			else
+			{
+				oSettings._iDisplayStart = 0;
+			}
+			_fnCalculateEnd( oSettings );
+			_fnDraw( oSettings );
+			
+			/* Rebuild search array 'offline' */
+			_fnBuildSearchArray( oSettings, 0 );
+		}
+		
+		/*
+		 * Function: _fnFilterCustom
+		 * Purpose:  Apply custom filtering functions
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnFilterCustom( oSettings )
+		{
+			var afnFilters = _oExt.afnFiltering;
+			for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ )
+			{
+				var iCorrector = 0;
+				for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ )
+				{
+					var iDisIndex = oSettings.aiDisplay[j-iCorrector];
+					
+					/* Check if we should use this row based on the filtering function */
+					if ( !afnFilters[i]( oSettings, oSettings.aoData[iDisIndex]._aData, iDisIndex ) )
+					{
+						oSettings.aiDisplay.splice( j-iCorrector, 1 );
+						iCorrector++;
+					}
+				}
+			}
+		}
+		
+		/*
+		 * Function: _fnFilterColumn
+		 * Purpose:  Filter the table on a per-column basis
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           string:sInput - string to filter on
+		 *           int:iColumn - column to filter
+		 *           bool:bEscapeRegex - escape regex or not
+		 */
+		function _fnFilterColumn ( oSettings, sInput, iColumn, bEscapeRegex )
+		{
+			if ( sInput === "" )
+			{
+				return;
+			}
+			
+			var iIndexCorrector = 0;
+			var sRegexMatch = bEscapeRegex ? _fnEscapeRegex( sInput ) : sInput;
+			var rpSearch = new RegExp( sRegexMatch, "i" );
+			
+			for ( var i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- )
+			{
+				var sData = _fnDataToSearch( oSettings.aoData[ oSettings.aiDisplay[i] ]._aData[iColumn],
+					oSettings.aoColumns[iColumn].sType );
+				if ( ! rpSearch.test( sData ) )
+				{
+					oSettings.aiDisplay.splice( i, 1 );
+					iIndexCorrector++;
+				}
+			}
+		}
+		
+		/*
+		 * Function: _fnFilter
+		 * Purpose:  Filter the data table based on user input and draw the table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           string:sInput - string to filter on
+		 *           int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
+		 *           bool:bEscapeRegex - escape regex or not
+		 */
+		function _fnFilter( oSettings, sInput, iForce, bEscapeRegex )
+		{
+			var i;
+			
+			/* Check if we are forcing or not - optional parameter */
+			if ( typeof iForce == 'undefined' || iForce === null )
+			{
+				iForce = 0;
+			}
+			
+			/* Need to take account of custom filtering functions always */
+			if ( _oExt.afnFiltering.length !== 0 )
+			{
+				iForce = 1;
+			}
+			
+			/* Generate the regular expression to use. Something along the lines of:
+			 * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
+			 */
+			var asSearch = bEscapeRegex ?
+				_fnEscapeRegex( sInput ).split( ' ' ) :
+				sInput.split( ' ' );
+			var sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$';
+			var rpSearch = new RegExp( sRegExpString, "i" ); /* case insensitive */
+			
+			/*
+			 * If the input is blank - we want the full data set
+			 */
+			if ( sInput.length <= 0 )
+			{
+				oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
+				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+			}
+			else
+			{
+				/*
+				 * We are starting a new search or the new search string is smaller 
+				 * then the old one (i.e. delete). Search from the master array
+			 	 */
+				if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length ||
+					   oSettings.oPreviousSearch.sSearch.length > sInput.length || iForce == 1 ||
+					   sInput.indexOf(oSettings.oPreviousSearch.sSearch) !== 0 )
+				{
+					/* Nuke the old display array - we are going to rebuild it */
+					oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
+					
+					/* Force a rebuild of the search array */
+					_fnBuildSearchArray( oSettings, 1 );
+					
+					/* Search through all records to populate the search array
+					 * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1 
+					 * mapping
+					 */
+					for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
+					{
+						if ( rpSearch.test(oSettings.asDataSearch[i]) )
+						{
+							oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] );
+						}
+					}
+			  }
+			  else
+				{
+			  	/* Using old search array - refine it - do it this way for speed
+			  	 * Don't have to search the whole master array again
+			 		 */
+			  	var iIndexCorrector = 0;
+			  	
+			  	/* Search the current results */
+			  	for ( i=0 ; i<oSettings.asDataSearch.length ; i++ )
+					{
+			  		if ( ! rpSearch.test(oSettings.asDataSearch[i]) )
+						{
+			  			oSettings.aiDisplay.splice( i-iIndexCorrector, 1 );
+			  			iIndexCorrector++;
+			  		}
+			  	}
+			  }
+			}
+			oSettings.oPreviousSearch.sSearch = sInput;
+			oSettings.oPreviousSearch.bEscapeRegex = bEscapeRegex;
+		}
+		
+		
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * Sorting
+		 */
+		
+		/*
+	 	 * Function: _fnSort
+		 * Purpose:  Change the order of the table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           bool:bApplyClasses - optional - should we apply classes or not
+		 * Notes:    We always sort the master array and then apply a filter again
+		 *   if it is needed. This probably isn't optimal - but atm I can't think
+		 *   of any other way which is (each has disadvantages)
+		 */
+		function _fnSort ( oSettings, bApplyClasses )
+		{
+			/*
+			 * Funny one this - we want to sort aiDisplayMaster - but according to aoData[]._aData
+			 *
+			 * function _fnSortText ( a, b )
+			 * {
+			 * 	var iTest;
+			 * 	var oSort = _oExt.oSort;
+			 * 	
+			 * 	iTest = oSort['string-asc']( aoData[ a ]._aData[ COL ], aoData[ b ]._aData[ COL ] );
+			 * 	if ( iTest === 0 )
+			 * 		...
+			 * }
+			 */
+			
+			/* Here is what we are looking to achieve here (custom sort functions add complication...)
+			 * function _fnSortText ( a, b )
+			 * {
+			 * 	var iTest;
+			 *  var oSort = _oExt.oSort;
+			 * 	iTest = oSort['string-asc']( a[0], b[0] );
+			 * 	if ( iTest === 0 )
+			 * 		iTest = oSort['string-asc']( a[1], b[1] );
+			 * 		if ( iTest === 0 )
+			 * 			iTest = oSort['string-asc']( a[2], b[2] );
+			 * 	
+			 * 	return iTest;
+			 * }
+			 */
+			var aaSort = [];
+			var oSort = _oExt.oSort;
+			var aoData = oSettings.aoData;
+			var iDataSort;
+			var iDataType;
+			var i;
+			
+			if ( oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null )
+			{
+				if ( oSettings.aaSortingFixed !== null )
+				{
+					aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
+				}
+				else
+				{
+					aaSort = oSettings.aaSorting.slice();
+				}
+				
+				if ( !window.runtime )
+				{
+					var fnLocalSorting;
+					var sDynamicSort = "fnLocalSorting = function(a,b){"+
+						"var iTest;";
+					
+					for ( i=0 ; i<aaSort.length-1 ; i++ )
+					{
+						iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
+						iDataType = oSettings.aoColumns[ iDataSort ].sType;
+						sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[i][1]+"']"+
+							"( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); if ( iTest === 0 )";
+					}
+					
+					iDataSort = oSettings.aoColumns[ aaSort[aaSort.length-1][0] ].iDataSort;
+					iDataType = oSettings.aoColumns[ iDataSort ].sType;
+					sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[aaSort.length-1][1]+"']"+
+						"( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); return iTest;}";
+					
+					/* The eval has to be done to a variable for IE */
+					eval( sDynamicSort );
+					oSettings.aiDisplayMaster.sort( fnLocalSorting );
+				}
+				else
+				{
+					/*
+					 * Support for Adobe AIR - AIR doesn't allow eval with a function
+					 * Note that for reasonable sized data sets this method is around 1.5 times slower than
+					 * the eval above (hence why it is not used all the time). Oddly enough, it is ever so
+					 * slightly faster for very small sets (presumably the eval has overhead).
+					 *   Single column (1083 records) - eval: 32mS   AIR: 38mS
+					 *   Two columns (1083 records) -   eval: 55mS   AIR: 66mS
+					 */
+					
+					/* Build a cached array so the sort doesn't have to process this stuff on every call */
+					var aAirSort = [];
+					var iLen = aaSort.length;
+					for ( i=0 ; i<iLen ; i++ )
+					{
+						iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
+						aAirSort.push( [
+							iDataSort,
+							oSettings.aoColumns[ iDataSort ].sType+'-'+aaSort[i][1]
+						] );
+					}
+					
+					oSettings.aiDisplayMaster.sort( function (a,b) {
+						var iTest;
+						for ( var i=0 ; i<iLen ; i++ )
+						{
+							iTest = oSort[ aAirSort[i][1] ]( aoData[a]._aData[aAirSort[i][0]], aoData[b]._aData[aAirSort[i][0]] );
+							if ( iTest !== 0 )
+							{
+								return iTest;
+							}
+						}
+						return 0;
+					} );
+				}
+			}
+			
+			/* Alter the sorting classes to take account of the changes */
+			if ( typeof bApplyClasses == 'undefined' || bApplyClasses )
+			{
+				_fnSortingClasses( oSettings );
+			}
+			
+			/* Copy the master data into the draw array and re-draw */
+			if ( oSettings.oFeatures.bFilter )
+			{
+				/* _fnFilter() will redraw the table for us */
+				_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
+			}
+			else
+			{
+				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+				oSettings._iDisplayStart = 0; /* reset display back to page 0 */
+				_fnCalculateEnd( oSettings );
+				_fnDraw( oSettings );
+			}
+		}
+		
+		/*
+		 * Function: _fnSortingClasses
+		 * Purpose:  Set the sortting classes on the header
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnSortingClasses( oSettings )
+		{
+			var i, j, iFound;
+			var aaSort, sClass;
+			var iColumns = oSettings.aoColumns.length;
+			var oClasses = oSettings.oClasses;
+			
+			for ( i=0 ; i<iColumns ; i++ )
+			{
+				$(oSettings.aoColumns[i].nTh).removeClass( oClasses.sSortAsc +" "+ oClasses.sSortDesc +
+				 	" "+ oClasses.sSortable );
+			}
+			
+			if ( oSettings.aaSortingFixed !== null )
+			{
+				aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
+			}
+			else
+			{
+				aaSort = oSettings.aaSorting.slice();
+			}
+			
+			/* Apply the required classes to the header */
+			for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+			{
+				if ( oSettings.aoColumns[i].bSortable && oSettings.aoColumns[i].bVisible )
+				{
+					sClass = oClasses.sSortable;
+					iFound = -1;
+					for ( j=0 ; j<aaSort.length ; j++ )
+					{
+						if ( aaSort[j][0] == i )
+						{
+							sClass = ( aaSort[j][1] == "asc" ) ?
+								oClasses.sSortAsc : oClasses.sSortDesc;
+							iFound = j;
+							break;
+						}
+					}
+					$(oSettings.aoColumns[i].nTh).addClass( sClass );
+					
+					if ( oSettings.bJUI )
+					{
+						/* jQuery UI uses extra markup */
+						var jqSpan = $("span", oSettings.aoColumns[i].nTh);
+						jqSpan.removeClass(oClasses.sSortJUIAsc +" "+ oClasses.sSortJUIDesc +" "+ 
+							oClasses.sSortJUI);
+						
+						var sSpanClass;
+						if ( iFound == -1 )
+						{
+						 	sSpanClass = oClasses.sSortJUI;
+						}
+						else if ( aaSort[iFound][1] == "asc" )
+						{
+							sSpanClass = oClasses.sSortJUIAsc;
+						}
+						else
+						{
+							sSpanClass = oClasses.sSortJUIDesc;
+						}
+						
+						jqSpan.addClass( sSpanClass );
+					}
+				}
+			}
+			
+			/* 
+			 * Apply the required classes to the table body
+			 * Note that this is given as a feature switch since it can significantly slow down a sort
+			 * on large data sets (adding and removing of classes is always slow at the best of times..)
+			 */
+			if ( oSettings.oFeatures.bSortClasses )
+			{
+				var nTrs = _fnGetTrNodes( oSettings );
+				sClass = oClasses.sSortColumn;
+				$('td', nTrs).removeClass( sClass+"1 "+sClass+"2 "+sClass+"3" );
+				
+				var iClass = 1;
+				for ( i=0 ; i<aaSort.length ; i++ )
+				{
+					var iVis = _fnColumnIndexToVisible(oSettings, aaSort[i][0]);
+					if ( iVis !== null )
+					{
+						/* Limit the number of classes to three */
+						if ( iClass <= 2 )
+						{
+							$('td:eq('+iVis+')', nTrs).addClass( sClass+iClass );
+						}
+						else
+						{
+							$('td:eq('+iVis+')', nTrs).addClass( sClass+'3' );
+						}
+						iClass++;
+					}
+				}
+			}
+		}
+		
+		/*
+		 * Function: _fnVisibleToColumnIndex
+		 * Purpose:  Covert the index of a visible column to the index in the data array (take account
+		 *   of hidden columns)
+		 * Returns:  int:i - the data index
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnVisibleToColumnIndex( oSettings, iMatch )
+		{
+			var iColumn = -1;
+			
+			for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
+			{
+				if ( oSettings.aoColumns[i].bVisible === true )
+				{
+					iColumn++;
+				}
+				
+				if ( iColumn == iMatch )
+				{
+					return i;
+				}
+			}
+			
+			return null;
+		}
+		
+		/*
+		 * Function: _fnColumnIndexToVisible
+		 * Purpose:  Covert the index of an index in the data array and convert it to the visible
+		 *   column index (take account of hidden columns)
+		 * Returns:  int:i - the data index
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnColumnIndexToVisible( oSettings, iMatch )
+		{
+			var iVisible = -1;
+			for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
+			{
+				if ( oSettings.aoColumns[i].bVisible === true )
+				{
+					iVisible++;
+				}
+				
+				if ( i == iMatch )
+				{
+					return oSettings.aoColumns[i].bVisible === true ? iVisible : null;
+				}
+			}
+			
+			return null;
+		}
+		
+		/*
+		 * Function: _fnVisbleColumns
+		 * Purpose:  Get the number of visible columns
+		 * Returns:  int:i - the number of visible columns
+		 * Inputs:   object:oS - dataTables settings object
+		 */
+		function _fnVisbleColumns( oS )
+		{
+			var iVis = 0;
+			for ( var i=0 ; i<oS.aoColumns.length ; i++ )
+			{
+				if ( oS.aoColumns[i].bVisible === true )
+				{
+					iVis++;
+				}
+			}
+			return iVis;
+		}
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * Support functions
+		 */
+		
+		/*
+		 * Function: _fnBuildSearchArray
+		 * Purpose:  Create an array which can be quickly search through
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           int:iMaster - use the master data array - optional
+		 */
+		function _fnBuildSearchArray ( oSettings, iMaster )
+		{
+			/* Clear out the old data */
+			oSettings.asDataSearch.splice( 0, oSettings.asDataSearch.length );
+			
+			var aArray = (typeof iMaster != 'undefined' && iMaster == 1) ?
+			 	oSettings.aiDisplayMaster : oSettings.aiDisplay;
+			
+			for ( var i=0, iLen=aArray.length ; i<iLen ; i++ )
+			{
+				oSettings.asDataSearch[i] = '';
+				for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
+				{
+					if ( oSettings.aoColumns[j].bSearchable )
+					{
+						var sData = oSettings.aoData[ aArray[i] ]._aData[j];
+						oSettings.asDataSearch[i] += _fnDataToSearch( sData, oSettings.aoColumns[j].sType )+' ';
+					}
+				}
+			}
+		}
+		
+		/*
+		 * Function: _fnDataToSearch
+		 * Purpose:  Convert raw data into something that the user can search on
+		 * Returns:  string: - search string
+		 * Inputs:   string:sData - data to be modified
+		 *           string:sType - data type
+		 */
+		function _fnDataToSearch ( sData, sType )
+		{
+			
+			if ( typeof _oExt.ofnSearch[sType] == "function" )
+			{
+				return _oExt.ofnSearch[sType]( sData );
+			}
+			else if ( sType == "html" )
+			{
+				return sData.replace(/\n/g," ").replace( /<.*?>/g, "" );
+			}
+			else if ( typeof sData == "string" )
+			{
+				return sData.replace(/\n/g," ");
+			}
+			return sData;
+		}
+		
+		/*
+		 * Function: _fnCalculateEnd
+		 * Purpose:  Rcalculate the end point based on the start point
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnCalculateEnd( oSettings )
+		{
+			if ( oSettings.oFeatures.bPaginate === false )
+			{
+				oSettings._iDisplayEnd = oSettings.aiDisplay.length;
+			}
+			else
+			{
+				/* Set the end point of the display - based on how many elements there are
+				 * still to display
+				 */
+				if ( oSettings._iDisplayStart + oSettings._iDisplayLength > oSettings.aiDisplay.length ||
+					   oSettings._iDisplayLength == -1 )
+				{
+					oSettings._iDisplayEnd = oSettings.aiDisplay.length;
+				}
+				else
+				{
+					oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength;
+				}
+			}
+		}
+		
+		/*
+		 * Function: _fnConvertToWidth
+		 * Purpose:  Convert a CSS unit width to pixels (e.g. 2em)
+		 * Returns:  int:iWidth - width in pixels
+		 * Inputs:   string:sWidth - width to be converted
+		 *           node:nParent - parent to get the with for (required for
+		 *             relative widths) - optional
+		 */
+		function _fnConvertToWidth ( sWidth, nParent )
+		{
+			if ( !sWidth || sWidth === null || sWidth === '' )
+			{
+				return 0;
+			}
+			
+			if ( typeof nParent == "undefined" )
+			{
+				nParent = document.getElementsByTagName('body')[0];
+			}
+			
+			var iWidth;
+			var nTmp = document.createElement( "div" );
+			nTmp.style.width = sWidth;
+			
+			nParent.appendChild( nTmp );
+			iWidth = nTmp.offsetWidth;
+			nParent.removeChild( nTmp );
+			
+			return ( iWidth );
+		}
+		
+		/*
+		 * Function: _fnCalculateColumnWidths
+		 * Purpose:  Calculate the width of columns for the table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnCalculateColumnWidths ( oSettings )
+		{
+			var iTableWidth = oSettings.nTable.offsetWidth;
+			var iTotalUserIpSize = 0;
+			var iTmpWidth;
+			var iVisibleColumns = 0;
+			var iColums = oSettings.aoColumns.length;
+			var i;
+			var oHeaders = $('thead th', oSettings.nTable);
+			
+			/* Convert any user input sizes into pixel sizes */
+			for ( i=0 ; i<iColums ; i++ )
+			{
+				if ( oSettings.aoColumns[i].bVisible )
+				{
+					iVisibleColumns++;
+					
+					if ( oSettings.aoColumns[i].sWidth !== null )
+					{
+						iTmpWidth = _fnConvertToWidth( oSettings.aoColumns[i].sWidth, 
+							oSettings.nTable.parentNode );
+						
+						/* Total up the user defined widths for later calculations */
+						iTotalUserIpSize += iTmpWidth;
+						
+						oSettings.aoColumns[i].sWidth = iTmpWidth+"px";
+					}
+				}
+			}
+			
+			/* If the number of columns in the DOM equals the number that we
+			 * have to process in dataTables, then we can use the offsets that are
+			 * created by the web-browser. No custom sizes can be set in order for
+			 * this to happen
+			 */
+			if ( iColums == oHeaders.length && iTotalUserIpSize === 0 && iVisibleColumns == iColums )
+			{
+				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+				{
+					oSettings.aoColumns[i].sWidth = oHeaders[i].offsetWidth+"px";
+				}
+			}
+			else
+			{
+				/* Otherwise we are going to have to do some calculations to get
+				 * the width of each column. Construct a 1 row table with the maximum
+				 * string sizes in the data, and any user defined widths
+				 */
+				var nCalcTmp = oSettings.nTable.cloneNode( false );
+				nCalcTmp.setAttribute( "id", '' );
+				
+				var sTableTmp = '<table class="'+nCalcTmp.className+'">';
+				var sCalcHead = "<tr>";
+				var sCalcHtml = "<tr>";
+				
+				/* Construct a tempory table which we will inject (invisibly) into
+				 * the dom - to let the browser do all the hard word
+				 */
+				for ( i=0 ; i<iColums ; i++ )
+				{
+					if ( oSettings.aoColumns[i].bVisible )
+					{
+						sCalcHead += '<th>'+oSettings.aoColumns[i].sTitle+'</th>';
+						
+						if ( oSettings.aoColumns[i].sWidth !== null )
+						{
+							var sWidth = '';
+							if ( oSettings.aoColumns[i].sWidth !== null )
+							{
+								sWidth = ' style="width:'+oSettings.aoColumns[i].sWidth+';"';
+							}
+							
+							sCalcHtml += '<td'+sWidth+' tag_index="'+i+'">'+fnGetMaxLenString( oSettings, i)+'</td>';
+						}
+						else
+						{
+							sCalcHtml += '<td tag_index="'+i+'">'+fnGetMaxLenString( oSettings, i)+'</td>';
+						}
+					}
+				}
+				
+				sCalcHead += "</tr>";
+				sCalcHtml += "</tr>";
+				
+				/* Create the tmp table node (thank you jQuery) */
+				nCalcTmp = $( sTableTmp + sCalcHead + sCalcHtml +'</table>' )[0];
+				nCalcTmp.style.width = iTableWidth + "px";
+				nCalcTmp.style.visibility = "hidden";
+				nCalcTmp.style.position = "absolute"; /* Try to aviod scroll bar */
+				
+				oSettings.nTable.parentNode.appendChild( nCalcTmp );
+				
+				var oNodes = $("td", nCalcTmp);
+				var iIndex;
+				
+				/* Gather in the browser calculated widths for the rows */
+				for ( i=0 ; i<oNodes.length ; i++ )
+				{
+					iIndex = oNodes[i].getAttribute('tag_index');
+					
+					oSettings.aoColumns[iIndex].sWidth = $("td", nCalcTmp)[i].offsetWidth +"px";
+				}
+				
+				oSettings.nTable.parentNode.removeChild( nCalcTmp );
+			}
+		}
+		
+		/*
+		 * Function: fnGetMaxLenString
+		 * Purpose:  Get the maximum strlen for each data column
+		 * Returns:  string: - max strlens for each column
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           int:iCol - column of interest
+		 */
+		function fnGetMaxLenString( oSettings, iCol )
+		{
+			var iMax = 0;
+			var iMaxIndex = -1;
+			
+			for ( var i=0 ; i<oSettings.aoData.length ; i++ )
+			{
+				if ( oSettings.aoData[i]._aData[iCol].length > iMax )
+				{
+					iMax = oSettings.aoData[i]._aData[iCol].length;
+					iMaxIndex = i;
+				}
+			}
+			
+			if ( iMaxIndex >= 0 )
+			{
+				return oSettings.aoData[iMaxIndex]._aData[iCol];
+			}
+			return '';
+		}
+		
+		/*
+		 * Function: _fnArrayCmp
+		 * Purpose:  Compare two arrays
+		 * Returns:  0 if match, 1 if length is different, 2 if no match
+		 * Inputs:   array:aArray1 - first array
+		 *           array:aArray2 - second array
+		 */
+		function _fnArrayCmp( aArray1, aArray2 )
+		{
+			if ( aArray1.length != aArray2.length )
+			{
+				return 1;
+			}
+			
+			for ( var i=0 ; i<aArray1.length ; i++ )
+			{
+				if ( aArray1[i] != aArray2[i] )
+				{
+					return 2;
+				}
+			}
+			
+			return 0;
+		}
+		
+		/*
+		 * Function: _fnDetectType
+		 * Purpose:  Get the sort type based on an input string
+		 * Returns:  string: - type (defaults to 'string' if no type can be detected)
+		 * Inputs:   string:sData - data we wish to know the type of
+		 * Notes:    This function makes use of the DataTables plugin objct _oExt 
+		 *   (.aTypes) such that new types can easily be added.
+		 */
+		function _fnDetectType( sData )
+		{
+			var aTypes = _oExt.aTypes;
+			var iLen = aTypes.length;
+			
+			for ( var i=0 ; i<iLen ; i++ )
+			{
+				var sType = aTypes[i]( sData );
+				if ( sType !== null )
+				{
+					return sType;
+				}
+			}
+			
+			return 'string';
+		}
+		
+		/*
+		 * Function: _fnSettingsFromNode
+		 * Purpose:  Return the settings object for a particular table
+		 * Returns:  object: Settings object - or null if not found
+		 * Inputs:   node:nTable - table we are using as a dataTable
+		 */
+		function _fnSettingsFromNode ( nTable )
+		{
+			for ( var i=0 ; i<_aoSettings.length ; i++ )
+			{
+				if ( _aoSettings[i].nTable == nTable )
+				{
+					return _aoSettings[i];
+				}
+			}
+			
+			return null;
+		}
+		
+		/*
+		 * Function: _fnGetDataMaster
+		 * Purpose:  Return an array with the full table data
+		 * Returns:  array array:aData - Master data array
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnGetDataMaster ( oSettings )
+		{
+			var aData = [];
+			var iLen = oSettings.aoData.length;
+			for ( var i=0 ; i<iLen; i++ )
+			{
+				if ( oSettings.aoData[i] === null )
+				{
+					aData.push( null );
+				}
+				else
+				{
+					aData.push( oSettings.aoData[i]._aData );
+				}
+			}
+			return aData;
+		}
+		
+		/*
+		 * Function: _fnGetTrNodes
+		 * Purpose:  Return an array with the TR nodes for the table
+		 * Returns:  array array:aData - TR array
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnGetTrNodes ( oSettings )
+		{
+			var aNodes = [];
+			var iLen = oSettings.aoData.length;
+			for ( var i=0 ; i<iLen ; i++ )
+			{
+				if ( oSettings.aoData[i] === null )
+				{
+					aNodes.push( null );
+				}
+				else
+				{
+					aNodes.push( oSettings.aoData[i].nTr );
+				}
+			}
+			return aNodes;
+		}
+		
+		/*
+		 * Function: _fnEscapeRegex
+		 * Purpose:  scape a string stuch that it can be used in a regular expression
+		 * Returns:  string: - escaped string
+		 * Inputs:   string:sVal - string to escape
+		 */
+		function _fnEscapeRegex ( sVal )
+		{
+			var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^' ];
+		  var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' );
+		  return sVal.replace(reReplace, '\\$1');
+		}
+		
+		/*
+		 * Function: _fnReOrderIndex
+		 * Purpose:  Figure out how to reorder a display list
+		 * Returns:  array int:aiReturn - index list for reordering
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnReOrderIndex ( oSettings, sColumns )
+		{
+			var aColumns = sColumns.split(',');
+			var aiReturn = [];
+			
+			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+			{
+				for ( var j=0 ; j<iLen ; j++ )
+				{
+					if ( oSettings.aoColumns[i].sName == aColumns[j] )
+					{
+						aiReturn.push( j );
+						break;
+					}
+				}
+			}
+			
+			return aiReturn;
+		}
+		
+		/*
+		 * Function: _fnColumnOrdering
+		 * Purpose:  Get the column ordering that DataTables expects
+		 * Returns:  string: - comma separated list of names
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnColumnOrdering ( oSettings )
+		{
+			var sNames = '';
+			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+			{
+				sNames += oSettings.aoColumns[i].sName+',';
+			}
+			if ( sNames.length == iLen )
+			{
+				return "";
+			}
+			return sNames.slice(0, -1);
+		}
+		
+		/*
+		 * Function: _fnClearTable
+		 * Purpose:  Nuke the table
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnClearTable( oSettings )
+		{
+			oSettings.aoData.length = 0;
+			oSettings.aiDisplayMaster.length = 0;
+			oSettings.aiDisplay.length = 0;
+			_fnCalculateEnd( oSettings );
+		}
+		
+		/*
+		 * Function: _fnSaveState
+		 * Purpose:  Save the state of a table in a cookie such that the page can be reloaded
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 */
+		function _fnSaveState ( oSettings )
+		{
+			if ( !oSettings.oFeatures.bStateSave )
+			{
+				return;
+			}
+			
+			/* Store the interesting variables */
+			var i;
+			var sValue = "{";
+			sValue += '"iStart": '+oSettings._iDisplayStart+',';
+			sValue += '"iEnd": '+oSettings._iDisplayEnd+',';
+			sValue += '"iLength": '+oSettings._iDisplayLength+',';
+			sValue += '"sFilter": "'+oSettings.oPreviousSearch.sSearch.replace('"','\\"')+'",';
+			sValue += '"sFilterEsc": '+oSettings.oPreviousSearch.bEscapeRegex+',';
+			
+			sValue += '"aaSorting": [ ';
+			for ( i=0 ; i<oSettings.aaSorting.length ; i++ )
+			{
+				sValue += "["+oSettings.aaSorting[i][0]+",'"+oSettings.aaSorting[i][1]+"'],";
+			}
+			sValue = sValue.substring(0, sValue.length-1);
+			sValue += "],";
+			
+			sValue += '"aaSearchCols": [ ';
+			for ( i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
+			{
+				sValue += "['"+oSettings.aoPreSearchCols[i].sSearch.replace("'","\'")+
+					"',"+oSettings.aoPreSearchCols[i].bEscapeRegex+"],";
+			}
+			sValue = sValue.substring(0, sValue.length-1);
+			sValue += "],";
+			
+			sValue += '"abVisCols": [ ';
+			for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
+			{
+				sValue += oSettings.aoColumns[i].bVisible+",";
+			}
+			sValue = sValue.substring(0, sValue.length-1);
+			sValue += "]";
+			
+			sValue += "}";
+			_fnCreateCookie( "SpryMedia_DataTables_"+oSettings.sInstance, sValue, 
+				oSettings.iCookieDuration );
+		}
+		
+		/*
+		 * Function: _fnLoadState
+		 * Purpose:  Attempt to load a saved table state from a cookie
+		 * Returns:  -
+		 * Inputs:   object:oSettings - dataTables settings object
+		 *           object:oInit - DataTables init object so we can override settings
+		 */
+		function _fnLoadState ( oSettings, oInit )
+		{
+			if ( !oSettings.oFeatures.bStateSave )
+			{
+				return;
+			}
+			
+			var oData;
+			var sData = _fnReadCookie( "SpryMedia_DataTables_"+oSettings.sInstance );
+			if ( sData !== null && sData !== '' )
+			{
+				/* Try/catch the JSON eval - if it is bad then we ignore it */
+				try
+				{
+					/* Use the JSON library for safety - if it is available */
+					if ( typeof JSON == 'object' && typeof JSON.parse == 'function' )
+					{
+						/* DT 1.4.0 used single quotes for a string - JSON.parse doesn't allow this and throws
+						 * an error. So for now we can do this. This can be removed in future it is just to
+						 * allow the tranfrer to 1.4.1+ to occur
+						 */
+						oData = JSON.parse( sData.replace(/'/g, '"') );
+					}
+					else
+					{
+						oData = eval( '('+sData+')' );
+					}
+				}
+				catch( e )
+				{
+					return;
+				}
+				
+				/* Restore key features */
+				oSettings._iDisplayStart = oData.iStart;
+				oSettings.iInitDisplayStart = oData.iStart;
+				oSettings._iDisplayEnd = oData.iEnd;
+				oSettings._iDisplayLength = oData.iLength;
+				oSettings.oPreviousSearch.sSearch = oData.sFilter;
+				oSettings.aaSorting = oData.aaSorting.slice();
+				
+				/* Search filtering - global reference added in 1.4.1 */
+				if ( typeof oData.sFilterEsc != 'undefined' )
+				{
+					oSettings.oPreviousSearch.bEscapeRegex = oData.sFilterEsc;
+				}
+				
+				/* Column filtering - added in 1.5.0 beta 6 */
+				if ( typeof oData.aaSearchCols != 'undefined' )
+				{
+					for ( var i=0 ; i<oData.aaSearchCols.length ; i++ )
+					{
+						oSettings.aoPreSearchCols[i] = {
+							"sSearch": oData.aaSearchCols[i][0],
+							"bEscapeRegex": oData.aaSearchCols[i][1]
+						};
+					}
+				}
+				
+				/* Column visibility state - added in 1.5.0 beta 10 */
+				if ( typeof oData.abVisCols != 'undefined' )
+				{
+					/* We need to override the settings in oInit for this */
+					if ( typeof oInit.aoColumns == 'undefined' )
+					{
+						oInit.aoColumns = [];
+					}
+					
+					for ( i=0 ; i<oData.abVisCols.length ; i++ )
+					{
+						if ( typeof oInit.aoColumns[i] == 'undefined' || oInit.aoColumns[i] === null )
+						{
+							oInit.aoColumns[i] = {};
+						}
+						
+						oInit.aoColumns[i].bVisible = oData.abVisCols[i];
+					}
+				}
+			}
+		}
+		
+		/*
+		 * Function: _fnCreateCookie
+		 * Purpose:  Create a new cookie with a value to store the state of a table
+		 * Returns:  -
+		 * Inputs:   string:sName - name of the cookie to create
+		 *           string:sValue - the value the cookie should take
+		 *           int:iSecs - duration of the cookie
+		 */
+		function _fnCreateCookie ( sName, sValue, iSecs )
+		{
+			var date = new Date();
+			date.setTime( date.getTime()+(iSecs*1000) );
+			
+			/* 
+			 * Shocking but true - it would appear IE has major issues with having the path being
+			 * set to anything but root. We need the cookie to be available based on the path, so we
+			 * have to append the pathname to the cookie name. Appalling.
+			 */
+			sName += '_'+window.location.pathname.replace(/[\/:]/g,"").toLowerCase();
+			
+			document.cookie = sName+"="+sValue+"; expires="+date.toGMTString()+"; path=/";
+		}
+		
+		/*
+		 * Function: _fnReadCookie
+		 * Purpose:  Read an old cookie to get a cookie with an old table state
+		 * Returns:  string: - contents of the cookie - or null if no cookie with that name found
+		 * Inputs:   string:sName - name of the cookie to read
+		 */
+		function _fnReadCookie ( sName )
+		{
+			var sNameEQ = sName +'_'+ window.location.pathname.replace(/[\/:]/g,"").toLowerCase() + "=";
+			var sCookieContents = document.cookie.split(';');
+			
+			for( var i=0 ; i<sCookieContents.length ; i++ )
+			{
+				var c = sCookieContents[i];
+				
+				while (c.charAt(0)==' ')
+				{
+					c = c.substring(1,c.length);
+				}
+				
+				if (c.indexOf(sNameEQ) === 0)
+				{
+					return c.substring(sNameEQ.length,c.length);
+				}
+			}
+			return null;
+		}
+		
+		/*
+		 * Function: _fnGetUniqueThs
+		 * Purpose:  Get an array of unique th elements, one for each column
+		 * Returns:  array node:aReturn - list of unique ths
+		 * Inputs:   node:nThead - The thead element for the table
+		 */
+		function _fnGetUniqueThs ( nThead )
+		{
+			var nTrs = nThead.getElementsByTagName('tr');
+			
+			/* Nice simple case */
+			if ( nTrs.length == 1 )
+			{
+				return nTrs[0].getElementsByTagName('th');
+			}
+			
+			/* Otherwise we need to figure out the layout array to get the nodes */
+			var aLayout = [], aReturn = [];
+			var ROWSPAN = 2, COLSPAN = 3, TDELEM = 4;
+			var i, j, k, iLen, jLen, iColumnShifted;
+			var fnShiftCol = function ( a, i, j ) {
+				while ( typeof a[i][j] != 'undefined' ) {
+					j++;
+				}
+				return j;
+			};
+			var fnAddRow = function ( i ) {
+				if ( typeof aLayout[i] == 'undefined' ) {
+					aLayout[i] = [];
+				}
+			};
+			
+			/* Calculate a layout array */
+			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
+			{
+				fnAddRow( i );
+				var iColumn = 0;
+				var nTds = [];
+				
+				for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ )
+				{
+					if ( nTrs[i].childNodes[j].nodeName == "TD" || nTrs[i].childNodes[j].nodeName == "TH" )
+					{
+						nTds.push( nTrs[i].childNodes[j] );
+					}
+				}
+				
+				for ( j=0, jLen=nTds.length ; j<jLen ; j++ )
+				{
+					var iColspan = nTds[j].getAttribute('colspan') * 1;
+					var iRowspan = nTds[j].getAttribute('rowspan') * 1;
+					
+					if ( !iColspan || iColspan===0 || iColspan===1 )
+					{
+						iColumnShifted = fnShiftCol( aLayout, i, iColumn );
+						aLayout[i][iColumnShifted] = (nTds[j].nodeName=="TD") ? TDELEM : nTds[j];
+						if ( iRowspan || iRowspan===0 || iRowspan===1 )
+						{
+							for ( k=1 ; k<iRowspan ; k++ )
+							{
+								fnAddRow( i+k );
+								aLayout[i+k][iColumnShifted] = ROWSPAN;
+							}
+						}
+						iColumn++;
+					}
+					else
+					{
+						iColumnShifted = fnShiftCol( aLayout, i, iColumn );
+						for ( k=0 ; k<iColspan ; k++ )
+						{
+							aLayout[i][iColumnShifted+k] = COLSPAN;
+						}
+						iColumn += iColspan;
+					}
+				}
+			}
+			
+			/* Convert the layout array into a node array
+			 * Note the use of aLayout[0] in the outloop, we want the outer loop to occur the same
+			 * number of times as there are columns. Unusual having nested loops this way around
+			 * but is what we need here.
+			 */
+			for ( i=0, iLen=aLayout[0].length ; i<iLen ; i++ )
+			{
+				for ( j=0, jLen=aLayout.length ; j<jLen ; j++ )
+				{
+					if ( typeof aLayout[j][i] == 'object' )
+					{
+						aReturn.push( aLayout[j][i] );
+					}
+				}
+			}
+			
+			return aReturn;
+		}
+		
+		/*
+		 * Function: _fnMap
+		 * Purpose:  See if a property is defined on one object, if so assign it to the other object
+		 * Returns:  - (done by reference)
+		 * Inputs:   object:oRet - target object
+		 *           object:oSrc - source object
+		 *           string:sName - property
+		 *           string:sMappedName - name to map too - optional, sName used if not given
+		 */
+		function _fnMap( oRet, oSrc, sName, sMappedName )
+		{
+			if ( typeof sMappedName == 'undefined' )
+			{
+				sMappedName = sName;
+			}
+			if ( typeof oSrc[sName] != 'undefined' )
+			{
+				oRet[sMappedName] = oSrc[sName];
+			}
+		}
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * API
+		 * 
+		 * I'm not overly happy with this solution - I'd much rather that there was a way of getting
+		 * a list of all the private functions and do what we need to dynamically - but that doesn't
+		 * appear to be possible. Bonkers. A better solution would be to provide a 'bind' type object
+		 * To do - bind type method in DTs 1.6.
+		 */
+		this.oApi._fnInitalise = _fnInitalise;
+		this.oApi._fnLanguageProcess = _fnLanguageProcess;
+		this.oApi._fnAddColumn = _fnAddColumn;
+		this.oApi._fnAddData = _fnAddData;
+		this.oApi._fnGatherData = _fnGatherData;
+		this.oApi._fnDrawHead = _fnDrawHead;
+		this.oApi._fnDraw = _fnDraw;
+		this.oApi._fnAjaxUpdate = _fnAjaxUpdate;
+		this.oApi._fnAddOptionsHtml = _fnAddOptionsHtml;
+		this.oApi._fnFeatureHtmlFilter = _fnFeatureHtmlFilter;
+		this.oApi._fnFeatureHtmlInfo = _fnFeatureHtmlInfo;
+		this.oApi._fnFeatureHtmlPaginate = _fnFeatureHtmlPaginate;
+		this.oApi._fnFeatureHtmlLength = _fnFeatureHtmlLength;
+		this.oApi._fnFeatureHtmlProcessing = _fnFeatureHtmlProcessing;
+		this.oApi._fnProcessingDisplay = _fnProcessingDisplay;
+		this.oApi._fnFilterComplete = _fnFilterComplete;
+		this.oApi._fnFilterColumn = _fnFilterColumn;
+		this.oApi._fnFilter = _fnFilter;
+		this.oApi._fnSortingClasses = _fnSortingClasses;
+		this.oApi._fnVisibleToColumnIndex = _fnVisibleToColumnIndex;
+		this.oApi._fnColumnIndexToVisible = _fnColumnIndexToVisible;
+		this.oApi._fnVisbleColumns = _fnVisbleColumns;
+		this.oApi._fnBuildSearchArray = _fnBuildSearchArray;
+		this.oApi._fnDataToSearch = _fnDataToSearch;
+		this.oApi._fnCalculateEnd = _fnCalculateEnd;
+		this.oApi._fnConvertToWidth = _fnConvertToWidth;
+		this.oApi._fnCalculateColumnWidths = _fnCalculateColumnWidths;
+		this.oApi._fnArrayCmp = _fnArrayCmp;
+		this.oApi._fnDetectType = _fnDetectType;
+		this.oApi._fnGetDataMaster = _fnGetDataMaster;
+		this.oApi._fnGetTrNodes = _fnGetTrNodes;
+		this.oApi._fnEscapeRegex = _fnEscapeRegex;
+		this.oApi._fnReOrderIndex = _fnReOrderIndex;
+		this.oApi._fnColumnOrdering = _fnColumnOrdering;
+		this.oApi._fnClearTable = _fnClearTable;
+		this.oApi._fnSaveState = _fnSaveState;
+		this.oApi._fnLoadState = _fnLoadState;
+		this.oApi._fnCreateCookie = _fnCreateCookie;
+		this.oApi._fnReadCookie = _fnReadCookie;
+		this.oApi._fnGetUniqueThs = _fnGetUniqueThs;
+		
+		/* Want to be able to reference "this" inside the this.each function */
+		var _that = this;
+		
+		
+		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+		 * Constructor
+		 */
+		return this.each(function()
+		{
+			/* Make a complete and independent copy of the settings object */
+			var oSettings = new classSettings();
+			_aoSettings.push( oSettings );
+			
+			var i=0, iLen;
+			var bInitHandedOff = false;
+			var bUsePassedData = false;
+			
+			/* Set the id */
+			var sId = this.getAttribute( 'id' );
+			if ( sId !== null )
+			{
+				oSettings.sTableId = sId;
+				oSettings.sInstance = sId;
+			}
+			else
+			{
+				oSettings.sInstance = _oExt._oExternConfig.iNextUnique ++;
+			}
+			
+			/* Set the table node */
+			oSettings.nTable = this;
+			
+			/* Bind the API functions to the settings, so we can perform actions whenever oSettings is
+			 * available
+			 */
+			oSettings.oApi = _that.oApi;
+			
+			/* Store the features that we have available */
+			if ( typeof oInit != 'undefined' && oInit !== null )
+			{
+				_fnMap( oSettings.oFeatures, oInit, "bPaginate" );
+				_fnMap( oSettings.oFeatures, oInit, "bLengthChange" );
+				_fnMap( oSettings.oFeatures, oInit, "bFilter" );
+				_fnMap( oSettings.oFeatures, oInit, "bSort" );
+				_fnMap( oSettings.oFeatures, oInit, "bInfo" );
+				_fnMap( oSettings.oFeatures, oInit, "bProcessing" );
+				_fnMap( oSettings.oFeatures, oInit, "bAutoWidth" );
+				_fnMap( oSettings.oFeatures, oInit, "bSortClasses" );
+				_fnMap( oSettings.oFeatures, oInit, "bServerSide" );
+				_fnMap( oSettings, oInit, "asStripClasses" );
+				_fnMap( oSettings, oInit, "fnRowCallback" );
+				_fnMap( oSettings, oInit, "fnHeaderCallback" );
+				_fnMap( oSettings, oInit, "fnFooterCallback" );
+				_fnMap( oSettings, oInit, "fnDrawCallback" );
+				_fnMap( oSettings, oInit, "fnInitComplete" );
+				_fnMap( oSettings, oInit, "fnServerData" );
+				_fnMap( oSettings, oInit, "aaSorting" );
+				_fnMap( oSettings, oInit, "aaSortingFixed" );
+				_fnMap( oSettings, oInit, "sPaginationType" );
+				_fnMap( oSettings, oInit, "sAjaxSource" );
+				_fnMap( oSettings, oInit, "sDom", "sDomPositioning" );
+				_fnMap( oSettings, oInit, "oSearch", "oPreviousSearch" );
+				_fnMap( oSettings, oInit, "aoSearchCols", "aoPreSearchCols" );
+				_fnMap( oSettings, oInit, "iDisplayLength", "_iDisplayLength" );
+				_fnMap( oSettings, oInit, "bJQueryUI", "bJUI" );
+				
+				if ( typeof oInit.bJQueryUI != 'undefined' )
+				{
+					/* Use the JUI classes object for display. You could clone the oStdClasses object if 
+					 * you want to have multiple tables with multiple independent classes 
+					 */
+					oSettings.oClasses = _oExt.oJUIClasses;
+					
+					if ( typeof oInit.sDom == 'undefined' )
+					{
+						/* Set the DOM to use a layout suitable for jQuery UI's theming */
+						oSettings.sDomPositioning = 
+							'<"fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix"lfr>'+
+							't'+
+							'<"fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix"ip>';
+					}
+				}
+				
+				if ( typeof oInit.iDisplayStart != 'undefined' && 
+				     typeof oSettings.iInitDisplayStart == 'undefined' ) {
+					/* Display start point, taking into account the save saving */
+					oSettings.iInitDisplayStart = oInit.iDisplayStart;
+					oSettings._iDisplayStart = oInit.iDisplayStart;
+				}
+				
+				/* Must be done after everything which can be overridden by a cookie! */
+				if ( typeof oInit.bStateSave != 'undefined' )
+				{
+					oSettings.oFeatures.bStateSave = oInit.bStateSave;
+					_fnLoadState( oSettings, oInit );
+				}
+				
+				if ( typeof oInit.aaData != 'undefined' ) {
+					bUsePassedData = true;
+				}
+				
+				/* Backwards compatability */
+				/* aoColumns / aoData - remove at some point... */
+				if ( typeof oInit != 'undefined' && typeof oInit.aoData != 'undefined' )
+				{
+					oInit.aoColumns = oInit.aoData;
+				}
+				
+				/* Language definitions */
+				if ( typeof oInit.oLanguage != 'undefined' )
+				{
+					if ( typeof oInit.oLanguage.sUrl != 'undefined' && oInit.oLanguage.sUrl !== "" )
+					{
+						/* Get the language definitions from a file */
+						oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl;
+						$.getJSON( oSettings.oLanguage.sUrl, null, function( json ) { 
+							_fnLanguageProcess( oSettings, json, true ); } );
+						bInitHandedOff = true;
+					}
+					else
+					{
+						_fnLanguageProcess( oSettings, oInit.oLanguage, false );
+					}
+				}
+				/* Warning: The _fnLanguageProcess function is async to the remainder of this function due
+				 * to the XHR. We use _bInitialised in _fnLanguageProcess() to check this the processing 
+				 * below is complete. The reason for spliting it like this is optimisation - we can fire
+				 * off the XHR (if needed) and then continue processing the data.
+				 */
+			}
+				
+			/* Add the strip classes now that we know which classes to apply - unless overruled */
+			if ( typeof oInit == 'undefined' || typeof oInit.asStripClasses == 'undefined' )
+			{
+				oSettings.asStripClasses.push( oSettings.oClasses.sStripOdd );
+				oSettings.asStripClasses.push( oSettings.oClasses.sStripEven );
+			}
+			
+			/* See if we should load columns automatically or use defined ones - a bit messy this... */
+			var nThead = this.getElementsByTagName('thead');
+			var nThs = nThead.length===0 ? null : _fnGetUniqueThs( nThead[0] );
+			var bUseCols = typeof oInit != 'undefined' && typeof oInit.aoColumns != 'undefined';
+			for ( i=0, iLen=bUseCols ? oInit.aoColumns.length : nThs.length ; i<iLen ; i++ )
+			{
+				var col = bUseCols ? oInit.aoColumns[i] : null;
+				var n = nThs ? nThs[i] : null;
+				_fnAddColumn( oSettings, col, n );
+			}
+			
+			/* Sanity check that there is a thead and tfoot. If not let's just create them */
+			if ( this.getElementsByTagName('thead').length === 0 )
+			{
+				this.appendChild( document.createElement( 'thead' ) );
+			}
+			
+			if ( this.getElementsByTagName('tbody').length === 0 )
+			{
+				this.appendChild( document.createElement( 'tbody' ) );
+			}
+			
+			/* Check if there is data passing into the constructor */
+			if ( bUsePassedData )
+			{
+				for ( i=0 ; i<oInit.aaData.length ; i++ )
+				{
+					_fnAddData( oSettings, oInit.aaData[ i ] );
+				}
+			}
+			else
+			{
+				/* Grab the data from the page */
+				_fnGatherData( oSettings );
+			}
+			
+			/* Copy the data index array */
+			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+			
+			/* Calculate sizes for columns */
+			if ( oSettings.oFeatures.bAutoWidth )
+			{
+				_fnCalculateColumnWidths( oSettings );
+			}
+			
+			/* Initialisation complete - table can be drawn */
+			oSettings.bInitialised = true;
+			
+			/* Check if we need to initialise the table (it might not have been handed off to the
+			 * language processor)
+			 */
+			if ( bInitHandedOff === false )
+			{
+				_fnInitalise( oSettings );
+			}
+		});
+	};
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/js/sparql.js	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,490 @@
+/**********************************************************
+  Copyright (c) 2006, 2007
+    Lee Feigenbaum ( lee AT thefigtrees DOT net )
+	Elias Torres   ( elias AT torrez DOT us )
+    Wing Yung      ( wingerz AT gmail DOT com )
+  All rights reserved.
+
+	Permission is hereby granted, free of charge, to any person obtaining a copy of
+	this software and associated documentation files (the "Software"), to deal in
+	the Software without restriction, including without limitation the rights to
+	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+	of the Software, and to permit persons to whom the Software is furnished to do
+	so, subject to the following conditions:
+
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+**********************************************************/
+
+/**
+ * Example client interactions
+ *
+ 
+ 	var sparqler = new SPARQL.Service("http://sparql.org/sparql");
+	sparqler.addDefaultGraph("http://thefigtrees.net/lee/ldf-card"); // inherited by all (future) queries
+	sparqler.addNamedGraph("http://torrez.us/elias/foaf.rdf");
+	sparqler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); // inherited by all (future) queries
+	sparqler.setPrefix("rdf", "http://xmlns.com/foaf/0.1/");
+	
+	sparqler.setRequestHeader("Authentication", "Basic: " + basicAuthString);
+	
+	//sparqler.wantOutputAs("application/json"); // for now we only do JSON
+
+	var query = sparqler.createQuery();
+	query.addDefualtGraph(...) query.addNamedGraph(...) query.setPrefix(...) query.setRequestHeader(...) // this query only
+
+	// query forms:
+
+	// passes standard JSON results object to success callback
+	query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
+	query.query("SELECT ?who ?mbox WHERE { ldf:LDF foaf:knows ?who . ?who foaf:mbox ?mbox }",
+		{failure: onFailure, success: function(json) { for (var x in json.head.vars) { ... } ...}}
+	);
+
+	// passes boolean value to success callback
+	query.ask("ASK ?person WHERE { ?person foaf:knows [ foaf:name "Dan Connolly" ] }",
+		{failure: onFailure, success: function(bool) { if (bool) ... }}
+	); 
+
+	// passes a single vector (array) of values representing a single column of results to success callback
+	query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
+	var addresses = query.selectValues("SELECT ?mbox WHERE { _:someone foaf:mbox ?mbox }",
+		{failure: onFailure, success: function(values) { for (var i = 0; i < values.length; i++) { ... values[i] ...} } }
+	); 
+
+	// passes a single value representing a single row of a single column (variable) to success callback
+	query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
+	var myAddress = query.selectSingleValue("SELECT ?mbox WHERE {ldf:LDF foaf:mbox ?mbox }",
+		{failure: onFailure, success: function(value) { alert("value is: " + value); } }
+	); 
+	
+	// shortcuts for all of the above (w/o ability to set any query-specific graphs or prefixes)
+	sparqler.query(...) sparqler.ask(...) sparqler.selectValues(...) sparqler.selectSingleValue(...)
+ 
+
+ */
+
+var SPARQL  = {}; // SPARQL namespace
+
+
+/**
+ * Both SPARQL service objects and SPARQL query objects receive one query utility method
+ * per entry in this dictionary. The key is the name of the method, and the value is a function
+ * that transforms the standard JSON output into a more useful form. The return value of a
+ * transformation function is passed into any 'success' callback function provided when the query
+ * is issued. The following transformations are included:
+ *   + query -- identity transform; returns the JSON structure unchanged
+ *   + ask -- for ASK queries; returns a boolean value indicating the answer to the query
+ *   + selectValues -- for SELECT queries with a single variable; returns an array containing
+ *       the answers to the query
+ *   + selectSingleValue -- for SELECT queries returning one column with one row; returns the
+ *       value in the first (and presumably, only) cell in the resultset
+ *   + selectValueArrays -- for SELECT queries returning independent columns; returns a hash
+ *       keyed on variable name with values as arrays of answers for that variable. Useful
+ *       for UNION queries.
+ *   Note that all of the transformations that return values directly lose any type information
+ *   and the ability to distinguish between URIs, blank nodes, and literals.
+ */
+SPARQL._query_transformations = {
+	query: function (o) { return o; },
+	ask: function (o) { return o["boolean"]; },
+	selectValues: function (o) {
+		var v = o.head.vars[0]; // assume one variable
+		var values = [];
+		for (var i = 0; i < o.results.bindings.length; i++)
+			values.push(o.results.bindings[i][v].value);
+		return values;
+	},
+	selectSingleValue: function(o) { return o.results.bindings[0][o.head.vars[0]].value; },
+	selectValueArrays: function(o) {
+		// factor by value (useful for UNION queries)
+		var ret = {};
+		for (var i = 0; i < o.head.vars.length; i++)
+			ret[o.head.vars[i]] = [];
+		for (var i = 0; i < o.results.bindings.length; i++)
+			for (var v in o.results.bindings[i])
+				if (ret[v] instanceof Array) ret[v].push(o.results.bindings[i][v].value);
+		return ret;
+	},
+    selectValueHashes: function(o) {
+        var hashes = [];
+        for (var i = 0; i < o.results.bindings.length; i++) {
+            var hash = {};
+            for (var v in o.results.bindings[i])
+                hash[v] = o.results.bindings[i][v].value;
+            hashes.push(hash);
+        }
+        return hashes;
+    }
+};
+
+SPARQL.statistics = {
+	queries_sent : 0,
+	successes    : 0,
+	failures     : 0
+};
+
+// A SPARQL service represents a single endpoint which implements the HTTP (GET or POST) 
+// bindings of the SPARQL Protocol. It provides convenience methods to set dataset and
+// prefix options for all queries created for this endpoint.
+SPARQL.Service = function(endpoint) {
+	//---------------
+	// private fields
+	var _endpoint = endpoint;
+	var _default_graphs = [];
+	var _named_graphs = [];
+	var _prefix_map = {};
+    var _method = 'POST';
+	var _output = 'json';
+	var _max_simultaneous = 0;
+	var _request_headers = {};
+
+	//----------
+	// accessors
+	this.endpoint = function() { return _endpoint; };
+	this.defaultGraphs = function() { return _default_graphs; };
+	this.namedGraphs = function() { return _named_graphs; };
+	this.prefixes = function() { return _prefix_map; };
+    this.method = function() { return _method; };
+    this.output = function() { return _output; };
+	this.maxSimultaneousQueries = function() { return _max_simultaneous; };
+	this.requestHeaders = function() { return _request_headers; };
+	
+	//---------
+	// mutators
+	function _add_graphs(toAdd, arr) {
+		if (toAdd instanceof Array)
+			for (var i = 0; i < toAdd.length; i++) arr.push(toAdd[i]);
+		else
+			arr.push(toAdd);
+	}
+	this.addDefaultGraph = function(g) { _add_graphs(g, this.defaultGraphs()); };
+	this.addNamedGraph = function(g) { _add_graphs(g, this.namedGraphs()); };
+	this.setPrefix = function(p, u) { this.prefixes()[p] = u; };
+	this.createQuery = function(p) { return new SPARQL.Query(this, p); };
+    this.setMethod = function(m) {
+        if (m != 'GET' && m != 'POST') throw("HTTP methods other than GET and POST are not supported.");
+        _method = m;
+    };
+	this.setOutput = function(o) { _output = o; };
+	this.setMaxSimultaneousQueries = function(m) { _max_simultaneous = m; };
+	this.setRequestHeader = function(h, v) { _request_headers[h] = v; };
+	
+	//---------------
+	// protected methods (should only be called within this module
+	this._active_queries = 0;
+	this._queued_queries = [];
+	this._next_in_queue  = 0;
+	this._canRun = function() { return this.maxSimultaneousQueries() <= 0 || this._active_queries < this.maxSimultaneousQueries();};
+	this._queue  = function(q,f, p) { 
+		if (!p) p = 0; 
+		if (p > 0) {
+			for (var i = 0; i < this._queued_queries.length; i++) {
+				if (this._queued_queries[i] != null && this._queued_queries[i][2] < p) {
+					this._queued_queries.splice(i, 0, [q, f, p]);
+					return;
+				}
+			}
+		}
+		this._queued_queries.push([q,f,p]); 
+	};
+	this._markRunning = function(q) { this._active_queries++; };
+	this._markDone    = function(q) { 
+		this._active_queries--; 
+		//document.getElementById('log').innerHTML+="query done. " + this._active_queries + " queries still active.<br>";
+		if (this._queued_queries[this._next_in_queue] != null && this._canRun()) {
+			var a = this._queued_queries[this._next_in_queue];
+			this._queued_queries[this._next_in_queue++] = null;
+			// a[0] is query object, a[1] is function to run query
+			//document.getElementById('log').innerHTML += "running query from Q<br>";
+			a[1]();
+		}
+	};
+
+	//---------------
+	// public methods
+
+	// use our varied transformations to create the various shortcut methods of actually 
+	// issuing queries without explicitly creating a query object
+	for (var query_form in SPARQL._query_transformations) {
+		// need the extra function to properly scope query_form (qf)
+		this[query_form] = (function(qf) {
+			return function(queryString, callback) {
+				var q = this.createQuery();
+				q._doQuery(queryString, callback, SPARQL._query_transformations[qf]);
+			};
+		})(query_form);
+	}
+	
+	//------------
+	// constructor
+    
+	if (!_endpoint)
+		return null;
+	
+	return this;
+}
+
+/**
+ * A SPARQL query object should be created using the createQuery method of a SPARQL
+ * service object. It allows prefixes and datasets to be defined specifically for
+ * a single query, and provides introspective methods to see the query string and the
+ * full (HTTP GET) URL of the query.
+ */
+SPARQL.Query = function(service, priority) {
+	//---------------
+	// private fields
+	var _conn = null;
+	var _service = service;
+	var _default_graphs = clone_obj(service.defaultGraphs()); // prevent future updates from affecting us
+	var _named_graphs = clone_obj(service.namedGraphs());
+	var _prefix_map = clone_obj(service.prefixes());
+	var _user_query = ''; // doesn't include auto-generated prefix declarations
+    var _method = service.method();
+	var _output = service.output();
+	var _priority = priority || 0;
+	var _request_headers = clone_obj(service.requestHeaders());
+
+	//------------------
+	// private functions
+	function _create_json(text) { 
+		if (!text)
+			return null;
+		// make sure this is safe JSON
+		// see: http://www.ietf.org/internet-drafts/draft-crockford-jsonorg-json-03.txt
+		
+		// (1) strip out quoted strings
+		var no_strings = text.replace(/"(\\.|[^"\\])*"/g, '');
+		// (2) make sure that all the characters are explicitly part of the JSON grammar
+		// (in particular, note as discussed in the IETF submission, there are no assignments
+		//  or function invocations allowed by this reg. exp.)
+		var hasBadCharacter = /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(no_strings);
+		// (3) evaluate the JSON string, returning its contents
+		if (!hasBadCharacter) {
+			try {
+				return eval('(' + text + ')');
+			} catch (e) {
+				return null;
+			}
+		}
+		return null; 
+	}	
+	
+	function clone_obj(o) { 
+		var o2 = o instanceof Array ? [] : {}; 
+		for(var x in o) {o2[x] = o[x];} 
+		return o2;
+	}
+
+	//----------------
+	// private methods
+	this._doCallback = function(cb, which, arg) {
+		//document.getElementById('log').innerHTML += "_doCallback ... <br>";
+		var user_data = "argument" in cb ? cb.argument : null;
+		if (which in cb) {
+			if (cb.scope) {
+                cb[which].apply(cb.scope, [arg, user_data]);
+			} else { 
+				cb[which](arg, user_data); 
+			}
+		}
+	}
+	
+	this._queryFailure = function(xhr, arg) {
+		SPARQL.statistics.failures++;
+		_service._markDone(this);
+		this._doCallback(arg.callback, 'failure', xhr /* just pass through the connection response object */);
+	};
+	this._querySuccess = function(xhr, arg) {
+        //alert(xhr.responseText);
+		SPARQL.statistics.successes++;
+		_service._markDone(this);
+		this._doCallback(arg.callback, 'success', arg.transformer(
+			_output == 'json' ? _create_json(xhr.responseText) : xhr.responseText
+		));
+	};
+	
+	function getXmlHttpRequest(url) {
+		// right now, this only does Firefox (Opera? Safari?)
+		return new XMLHttpRequest();
+	}
+	
+	this._doQuery = function(queryString, callback, transformer) {
+		_user_query = queryString;
+		if (_service._canRun()) {
+			try {
+				if (_method != 'POST' && _method != 'GET')
+					throw("HTTP methods other than GET and POST are not supported.");
+			
+				var url = _method == 'GET' ? this.queryUrl() : this.service().endpoint();
+				var xhr = getXmlHttpRequest(url);
+				var content = null;
+
+				try {
+                    if (!document.domain || (url.match(/^https?:\/\//) && url.slice(7, document.domain.length + 7) != document.domain && window.netscape && netscape.security && netscape.security.PrivilegeManager)) {
+						netscape.security.PrivilegeManager.enablePrivilege( "UniversalBrowserRead");
+						netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect"); 
+					}
+				} catch(e) {
+					alert("Cross-site requests prohibited. You will only be able to SPARQL the origin site: " + e);
+                    return;
+				}
+				
+				xhr.open(_method, url, true /* async */);
+				
+				// set the headers, including the content-type for POSTed queries
+				for (var header in this.requestHeaders())
+                    if (typeof(this.requestHeaders()[header]) != "function")
+	    				xhr.setRequestHeader(header, this.requestHeaders()[header]);
+				if (_method == 'POST') {
+					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+					content = this.queryParameters();
+				}
+	
+				SPARQL.statistics.queries_sent++;
+				_service._markRunning(this);
+	
+				var callbackData = {
+					scope:this, 
+					success: this._querySuccess, 
+					failure: this._queryFailure,
+					argument: {
+						transformer: transformer,
+						callback: callback
+					}
+				};
+				
+				// I've seen some strange race-condition behavior (strange since normally
+				// JS is single-threaded, so synchronization conditions don't occur barring 
+				// reentrancy) with onreadystatechange. Instead, we poll asynchronously to
+				// determine when the request is done.
+				var token = window.setInterval(
+					function () {
+						if (xhr.readyState == 4) { // ready!
+							// clear this handler
+							window.clearInterval(token);
+							// we check the status to know which callback to call
+							if (xhr.status >= 200 && xhr.status < 300)
+								callbackData.success.apply(callbackData.scope, [xhr, callbackData.argument]);
+							else
+								callbackData.failure.apply(callbackData.scope, [xhr, callbackData.argument]);
+						}
+					},
+					200 /* maybe this should be customizable */
+				);			
+	
+				xhr.send(content);
+			} catch (e) {
+				alert("Error sending SPARQL query: " + e);
+			}
+		} else {
+			var self = this;
+			_service._queue(self, function() { self._doQuery(queryString, callback, transformer); }, _priority);
+		}
+	};
+
+	
+	//----------
+	// accessors
+	this.request = function() { return _conn; };
+	this.service = function() { return _service; };
+	this.defaultGraphs = function() { return _default_graphs; };
+	this.namedGraphs = function() { return _named_graphs; };
+	this.prefixes = function() { return _prefix_map; };
+    this.method = function() { return _method; };
+    this.requestHeaders = function() { return _request_headers; };
+
+
+    /**
+     * Returns the SPARQL query represented by this object. The parameter, which can
+     * be omitted, determines whether or not auto-generated PREFIX clauses are included
+     * in the returned query string.
+     */
+	this.queryString = function(excludePrefixes) {
+		var preamble = '';
+		if (!excludePrefixes) {
+			for (var prefix in this.prefixes()) {
+				if(typeof(this.prefixes()[prefix]) != 'string') continue;
+				preamble += 'PREFIX ' + prefix + ': <' + this.prefixes()[prefix] + '> ';
+			}
+		}
+		return preamble + _user_query;
+	};
+	
+    /**
+     * Returns the HTTP query parameters to invoke this query. This includes entries for
+     * all of the default graphs, the named graphs, the SPARQL query itself, and an 
+     * output parameter to specify JSON (or other) output is desired.
+     */
+	this.queryParameters = function () {
+		var urlQueryString = '';
+		var i;
+		
+		// add default and named graphs to the protocol invocation
+		for (i = 0; i < this.defaultGraphs().length; i++) urlQueryString += 'default-graph-uri=' + encodeURIComponent(this.defaultGraphs()[i]) + '&';
+		for (i = 0; i < this.namedGraphs().length; i++) urlQueryString += 'named-graph-uri=' + encodeURIComponent(this.namedGraphs()[i]) + '&';
+		
+		// specify JSON output (currently output= supported by latest Joseki) (or other output)
+		urlQueryString += 'output=' + _output + '&soft-limit=-1&';
+		return urlQueryString + 'query=' + encodeURIComponent(this.queryString());
+	}
+	
+    /**
+     * Returns the HTTP GET URL to invoke this query. (Note that this returns a full HTTP GET URL 
+     * even if this query is set to actually use POST.)
+     */
+	this.queryUrl = function() {
+		var url = this.service().endpoint() + '?';
+		return url + this.queryParameters();
+	};
+	
+	//---------
+	// mutators
+	function _add_graphs(toAdd, arr) {
+		if (toAdd instanceof Array)
+			for (var i = 0; i < toAdd.length; i++) arr.push(toAdd[i]);
+		else
+			arr.push(toAdd);
+	}
+	this.addDefaultGraph = function(g) { _add_graphs(g, this.defaultGraphs()); };
+	this.addNamedGraph = function(g) { _add_graphs(g, this.namedGraphs()); };
+	this.setPrefix = function(p, u) { this.prefixes()[p] = u; };
+    this.setMethod = function(m) {
+        if (m != 'GET' && m != 'POST') throw("HTTP methods other than GET and POST are not supported.");
+        _method = m;
+    };
+	this.setRequestHeader = function(h, v) { _request_headers[h] = v; };
+	
+	//---------------
+	// public methods
+
+	// use our varied transformations to create the various methods of actually issuing 
+	// queries
+	for (var query_form in SPARQL._query_transformations) {
+		// need the extra function to properly scope query_form (qf)
+		this[query_form] = (function(qf) {
+			return function(queryString, callback) {
+				this._doQuery(queryString, callback, SPARQL._query_transformations[qf]);
+			};
+		})(query_form);
+	}
+	
+
+	//------------
+	// constructor
+	
+	return this;
+}
+
+// Nothing to see here, yet.
+SPARQL.QueryUtilities = {
+};
+