changeset 1284:6c819878ac85

Major updates. Specification Nodes now own file (specification.js). Updating Analysis to allow filtering based on survey responses.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Thu, 31 Mar 2016 13:31:42 +0100
parents b24d861c96b3
children ae57d9f618cb
files analysis/analysis.css analysis/analysis.js analysis/index.html core.js index.html scripts/get_filtered_count.php scripts/get_tests.php specification.js test_create/test_create.html
diffstat 9 files changed, 1230 insertions(+), 1309 deletions(-) [+]
line wrap: on
line diff
--- a/analysis/analysis.css	Tue Mar 29 14:37:07 2016 +0100
+++ b/analysis/analysis.css	Thu Mar 31 13:31:42 2016 +0100
@@ -7,4 +7,10 @@
     padding-left: 15px;
     background-color: rgb(200,200,200);
     border: 2px dashed black;
+}
+table td tr{
+    padding: 5px;
+}
+div.filter-entry{
+    padding: 5px;
 }
\ No newline at end of file
--- a/analysis/analysis.js	Tue Mar 29 14:37:07 2016 +0100
+++ b/analysis/analysis.js	Thu Mar 31 13:31:42 2016 +0100
@@ -2,11 +2,119 @@
 * Analysis script for WAET
 */
 
-var chartContext;
+// Firefox does not have an XMLDocument.prototype.getElementsByName
+// and there is no searchAll style command, this custom function will
+// search all children recusrively for the name. Used for XSD where all
+// element nodes must have a name and therefore can pull the schema node
+XMLDocument.prototype.getAllElementsByName = function(name)
+{
+    name = String(name);
+    var selected = this.documentElement.getAllElementsByName(name);
+    return selected;
+}
+
+Element.prototype.getAllElementsByName = function(name)
+{
+    name = String(name);
+    var selected = [];
+    var node = this.firstElementChild;
+    while(node != null)
+    {
+        if (node.getAttribute('name') == name)
+        {
+            selected.push(node);
+        }
+        if (node.childElementCount > 0)
+        {
+            selected = selected.concat(node.getAllElementsByName(name));
+        }
+        node = node.nextElementSibling;
+    }
+    return selected;
+}
+
+XMLDocument.prototype.getAllElementsByTagName = function(name)
+{
+    name = String(name);
+    var selected = this.documentElement.getAllElementsByTagName(name);
+    return selected;
+}
+
+Element.prototype.getAllElementsByTagName = function(name)
+{
+    name = String(name);
+    var selected = [];
+    var node = this.firstElementChild;
+    while(node != null)
+    {
+        if (node.nodeName == name)
+        {
+            selected.push(node);
+        }
+        if (node.childElementCount > 0)
+        {
+            selected = selected.concat(node.getAllElementsByTagName(name));
+        }
+        node = node.nextElementSibling;
+    }
+    return selected;
+}
+
+// Firefox does not have an XMLDocument.prototype.getElementsByName
+if (typeof XMLDocument.prototype.getElementsByName != "function") {
+    XMLDocument.prototype.getElementsByName = function(name)
+    {
+        name = String(name);
+        var node = this.documentElement.firstElementChild;
+        var selected = [];
+        while(node != null)
+        {
+            if (node.getAttribute('name') == name)
+            {
+                selected.push(node);
+            }
+            node = node.nextElementSibling;
+        }
+        return selected;
+    }
+}
+
+var chartContext, testData;
 window.onload = function() {
     // Load the Visualization API and the corechart package.
-      google.charts.load('current', {'packages':['corechart']});
+    google.charts.load('current', {'packages':['corechart']});
     chartContext = new Chart();
+    testData = new Data();
+}
+
+function get(url) {
+  // Return a new promise.
+  return new Promise(function(resolve, reject) {
+    // Do the usual XHR stuff
+    var req = new XMLHttpRequest();
+    req.open('GET', url);
+    req.onload = function() {
+      // This is called even on 404 etc
+      // so check the status
+      if (req.status == 200) {
+        // Resolve the promise with the response text
+        resolve(req.response);
+      }
+      else {
+        // Otherwise reject with the status text
+        // which will hopefully be a meaningful error
+        reject(Error(req.statusText));
+      }
+    };
+
+    // Handle network errors
+    req.onerror = function() {
+      reject(Error("Network Error"));
+    };
+
+    // Make the request
+    req.send();
+  });
 }
 
 function arrayMean(values) {
@@ -115,30 +223,9 @@
 }
 
 function Chart() {
-    this.valueData = null;
-    this.commentData = null;
-    this.loadStatus = 0;
+    this.valueData;
     this.charts = [];
     
-    var XMLHttp = new XMLHttpRequest();
-    XMLHttp.parent = this;
-    XMLHttp.open("GET","../scripts/score_parser.php?format=JSON",true);
-    XMLHttp.onload = function() {
-        // Now we have the JSON data, extract
-        this.parent.valueData = JSON.parse(this.responseText);
-        this.parent.loadStatus++;
-    }
-    XMLHttp.send();
-    var XMLHttp2 = new XMLHttpRequest();
-    XMLHttp2.parent = this;
-    XMLHttp2.open("GET","../scripts/comment_parser.php?format=JSON",true);
-    XMLHttp2.onload = function() {
-        // Now we have the JSON data, extract
-        this.parent.commentData = JSON.parse(this.responseText);
-        this.parent.loadStatus++;
-    }
-    XMLHttp2.send();
-    
     this.chartObject = function(name) {
         // Create the charting object
         this.name = name;
@@ -491,4 +578,226 @@
             }
         }
     }
+}
+
+function Data() {
+    // This holds the link between the server side calculations and the client side visualisation of the data
+    
+    // Dynamically generate the test filtering / page filterting tools
+    var self = this;
+    // Collect the test types and counts
+    this.testSavedDiv = document.getElementById("test-saved");
+    this.testSaves = null;
+    this.selectURL = null;
+
+    this.specification = new Specification();
+    get("../test-schema.xsd").then(function(response){
+        var parse = new DOMParser();
+        self.specification.schema = parse.parseFromString(response,'text/xml');
+    },function(error){
+        console.log("ERROR: Could not get Test Schema");
+    });
+    this.update = function(url) {
+        var self = this;
+        get(url).then(function(response){
+            var parse = new DOMParser();
+            self.specification.decode(parse.parseFromString(response,'text/xml'));
+            interfaceContext.generateFilters(self.specification);
+            return true;
+        },function(error){
+            console.log("ERROR: Could not get"+url);
+            return false;
+        });
+    }
+    var get_test = new XMLHttpRequest();
+    get_test.parent = this;
+    get_test.open("GET","../scripts/get_tests.php?format=JSON",true);
+    get_test.onload = function() {
+        this.parent.testSavedDiv.innerHTML = null;
+        var table = document.createElement("table");
+        table.innerHTML = "<tr><td>Test Filename</td><td>Count</td><td>Include</td></tr>";
+        this.parent.testSaves = JSON.parse(this.responseText);
+        for (var test of this.parent.testSaves.tests) {
+            var tableRow = document.createElement("tr");
+            var tableRowFilename = document.createElement("td");
+            tableRowFilename.textContent = test.testName;
+            var tableRowCount = document.createElement("td");
+            tableRowCount.textContent = test.files.length;
+            tableRow.appendChild(tableRowFilename);
+            tableRow.appendChild(tableRowCount);
+            var tableRowInclude = document.createElement("td");
+            var tableRowIncludeInput = document.createElement("input");
+            tableRowIncludeInput.type = "radio";
+            tableRowIncludeInput.name = "test-include";
+            tableRowIncludeInput.setAttribute("source",test.testName);
+            tableRowIncludeInput.addEventListener("change",this.parent);
+            tableRowInclude.appendChild(tableRowIncludeInput);
+            tableRow.appendChild(tableRowInclude);
+            table.appendChild(tableRow);
+        }
+        this.parent.testSavedDiv.appendChild(table);
+    }
+    get_test.send();
+    this.handleEvent = function(event) {
+        if (event.currentTarget.nodeName == "INPUT" && event.currentTarget.name == "test-include") {
+            // Changed the test specification
+            this.selectURL = event.currentTarget.getAttribute("source");
+            this.update(this.selectURL);
+        }
+    }
+}
+
+var interfaceContext = new function() {
+    // This creates the interface for the user to connect with the dynamic back-end to retrieve data
+    this.rootDOM = document.createElement("div");
+    this.filterDOM = document.createElement("div");
+    this.filterDOM.innerHTML = "<p>PreTest Filters</p><div id='filter-count'></div>";
+    this.filterObjects = [];
+    this.generateFilters = function(specification) {
+        // Filters are based on the pre and post global surverys
+        var FilterObject = function(parent,specification) {
+            this.parent = parent;
+            this.specification = specification;
+            this.rootDOM = document.createElement("div");
+            this.rootDOM.innerHTML = "<span>ID: "+specification.id+", Type: "+specification.type+"</span>";
+            this.rootDOM.className = "filter-entry";
+            this.handleEvent = function(event) {
+                switch(this.specification.type) {
+                    case "number":
+                        var name = event.currentTarget.name;
+                        eval("this."+name+" = event.currentTarget.value");
+                        break;
+                    case "checkbox":
+                        break;
+                    case "radio":
+                        break;
+                }
+                this.parent.getFileCount();
+            }
+            this.getFilterPairs = function() {
+                var pairs = [];
+                switch(this.specification.type) {
+                    case "number":
+                        if (this.min != "") {
+                            pairs.push([specification.id+"-min",this.min]);
+                        }
+                        if (this.max != "") {
+                            pairs.push([specification.id+"-max",this.max]);
+                        }
+                        break;
+                    case "radio":
+                    case "checkbox":
+                        for (var i=0; i<this.options.length; i++) {
+                            if (!this.options[i].checked) {
+                                pairs.push([specification.id+"-exclude-"+i,specification.options[i].name]);
+                            }
+                        }
+                        break;
+                }
+                return pairs;
+            }
+            switch(specification.type) {
+                case "number":
+                    // Number can be ranged by min/max levels
+                    this.min = "";
+                    this.max = "";
+                    this.minDOM = document.createElement("input");
+                    this.minDOM.type = "number";
+                    this.minDOM.name = "min";
+                    this.minDOM.addEventListener("change",this);
+                    this.minDOMText = document.createElement("span");
+                    this.minDOMText.textContent = "Minimum: ";
+                    var pairHolder = document.createElement("div");
+                    pairHolder.appendChild(this.minDOMText);
+                    pairHolder.appendChild(this.minDOM);
+                    this.rootDOM.appendChild(pairHolder);
+                    
+                    this.maxDOM = document.createElement("input");
+                    this.maxDOM.type = "number";
+                    this.maxDOM.name = "max";
+                    this.maxDOM.addEventListener("change",this);
+                    this.maxDOMText = document.createElement("span");
+                    this.maxDOMText.textContent = "Maximum: ";
+                    var pairHolder = document.createElement("div");
+                    pairHolder.appendChild(this.maxDOMText);
+                    pairHolder.appendChild(this.maxDOM);
+                    this.rootDOM.appendChild(pairHolder);
+                    break;
+                case "radio":
+                case "checkbox":
+                    this.options = [];
+                    for (var i=0; i<specification.options.length; i++) {
+                        var option = specification.options[i];
+                        var pairHolder = document.createElement("div");
+                        var text = document.createElement("span");
+                        text.textContent = option.text;
+                        var check = document.createElement("input");
+                        check.type = "checkbox";
+                        check.setAttribute("option-index",i);
+                        check.checked = true;
+                        check.addEventListener("click",this);
+                        this.options.push(check);
+                        pairHolder.appendChild(text);
+                        pairHolder.appendChild(check);
+                        this.rootDOM.appendChild(pairHolder);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        for (var survey_entry of specification.preTest.options) {
+            switch(survey_entry.type) {
+                case "number":
+                case "radio":
+                case "checkbox":
+                    var node = new FilterObject(this,survey_entry);
+                    this.filterObjects.push(node);
+                    this.filterDOM.appendChild(node.rootDOM);
+                    break;
+                default:
+                    break;
+            }
+        }
+        for (var survey_entry of specification.postTest.options) {
+            switch(survey_entry.type) {
+                case "number":
+                case "radio":
+                case "checkbox":
+                    var node = new FilterObject(this,survey_entry);
+                    this.filterObjects.push(node);
+                    this.filterDOM.appendChild(node.rootDOM);
+                    break;
+                default:
+                    break;
+            }
+        }
+        document.getElementById("test-saved").appendChild(this.filterDOM);
+    }
+    this.getFileCount = function() {
+        // First we must get the filter pairs
+        var pairs = [];
+        for (var obj of this.filterObjects) {
+            pairs = pairs.concat(obj.getFilterPairs());
+        }
+        var req_str = "../scripts/get_filtered_count.php?url="+testData.selectURL;
+        var index = 0;
+        while(pairs[index] != undefined) {
+            req_str += '&';
+            req_str += pairs[index][0]+"="+pairs[index][1];
+            index++;
+        }
+        get(req_str).then(function(response){
+            var urls = JSON.parse(response);
+            var str = "Filtered to "+urls.urls.length+" file";
+            if (urls.urls.length != 1) {
+                str += "s.";
+            } else {
+                str += ".";
+            }
+            document.getElementById("filter-count").textContent = str;
+        },function(error){
+            console.error(error);
+        });
+    }
 }
\ No newline at end of file
--- a/analysis/index.html	Tue Mar 29 14:37:07 2016 +0100
+++ b/analysis/index.html	Thu Mar 31 13:31:42 2016 +0100
@@ -4,9 +4,11 @@
         <link rel='stylesheet' href="analysis.css" type="text/css"/>
         <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
         <script type="text/javascript" src="analysis.js"></script>
+        <script type="text/javascript" src="../specification.js"></script>
     </head>
     <body>
         <h1>Web Audio Evaluation Toolbox: Analysis</h1>
+        <div id="test-saved"></div>
         <button onclick="chartContext.clear();">Clear Charts</button>
         <div id="test-charts">
             <p>Charts per test</p>
--- a/core.js	Tue Mar 29 14:37:07 2016 +0100
+++ b/core.js	Thu Mar 31 13:31:42 2016 +0100
@@ -28,6 +28,16 @@
 // Add a prototype to the bufferNode to hold the computed LUFS loudness
 AudioBuffer.prototype.lufs = undefined;
 
+// Convert relative URLs into absolutes
+function escapeHTML(s) {
+    return s.split('&').join('&amp;').split('<').join('&lt;').split('"').join('&quot;');
+}
+function qualifyURL(url) {
+    var el= document.createElement('div');
+    el.innerHTML= '<a href="'+escapeHTML(url)+'">x</a>';
+    return el.firstChild.href;
+}
+
 // Firefox does not have an XMLDocument.prototype.getElementsByName
 // and there is no searchAll style command, this custom function will
 // search all children recusrively for the name. Used for XSD where all
@@ -1781,644 +1791,6 @@
 	console.log(outputSequence.toString()); 	// print randomised array to console
 	return holdArr;
 }
-
-function Specification() {
-	// Handles the decoding of the project specification XML into a simple JavaScript Object.
-	
-	this.interface = null;
-	this.projectReturn = "null";
-	this.randomiseOrder = null;
-	this.testPages = null;
-	this.pages = [];
-	this.metrics = null;
-	this.interfaces = null;
-	this.loudness = null;
-	this.errors = [];
-	this.schema = null;
-    this.exitText = "Thank you.";
-	
-	this.processAttribute = function(attribute,schema,schemaRoot)
-	{
-		// attribute is the string returned from getAttribute on the XML
-		// schema is the <xs:attribute> node
-		if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
-		{
-			schema = schemaRoot.getAllElementsByName(schema.getAttribute('ref'))[0];
-		}
-		var defaultOpt = schema.getAttribute('default');
-		if (attribute == null) {
-			attribute = defaultOpt;
-		}
-		var dataType = schema.getAttribute('type');
-		if (typeof dataType == "string") { dataType = dataType.substr(3);}
-		else {dataType = "string";}
-		if (attribute == null)
-		{
-			return attribute;
-		}
-		switch(dataType)
-		{
-		case "boolean":
-			if (attribute == 'true'){attribute = true;}else{attribute=false;}
-			break;
-		case "negativeInteger":
-		case "positiveInteger":
-		case "nonNegativeInteger":
-		case "nonPositiveInteger":
-		case "integer":
-		case "decimal":
-		case "short":
-			attribute = Number(attribute);
-			break;
-		case "string":
-		default:
-			attribute = String(attribute);
-			break;
-		}
-		return attribute;
-	};
-	
-	this.decode = function(projectXML) {
-		this.errors = [];
-		// projectXML - DOM Parsed document
-		this.projectXML = projectXML.childNodes[0];
-		var setupNode = projectXML.getElementsByTagName('setup')[0];
-		var schemaSetup = this.schema.getAllElementsByName('setup')[0];
-		// First decode the attributes
-		var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
-		for (var i in attributes)
-		{
-			if (isNaN(Number(i)) == true){break;}
-			var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref');
-			var projectAttr = setupNode.getAttribute(attributeName);
-			projectAttr = this.processAttribute(projectAttr,attributes[i],this.schema);
-			switch(typeof projectAttr)
-			{
-			case "number":
-			case "boolean":
-				eval('this.'+attributeName+' = '+projectAttr);
-				break;
-			case "string":
-				eval('this.'+attributeName+' = "'+projectAttr+'"');
-				break;
-			}
-			
-		}
-        
-        var exitTextNode = setupNode.getElementsByTagName('exitText');
-        if (exitTextNode.length == 1) {
-            this.exitText = exitTextNode[0].textContent;
-        }
-		
-		this.metrics = new this.metricNode();
-		
-		this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
-		
-		// Now process the survey node options
-		var survey = setupNode.getElementsByTagName('survey');
-		for (var i in survey) {
-			if (isNaN(Number(i)) == true){break;}
-			var location = survey[i].getAttribute('location');
-			if (location == 'pre' || location == 'before')
-			{
-				if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
-				else {
-					this.preTest = new this.surveyNode();
-					this.preTest.decode(this,survey[i]);
-				}
-			} else if (location == 'post' || location == 'after') {
-				if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
-				else {
-					this.postTest = new this.surveyNode();
-					this.postTest.decode(this,survey[i]);
-				}
-			}
-		}
-		
-		var interfaceNode = setupNode.getElementsByTagName('interface');
-		if (interfaceNode.length > 1)
-		{
-			this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
-		}
-		this.interfaces = new this.interfaceNode();
-		if (interfaceNode.length != 0)
-		{
-			interfaceNode = interfaceNode[0];
-			this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
-		}
-		
-		// Page tags
-		var pageTags = projectXML.getElementsByTagName('page');
-		var pageSchema = this.schema.getAllElementsByName('page')[0];
-		for (var i=0; i<pageTags.length; i++)
-		{
-			var node = new this.page();
-			node.decode(this,pageTags[i],pageSchema);
-			this.pages.push(node);
-		}
-	};
-	
-	this.encode = function()
-	{
-		var RootDocument = document.implementation.createDocument(null,"waet");
-		var root = RootDocument.children[0];
-        root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
-        root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
-		// Build setup node
-        var setup = RootDocument.createElement("setup");
-        var schemaSetup = this.schema.getAllElementsByName('setup')[0];
-        // First decode the attributes
-        var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
-        for (var i=0; i<attributes.length; i++)
-        {
-            var name = attributes[i].getAttribute("name");
-            if (name == undefined) {
-                name = attributes[i].getAttribute("ref");
-            }
-            if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
-            {
-                eval("setup.setAttribute('"+name+"',this."+name+")");
-            }
-        }
-        root.appendChild(setup);
-        // Survey node
-        if (this.exitText != null) {
-            var exitTextNode = RootDocument.createElement('exitText');
-            exitTextNode.textContent = this.exitText;
-            setup.appendChild(exitTextNode);
-        }
-        setup.appendChild(this.preTest.encode(RootDocument));
-        setup.appendChild(this.postTest.encode(RootDocument));
-        setup.appendChild(this.metrics.encode(RootDocument));
-        setup.appendChild(this.interfaces.encode(RootDocument));
-        for (var page of this.pages)
-        {
-            root.appendChild(page.encode(RootDocument));
-        }
-		return RootDocument;
-	};
-	
-	this.surveyNode = function() {
-		this.location = null;
-		this.options = [];
-        this.parent = null;
-		this.schema = specification.schema.getAllElementsByName('survey')[0];
-		
-		this.OptionNode = function() {
-			this.type = undefined;
-			this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
-			this.id = undefined;
-            this.name = undefined;
-			this.mandatory = undefined;
-			this.statement = undefined;
-			this.boxsize = undefined;
-			this.options = [];
-			this.min = undefined;
-			this.max = undefined;
-			this.step = undefined;
-			
-			this.decode = function(parent,child)
-			{
-				var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
-				for (var i in attributeMap){
-					if(isNaN(Number(i)) == true){break;}
-					var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
-					var projectAttr = child.getAttribute(attributeName);
-					projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
-					switch(typeof projectAttr)
-					{
-					case "number":
-					case "boolean":
-						eval('this.'+attributeName+' = '+projectAttr);
-						break;
-					case "string":
-						eval('this.'+attributeName+' = "'+projectAttr+'"');
-						break;
-					}
-				}
-				this.statement = child.getElementsByTagName('statement')[0].textContent;
-				if (this.type == "checkbox" || this.type == "radio") {
-					var children = child.getElementsByTagName('option');
-					if (children.length == null) {
-						console.log('Malformed' +child.nodeName+ 'entry');
-						this.statement = 'Malformed' +child.nodeName+ 'entry';
-						this.type = 'statement';
-					} else {
-						this.options = [];
-						for (var i in children)
-						{
-							if (isNaN(Number(i))==true){break;}
-							this.options.push({
-								name: children[i].getAttribute('name'),
-								text: children[i].textContent
-							});
-						}
-					}
-				}
-			};
-			
-			this.exportXML = function(doc)
-			{
-				var node = doc.createElement('surveyentry');
-				node.setAttribute('type',this.type);
-				var statement = doc.createElement('statement');
-				statement.textContent = this.statement;
-				node.appendChild(statement);
-                node.id = this.id;
-                if (this.name != undefined) { node.setAttribute("name",this.name);}
-                if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
-                node.id = this.id;
-                if (this.name != undefined) {node.setAttribute("name",this.name);}
-                switch(this.type)
-                {
-                    case "checkbox":
-                    case "radio":
-                        for (var i=0; i<this.options.length; i++)
-                        {
-                            var option = this.options[i];
-                            var optionNode = doc.createElement("option");
-                            optionNode.setAttribute("name",option.name);
-                            optionNode.textContent = option.text;
-                            node.appendChild(optionNode);
-                        }
-                    case "number":
-                        if (this.min != undefined) {node.setAttribute("min", this.min);}
-                        if (this.max != undefined) {node.setAttribute("max", this.max);}
-                    case "question":
-                        if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
-                        if (this.mandatory != undefined) {node.setAttribute("mandatory",this.mandatory);}
-                    default:
-                        break;
-                }
-				return node;
-			};
-		};
-		this.decode = function(parent,xml) {
-            this.parent = parent;
-			this.location = xml.getAttribute('location');
-			if (this.location == 'before'){this.location = 'pre';}
-			else if (this.location == 'after'){this.location = 'post';}
-			for (var i in xml.children)
-			{
-				if(isNaN(Number(i))==true){break;}
-				var node = new this.OptionNode();
-				node.decode(parent,xml.children[i]);
-				this.options.push(node);
-			}
-		};
-		this.encode = function(doc) {
-			var node = doc.createElement('survey');
-			node.setAttribute('location',this.location);
-			for (var i=0; i<this.options.length; i++)
-			{
-				node.appendChild(this.options[i].exportXML(doc));
-			}
-			return node;
-		};
-	};
-	
-	this.interfaceNode = function()
-	{
-		this.title = null;
-		this.name = null;
-		this.options = [];
-		this.scales = [];
-		this.schema = specification.schema.getAllElementsByName('interface')[1];
-		
-		this.decode = function(parent,xml) {
-			this.name = xml.getAttribute('name');
-			var titleNode = xml.getElementsByTagName('title');
-			if (titleNode.length == 1)
-			{
-				this.title = titleNode[0].textContent;
-			}
-			var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
-			// Extract interfaceoption node schema
-			var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
-			var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
-			for (var i=0; i<interfaceOptionNodes.length; i++)
-			{
-				var ioNode = interfaceOptionNodes[i];
-				var option = {};
-				for (var j=0; j<attributeMap.length; j++)
-				{
-					var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
-					var projectAttr = ioNode.getAttribute(attributeName);
-                    if(parent.processAttribute) {
-                        parent.processAttribute(projectAttr, attributeMap[j], parent.schema)
-                    } else {
-                        parent.parent.processAttribute(projectAttr, attributeMap[j], parent.parent.schema)
-                    }
-					switch(typeof projectAttr)
-					{
-					case "number":
-					case "boolean":
-						eval('option.'+attributeName+' = '+projectAttr);
-						break;
-					case "string":
-						eval('option.'+attributeName+' = "'+projectAttr+'"');
-						break;
-					}
-				}
-				this.options.push(option);
-			}
-			
-			// Now the scales nodes
-			var scaleParent = xml.getElementsByTagName('scales');
-			if (scaleParent.length == 1) {
-				scaleParent = scaleParent[0];
-				for (var i=0; i<scaleParent.children.length; i++) {
-					var child = scaleParent.children[i];
-					this.scales.push({
-						text: child.textContent,
-						position: Number(child.getAttribute('position'))
-					});
-				}
-			}
-		};
-		
-		this.encode = function(doc) {
-			var node = doc.createElement("interface");
-            if (typeof name == "string")
-                node.setAttribute("name",this.name);
-            for (var option of this.options)
-            {
-                var child = doc.createElement("interfaceoption");
-                child.setAttribute("type",option.type);
-                child.setAttribute("name",option.name);
-                node.appendChild(child);
-            }
-            if (this.scales.length != 0) {
-                var scales = doc.createElement("scales");
-                for (var scale of this.scales)
-                {
-                    var child = doc.createElement("scalelabel");
-                    child.setAttribute("position",scale.position);
-                    child.textContent = scale.text;
-                    scales.appendChild(child);
-                }
-                node.appendChild(scales);
-            }
-            return node;
-		};
-	};
-	
-    this.metricNode = function() {
-        this.enabled = [];
-        this.decode = function(parent, xml) {
-            var children = xml.getElementsByTagName('metricenable');
-            for (var i in children) { 
-                if (isNaN(Number(i)) == true){break;}
-                this.enabled.push(children[i].textContent);
-            }
-        }
-        this.encode = function(doc) {
-            var node = doc.createElement('metric');
-            for (var i in this.enabled)
-            {
-                if (isNaN(Number(i)) == true){break;}
-                var child = doc.createElement('metricenable');
-                child.textContent = this.enabled[i];
-                node.appendChild(child);
-            }
-            return node;
-        }
-    }
-    
-	this.page = function() {
-		this.presentedId = undefined;
-		this.id = undefined;
-		this.hostURL = undefined;
-		this.randomiseOrder = undefined;
-		this.loop = undefined;
-		this.showElementComments = undefined;
-		this.outsideReference = null;
-		this.loudness = null;
-        this.label = null;
-		this.preTest = null;
-		this.postTest = null;
-		this.interfaces = [];
-		this.commentBoxPrefix = "Comment on track";
-		this.audioElements = [];
-		this.commentQuestions = [];
-		this.schema = specification.schema.getAllElementsByName("page")[0];
-        this.parent = null;
-		
-		this.decode = function(parent,xml)
-		{
-            this.parent = parent;
-			var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
-			for (var i=0; i<attributeMap.length; i++)
-			{
-				var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
-				var projectAttr = xml.getAttribute(attributeName);
-				projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
-				switch(typeof projectAttr)
-				{
-				case "number":
-				case "boolean":
-					eval('this.'+attributeName+' = '+projectAttr);
-					break;
-				case "string":
-					eval('this.'+attributeName+' = "'+projectAttr+'"');
-					break;
-				}
-			}
-			
-			// Get the Comment Box Prefix
-			var CBP = xml.getElementsByTagName('commentboxprefix');
-			if (CBP.length != 0) {
-				this.commentBoxPrefix = CBP[0].textContent;
-			}
-			
-			// Now decode the interfaces
-			var interfaceNode = xml.getElementsByTagName('interface');
-			for (var i=0; i<interfaceNode.length; i++)
-			{
-				var node = new parent.interfaceNode();
-				node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
-				this.interfaces.push(node);
-			}
-			
-			// Now process the survey node options
-			var survey = xml.getElementsByTagName('survey');
-			var surveySchema = parent.schema.getAllElementsByName('survey')[0];
-			for (var i in survey) {
-				if (isNaN(Number(i)) == true){break;}
-				var location = survey[i].getAttribute('location');
-				if (location == 'pre' || location == 'before')
-				{
-					if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
-					else {
-						this.preTest = new parent.surveyNode();
-						this.preTest.decode(parent,survey[i],surveySchema);
-					}
-				} else if (location == 'post' || location == 'after') {
-					if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
-					else {
-						this.postTest = new parent.surveyNode();
-						this.postTest.decode(parent,survey[i],surveySchema);
-					}
-				}
-			}
-			
-			// Now process the audioelement tags
-			var audioElements = xml.getElementsByTagName('audioelement');
-			for (var i=0; i<audioElements.length; i++)
-			{
-				var node = new this.audioElementNode();
-				node.decode(this,audioElements[i]);
-				this.audioElements.push(node);
-			}
-			
-			// Now decode the commentquestions
-			var commentQuestions = xml.getElementsByTagName('commentquestion');
-			for (var i=0; i<commentQuestions.length; i++)
-			{
-				var node = new this.commentQuestionNode();
-				node.decode(parent,commentQuestions[i]);
-				this.commentQuestions.push(node);
-			}
-		};
-		
-		this.encode = function(root)
-		{
-			var AHNode = root.createElement("page");
-            // First decode the attributes
-            var attributes = this.schema.getAllElementsByTagName('xs:attribute');
-            for (var i=0; i<attributes.length; i++)
-            {
-                var name = attributes[i].getAttribute("name");
-                if (name == undefined) {
-                    name = attributes[i].getAttribute("ref");
-                }
-                if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
-                {
-                    eval("AHNode.setAttribute('"+name+"',this."+name+")");
-                }
-            }
-			if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
-            // <commentboxprefix>
-            var commentboxprefix = root.createElement("commentboxprefix");
-            commentboxprefix.textContent = this.commentBoxPrefix;
-            AHNode.appendChild(commentboxprefix);
-            
-			for (var i=0; i<this.interfaces.length; i++)
-			{
-				AHNode.appendChild(this.interfaces[i].encode(root));
-			}
-			
-			for (var i=0; i<this.audioElements.length; i++) {
-				AHNode.appendChild(this.audioElements[i].encode(root));
-			}
-			// Create <CommentQuestion>
-			for (var i=0; i<this.commentQuestions.length; i++)
-			{
-				AHNode.appendChild(this.commentQuestions[i].encode(root));
-			}
-			
-			AHNode.appendChild(this.preTest.encode(root));
-            AHNode.appendChild(this.postTest.encode(root));
-			return AHNode;
-		};
-		
-		this.commentQuestionNode = function() {
-			this.id = null;
-            this.name = undefined;
-			this.type = undefined;
-			this.options = [];
-			this.statement = undefined;
-			this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
-			this.decode = function(parent,xml)
-			{
-				this.id = xml.id;
-                this.name = xml.getAttribute('name');
-				this.type = xml.getAttribute('type');
-				this.statement = xml.getElementsByTagName('statement')[0].textContent;
-				var optNodes = xml.getElementsByTagName('option');
-				for (var i=0; i<optNodes.length; i++)
-				{
-					var optNode = optNodes[i];
-					this.options.push({
-						name: optNode.getAttribute('name'),
-						text: optNode.textContent
-					});
-				}
-			};
-			
-			this.encode = function(root)
-			{
-				var node = root.createElement("commentquestion");
-                node.id = this.id;
-                node.setAttribute("type",this.type);
-                if (this.name != undefined){node.setAttribute("name",this.name);}
-                var statement = root.createElement("statement");
-                statement.textContent = this.statement;
-                node.appendChild(statement);
-                for (var option of this.options)
-                {
-                    var child = root.createElement("option");
-                    child.setAttribute("name",option.name);
-                    child.textContent = option.text;
-                    node.appendChild(child);
-                }
-                return node;
-			};
-		};
-		
-		this.audioElementNode = function() {
-			this.url = null;
-			this.id = null;
-            this.name = null;
-			this.parent = null;
-			this.type = null;
-			this.marker = null;
-			this.enforce = false;
-			this.gain = 0.0;
-			this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
-			this.parent = null;
-			this.decode = function(parent,xml)
-			{
-				this.parent = parent;
-				var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
-				for (var i=0; i<attributeMap.length; i++)
-				{
-					var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
-					var projectAttr = xml.getAttribute(attributeName);
-					projectAttr = parent.parent.processAttribute(projectAttr,attributeMap[i],parent.parent.schema);
-					switch(typeof projectAttr)
-					{
-					case "number":
-					case "boolean":
-						eval('this.'+attributeName+' = '+projectAttr);
-						break;
-					case "string":
-						eval('this.'+attributeName+' = "'+projectAttr+'"');
-						break;
-					}
-				}
-				
-			};
-			this.encode = function(root)
-			{
-				var AENode = root.createElement("audioelement");
-				var attributes = this.schema.getAllElementsByTagName('xs:attribute');
-                for (var i=0; i<attributes.length; i++)
-                {
-                    var name = attributes[i].getAttribute("name");
-                    if (name == undefined) {
-                        name = attributes[i].getAttribute("ref");
-                    }
-                    if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
-                    {
-                        eval("AENode.setAttribute('"+name+"',this."+name+")");
-                    }
-                }
-				return AENode;
-			};
-		};
-	};
-}
 			
 function Interface(specificationObject) {
 	// This handles the bindings between the interface and the audioEngineContext;
@@ -3356,6 +2728,7 @@
             this.root = this.document.childNodes[0];
             var projectDocument = specification.projectXML;
             projectDocument.setAttribute('file-name',url);
+            projectDocument.setAttribute('url',qualifyURL(url));
             this.root.appendChild(projectDocument);
             this.root.appendChild(interfaceContext.returnDateNode());
             this.root.appendChild(interfaceContext.returnNavigator());
@@ -3516,6 +2889,7 @@
             if (element.name != undefined){aeNode.setAttribute('name',element.name)};
 			aeNode.setAttribute('type',element.type);
 			aeNode.setAttribute('url', element.url);
+            aeNode.setAttribute('fqurl',qualifyURL(element.url));
 			aeNode.setAttribute('gain', element.gain);
 			if (element.type == 'anchor' || element.type == 'reference')
 			{
--- a/index.html	Tue Mar 29 14:37:07 2016 +0100
+++ b/index.html	Thu Mar 31 13:31:42 2016 +0100
@@ -16,11 +16,12 @@
 		<link rel='stylesheet' type='text/css' href='core.css'>
 		<!-- Use jQuery hosted from Google CDN -->
 		<!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>-->
-		<script src="jquery-2.1.4.js"></script>
-		<script src='core.js'></script>
-		<script src='loudness.js'></script>
-		<script src='xmllint.js'></script>
-        <script src='WAVE.js'></script>
+		<script type="text/javascript" src="jquery-2.1.4.js"></script>
+        <script type="text/javascript" src='specification.js'></script>
+		<script type="text/javascript" src='core.js'></script>
+		<script type="text/javascript" src='loudness.js'></script>
+		<script type="text/javascript" src='xmllint.js'></script>
+        <script type="text/javascript" src='WAVE.js'></script>
 		<script type="text/javascript">
 			// SEARCH QUERY: By using the GET Request option ?url=loca/path/to/project.xml in the URL bar, you can load a project quickly
 			if (window.location.search.length != 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/get_filtered_count.php	Thu Mar 31 13:31:42 2016 +0100
@@ -0,0 +1,134 @@
+<?php
+//http://stackoverflow.com/questions/4444475/transfrom-relative-path-into-absolute-url-using-php
+function rel2abs($rel, $base)
+{
+    /* return if already absolute URL */
+    if (parse_url($rel, PHP_URL_SCHEME) != '' || substr($rel, 0, 2) == '//') return $rel;
+
+    /* queries and anchors */
+    if ($rel[0]=='#' || $rel[0]=='?') return $base.$rel;
+
+    /* parse base URL and convert to local variables:
+     $scheme, $host, $path */
+    extract(parse_url($base));
+
+    /* remove non-directory element from path */
+    $path = preg_replace('#/[^/]*$#', '', $path);
+
+    /* destroy path if relative url points to root */
+    if ($rel[0] == '/') $path = '';
+
+    /* dirty absolute URL */
+    $abs = "$host$path/$rel";
+
+    /* replace '//' or '/./' or '/foo/../' with '/' */
+    $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
+    for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}
+
+    /* absolute URL is ready! */
+    return $scheme.'://'.$abs;
+}
+
+/*
+    This looks for files that pass the filtering response
+    The filtering system uses key-value pairs
+    The key is double encoded using a '-'. The first part is the ID of the item to filter,
+    the second is the method:
+        min - Minimum Inclusive
+        max - Maximum Inclusive
+        exclude-# - exclude, followed by a number to uniquely add, (will create a triple [], ignore the third as random)
+*/
+$keys = [];
+$waet_url = null;
+foreach ($_GET as $key => $value) {
+    $key = explode("-",$key);
+    if ($key[0] == "url") {
+        $waet_url = $value;
+    } else {
+        $v_pair = [$key[1],$value];
+        if(array_key_exists($key[0],$keys)) {
+            // We have some data
+            array_push($keys[$key[0]],$v_pair);
+        } else {
+            // Create new key data
+            $keys[$key[0]] = [$v_pair];
+        }
+    }
+}
+
+$files = [];
+$saves = glob("../saves/*.xml");
+if (is_array($saves))
+{
+    foreach($saves as $filename) {
+        $xml_string = file_get_contents($filename, FILE_TEXT);
+        $xml_object = simplexml_load_string($xml_string);
+        if ($xml_object) {
+            // First we must check the URLs match
+            $waet = $xml_object->waet[0];
+            if (urldecode($waet["url"])==$waet_url) {
+                // It is part of the dataset, so now perform checks
+                $continue = true;
+                foreach($keys as $keyId => $keyArr) {
+                    $elem = $xml_object->xpath("//*[@ref='".$keyId."']");
+                    $elem = $elem[0]; // Can only be one.
+                    switch ($elem["type"]) {
+                        case "number":
+                            // Number, we must check for min/max
+                            $value =  (real)$elem->response;
+                            foreach ($keyArr as $keyCheck) {
+                                if ($keyCheck[0] == 'min' && $value < $keyCheck[1]) {
+                                    $continue = false;
+                                    break;
+                                } else if ($keyCheck[0] == 'max' && $value > $keyCheck[1]) {
+                                    $continue = false;
+                                    break;
+                                }
+                            }
+                            break;
+                        case "checkbox":
+                            // Will have an array of <response>
+                            foreach ($elem->response as $response) {
+                                foreach ($keyArr as $keyCheck) {
+                                    if ($response["name"] == $keyCheck[1]) {
+                                        if($response["checked"] == "true" && $keyCheck[0] == "exclude") {
+                                            $continue = false;
+                                            break;
+                                        }
+                                    }
+                                }
+                                if($continue == false) {
+                                    break;
+                                }
+                            }
+                            break;
+                        case "radio":
+                            foreach ($keyArr as $keyCheck) {
+                                if ($keyCheck[0] == "exclude" && $elem->response["name"] == $keyCheck[1]) {
+
+                                    $continue = false;
+                                    break;
+                                }
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    if ($continue == false) {
+                        break;
+                    }
+                }
+                if ($continue) {
+                    array_push($files,rel2abs($filename,"http://".$_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']));
+                }
+            }
+        }
+    }
+}
+if (count($files) == 0) {
+    echo '{"urls": []}';
+} else {
+    echo '{"urls": ["'.implode('","',$files).'"]}';
+}
+
+?>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/get_tests.php	Thu Mar 31 13:31:42 2016 +0100
@@ -0,0 +1,96 @@
+<?php
+/*
+    Get Tests
+    
+    This script returns the XML test names available, plus the number of tests
+*/
+
+//http://stackoverflow.com/questions/4444475/transfrom-relative-path-into-absolute-url-using-php
+function rel2abs($rel, $base)
+{
+    /* return if already absolute URL */
+    if (parse_url($rel, PHP_URL_SCHEME) != '' || substr($rel, 0, 2) == '//') return $rel;
+
+    /* queries and anchors */
+    if ($rel[0]=='#' || $rel[0]=='?') return $base.$rel;
+
+    /* parse base URL and convert to local variables:
+     $scheme, $host, $path */
+    extract(parse_url($base));
+
+    /* remove non-directory element from path */
+    $path = preg_replace('#/[^/]*$#', '', $path);
+
+    /* destroy path if relative url points to root */
+    if ($rel[0] == '/') $path = '';
+
+    /* dirty absolute URL */
+    $abs = "$host$path/$rel";
+
+    /* replace '//' or '/./' or '/foo/../' with '/' */
+    $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
+    for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}
+
+    /* absolute URL is ready! */
+    return $scheme.'://'.$abs;
+}
+
+// XML Saves location - assumes it will be saves/
+$data = [];
+$saves = glob("../saves/*.xml");
+if (is_array($saves))
+{
+    foreach($saves as $filename) {
+        $xml_string = file_get_contents($filename, FILE_TEXT);
+        $xml_object = simplexml_load_string($xml_string);
+        if ($xml_object) {
+            $filename = rel2abs($filename,"http://".$_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']);
+            $waet = $xml_object->waet[0];
+            $testName = urldecode($waet["url"]);
+            if(array_key_exists($testName,$data)) {
+                // Key exists
+                array_push($data[$testName],$filename);
+            } else {
+                // Key does not exist
+                $data[$testName] = [$filename];
+            }
+        }
+    }
+}
+
+// Now read the format response
+$format = "JSON";
+if (array_key_exists("format",$_GET)) {
+    $format = $_GET["format"];
+}
+switch($format) {
+    case "JSON":
+        // Return JSON
+        $doc_root = '{"tests": [';
+        $keys = array_keys($data);
+        $numTests = count($data);
+        for ($testIndex = 0; $testIndex < $numTests; $testIndex++) {
+            $test_root = '{"testName": "'.$keys[$testIndex].'", "files": [';
+            $numFiles = count($data[$keys[$testIndex]]);
+            for ($countIndex=0; $countIndex < $numFiles; $countIndex++) {
+                $test_root = $test_root.'"'.$data[$keys[$testIndex]][$countIndex].'"';
+                if ($countIndex == $numFiles-1) {
+                    $test_root = $test_root.']}';
+                } else {
+                    $test_root = $test_root.',';
+                }
+            }
+            $doc_root = $doc_root.$test_root;
+            if ($testIndex == $numTests-1) {
+                $doc_root = $doc_root.']}';
+            } else {
+                $doc_root = $doc_root.',';
+            }
+        }
+        echo $doc_root;
+        break;
+    default:
+        echo '{"error": "format can only be JSON"}';
+}
+
+?>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/specification.js	Thu Mar 31 13:31:42 2016 +0100
@@ -0,0 +1,639 @@
+function Specification() {
+	// Handles the decoding of the project specification XML into a simple JavaScript Object.
+	
+	this.interface = null;
+	this.projectReturn = "null";
+	this.randomiseOrder = null;
+	this.testPages = null;
+	this.pages = [];
+	this.metrics = null;
+	this.interfaces = null;
+	this.loudness = null;
+	this.errors = [];
+	this.schema = null;
+    this.exitText = "Thank you.";
+	
+	this.processAttribute = function(attribute,schema,schemaRoot)
+	{
+		// attribute is the string returned from getAttribute on the XML
+		// schema is the <xs:attribute> node
+		if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
+		{
+			schema = schemaRoot.getAllElementsByName(schema.getAttribute('ref'))[0];
+		}
+		var defaultOpt = schema.getAttribute('default');
+		if (attribute == null) {
+			attribute = defaultOpt;
+		}
+		var dataType = schema.getAttribute('type');
+		if (typeof dataType == "string") { dataType = dataType.substr(3);}
+		else {dataType = "string";}
+		if (attribute == null)
+		{
+			return attribute;
+		}
+		switch(dataType)
+		{
+		case "boolean":
+			if (attribute == 'true'){attribute = true;}else{attribute=false;}
+			break;
+		case "negativeInteger":
+		case "positiveInteger":
+		case "nonNegativeInteger":
+		case "nonPositiveInteger":
+		case "integer":
+		case "decimal":
+		case "short":
+			attribute = Number(attribute);
+			break;
+		case "string":
+		default:
+			attribute = String(attribute);
+			break;
+		}
+		return attribute;
+	};
+	
+	this.decode = function(projectXML) {
+		this.errors = [];
+		// projectXML - DOM Parsed document
+		this.projectXML = projectXML.childNodes[0];
+		var setupNode = projectXML.getElementsByTagName('setup')[0];
+		var schemaSetup = this.schema.getAllElementsByName('setup')[0];
+		// First decode the attributes
+		var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
+		for (var i in attributes)
+		{
+			if (isNaN(Number(i)) == true){break;}
+			var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref');
+			var projectAttr = setupNode.getAttribute(attributeName);
+			projectAttr = this.processAttribute(projectAttr,attributes[i],this.schema);
+			switch(typeof projectAttr)
+			{
+			case "number":
+			case "boolean":
+				eval('this.'+attributeName+' = '+projectAttr);
+				break;
+			case "string":
+				eval('this.'+attributeName+' = "'+projectAttr+'"');
+				break;
+			}
+			
+		}
+        
+        var exitTextNode = setupNode.getElementsByTagName('exitText');
+        if (exitTextNode.length == 1) {
+            this.exitText = exitTextNode[0].textContent;
+        }
+		
+		this.metrics = new this.metricNode();
+		
+		this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
+		
+		// Now process the survey node options
+		var survey = setupNode.getElementsByTagName('survey');
+		for (var i in survey) {
+			if (isNaN(Number(i)) == true){break;}
+			var location = survey[i].getAttribute('location');
+			if (location == 'pre' || location == 'before')
+			{
+				if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
+				else {
+					this.preTest = new this.surveyNode(this);
+					this.preTest.decode(this,survey[i]);
+				}
+			} else if (location == 'post' || location == 'after') {
+				if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
+				else {
+					this.postTest = new this.surveyNode(this);
+					this.postTest.decode(this,survey[i]);
+				}
+			}
+		}
+		
+		var interfaceNode = setupNode.getElementsByTagName('interface');
+		if (interfaceNode.length > 1)
+		{
+			this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
+		}
+		this.interfaces = new this.interfaceNode(this);
+		if (interfaceNode.length != 0)
+		{
+			interfaceNode = interfaceNode[0];
+			this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
+		}
+		
+		// Page tags
+		var pageTags = projectXML.getElementsByTagName('page');
+		var pageSchema = this.schema.getAllElementsByName('page')[0];
+		for (var i=0; i<pageTags.length; i++)
+		{
+			var node = new this.page(this);
+			node.decode(this,pageTags[i],pageSchema);
+			this.pages.push(node);
+		}
+	};
+	
+	this.encode = function()
+	{
+		var RootDocument = document.implementation.createDocument(null,"waet");
+		var root = RootDocument.children[0];
+        root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
+        root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
+		// Build setup node
+        var setup = RootDocument.createElement("setup");
+        var schemaSetup = this.schema.getAllElementsByName('setup')[0];
+        // First decode the attributes
+        var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
+        for (var i=0; i<attributes.length; i++)
+        {
+            var name = attributes[i].getAttribute("name");
+            if (name == undefined) {
+                name = attributes[i].getAttribute("ref");
+            }
+            if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
+            {
+                eval("setup.setAttribute('"+name+"',this."+name+")");
+            }
+        }
+        root.appendChild(setup);
+        // Survey node
+        if (this.exitText != null) {
+            var exitTextNode = RootDocument.createElement('exitText');
+            exitTextNode.textContent = this.exitText;
+            setup.appendChild(exitTextNode);
+        }
+        setup.appendChild(this.preTest.encode(RootDocument));
+        setup.appendChild(this.postTest.encode(RootDocument));
+        setup.appendChild(this.metrics.encode(RootDocument));
+        setup.appendChild(this.interfaces.encode(RootDocument));
+        for (var page of this.pages)
+        {
+            root.appendChild(page.encode(RootDocument));
+        }
+		return RootDocument;
+	};
+	
+	this.surveyNode = function(specification) {
+		this.location = null;
+		this.options = [];
+        this.parent = null;
+		this.schema = specification.schema.getAllElementsByName('survey')[0];
+        this.specification = specification;
+		
+		this.OptionNode = function(specification) {
+			this.type = undefined;
+			this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
+			this.id = undefined;
+            this.name = undefined;
+			this.mandatory = undefined;
+			this.statement = undefined;
+			this.boxsize = undefined;
+			this.options = [];
+			this.min = undefined;
+			this.max = undefined;
+			this.step = undefined;
+			
+			this.decode = function(parent,child)
+			{
+				var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
+				for (var i in attributeMap){
+					if(isNaN(Number(i)) == true){break;}
+					var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
+					var projectAttr = child.getAttribute(attributeName);
+					projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
+					switch(typeof projectAttr)
+					{
+					case "number":
+					case "boolean":
+						eval('this.'+attributeName+' = '+projectAttr);
+						break;
+					case "string":
+						eval('this.'+attributeName+' = "'+projectAttr+'"');
+						break;
+					}
+				}
+				this.statement = child.getElementsByTagName('statement')[0].textContent;
+				if (this.type == "checkbox" || this.type == "radio") {
+					var children = child.getElementsByTagName('option');
+					if (children.length == null) {
+						console.log('Malformed' +child.nodeName+ 'entry');
+						this.statement = 'Malformed' +child.nodeName+ 'entry';
+						this.type = 'statement';
+					} else {
+						this.options = [];
+						for (var i in children)
+						{
+							if (isNaN(Number(i))==true){break;}
+							this.options.push({
+								name: children[i].getAttribute('name'),
+								text: children[i].textContent
+							});
+						}
+					}
+				}
+			};
+			
+			this.exportXML = function(doc)
+			{
+				var node = doc.createElement('surveyentry');
+				node.setAttribute('type',this.type);
+				var statement = doc.createElement('statement');
+				statement.textContent = this.statement;
+				node.appendChild(statement);
+                node.id = this.id;
+                if (this.name != undefined) { node.setAttribute("name",this.name);}
+                if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
+                node.id = this.id;
+                if (this.name != undefined) {node.setAttribute("name",this.name);}
+                switch(this.type)
+                {
+                    case "checkbox":
+                    case "radio":
+                        for (var i=0; i<this.options.length; i++)
+                        {
+                            var option = this.options[i];
+                            var optionNode = doc.createElement("option");
+                            optionNode.setAttribute("name",option.name);
+                            optionNode.textContent = option.text;
+                            node.appendChild(optionNode);
+                        }
+                    case "number":
+                        if (this.min != undefined) {node.setAttribute("min", this.min);}
+                        if (this.max != undefined) {node.setAttribute("max", this.max);}
+                    case "question":
+                        if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
+                        if (this.mandatory != undefined) {node.setAttribute("mandatory",this.mandatory);}
+                    default:
+                        break;
+                }
+				return node;
+			};
+		};
+		this.decode = function(parent,xml) {
+            this.parent = parent;
+			this.location = xml.getAttribute('location');
+			if (this.location == 'before'){this.location = 'pre';}
+			else if (this.location == 'after'){this.location = 'post';}
+			for (var i in xml.children)
+			{
+				if(isNaN(Number(i))==true){break;}
+				var node = new this.OptionNode(this.specification);
+				node.decode(parent,xml.children[i]);
+				this.options.push(node);
+			}
+		};
+		this.encode = function(doc) {
+			var node = doc.createElement('survey');
+			node.setAttribute('location',this.location);
+			for (var i=0; i<this.options.length; i++)
+			{
+				node.appendChild(this.options[i].exportXML(doc));
+			}
+			return node;
+		};
+	};
+	
+	this.interfaceNode = function(specification)
+	{
+		this.title = null;
+		this.name = null;
+		this.options = [];
+		this.scales = [];
+		this.schema = specification.schema.getAllElementsByName('interface')[1];
+		
+		this.decode = function(parent,xml) {
+			this.name = xml.getAttribute('name');
+			var titleNode = xml.getElementsByTagName('title');
+			if (titleNode.length == 1)
+			{
+				this.title = titleNode[0].textContent;
+			}
+			var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
+			// Extract interfaceoption node schema
+			var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
+			var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
+			for (var i=0; i<interfaceOptionNodes.length; i++)
+			{
+				var ioNode = interfaceOptionNodes[i];
+				var option = {};
+				for (var j=0; j<attributeMap.length; j++)
+				{
+					var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
+					var projectAttr = ioNode.getAttribute(attributeName);
+                    if(parent.processAttribute) {
+                        parent.processAttribute(projectAttr, attributeMap[j], parent.schema)
+                    } else {
+                        parent.parent.processAttribute(projectAttr, attributeMap[j], parent.parent.schema)
+                    }
+					switch(typeof projectAttr)
+					{
+					case "number":
+					case "boolean":
+						eval('option.'+attributeName+' = '+projectAttr);
+						break;
+					case "string":
+						eval('option.'+attributeName+' = "'+projectAttr+'"');
+						break;
+					}
+				}
+				this.options.push(option);
+			}
+			
+			// Now the scales nodes
+			var scaleParent = xml.getElementsByTagName('scales');
+			if (scaleParent.length == 1) {
+				scaleParent = scaleParent[0];
+				for (var i=0; i<scaleParent.children.length; i++) {
+					var child = scaleParent.children[i];
+					this.scales.push({
+						text: child.textContent,
+						position: Number(child.getAttribute('position'))
+					});
+				}
+			}
+		};
+		
+		this.encode = function(doc) {
+			var node = doc.createElement("interface");
+            if (typeof name == "string")
+                node.setAttribute("name",this.name);
+            for (var option of this.options)
+            {
+                var child = doc.createElement("interfaceoption");
+                child.setAttribute("type",option.type);
+                child.setAttribute("name",option.name);
+                node.appendChild(child);
+            }
+            if (this.scales.length != 0) {
+                var scales = doc.createElement("scales");
+                for (var scale of this.scales)
+                {
+                    var child = doc.createElement("scalelabel");
+                    child.setAttribute("position",scale.position);
+                    child.textContent = scale.text;
+                    scales.appendChild(child);
+                }
+                node.appendChild(scales);
+            }
+            return node;
+		};
+	};
+	
+    this.metricNode = function() {
+        this.enabled = [];
+        this.decode = function(parent, xml) {
+            var children = xml.getElementsByTagName('metricenable');
+            for (var i in children) { 
+                if (isNaN(Number(i)) == true){break;}
+                this.enabled.push(children[i].textContent);
+            }
+        }
+        this.encode = function(doc) {
+            var node = doc.createElement('metric');
+            for (var i in this.enabled)
+            {
+                if (isNaN(Number(i)) == true){break;}
+                var child = doc.createElement('metricenable');
+                child.textContent = this.enabled[i];
+                node.appendChild(child);
+            }
+            return node;
+        }
+    }
+    
+	this.page = function(specification) {
+		this.presentedId = undefined;
+		this.id = undefined;
+		this.hostURL = undefined;
+		this.randomiseOrder = undefined;
+		this.loop = undefined;
+		this.showElementComments = undefined;
+		this.outsideReference = null;
+		this.loudness = null;
+        this.label = null;
+		this.preTest = null;
+		this.postTest = null;
+		this.interfaces = [];
+		this.commentBoxPrefix = "Comment on track";
+		this.audioElements = [];
+		this.commentQuestions = [];
+		this.schema = specification.schema.getAllElementsByName("page")[0];
+        this.specification = specification;
+        this.parent = null;
+		
+		this.decode = function(parent,xml)
+		{
+            this.parent = parent;
+			var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
+			for (var i=0; i<attributeMap.length; i++)
+			{
+				var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
+				var projectAttr = xml.getAttribute(attributeName);
+				projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
+				switch(typeof projectAttr)
+				{
+				case "number":
+				case "boolean":
+					eval('this.'+attributeName+' = '+projectAttr);
+					break;
+				case "string":
+					eval('this.'+attributeName+' = "'+projectAttr+'"');
+					break;
+				}
+			}
+			
+			// Get the Comment Box Prefix
+			var CBP = xml.getElementsByTagName('commentboxprefix');
+			if (CBP.length != 0) {
+				this.commentBoxPrefix = CBP[0].textContent;
+			}
+			
+			// Now decode the interfaces
+			var interfaceNode = xml.getElementsByTagName('interface');
+			for (var i=0; i<interfaceNode.length; i++)
+			{
+				var node = new parent.interfaceNode(this.specification);
+				node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
+				this.interfaces.push(node);
+			}
+			
+			// Now process the survey node options
+			var survey = xml.getElementsByTagName('survey');
+			var surveySchema = parent.schema.getAllElementsByName('survey')[0];
+			for (var i in survey) {
+				if (isNaN(Number(i)) == true){break;}
+				var location = survey[i].getAttribute('location');
+				if (location == 'pre' || location == 'before')
+				{
+					if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
+					else {
+						this.preTest = new parent.surveyNode(this.specification);
+						this.preTest.decode(parent,survey[i],surveySchema);
+					}
+				} else if (location == 'post' || location == 'after') {
+					if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
+					else {
+						this.postTest = new parent.surveyNode(this.specification);
+						this.postTest.decode(parent,survey[i],surveySchema);
+					}
+				}
+			}
+			
+			// Now process the audioelement tags
+			var audioElements = xml.getElementsByTagName('audioelement');
+			for (var i=0; i<audioElements.length; i++)
+			{
+				var node = new this.audioElementNode(this.specification);
+				node.decode(this,audioElements[i]);
+				this.audioElements.push(node);
+			}
+			
+			// Now decode the commentquestions
+			var commentQuestions = xml.getElementsByTagName('commentquestion');
+			for (var i=0; i<commentQuestions.length; i++)
+			{
+				var node = new this.commentQuestionNode(this.specification);
+				node.decode(parent,commentQuestions[i]);
+				this.commentQuestions.push(node);
+			}
+		};
+		
+		this.encode = function(root)
+		{
+			var AHNode = root.createElement("page");
+            // First decode the attributes
+            var attributes = this.schema.getAllElementsByTagName('xs:attribute');
+            for (var i=0; i<attributes.length; i++)
+            {
+                var name = attributes[i].getAttribute("name");
+                if (name == undefined) {
+                    name = attributes[i].getAttribute("ref");
+                }
+                if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
+                {
+                    eval("AHNode.setAttribute('"+name+"',this."+name+")");
+                }
+            }
+			if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
+            // <commentboxprefix>
+            var commentboxprefix = root.createElement("commentboxprefix");
+            commentboxprefix.textContent = this.commentBoxPrefix;
+            AHNode.appendChild(commentboxprefix);
+            
+			for (var i=0; i<this.interfaces.length; i++)
+			{
+				AHNode.appendChild(this.interfaces[i].encode(root));
+			}
+			
+			for (var i=0; i<this.audioElements.length; i++) {
+				AHNode.appendChild(this.audioElements[i].encode(root));
+			}
+			// Create <CommentQuestion>
+			for (var i=0; i<this.commentQuestions.length; i++)
+			{
+				AHNode.appendChild(this.commentQuestions[i].encode(root));
+			}
+			
+			AHNode.appendChild(this.preTest.encode(root));
+            AHNode.appendChild(this.postTest.encode(root));
+			return AHNode;
+		};
+		
+		this.commentQuestionNode = function(specification) {
+			this.id = null;
+            this.name = undefined;
+			this.type = undefined;
+			this.options = [];
+			this.statement = undefined;
+			this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
+			this.decode = function(parent,xml)
+			{
+				this.id = xml.id;
+                this.name = xml.getAttribute('name');
+				this.type = xml.getAttribute('type');
+				this.statement = xml.getElementsByTagName('statement')[0].textContent;
+				var optNodes = xml.getElementsByTagName('option');
+				for (var i=0; i<optNodes.length; i++)
+				{
+					var optNode = optNodes[i];
+					this.options.push({
+						name: optNode.getAttribute('name'),
+						text: optNode.textContent
+					});
+				}
+			};
+			
+			this.encode = function(root)
+			{
+				var node = root.createElement("commentquestion");
+                node.id = this.id;
+                node.setAttribute("type",this.type);
+                if (this.name != undefined){node.setAttribute("name",this.name);}
+                var statement = root.createElement("statement");
+                statement.textContent = this.statement;
+                node.appendChild(statement);
+                for (var option of this.options)
+                {
+                    var child = root.createElement("option");
+                    child.setAttribute("name",option.name);
+                    child.textContent = option.text;
+                    node.appendChild(child);
+                }
+                return node;
+			};
+		};
+		
+		this.audioElementNode = function(specification) {
+			this.url = null;
+			this.id = null;
+            this.name = null;
+			this.parent = null;
+			this.type = null;
+			this.marker = null;
+			this.enforce = false;
+			this.gain = 0.0;
+			this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
+			this.parent = null;
+			this.decode = function(parent,xml)
+			{
+				this.parent = parent;
+				var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
+				for (var i=0; i<attributeMap.length; i++)
+				{
+					var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
+					var projectAttr = xml.getAttribute(attributeName);
+					projectAttr = parent.parent.processAttribute(projectAttr,attributeMap[i],parent.parent.schema);
+					switch(typeof projectAttr)
+					{
+					case "number":
+					case "boolean":
+						eval('this.'+attributeName+' = '+projectAttr);
+						break;
+					case "string":
+						eval('this.'+attributeName+' = "'+projectAttr+'"');
+						break;
+					}
+				}
+				
+			};
+			this.encode = function(root)
+			{
+				var AENode = root.createElement("audioelement");
+				var attributes = this.schema.getAllElementsByTagName('xs:attribute');
+                for (var i=0; i<attributes.length; i++)
+                {
+                    var name = attributes[i].getAttribute("name");
+                    if (name == undefined) {
+                        name = attributes[i].getAttribute("ref");
+                    }
+                    if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
+                    {
+                        eval("AENode.setAttribute('"+name+"',this."+name+")");
+                    }
+                }
+				return AENode;
+			};
+		};
+	};
+}
\ No newline at end of file
--- a/test_create/test_create.html	Tue Mar 29 14:37:07 2016 +0100
+++ b/test_create/test_create.html	Thu Mar 31 13:31:42 2016 +0100
@@ -6,650 +6,10 @@
     <script type="text/javascript">
         window.onbeforeunload = function (e) {var message = 'If you leave the page now, any unsaved changes will be lost', e = e || window.event; if (e) { e.returnValue = message;}return message;};
         // Copy of Specifiation node from Core.js
-        function Specification() {
-            // Handles the decoding of the project specification XML into a simple JavaScript Object.
-
-            this.interface = null;
-            this.projectReturn = "null";
-            this.randomiseOrder = null;
-            this.testPages = null;
-            this.pages = [];
-            this.metrics = null;
-            this.interfaces = null;
-            this.loudness = null;
-            this.errors = [];
-            this.schema = null;
-            this.exitText = "Thank you.";
-
-            this.processAttribute = function(attribute,schema,schemaRoot)
-            {
-                // attribute is the string returned from getAttribute on the XML
-                // schema is the <xs:attribute> node
-                if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
-                {
-                    schema = schemaRoot.getAllElementsByName(schema.getAttribute('ref'))[0];
-                }
-                var defaultOpt = schema.getAttribute('default');
-                if (attribute == null) {
-                    attribute = defaultOpt;
-                }
-                var dataType = schema.getAttribute('type');
-                if (typeof dataType == "string") { dataType = dataType.substr(3);}
-                else {dataType = "string";}
-                if (attribute == null)
-                {
-                    return attribute;
-                }
-                switch(dataType)
-                {
-                case "boolean":
-                    if (attribute == 'true'){attribute = true;}else{attribute=false;}
-                    break;
-                case "negativeInteger":
-                case "positiveInteger":
-                case "nonNegativeInteger":
-                case "nonPositiveInteger":
-                case "integer":
-                case "decimal":
-                case "short":
-                    attribute = Number(attribute);
-                    break;
-                case "string":
-                default:
-                    attribute = String(attribute);
-                    break;
-                }
-                return attribute;
-            };
-
-            this.decode = function(projectXML) {
-                this.errors = [];
-                // projectXML - DOM Parsed document
-                this.projectXML = projectXML.childNodes[0];
-                var setupNode = projectXML.getElementsByTagName('setup')[0];
-                var schemaSetup = this.schema.getAllElementsByName('setup')[0];
-                // First decode the attributes
-                var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
-                for (var i in attributes)
-                {
-                    if (isNaN(Number(i)) == true){break;}
-                    var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref');
-                    var projectAttr = setupNode.getAttribute(attributeName);
-                    projectAttr = this.processAttribute(projectAttr,attributes[i],this.schema);
-                    switch(typeof projectAttr)
-                    {
-                    case "number":
-                    case "boolean":
-                        eval('this.'+attributeName+' = '+projectAttr);
-                        break;
-                    case "string":
-                        eval('this.'+attributeName+' = "'+projectAttr+'"');
-                        break;
-                    }
-
-                }
-
-                var exitTextNode = setupNode.getElementsByTagName('exitText');
-                if (exitTextNode.length == 1) {
-                    this.exitText = exitTextNode[0].textContent;
-                }
-
-                this.metrics = new this.metricNode();
-
-                this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
-
-                // Now process the survey node options
-                var survey = setupNode.getElementsByTagName('survey');
-                for (var i in survey) {
-                    if (isNaN(Number(i)) == true){break;}
-                    var location = survey[i].getAttribute('location');
-                    if (location == 'pre' || location == 'before')
-                    {
-                        if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
-                        else {
-                            this.preTest = new this.surveyNode();
-                            this.preTest.decode(this,survey[i]);
-                        }
-                    } else if (location == 'post' || location == 'after') {
-                        if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
-                        else {
-                            this.postTest = new this.surveyNode();
-                            this.postTest.decode(this,survey[i]);
-                        }
-                    }
-                }
-
-                var interfaceNode = setupNode.getElementsByTagName('interface');
-                if (interfaceNode.length > 1)
-                {
-                    this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
-                }
-                this.interfaces = new this.interfaceNode();
-                if (interfaceNode.length != 0)
-                {
-                    interfaceNode = interfaceNode[0];
-                    this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
-                }
-
-                // Page tags
-                var pageTags = projectXML.getElementsByTagName('page');
-                var pageSchema = this.schema.getAllElementsByName('page')[0];
-                for (var i=0; i<pageTags.length; i++)
-                {
-                    var node = new this.page();
-                    node.decode(this,pageTags[i],pageSchema);
-                    this.pages.push(node);
-                }
-            };
-
-            this.encode = function()
-            {
-                var RootDocument = document.implementation.createDocument(null,"waet");
-                var root = RootDocument.children[0];
-                root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
-                root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
-                // Build setup node
-                var setup = RootDocument.createElement("setup");
-                var schemaSetup = this.schema.getAllElementsByName('setup')[0];
-                // First decode the attributes
-                var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
-                for (var i=0; i<attributes.length; i++)
-                {
-                    var name = attributes[i].getAttribute("name");
-                    if (name == undefined) {
-                        name = attributes[i].getAttribute("ref");
-                    }
-                    if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
-                    {
-                        eval("setup.setAttribute('"+name+"',this."+name+")");
-                    }
-                }
-                root.appendChild(setup);
-                // Survey node
-                if (this.exitText != null) {
-                    var exitTextNode = RootDocument.createElement('exitText');
-                    exitTextNode.textContent = this.exitText;
-                    setup.appendChild(exitTextNode);
-                }
-                setup.appendChild(this.preTest.encode(RootDocument));
-                setup.appendChild(this.postTest.encode(RootDocument));
-                setup.appendChild(this.metrics.encode(RootDocument));
-                setup.appendChild(this.interfaces.encode(RootDocument));
-                for (var page of this.pages)
-                {
-                    root.appendChild(page.encode(RootDocument));
-                }
-                return RootDocument;
-            };
-
-            this.surveyNode = function() {
-                this.location = null;
-                this.options = [];
-                this.parent = null;
-                this.schema = specification.schema.getAllElementsByName('survey')[0];
-
-                this.OptionNode = function() {
-                    this.type = undefined;
-                    this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
-                    this.id = undefined;
-                    this.name = undefined;
-                    this.mandatory = undefined;
-                    this.statement = undefined;
-                    this.boxsize = undefined;
-                    this.options = [];
-                    this.min = undefined;
-                    this.max = undefined;
-                    this.step = undefined;
-
-                    this.decode = function(parent,child)
-                    {
-                        var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
-                        for (var i in attributeMap){
-                            if(isNaN(Number(i)) == true){break;}
-                            var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
-                            var projectAttr = child.getAttribute(attributeName);
-                            projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
-                            switch(typeof projectAttr)
-                            {
-                            case "number":
-                            case "boolean":
-                                eval('this.'+attributeName+' = '+projectAttr);
-                                break;
-                            case "string":
-                                eval('this.'+attributeName+' = "'+projectAttr+'"');
-                                break;
-                            }
-                        }
-                        this.statement = child.getElementsByTagName('statement')[0].textContent;
-                        if (this.type == "checkbox" || this.type == "radio") {
-                            var children = child.getElementsByTagName('option');
-                            if (children.length == null) {
-                                console.log('Malformed' +child.nodeName+ 'entry');
-                                this.statement = 'Malformed' +child.nodeName+ 'entry';
-                                this.type = 'statement';
-                            } else {
-                                this.options = [];
-                                for (var i in children)
-                                {
-                                    if (isNaN(Number(i))==true){break;}
-                                    this.options.push({
-                                        name: children[i].getAttribute('name'),
-                                        text: children[i].textContent
-                                    });
-                                }
-                            }
-                        }
-                    };
-
-                    this.exportXML = function(doc)
-                    {
-                        var node = doc.createElement('surveyentry');
-                        node.setAttribute('type',this.type);
-                        var statement = doc.createElement('statement');
-                        statement.textContent = this.statement;
-                        node.appendChild(statement);
-                        node.id = this.id;
-                        if (this.name != undefined) { node.setAttribute("name",this.name);}
-                        if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
-                        node.id = this.id;
-                        if (this.name != undefined) {node.setAttribute("name",this.name);}
-                        switch(this.type)
-                        {
-                            case "checkbox":
-                            case "radio":
-                                for (var i=0; i<this.options.length; i++)
-                                {
-                                    var option = this.options[i];
-                                    var optionNode = doc.createElement("option");
-                                    optionNode.setAttribute("name",option.name);
-                                    optionNode.textContent = option.text;
-                                    node.appendChild(optionNode);
-                                }
-                            case "number":
-                                if (this.min != undefined) {node.setAttribute("min", this.min);}
-                                if (this.max != undefined) {node.setAttribute("max", this.max);}
-                            case "question":
-                                if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
-                                if (this.mandatory != undefined) {node.setAttribute("mandatory",this.mandatory);}
-                            default:
-                                break;
-                        }
-                        return node;
-                    };
-                };
-                this.decode = function(parent,xml) {
-                    this.parent = parent;
-                    this.location = xml.getAttribute('location');
-                    if (this.location == 'before'){this.location = 'pre';}
-                    else if (this.location == 'after'){this.location = 'post';}
-                    for (var i in xml.children)
-                    {
-                        if(isNaN(Number(i))==true){break;}
-                        var node = new this.OptionNode();
-                        node.decode(parent,xml.children[i]);
-                        this.options.push(node);
-                    }
-                };
-                this.encode = function(doc) {
-                    var node = doc.createElement('survey');
-                    node.setAttribute('location',this.location);
-                    for (var i=0; i<this.options.length; i++)
-                    {
-                        node.appendChild(this.options[i].exportXML(doc));
-                    }
-                    return node;
-                };
-            };
-
-            this.interfaceNode = function()
-            {
-                this.title = null;
-                this.name = null;
-                this.options = [];
-                this.scales = [];
-                this.schema = specification.schema.getAllElementsByName('interface')[1];
-
-                this.decode = function(parent,xml) {
-                    this.name = xml.getAttribute('name');
-                    var titleNode = xml.getElementsByTagName('title');
-                    if (titleNode.length == 1)
-                    {
-                        this.title = titleNode[0].textContent;
-                    }
-                    var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
-                    // Extract interfaceoption node schema
-                    var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
-                    var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
-                    for (var i=0; i<interfaceOptionNodes.length; i++)
-                    {
-                        var ioNode = interfaceOptionNodes[i];
-                        var option = {};
-                        for (var j=0; j<attributeMap.length; j++)
-                        {
-                            var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
-                            var projectAttr = ioNode.getAttribute(attributeName);
-                            if(parent.processAttribute) {
-                                parent.processAttribute(projectAttr, attributeMap[j], parent.schema)
-                            } else {
-                                parent.parent.processAttribute(projectAttr, attributeMap[j], parent.parent.schema)
-                            }
-                            switch(typeof projectAttr)
-                            {
-                            case "number":
-                            case "boolean":
-                                eval('option.'+attributeName+' = '+projectAttr);
-                                break;
-                            case "string":
-                                eval('option.'+attributeName+' = "'+projectAttr+'"');
-                                break;
-                            }
-                        }
-                        this.options.push(option);
-                    }
-
-                    // Now the scales nodes
-                    var scaleParent = xml.getElementsByTagName('scales');
-                    if (scaleParent.length == 1) {
-                        scaleParent = scaleParent[0];
-                        for (var i=0; i<scaleParent.children.length; i++) {
-                            var child = scaleParent.children[i];
-                            this.scales.push({
-                                text: child.textContent,
-                                position: Number(child.getAttribute('position'))
-                            });
-                        }
-                    }
-                };
-
-                this.encode = function(doc) {
-                    var node = doc.createElement("interface");
-                    if (typeof name == "string")
-                        node.setAttribute("name",this.name);
-                    for (var option of this.options)
-                    {
-                        var child = doc.createElement("interfaceoption");
-                        child.setAttribute("type",option.type);
-                        child.setAttribute("name",option.name);
-                        node.appendChild(child);
-                    }
-                    if (this.scales.length != 0) {
-                        var scales = doc.createElement("scales");
-                        for (var scale of this.scales)
-                        {
-                            var child = doc.createElement("scalelabel");
-                            child.setAttribute("position",scale.position);
-                            child.textContent = scale.text;
-                            scales.appendChild(child);
-                        }
-                        node.appendChild(scales);
-                    }
-                    return node;
-                };
-            };
-
-            this.metricNode = function() {
-                this.enabled = [];
-                this.decode = function(parent, xml) {
-                    var children = xml.getElementsByTagName('metricenable');
-                    for (var i in children) { 
-                        if (isNaN(Number(i)) == true){break;}
-                        this.enabled.push(children[i].textContent);
-                    }
-                }
-                this.encode = function(doc) {
-                    var node = doc.createElement('metric');
-                    for (var i in this.enabled)
-                    {
-                        if (isNaN(Number(i)) == true){break;}
-                        var child = doc.createElement('metricenable');
-                        child.textContent = this.enabled[i];
-                        node.appendChild(child);
-                    }
-                    return node;
-                }
-            }
-
-            this.page = function() {
-                this.presentedId = undefined;
-                this.id = undefined;
-                this.hostURL = undefined;
-                this.randomiseOrder = undefined;
-                this.loop = undefined;
-                this.showElementComments = undefined;
-                this.outsideReference = null;
-                this.loudness = null;
-                this.label = null;
-                this.preTest = null;
-                this.postTest = null;
-                this.interfaces = [];
-                this.commentBoxPrefix = "Comment on track";
-                this.audioElements = [];
-                this.commentQuestions = [];
-                this.schema = specification.schema.getAllElementsByName("page")[0];
-                this.parent = null;
-
-                this.decode = function(parent,xml)
-                {
-                    this.parent = parent;
-                    var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
-                    for (var i=0; i<attributeMap.length; i++)
-                    {
-                        var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
-                        var projectAttr = xml.getAttribute(attributeName);
-                        projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
-                        switch(typeof projectAttr)
-                        {
-                        case "number":
-                        case "boolean":
-                            eval('this.'+attributeName+' = '+projectAttr);
-                            break;
-                        case "string":
-                            eval('this.'+attributeName+' = "'+projectAttr+'"');
-                            break;
-                        }
-                    }
-
-                    // Get the Comment Box Prefix
-                    var CBP = xml.getElementsByTagName('commentboxprefix');
-                    if (CBP.length != 0) {
-                        this.commentBoxPrefix = CBP[0].textContent;
-                    }
-
-                    // Now decode the interfaces
-                    var interfaceNode = xml.getElementsByTagName('interface');
-                    for (var i=0; i<interfaceNode.length; i++)
-                    {
-                        var node = new parent.interfaceNode();
-                        node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
-                        this.interfaces.push(node);
-                    }
-
-                    // Now process the survey node options
-                    var survey = xml.getElementsByTagName('survey');
-                    var surveySchema = parent.schema.getAllElementsByName('survey')[0];
-                    for (var i in survey) {
-                        if (isNaN(Number(i)) == true){break;}
-                        var location = survey[i].getAttribute('location');
-                        if (location == 'pre' || location == 'before')
-                        {
-                            if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
-                            else {
-                                this.preTest = new parent.surveyNode();
-                                this.preTest.decode(parent,survey[i],surveySchema);
-                            }
-                        } else if (location == 'post' || location == 'after') {
-                            if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
-                            else {
-                                this.postTest = new parent.surveyNode();
-                                this.postTest.decode(parent,survey[i],surveySchema);
-                            }
-                        }
-                    }
-
-                    // Now process the audioelement tags
-                    var audioElements = xml.getElementsByTagName('audioelement');
-                    for (var i=0; i<audioElements.length; i++)
-                    {
-                        var node = new this.audioElementNode();
-                        node.decode(this,audioElements[i]);
-                        this.audioElements.push(node);
-                    }
-
-                    // Now decode the commentquestions
-                    var commentQuestions = xml.getElementsByTagName('commentquestion');
-                    for (var i=0; i<commentQuestions.length; i++)
-                    {
-                        var node = new this.commentQuestionNode();
-                        node.decode(parent,commentQuestions[i]);
-                        this.commentQuestions.push(node);
-                    }
-                };
-
-                this.encode = function(root)
-                {
-                    var AHNode = root.createElement("page");
-                    // First decode the attributes
-                    var attributes = this.schema.getAllElementsByTagName('xs:attribute');
-                    for (var i=0; i<attributes.length; i++)
-                    {
-                        var name = attributes[i].getAttribute("name");
-                        if (name == undefined) {
-                            name = attributes[i].getAttribute("ref");
-                        }
-                        if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
-                        {
-                            eval("AHNode.setAttribute('"+name+"',this."+name+")");
-                        }
-                    }
-                    if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
-                    // <commentboxprefix>
-                    var commentboxprefix = root.createElement("commentboxprefix");
-                    commentboxprefix.textContent = this.commentBoxPrefix;
-                    AHNode.appendChild(commentboxprefix);
-
-                    for (var i=0; i<this.interfaces.length; i++)
-                    {
-                        AHNode.appendChild(this.interfaces[i].encode(root));
-                    }
-
-                    for (var i=0; i<this.audioElements.length; i++) {
-                        AHNode.appendChild(this.audioElements[i].encode(root));
-                    }
-                    // Create <CommentQuestion>
-                    for (var i=0; i<this.commentQuestions.length; i++)
-                    {
-                        AHNode.appendChild(this.commentQuestions[i].encode(root));
-                    }
-
-                    AHNode.appendChild(this.preTest.encode(root));
-                    AHNode.appendChild(this.postTest.encode(root));
-                    return AHNode;
-                };
-
-                this.commentQuestionNode = function() {
-                    this.id = null;
-                    this.name = undefined;
-                    this.type = undefined;
-                    this.options = [];
-                    this.statement = undefined;
-                    this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
-                    this.decode = function(parent,xml)
-                    {
-                        this.id = xml.id;
-                        this.name = xml.getAttribute('name');
-                        this.type = xml.getAttribute('type');
-                        this.statement = xml.getElementsByTagName('statement')[0].textContent;
-                        var optNodes = xml.getElementsByTagName('option');
-                        for (var i=0; i<optNodes.length; i++)
-                        {
-                            var optNode = optNodes[i];
-                            this.options.push({
-                                name: optNode.getAttribute('name'),
-                                text: optNode.textContent
-                            });
-                        }
-                    };
-
-                    this.encode = function(root)
-                    {
-                        var node = root.createElement("commentquestion");
-                        node.id = this.id;
-                        node.setAttribute("type",this.type);
-                        if (this.name != undefined){node.setAttribute("name",this.name);}
-                        var statement = root.createElement("statement");
-                        statement.textContent = this.statement;
-                        node.appendChild(statement);
-                        for (var option of this.options)
-                        {
-                            var child = root.createElement("option");
-                            child.setAttribute("name",option.name);
-                            child.textContent = option.text;
-                            node.appendChild(child);
-                        }
-                        return node;
-                    };
-                };
-
-                this.audioElementNode = function() {
-                    this.url = null;
-                    this.id = null;
-                    this.name = null;
-                    this.parent = null;
-                    this.type = null;
-                    this.marker = null;
-                    this.enforce = false;
-                    this.gain = 0.0;
-                    this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
-                    this.parent = null;
-                    this.decode = function(parent,xml)
-                    {
-                        this.parent = parent;
-                        var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
-                        for (var i=0; i<attributeMap.length; i++)
-                        {
-                            var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
-                            var projectAttr = xml.getAttribute(attributeName);
-                            projectAttr = parent.parent.processAttribute(projectAttr,attributeMap[i],parent.parent.schema);
-                            switch(typeof projectAttr)
-                            {
-                            case "number":
-                            case "boolean":
-                                eval('this.'+attributeName+' = '+projectAttr);
-                                break;
-                            case "string":
-                                eval('this.'+attributeName+' = "'+projectAttr+'"');
-                                break;
-                            }
-                        }
-
-                    };
-                    this.encode = function(root)
-                    {
-                        var AENode = root.createElement("audioelement");
-                        var attributes = this.schema.getAllElementsByTagName('xs:attribute');
-                        for (var i=0; i<attributes.length; i++)
-                        {
-                            var name = attributes[i].getAttribute("name");
-                            if (name == undefined) {
-                                name = attributes[i].getAttribute("ref");
-                            }
-                            if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
-                            {
-                                eval("AENode.setAttribute('"+name+"',this."+name+")");
-                            }
-                        }
-                        return AENode;
-                    };
-                };
-            };
-        }
-
     </script>
     <script src="../jquery-2.1.4.js"></script>
-    <script type="text/javascript" src="test_core.js"/>
-    <script type="text/javascript">
-        
-    </script>
+    <script type="text/javascript" src='../specification.js'></script>
+    <script type="text/javascript" src="test_core.js"></script>
 </head>
 <body>
     <div id="popupHolder"></div>