diff core.js @ 1124:0f161e776cb4

--UNSTABLE-- Major revision. Updated Specification including verification. Added storage collector for XML results. Popup more stable.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Wed, 06 Jan 2016 10:36:37 +0000
parents 2ecf6ae18625
children 3f65e594154d
line wrap: on
line diff
--- a/core.js	Tue Dec 29 13:54:56 2015 +0000
+++ b/core.js	Wed Jan 06 10:36:37 2016 +0000
@@ -8,8 +8,10 @@
 /* create the web audio API context and store in audioContext*/
 var audioContext; // Hold the browser web audio API
 var projectXML; // Hold the parsed setup XML
+var schemaXSD; // Hold the parsed schema XSD
 var specification;
 var interfaceContext;
+var storage;
 var popup; // Hold the interfacePopup object
 var testState;
 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order
@@ -44,6 +46,9 @@
 	
 	// Create the interface object
 	interfaceContext = new Interface(specification);
+	
+	// Create the storage object
+	storage = new Storage();
 	// Define window callbacks for interface
 	window.onresize = function(event){interfaceContext.resizeWindow(event);};
 };
@@ -51,12 +56,21 @@
 function loadProjectSpec(url) {
 	// Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
 	// If url is null, request client to upload project XML document
-	var r = new XMLHttpRequest();
-	r.open('GET',url,true);
-	r.onload = function() {
-		loadProjectSpecCallback(r.response);
+	var xmlhttp = new XMLHttpRequest();
+	xmlhttp.open("GET",'test-schema.xsd',true);
+	xmlhttp.onload = function()
+	{
+		schemaXSD = xmlhttp.response;
+		var parse = new DOMParser();
+		specification.schema = parse.parseFromString(xmlhttp.response,'text/xml');
+		var r = new XMLHttpRequest();
+		r.open('GET',url,true);
+		r.onload = function() {
+			loadProjectSpecCallback(r.response);
+		};
+		r.send();
 	};
-	r.send();
+	xmlhttp.send();
 };
 
 function loadProjectSpecCallback(response) {
@@ -64,6 +78,35 @@
 	//var decode = $.parseXML(response);
 	//projectXML = $(decode);
 	
+	// First perform XML schema validation
+	var Module = {
+		xml: response,
+		schema: schemaXSD,
+		arguments:["--noout", "--schema", 'test-schema.xsd','document.xml']
+	};
+	
+	var xmllint = validateXML(Module);
+	console.log(xmllint);
+	if(xmllint != 'document.xml validates\n')
+	{
+		document.getElementsByTagName('body')[0].innerHTML = null;
+		var msg = document.createElement("h3");
+		msg.textContent = "FATAL ERROR";
+		var span = document.createElement("h4");
+		span.textContent = "The XML validator returned the following errors when decoding your XML file";
+		document.getElementsByTagName('body')[0].appendChild(msg);
+		document.getElementsByTagName('body')[0].appendChild(span);
+		xmllint = xmllint.split('\n');
+		for (var i in xmllint)
+		{
+			document.getElementsByTagName('body')[0].appendChild(document.createElement('br'));
+			var span = document.createElement("span");
+			span.textContent = xmllint[i];
+			document.getElementsByTagName('body')[0].appendChild(span);
+		}
+		return;
+	}
+	
 	var parse = new DOMParser();
 	projectXML = parse.parseFromString(response,'text/xml');
 	var errorNode = projectXML.getElementsByTagName('parsererror');
@@ -82,11 +125,12 @@
 	
 	// Build the specification
 	specification.decode(projectXML);
+	storage.initialise();
 	
 	// Detect the interface to use and load the relevant javascripts.
 	var interfaceJS = document.createElement('script');
 	interfaceJS.setAttribute("type","text/javascript");
-	if (specification.interfaceType == 'APE') {
+	if (specification.interface == 'APE') {
 		interfaceJS.setAttribute("src","ape.js");
 		
 		// APE comes with a css file
@@ -96,7 +140,7 @@
 		css.href = 'ape.css';
 		
 		document.getElementsByTagName("head")[0].appendChild(css);
-	} else if (specification.interfaceType == "MUSHRA")
+	} else if (specification.interface == "MUSHRA")
 	{
 		interfaceJS.setAttribute("src","mushra.js");
 		
@@ -113,12 +157,9 @@
 	// Create the audio engine object
 	audioEngineContext = new AudioEngine(specification);
 	
-	testState.stateMap.push(specification.preTest);
-	
-	$(specification.audioHolders).each(function(index,elem){
-		testState.stateMap.push(elem);
+	$(specification.pages).each(function(index,elem){
 		$(elem.audioElements).each(function(i,audioElem){
-			var URL = audioElem.parent.hostURL + audioElem.url;
+			var URL = elem.hostURL + audioElem.url;
 			var buffer = null;
 			for (var i=0; i<audioEngineContext.buffers.length; i++)
 			{
@@ -136,8 +177,6 @@
 			}
 		});
 	});
-	
-	testState.stateMap.push(specification.postTest);
 }
 
 function createProjectSave(destURL) {
@@ -175,7 +214,7 @@
 			} else {
 				if (xmlhttp.responseXML == null)
 				{
-					return createProjectSave(null);
+					createProjectSave('null');
 				}
 				var response = xmlhttp.responseXML.childNodes[0];
 				if (response.getAttribute('state') == "OK")
@@ -231,18 +270,7 @@
 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
 function interfaceXMLSave(){
 	// Create the XML string to be exported with results
-	var xmlDoc = document.createElement("BrowserEvaluationResult");
-	var projectDocument = specification.projectXML;
-	projectDocument.setAttribute('file-name',url);
-	xmlDoc.appendChild(projectDocument);
-	xmlDoc.appendChild(returnDateNode());
-	xmlDoc.appendChild(interfaceContext.returnNavigator());
-	for (var i=0; i<testState.stateResults.length; i++)
-	{
-		xmlDoc.appendChild(testState.stateResults[i]);
-	}
-	
-	return xmlDoc;
+	return storage.finish();
 }
 
 function linearToDecibel(gain)
@@ -265,7 +293,8 @@
 	this.buttonPrevious = null;
 	this.popupOptions = null;
 	this.currentIndex = null;
-	this.responses = null;
+	this.node = null;
+	this.store = null;
 	$(window).keypress(function(e){
 			if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
 			{
@@ -366,15 +395,10 @@
 		// This will take the node from the popupOptions and display it
 		var node = this.popupOptions[this.currentIndex];
 		this.popupResponse.innerHTML = null;
-		if (node.type == 'statement') {
-			this.popupTitle.textContent = null;
-			var statement = document.createElement('span');
-			statement.textContent = node.statement;
-			this.popupResponse.appendChild(statement);
-		} else if (node.type == 'question') {
-			this.popupTitle.textContent = node.question;
+		this.popupTitle.textContent = node.specification.statement;
+		if (node.specification.type == 'question') {
 			var textArea = document.createElement('textarea');
-			switch (node.boxsize) {
+			switch (node.specification.boxsize) {
 			case 'small':
 				textArea.cols = "20";
 				textArea.rows = "1";
@@ -394,11 +418,8 @@
 			}
 			this.popupResponse.appendChild(textArea);
 			textArea.focus();
-		} else if (node.type == 'checkbox') {
-			this.popupTitle.textContent = node.statement;
-			var optHold = this.popupResponse;
-			for (var i=0; i<node.options.length; i++) {
-				var option = node.options[i];
+		} else if (node.specification.type == 'checkbox') {
+			for (var option of node.specification.options) {
 				var input = document.createElement('input');
 				input.id = option.name;
 				input.type = 'checkbox';
@@ -409,17 +430,14 @@
 				hold.style.padding = '4px';
 				hold.appendChild(input);
 				hold.appendChild(span);
-				optHold.appendChild(hold);
+				this.popupResponse.appendChild(hold);
 			}
-		} else if (node.type == 'radio') {
-			this.popupTitle.textContent = node.statement;
-			var optHold = this.popupResponse;
-			for (var i=0; i<node.options.length; i++) {
-				var option = node.options[i];
+		} else if (node.specification.type == 'radio') {
+			for (var option of node.specification.options) {
 				var input = document.createElement('input');
 				input.id = option.name;
 				input.type = 'radio';
-				input.name = node.id;
+				input.name = node.specification.id;
 				var span = document.createElement('span');
 				span.textContent = option.text;
 				var hold = document.createElement('div');
@@ -427,15 +445,14 @@
 				hold.style.padding = '4px';
 				hold.appendChild(input);
 				hold.appendChild(span);
-				optHold.appendChild(hold);
+				this.popupResponse.appendChild(hold);
 			}
-		} else if (node.type == 'number') {
-			this.popupTitle.textContent = node.statement;
+		} else if (node.specification.type == 'number') {
 			var input = document.createElement('input');
 			input.type = 'textarea';
-			if (node.min != null) {input.min = node.min;}
-			if (node.max != null) {input.max = node.max;}
-			if (node.step != null) {input.step = node.step;}
+			if (node.min != null) {input.min = node.specification.min;}
+			if (node.max != null) {input.max = node.specification.max;}
+			if (node.step != null) {input.step = node.specification.step;}
 			this.popupResponse.appendChild(input);
 		}
 		var content_height = Number(this.popup.offsetHeight.toFixed());
@@ -445,7 +462,7 @@
 		this.buttonProceed.style.top = content_height;
 		this.buttonPrevious.style.top = content_height;
 		if(this.currentIndex+1 == this.popupOptions.length) {
-			if (this.responses.nodeName == "PRETEST") {
+			if (this.node.location == "pre") {
 				this.buttonProceed.textContent = 'Start';
 			} else {
 				this.buttonProceed.textContent = 'Submit';
@@ -459,19 +476,20 @@
 			this.buttonPrevious.style.visibility = 'hidden';
 	};
 	
-	this.initState = function(node) {
+	this.initState = function(node,store) {
 		//Call this with your preTest and postTest nodes when needed to
 		// initialise the popup procedure.
-		this.popupOptions = node.options;
-		if (this.popupOptions.length > 0) {
-			if (node.type == 'pretest') {
-				this.responses = document.createElement('PreTest');
-			} else if (node.type == 'posttest') {
-				this.responses = document.createElement('PostTest');
-			} else {
-				console.log ('WARNING - popup node neither pre or post!');
-				this.responses = document.createElement('responses');
-			}
+		if (node.options.length > 0) {
+			this.popupOptions = [];
+			this.node = node;
+			this.store = store;
+			for (var opt of node.options)
+			{
+				this.popupOptions.push({
+					specification: opt,
+					response: null
+				});
+			}			
 			this.currentIndex = 0;
 			this.showPopup();
 			this.postNode();
@@ -483,56 +501,54 @@
 	this.proceedClicked = function() {
 		// Each time the popup button is clicked!
 		var node = this.popupOptions[this.currentIndex];
-		if (node.type == 'question') {
+		if (node.specification.type == 'question') {
 			// Must extract the question data
 			var textArea = $(popup.popupContent).find('textarea')[0];
-			if (node.mandatory == true && textArea.value.length == 0) {
+			if (node.specification.mandatory == true && textArea.value.length == 0) {
 				alert('This question is mandatory');
 				return;
 			} else {
 				// Save the text content
-				var hold = document.createElement('comment');
-				hold.id = node.id;
-				hold.innerHTML = textArea.value;
-				console.log("Question: "+ node.question);
+				console.log("Question: "+ node.specification.statement);
 				console.log("Question Response: "+ textArea.value);
-				this.responses.appendChild(hold);
+				node.response = textArea.value;
 			}
-		} else if (node.type == 'checkbox') {
+		} else if (node.specification.type == 'checkbox') {
 			// Must extract checkbox data
+			console.log("Checkbox: "+ node.statement);
+			var inputs = this.popupResponse.getElementsByTagName('input');
+			node.response = [];
+			for (var i=0; i<node.specification.options.length; i++) {
+				node.response.push({
+					name: node.specification.options[i].name,
+					text: node.specification.options[i].text,
+					checked: inputs[i].checked
+				});
+			}
+		} else if (node.specification.type == "radio") {
 			var optHold = this.popupResponse;
-			var hold = document.createElement('checkbox');
-			console.log("Checkbox: "+ node.statement);
-			hold.id = node.id;
-			for (var i=0; i<optHold.childElementCount; i++) {
-				var input = optHold.childNodes[i].getElementsByTagName('input')[0];
-				var statement = optHold.childNodes[i].getElementsByTagName('span')[0];
-				var response = document.createElement('option');
-				response.setAttribute('name',input.id);
-				response.textContent = input.checked;
-				hold.appendChild(response);
-				console.log(input.id +': '+ input.checked);
-			}
-			this.responses.appendChild(hold);
-		} else if (node.type == "radio") {
-			var optHold = this.popupResponse;
-			var hold = document.createElement('radio');
-			console.log("Checkbox: "+ node.statement);
-			var responseID = null;
+			console.log("Radio: "+ node.specification.statement);
+			node.response = null;
 			var i=0;
-			while(responseID == null) {
-				var input = optHold.childNodes[i].getElementsByTagName('input')[0];
-				if (input.checked == true) {
-					responseID = i;
-					console.log("Selected: "+ node.options[i].name);
+			var inputs = optHold.getElementsByTagName('input');
+			while(node.response == null) {
+				if (i == inputs.length)
+				{
+					if (node.specification.mandatory == true)
+					{
+						alert("This radio is mandatory");
+					} else {
+						node.response = -1;
+					}
+					return;
+				}
+				if (inputs[i].checked == true) {
+					node.response = node.specification.options[i];
+					console.log("Selected: "+ node.specification.options[i].name);
 				}
 				i++;
 			}
-			hold.id = node.id;
-			hold.setAttribute('name',node.options[responseID].name);
-			hold.textContent = node.options[responseID].text;
-			this.responses.appendChild(hold);
-		} else if (node.type == "number") {
+		} else if (node.specification.type == "number") {
 			var input = this.popupContent.getElementsByTagName('input')[0];
 			if (node.mandatory == true && input.value.length == 0) {
 				alert('This question is mandatory. Please enter a number');
@@ -551,10 +567,7 @@
 				alert('Number is above the maximum value of '+node.max);
 				return;
 			}
-			var hold = document.createElement('number');
-			hold.id = node.id;
-			hold.textContent = input.value;
-			this.responses.appendChild(hold);
+			node.response = input.value;
 		}
 		this.currentIndex++;
 		if (this.currentIndex < this.popupOptions.length) {
@@ -562,10 +575,9 @@
 		} else {
 			// Reached the end of the popupOptions
 			this.hidePopup();
-			if (this.responses.nodeName == testState.stateResults[testState.stateIndex].nodeName) {
-				testState.stateResults[testState.stateIndex] = this.responses;
-			} else {
-				testState.stateResults[testState.stateIndex].appendChild(this.responses);
+			for (var node of this.popupOptions)
+			{
+				this.store.postResult(node);
 			}
 			advanceState();
 		}
@@ -632,13 +644,39 @@
 {
 	// Object prototype for tracking and managing the test state
 	this.stateMap = [];
+	this.preTestSurvey = null;
+	this.postTestSurvey = null;
 	this.stateIndex = null;
-	this.currentStateMap = [];
-	this.currentIndex = null;
+	this.currentStateMap = null;
+	this.currentStatePosition = null;
 	this.currentTestId = 0;
 	this.stateResults = [];
 	this.timerCallBackHolders = null;
 	this.initialise = function(){
+		
+		// Get the data from Specification
+		var pageHolder = [];
+		for (var page of specification.pages)
+		{
+			pageHolder.push(page);
+		}
+		if (specification.randomiseOrder)
+		{
+			pageHolder = randomiseOrder(pageHolder);
+		}
+		for (var i=0; i<pageHolder.length; i++)
+		{
+			pageHolder[i].presentedId = i;
+		}
+		for (var i=0; i<specification.pages.length; i++)
+		{
+			if (specification.testPages < i && specification.testPages != 0) {break;}
+			this.stateMap.push(pageHolder[i]);
+			
+		}
+		if (specification.preTest != null) {this.preTestSurvey = specification.preTest;}
+		if (specification.postTest != null) {this.postTestSurvey = specification.postTest;}
+		
 		if (this.stateMap.length > 0) {
 			if(this.stateIndex != null) {
 				console.log('NOTE - State already initialise');
@@ -666,97 +704,92 @@
 		}
 		if (this.stateIndex == -1) {
 			console.log('Starting test...');
-		}
-		if (this.currentIndex == null){
-			if (this.currentStateMap.type == "audioHolder") {
-				// Save current page
-				this.testPageCompleted(this.stateResults[this.stateIndex],this.currentStateMap,this.currentTestId);
-				this.currentTestId++;
+			if (this.preTestSurvey != null)
+			{
+				popup.initState(this.preTestSurvey,storage.globalPreTest);
 			}
 			this.stateIndex++;
-			if (this.stateIndex >= this.stateMap.length) {
-				console.log('Test Completed');
-				createProjectSave(specification.projectReturn);
+		} else if (this.stateIndex == this.stateMap.length)
+		{
+			// All test pages complete, post test
+			console.log('Ending test ...');
+			this.stateIndex++;
+			if (this.postTestSurvey == null) {
+				this.advanceState();
 			} else {
+				popup.initState(this.postTestSurvey,storage.globalPostTest);
+			}
+		} else if (this.stateIndex > this.stateMap.length)
+		{
+			createProjectSave(specification.projectReturn);
+		}
+		else
+		{
+			if (this.currentStateMap == null)
+			{
 				this.currentStateMap = this.stateMap[this.stateIndex];
-				if (this.currentStateMap.type == "audioHolder") {
-					console.log('Loading test page');
-					interfaceContext.newPage(this.currentStateMap);
-					this.initialiseInnerState(this.currentStateMap);
-				} else if (this.currentStateMap.type == "pretest" || this.currentStateMap.type == "posttest") {
-					if (this.currentStateMap.options.length >= 1) {
-						popup.initState(this.currentStateMap);
-					} else {
-						this.advanceState();
-					}
+				storage.createTestPageStore(this.currentStateMap);
+				if (this.currentStateMap.preTest != null)
+				{
+					this.currentStatePosition = 'pre';
+					popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest);
 				} else {
+					this.currentStatePosition = 'test';
+				}
+				interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]);
+				return;
+			}
+			switch(this.currentStatePosition)
+			{
+			case 'pre':
+				this.currentStatePosition = 'test';
+				break;
+			case 'test':
+				this.currentStatePosition = 'post';
+				// Save the data
+				this.testPageCompleted();
+				if (this.currentStateMap.postTest == null)
+				{
 					this.advanceState();
+					return;
+				} else {
+					popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest);
 				}
-			}
-		} else {
-			this.advanceInnerState();
+				break;
+			case 'post':
+				this.stateIndex++;
+				this.currentStateMap = null;
+				this.advanceState();
+				break;
+			};
 		}
 	};
 	
-	this.testPageCompleted = function(store, testXML, testId) {
+	this.testPageCompleted = function() {
 		// Function called each time a test page has been completed
-		var metric = document.createElement('metric');
+		var storePoint = storage.testPages[this.stateIndex];
+		// First get the test metric
+		
+		var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
 		if (audioEngineContext.metric.enableTestTimer)
 		{
-			var testTime = document.createElement('metricResult');
+			var testTime = storePoint.parent.document.createElement('metricresult');
 			testTime.id = 'testTime';
 			testTime.textContent = audioEngineContext.timer.testDuration;
 			metric.appendChild(testTime);
 		}
-		store.appendChild(metric);
+		
 		var audioObjects = audioEngineContext.audioObjects;
-		for (var i=0; i<audioObjects.length; i++) 
+		for (var ao of audioEngineContext.audioObjects) 
 		{
-			var audioElement = audioEngineContext.audioObjects[i].exportXMLDOM();
-			audioElement.setAttribute('presentedId',i);
-			store.appendChild(audioElement);
+			ao.exportXMLDOM();
 		}
-		$(interfaceContext.commentQuestions).each(function(index,element){
-			var node = element.exportXMLDOM();
-			store.appendChild(node);
-		});
-		pageXMLSave(store, testXML);
+		for (var element of interfaceContext.commentQuestions)
+		{
+			element.exportXMLDOM(storePoint);
+		}
+		pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
 	};
-	
-	this.initialiseInnerState = function(node) {
-		// Parses the received testXML for pre and post test options
-		this.currentStateMap = [];
-		var preTest = node.preTest;
-		var postTest = node.postTest;
-		if (preTest == undefined) {preTest = document.createElement("preTest");}
-		if (postTest == undefined){postTest= document.createElement("postTest");}
-		this.currentStateMap.push(preTest);
-		this.currentStateMap.push(node);
-		this.currentStateMap.push(postTest);
-		this.currentIndex = -1;
-		this.advanceInnerState();
-	};
-	
-	this.advanceInnerState = function() {
-		this.currentIndex++;
-		if (this.currentIndex >= this.currentStateMap.length) {
-			this.currentIndex = null;
-			this.currentStateMap = this.stateMap[this.stateIndex];
-			this.advanceState();
-		} else {
-			if (this.currentStateMap[this.currentIndex].type == "audioHolder") {
-				console.log("Loading test page"+this.currentTestId);
-			} else if (this.currentStateMap[this.currentIndex].type == "pretest") {
-				popup.initState(this.currentStateMap[this.currentIndex]);
-			} else if (this.currentStateMap[this.currentIndex].type == "posttest") {
-				popup.initState(this.currentStateMap[this.currentIndex]);
-			} else {
-				this.advanceInnerState();
-			}
-		}
-	};
-	
-	this.previousState = function(){};
 }
 
 function AudioEngine(specification) {
@@ -783,6 +816,8 @@
 	
 	this.loopPlayback = false;
 	
+	this.pageStore = null;
+	
 	// Create store for new audioObjects
 	this.audioObjects = [];
 	
@@ -891,7 +926,7 @@
 						this.audioObjects[i].outputGain.gain.value = 0.0;
 						this.audioObjects[i].stop();
 					} else if (i == id) {
-						this.audioObjects[id].outputGain.gain.value = this.audioObjects[id].specification.gain*this.audioObjects[id].buffer.buffer.playbackGain;
+						this.audioObjects[id].outputGain.gain.value = this.audioObjects[id].onplayGain;
 						this.audioObjects[id].play(audioContext.currentTime+0.01);
 					}
 				}
@@ -921,7 +956,7 @@
 		this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
 
 		// Check if audioObject buffer is currently stored by full URL
-		var URL = element.parent.hostURL + element.url;
+		var URL = testState.currentStateMap.hostURL + element.url;
 		var buffer = null;
 		for (var i=0; i<this.buffers.length; i++)
 		{
@@ -941,6 +976,16 @@
 		this.audioObjects[audioObjectId].specification = element;
 		this.audioObjects[audioObjectId].url = URL;
 		buffer.users.push(this.audioObjects[audioObjectId]);
+		// Obtain store node
+		var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
+		for (var i=0; i<aeNodes.length; i++)
+		{
+			if(aeNodes[i].id == element.id)
+			{
+				this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
+				break;
+			}
+		}
 		if (buffer.buffer != null)
 		{
 			this.audioObjects[audioObjectId].bufferLoaded(buffer);
@@ -948,7 +993,8 @@
 		return this.audioObjects[audioObjectId];
 	};
 	
-	this.newTestPage = function() {
+	this.newTestPage = function(store) {
+		this.pageStore = store;
 		this.state = 0;
 		this.audioObjectsReady = false;
 		this.metric.reset();
@@ -1021,6 +1067,7 @@
 	this.state = 0; // 0 - no data, 1 - ready
 	this.url = null; // Hold the URL given for the output back to the results.
 	this.metric = new metricTracker(this);
+	this.storeDOM = null;
 	
 	// Bindings for GUI
 	this.interfaceDOM = null;
@@ -1032,6 +1079,7 @@
 	
 	// Default output gain to be zero
 	this.outputGain.gain.value = 0.0;
+	this.onplayGain = 1.0;
 	
 	// Connect buffer to the audio graph
 	this.outputGain.connect(audioEngineContext.outputGain);
@@ -1074,10 +1122,14 @@
 		if (this.interfaceDOM != null) {
 			this.interfaceDOM.enable();
 		}
+		this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain;
+		
+		this.storeDOM.setAttribute('presentedId',this.id);
+		this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
 	};
     
 	this.loopStart = function() {
-		this.outputGain.gain.value =  this.specification.gain*this.buffer.buffer.playbackGain;
+		this.outputGain.gain.value = this.onplayGain;
 		this.metric.startListening(audioEngineContext.timer.getTestTime());
 	};
 	
@@ -1138,34 +1190,30 @@
 	};
 	
 	this.exportXMLDOM = function() {
-		var root = document.createElement('audioElement');
-		root.id = this.specification.id;
-		root.setAttribute('url',this.specification.url);
-		var file = document.createElement('file');
+		var file = storage.document.createElement('file');
 		file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
 		file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
 		file.setAttribute('sampleCount',this.buffer.buffer.length);
 		file.setAttribute('duration',this.buffer.buffer.duration);
-		root.appendChild(file);
-		if (this.specification.type != 'outsidereference') {
+		this.storeDOM.appendChild(file);
+		if (this.specification.type != 'outside-reference') {
 			var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
 			if (interfaceXML.length == undefined) {
-				root.appendChild(interfaceXML);
+				this.storeDOM.appendChild(interfaceXML);
 			} else {
 				for (var i=0; i<interfaceXML.length; i++)
 				{
-					root.appendChild(interfaceXML[i]);
+					this.storeDOM.appendChild(interfaceXML[i]);
 				}
 			}
-			root.appendChild(this.commentDOM.exportXMLDOM(this));
-			if(this.specification.type == 'anchor') {
-				root.setAttribute('anchor',true);
-			} else if(this.specification.type == 'reference') {
-				root.setAttribute('reference',true);
-			}
+			this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
 		}
-		root.appendChild(this.metric.exportXMLDOM());
-		return root;
+		var nodes = this.metric.exportXMLDOM();
+		var mroot = this.storeDOM.getElementsByTagName('metric')[0];
+		for (var i=0; i<nodes.length; i++)
+		{
+			mroot.appendChild(nodes[i]);
+		}
 	};
 }
 
@@ -1232,10 +1280,10 @@
 	this.enableFlagMoved = false;
 	this.enableTestTimer = false;
 	// Obtain the metrics enabled
-	for (var i=0; i<specification.metrics.length; i++)
+	for (var i=0; i<specification.metrics.enabled.length; i++)
 	{
-		var node = specification.metrics[i];
-		switch(node.enabled)
+		var node = specification.metrics.enabled[i];
+		switch(node)
 		{
 		case 'testTimer':
 			this.enableTestTimer = true;
@@ -1283,7 +1331,7 @@
 	this.hasComments = false;
 	this.parent = caller;
 	
-	this.initialised = function(position)
+	this.initialise = function(position)
 	{
 		if (this.initialPosition == -1) {
 			this.initialPosition = position;
@@ -1340,21 +1388,21 @@
 	};
 	
 	this.exportXMLDOM = function() {
-		var root = document.createElement('metric');
+		var storeDOM = [];
 		if (audioEngineContext.metric.enableElementTimer) {
-			var mElementTimer = document.createElement('metricresult');
+			var mElementTimer = storage.document.createElement('metricresult');
 			mElementTimer.setAttribute('name','enableElementTimer');
 			mElementTimer.textContent = this.listenedTimer;
-			root.appendChild(mElementTimer);
+			storeDOM.push(mElementTimer);
 		}
 		if (audioEngineContext.metric.enableElementTracker) {
-			var elementTrackerFull = document.createElement('metricResult');
+			var elementTrackerFull = storage.document.createElement('metricResult');
 			elementTrackerFull.setAttribute('name','elementTrackerFull');
 			for (var k=0; k<this.movementTracker.length; k++)
 			{
-				var timePos = document.createElement('timePos');
+				var timePos = storage.document.createElement('timePos');
 				timePos.id = k;
-				var time = document.createElement('time');
+				var time = storage.document.createElement('time');
 				time.textContent = this.movementTracker[k][0];
 				var position = document.createElement('position');
 				position.textContent = this.movementTracker[k][1];
@@ -1362,36 +1410,36 @@
 				timePos.appendChild(position);
 				elementTrackerFull.appendChild(timePos);
 			}
-			root.appendChild(elementTrackerFull);
+			storeDOM.push(elementTrackerFull);
 		}
 		if (audioEngineContext.metric.enableElementListenTracker) {
-			var elementListenTracker = document.createElement('metricResult');
+			var elementListenTracker = storage.document.createElement('metricResult');
 			elementListenTracker.setAttribute('name','elementListenTracker');
 			for (var k=0; k<this.listenTracker.length; k++) {
 				elementListenTracker.appendChild(this.listenTracker[k]);
 			}
-			root.appendChild(elementListenTracker);
+			storeDOM.push(elementListenTracker);
 		}
 		if (audioEngineContext.metric.enableElementInitialPosition) {
-			var elementInitial = document.createElement('metricResult');
+			var elementInitial = storage.document.createElement('metricResult');
 			elementInitial.setAttribute('name','elementInitialPosition');
 			elementInitial.textContent = this.initialPosition;
-			root.appendChild(elementInitial);
+			storeDOM.push(elementInitial);
 		}
 		if (audioEngineContext.metric.enableFlagListenedTo) {
-			var flagListenedTo = document.createElement('metricResult');
+			var flagListenedTo = storage.document.createElement('metricResult');
 			flagListenedTo.setAttribute('name','elementFlagListenedTo');
 			flagListenedTo.textContent = this.wasListenedTo;
-			root.appendChild(flagListenedTo);
+			storeDOM.push(flagListenedTo);
 		}
 		if (audioEngineContext.metric.enableFlagMoved) {
-			var flagMoved = document.createElement('metricResult');
+			var flagMoved = storage.document.createElement('metricResult');
 			flagMoved.setAttribute('name','elementFlagMoved');
 			flagMoved.textContent = this.wasMoved;
-			root.appendChild(flagMoved);
+			storeDOM.push(flagMoved);
 		}
 		if (audioEngineContext.metric.enableFlagComments) {
-			var flagComments = document.createElement('metricResult');
+			var flagComments = storage.document.createElement('metricResult');
 			flagComments.setAttribute('name','elementFlagComments');
 			if (this.parent.commentDOM == null)
 				{flag.textContent = 'false';}
@@ -1399,10 +1447,9 @@
 				{flag.textContent = 'false';}
 			else 
 				{flag.textContet = 'true';}
-			root.appendChild(flagComments);
+			storeDOM.push(flagComments);
 		}
-		
-		return root;
+		return storeDOM;
 	};
 }
 
@@ -1479,23 +1526,16 @@
 function Specification() {
 	// Handles the decoding of the project specification XML into a simple JavaScript Object.
 	
-	this.interfaceType = null;
-	this.commonInterface = new function()
-	{
-		this.options = [];
-		this.optionNode = function(input)
-		{
-			var name = input.getAttribute('name');
-			this.type = name;
-			if(this.type == "option")
-			{
-				this.name = input.id;
-			} else if (this.type == "check")
-			{
-				this.check = input.id;
-			}
-		};
-	};
+	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.randomiseOrder = function(input)
 	{
@@ -1524,223 +1564,163 @@
 		console.log(outputSequence.toString()); 	// print randomised array to console
 		return holdArr;
 	};
-	this.projectReturn = null;
-	this.randomiseOrder = null;
-	this.collectMetrics = null;
-	this.testPages = null;
-	this.audioHolders = [];
-	this.metrics = [];
-	this.loudness = null;
+	
+	this.processAttribute = function(attribute,schema)
+	{
+		// 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 = this.schema.getElementsByName(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];
-		this.interfaceType = setupNode.getAttribute('interface');
-		this.projectReturn = setupNode.getAttribute('projectReturn');
-		this.testPages = setupNode.getAttribute('testPages');
-		if (setupNode.getAttribute('randomiseOrder') == "true") {
-			this.randomiseOrder = true;
-		} else {this.randomiseOrder = false;}
-		if (setupNode.getAttribute('collectMetrics') == "true") {
-			this.collectMetrics = true;
-		} else {this.collectMetrics = false;}
-		if (isNaN(Number(this.testPages)) || this.testPages == undefined)
+		var schemaSetup = this.schema.getElementsByName('setup')[0];
+		// First decode the attributes
+		var attributes = schemaSetup.getElementsByTagName('attribute');
+		for (var i in attributes)
 		{
-			this.testPages = null;
-		} else {
-			this.testPages = Number(this.testPages);
-			if (this.testPages == 0) {this.testPages = null;}
-		}
-		if (setupNode.getAttribute('loudness') != null)
-		{
-			var XMLloudness = setupNode.getAttribute('loudness');
-			if (isNaN(Number(XMLloudness)) == false)
+			if (isNaN(Number(i)) == true){break;}
+			var attributeName = attributes[i].getAttribute('name');
+			var projectAttr = setupNode.getAttribute(attributeName);
+			projectAttr = this.processAttribute(projectAttr,attributes[i]);
+			switch(typeof projectAttr)
 			{
-				this.loudness = Number(XMLloudness);
+			case "number":
+			case "boolean":
+				eval('this.'+attributeName+' = '+projectAttr);
+				break;
+			case "string":
+				eval('this.'+attributeName+' = "'+projectAttr+'"');
+				break;
 			}
-		}
-		var metricCollection = setupNode.getElementsByTagName('Metric');
-		
-		var setupPreTestNode = setupNode.getElementsByTagName('PreTest');
-		if (setupPreTestNode.length != 0)
-		{
-			setupPreTestNode = setupPreTestNode[0];
-			this.preTest.construct(setupPreTestNode);
+			
 		}
 		
-		var setupPostTestNode = setupNode.getElementsByTagName('PostTest');
-		if (setupPostTestNode.length != 0)
-		{
-			setupPostTestNode = setupPostTestNode[0];
-			this.postTest.construct(setupPostTestNode);
-		}
+		this.metrics = {
+			enabled: [],
+			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);
+				}
+			},
+			encode: function(root) {
+				var node = root.createElement('metric');
+				for (var i in this.enabled)
+				{
+					if (isNaN(Number(i)) == true){break;}
+					var child = root.createElement('metricenable');
+					child.textContent = this.enabled[i];
+					node.appendChild(child);
+				}
+				return node;
+			}
+		};
 		
-		if (metricCollection.length > 0) {
-			metricCollection = metricCollection[0].getElementsByTagName('metricEnable');
-			for (var i=0; i<metricCollection.length; i++) {
-				this.metrics.push(new this.metricNode(metricCollection[i].textContent));
+		this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
+		
+		// Now process the survey node options
+		var survey = setupNode.getElementsByTagName('survey');
+		var surveySchema = specification.schema.getElementsByName('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 this.surveyNode();
+					this.preTest.decode(this,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 this.surveyNode();
+					this.postTest.decode(this,survey[i],surveySchema);
+				}
 			}
 		}
 		
-		var commonInterfaceNode = setupNode.getElementsByTagName('interface');
-		if (commonInterfaceNode.length > 0) {
-			commonInterfaceNode = commonInterfaceNode[0];
-		} else {
-			commonInterfaceNode = undefined;
+		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.getElementsByName('interface')[1]);
 		}
 		
-		this.commonInterface = new function() {
-			this.OptionNode = function(child) {
-				this.type = child.nodeName;
-				if (this.type == 'option')
-				{
-					this.name = child.getAttribute('name');
-				}
-				else if (this.type == 'check') {
-					this.check = child.getAttribute('name');
-					if (this.check == 'scalerange') {
-						this.min = child.getAttribute('min');
-						this.max = child.getAttribute('max');
-						if (this.min == null) {this.min = 1;}
-						else if (Number(this.min) > 1 && this.min != null) {
-							this.min = Number(this.min)/100;
-						} else {
-							this.min = Number(this.min);
-						}
-						if (this.max == null) {this.max = 0;}
-						else if (Number(this.max) > 1 && this.max != null) {
-							this.max = Number(this.max)/100;
-						} else {
-							this.max = Number(this.max);
-						}
-					}
-				} else if (this.type == 'anchor' || this.type == 'reference') {
-					this.value = Number(child.textContent);
-					this.enforce = child.getAttribute('enforce');
-					if (this.enforce == 'true') {this.enforce = true;}
-					else {this.enforce = false;}
-				}
-			};
-			this.options = [];
-			if (commonInterfaceNode != undefined) {
-				var child = commonInterfaceNode.firstElementChild;
-				while (child != undefined) {
-					this.options.push(new this.OptionNode(child));
-					child = child.nextElementSibling;
-				}
-			}
-		};
-		
-		var audioHolders = projectXML.getElementsByTagName('audioHolder');
-		for (var i=0; i<audioHolders.length; i++) {
-			var node = new this.audioHolderNode(this);
-			node.decode(this,audioHolders[i]);
-			this.audioHolders.push(node);
-		}
-		
-		// New check if we need to randomise the test order
-		if (this.randomiseOrder && typeof randomiseOrder === "function")
+		// Page tags
+		var pageTags = projectXML.getElementsByTagName('page');
+		var pageSchema = this.schema.getElementsByName('page')[0];
+		for (var i=0; i<pageTags.length; i++)
 		{
-	 		this.audioHolders = randomiseOrder(this.audioHolders);
-	 		for (var i=0; i<this.audioHolders.length; i++)
-	 		{
-	 			this.audioHolders[i].presentedId = i;
-	 		}
-		}
-		
-		if (this.testPages != null || this.testPages != undefined)
-		{
-			if (this.testPages > audioHolders.length)
-			{
-				console.log('Warning: You have specified '+audioHolders.length+' tests but requested '+this.testPages+' be completed!');
-				this.testPages = audioHolders.length;
-			}
-			var aH = this.audioHolders;
-			this.audioHolders = [];
-			for (var i=0; i<this.testPages; i++)
-			{
-				this.audioHolders.push(aH[i]);
-			}
+			var node = new this.page();
+			node.decode(this,pageTags[i],pageSchema);
+			this.pages.push(node);
 		}
 	};
 	
 	this.encode = function()
 	{
-		var root = document.implementation.createDocument(null,"BrowserEvalProjectDocument");
-		// First get all the <setup> tag compiled
-		var setupNode = root.createElement("setup");
-		setupNode.setAttribute('interface',this.interfaceType);
-		setupNode.setAttribute('projectReturn',this.projectReturn);
-		setupNode.setAttribute('randomiseOrder',this.randomiseOrder);
-		setupNode.setAttribute('collectMetrics',this.collectMetrics);
-		setupNode.setAttribute('testPages',this.testPages);
-		if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
+		var root = document.implementation.createDocument(null,"waet");
 		
-		var setupPreTest = root.createElement("PreTest");
-		for (var i=0; i<this.preTest.options.length; i++)
-		{
-			setupPreTest.appendChild(this.preTest.options[i].exportXML(root));
-		}
+		// Build setup node
 		
-		var setupPostTest = root.createElement("PostTest");
-		for (var i=0; i<this.postTest.options.length; i++)
-		{
-			setupPostTest.appendChild(this.postTest.options[i].exportXML(root));
-		}
-		
-		setupNode.appendChild(setupPreTest);
-		setupNode.appendChild(setupPostTest);
-		
-		// <Metric> tag
-		var Metric = root.createElement("Metric");
-		for (var i=0; i<this.metrics.length; i++)
-		{
-			var metricEnable = root.createElement("metricEnable");
-			metricEnable.textContent = this.metrics[i].enabled;
-			Metric.appendChild(metricEnable);
-		}
-		setupNode.appendChild(Metric);
-		
-		// <interface> tag
-		var CommonInterface = root.createElement("interface");
-		for (var i=0; i<this.commonInterface.options.length; i++)
-		{
-			var CIObj = this.commonInterface.options[i];
-			var CINode = root.createElement(CIObj.type);
-			if (CIObj.type == "check") {CINode.setAttribute("name",CIObj.check);}
-			else {CINode.setAttribute("name",CIObj.name);}
-			CommonInterface.appendChild(CINode);
-		}
-		setupNode.appendChild(CommonInterface);
-		
-		root.getElementsByTagName("BrowserEvalProjectDocument")[0].appendChild(setupNode);
-		// Time for the <audioHolder> tags
-		for (var ahIndex = 0; ahIndex < this.audioHolders.length; ahIndex++)
-		{
-			var node = this.audioHolders[ahIndex].encode(root);
-			root.getElementsByTagName("BrowserEvalProjectDocument")[0].appendChild(node);
-		}
 		return root;
 	};
 	
-	this.prepostNode = function(type) {
-		this.type = type;
+	this.surveyNode = function() {
+		this.location = null;
 		this.options = [];
+		this.schema = null;
 		
 		this.OptionNode = function() {
-			
-			this.childOption = function() {
-				this.type = 'option';
-				this.id = null;
-				this.name = undefined;
-				this.text = null;
-			};
-			
 			this.type = undefined;
+			this.schema = undefined;
 			this.id = undefined;
 			this.mandatory = undefined;
-			this.question = undefined;
 			this.statement = undefined;
 			this.boxsize = undefined;
 			this.options = [];
@@ -1748,69 +1728,62 @@
 			this.max = undefined;
 			this.step = undefined;
 			
-			this.decode = function(child)
+			this.decode = function(parent,child,schema)
 			{
-				this.type = child.nodeName;
-				if (child.nodeName == "question") {
-					this.id = child.id;
-					this.mandatory;
-					if (child.getAttribute('mandatory') == "true") {this.mandatory = true;}
-					else {this.mandatory = false;}
-					this.question = child.textContent;
-					if (child.getAttribute('boxsize') == null) {
-						this.boxsize = 'normal';
-					} else {
-						this.boxsize = child.getAttribute('boxsize');
+				this.schema = schema;
+				var attributeMap = schema.getElementsByTagName('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]);
+					switch(typeof projectAttr)
+					{
+					case "number":
+					case "boolean":
+						eval('this.'+attributeName+' = '+projectAttr);
+						break;
+					case "string":
+						eval('this.'+attributeName+' = "'+projectAttr+'"');
+						break;
 					}
-				} else if (child.nodeName == "statement") {
-					this.statement = child.textContent;
-				} else if (child.nodeName == "checkbox" || child.nodeName == "radio") {
-					var element = child.firstElementChild;
-					this.id = child.id;
-					if (element == null) {
+				}
+				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 = [];
-						while (element != null) {
-							if (element.nodeName == 'statement' && this.statement == undefined){
-								this.statement = element.textContent;
-							} else if (element.nodeName == 'option') {
-								var node = new this.childOption();
-                                if(element.getAttribute('id') != null) {
-                                    console.log(child.nodeName + ' node attribute id is deprecated, use name instead');
-                                    node.name = element.id;
-                                }
-								node.name = element.getAttribute('name');
-								node.text = element.textContent;
-								this.options.push(node);
-							}
-							element = element.nextElementSibling;
+						for (var i in children)
+						{
+							if (isNaN(Number(i))==true){break;}
+							this.options.push({
+								name: children[i].getAttribute('name'),
+								text: children[i].textContent
+							});
 						}
 					}
-				} else if (child.nodeName == "number") {
-					this.statement = child.textContent;
-					this.id = child.id;
-					this.min = child.getAttribute('min');
-					this.max = child.getAttribute('max');
-					this.step = child.getAttribute('step');
 				}
 			};
 			
 			this.exportXML = function(root)
 			{
-				var node = root.createElement(this.type);
+				var node = root.createElement('surveyelement');
+				node.setAttribute('type',this.type);
+				var statement = root.createElement('statement');
+				statement.textContent = this.statement;
+				node.appendChild(statement);
 				switch(this.type)
 				{
 				case "statement":
-					node.textContent = this.statement;
 					break;
 				case "question":
 					node.id = this.id;
 					node.setAttribute("mandatory",this.mandatory);
 					node.setAttribute("boxsize",this.boxsize);
-					node.textContent = this.question;
 					break;
 				case "number":
 					node.id = this.id;
@@ -1818,27 +1791,10 @@
 					node.setAttribute("min", this.min);
 					node.setAttribute("max", this.max);
 					node.setAttribute("step", this.step);
-					node.textContent = this.statement;
 					break;
 				case "checkbox":
-					node.id = this.id;
-					var statement = root.createElement("statement");
-					statement.textContent = this.statement;
-					node.appendChild(statement);
-					for (var i=0; i<this.options.length; i++)
-					{
-						var option = this.options[i];
-						var optionNode = root.createElement("option");
-						optionNode.id = option.id;
-						optionNode.textContent = option.text;
-						node.appendChild(optionNode);
-					}
-					break;
 				case "radio":
 					node.id = this.id;
-					var statement = root.createElement("statement");
-					statement.textContent = this.statement;
-					node.appendChild(statement);
 					for (var i=0; i<this.options.length; i++)
 					{
 						var option = this.options[i];
@@ -1852,145 +1808,191 @@
 				return node;
 			};
 		};
-		this.construct = function(Collection)
-		{
-			if (Collection.childElementCount != 0) {
-				var child = Collection.firstElementChild;
+		this.decode = function(parent,xml,schema) {
+			this.schema = schema;
+			this.location = xml.getAttribute('location');
+			if (this.location == 'before'){this.location = 'pre';}
+			else if (this.location == 'after'){this.location = 'post';}
+			var surveyentrySchema = schema.getElementsByTagName('element')[0];
+			for (var i in xml.children)
+			{
+				if(isNaN(Number(i))==true){break;}
 				var node = new this.OptionNode();
-				node.decode(child);
+				node.decode(parent,xml.children[i],surveyentrySchema);
 				this.options.push(node);
-				while (child.nextElementSibling != null) {
-					child = child.nextElementSibling;
-					node = new this.OptionNode();
-					node.decode(child);
-					this.options.push(node);
+			}
+		};
+		this.encode = function(root) {
+			var node = root.createElement('survey');
+			node.setAttribute('location',this.location);
+			for (var i=0; i<this.options.length; i++)
+			{
+				node.appendChild(this.options[i].exportXML());
+			}
+			return node;
+		};
+	};
+	
+	this.interfaceNode = function()
+	{
+		this.title = null;
+		this.name = null;
+		this.options = [];
+		this.scales = [];
+		this.schema = null;
+		
+		this.decode = function(parent,xml,schema) {
+			this.schema = schema;
+			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 = schema.getElementsByTagName('element');
+			for (var i=0; i<interfaceOptionNodeSchema.length; i++) {
+				if (interfaceOptionNodeSchema[i].getAttribute('name') == 'interfaceoption') {
+					interfaceOptionNodeSchema = interfaceOptionNodeSchema[i];
+					break;
+				}
+			}
+			var attributeMap = interfaceOptionNodeSchema.getElementsByTagName('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);
+					projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
+					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.preTest = new this.prepostNode("pretest");
-	this.postTest = new this.prepostNode("posttest");
-	
-	this.metricNode = function(name) {
-		this.enabled = name;
+		
+		this.encode = function(root) {
+			
+		};
 	};
 	
-	this.audioHolderNode = function(parent) {
-		this.type = 'audioHolder';
+	this.page = function() {
 		this.presentedId = undefined;
 		this.id = undefined;
 		this.hostURL = undefined;
-		this.sampleRate = undefined;
 		this.randomiseOrder = undefined;
 		this.loop = undefined;
-		this.elementComments = undefined;
+		this.showElementComments = undefined;
 		this.outsideReference = null;
 		this.loudness = null;
-		this.initialPosition = null;
-		this.preTest = new parent.prepostNode("pretest");
-		this.postTest = new parent.prepostNode("pretest");
+		this.preTest = null;
+		this.postTest = null;
 		this.interfaces = [];
 		this.commentBoxPrefix = "Comment on track";
 		this.audioElements = [];
 		this.commentQuestions = [];
+		this.schema = null;
 		
-		this.decode = function(parent,xml)
+		this.decode = function(parent,xml,schema)
 		{
-			this.presentedId = parent.audioHolders.length;
-			this.id = xml.id;
-			this.hostURL = xml.getAttribute('hostURL');
-			this.sampleRate = xml.getAttribute('sampleRate');
-			if (xml.getAttribute('randomiseOrder') == "true") {this.randomiseOrder = true;}
-			else {this.randomiseOrder = false;}
-			this.repeatCount = xml.getAttribute('repeatCount');
-			if (xml.getAttribute('loop') == 'true') {this.loop = true;}
-			else {this.loop == false;}
-			if (xml.getAttribute('elementComments') == "true") {this.elementComments = true;}
-			else {this.elementComments = false;}
-			if (typeof parent.loudness === "number")
+			this.schema = schema;
+			var attributeMap = this.schema.getElementsByTagName('attribute');
+			for (var i=0; i<attributeMap.length; i++)
 			{
-				this.loudness = parent.loudness;
-			}
-			if (typeof xml.getAttribute('initial-position') === "string")
-			{
-				var xmlInitialPosition = Number(xml.getAttribute('initial-position'));
-				if (isNaN(xmlInitialPosition) == false)
+				var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
+				var projectAttr = xml.getAttribute(attributeName);
+				projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
+				switch(typeof projectAttr)
 				{
-					if (xmlInitialPosition > 1)
-					{
-						xmlInitialPosition /= 100;
-					}
-					this.initialPosition = xmlInitialPosition;
-				}
-			}
-			if (xml.getAttribute('loudness') != null)
-			{
-				var XMLloudness = xml.getAttribute('loudness');
-				if (isNaN(Number(XMLloudness)) == false)
-				{
-					this.loudness = Number(XMLloudness);
-				}
-			}
-			var setupPreTestNode = xml.getElementsByTagName('PreTest');
-			if (setupPreTestNode.length != 0)
-			{
-				setupPreTestNode = setupPreTestNode[0];
-				this.preTest.construct(setupPreTestNode);
-			}
-			
-			var setupPostTestNode = xml.getElementsByTagName('PostTest');
-			if (setupPostTestNode.length != 0)
-			{
-				setupPostTestNode = setupPostTestNode[0];
-				this.postTest.construct(setupPostTestNode);
-			}
-			
-			var interfaceDOM = xml.getElementsByTagName('interface');
-			for (var i=0; i<interfaceDOM.length; i++) {
-				var node = new this.interfaceNode();
-				node.decode(interfaceDOM[i]);
-				this.interfaces.push(node);
-			}
-			this.commentBoxPrefix = xml.getElementsByTagName('commentBoxPrefix');
-			if (this.commentBoxPrefix.length != 0) {
-				this.commentBoxPrefix = this.commentBoxPrefix[0].textContent;
-			} else {
-				this.commentBoxPrefix = "Comment on track";
-			}
-			var audioElementsDOM = xml.getElementsByTagName('audioElements');
-			var outsideReferenceHolder = null;
-			for (var i=0; i<audioElementsDOM.length; i++) {
-				var node = new this.audioElementNode();
-				node.decode(this,audioElementsDOM[i]);
-				if (audioElementsDOM[i].getAttribute('type') == 'outsidereference') {
-					if (this.outsideReference == null) {
-						outsideReferenceHolder = node;
-						this.outsideReference = i;
-					} else {
-						console.log('Error only one audioelement can be of type outsidereference per audioholder');
-						this.audioElements.push(node);
-						console.log('Element id '+audioElementsDOM[i].id+' made into normal node');
-					}
-				} else {
-					this.audioElements.push(node);
+				case "number":
+				case "boolean":
+					eval('this.'+attributeName+' = '+projectAttr);
+					break;
+				case "string":
+					eval('this.'+attributeName+' = "'+projectAttr+'"');
+					break;
 				}
 			}
 			
-			if (this.randomiseOrder == true && typeof randomiseOrder === "function")
-			{
-				this.audioElements = randomiseOrder(this.audioElements);
-			}
-			if (outsideReferenceHolder != null)
-			{
-				this.audioElements.push(outsideReferenceHolder);
-				this.outsideReference = this.audioElements.length-1;
+			// 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.getElementsByName('interface')[1]);
+				this.interfaces.push(node);
+			}
 			
-			var commentQuestionsDOM = xml.getElementsByTagName('CommentQuestion');
-			for (var i=0; i<commentQuestionsDOM.length; i++) {
+			// Now process the survey node options
+			var survey = xml.getElementsByTagName('survey');
+			var surveySchema = parent.schema.getElementsByName('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');
+			var audioElementSchema = parent.schema.getElementsByName('audioelement')[0];
+			for (var i=0; i<audioElements.length; i++)
+			{
+				var node = new this.audioElementNode();
+				node.decode(this,audioElements[i],audioElementSchema);
+				this.audioElements.push(node);
+			}
+			
+			// Now decode the commentquestions
+			var commentQuestions = xml.getElementsByTagName('commentquestion');
+			var commentQuestionSchema = parent.schema.getElementsByName('commentquestion')[0];
+			for (var i=0; i<commentQuestions.length; i++)
+			{
 				var node = new this.commentQuestionNode();
-				node.decode(commentQuestionsDOM[i]);
+				node.decode(parent,commentQuestions[i],commentQuestionSchema);
 				this.commentQuestions.push(node);
 			}
 		};
@@ -2040,58 +2042,31 @@
 			return AHNode;
 		};
 		
-		this.interfaceNode = function() {
-			this.title = undefined;
+		this.commentQuestionNode = function() {
+			this.id = null;
+			this.type = undefined;
 			this.options = [];
-			this.scale = [];
-			this.name = undefined;
-			this.decode = function(DOM)
+			this.statement = undefined;
+			this.schema = null;
+			this.decode = function(parent,xml,schema)
 			{
-				var title = DOM.getElementsByTagName('title');
-				if (title.length == 0) {this.title = null;}
-				else {this.title = title[0].textContent;}
-				var name = DOM.getAttribute("name");
-				if (name != undefined) {this.name = name;}
-				this.options = parent.commonInterface.options;
-				var scale = DOM.getElementsByTagName('scale');
-				this.scale = [];
-				for (var i=0; i<scale.length; i++) {
-					var arr = [null, null];
-					arr[0] = scale[i].getAttribute('position');
-					arr[1] = scale[i].textContent;
-					this.scale.push(arr);
+				this.id = xml.id;
+				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("interface");
-				if (this.title != undefined)
-				{
-					var title = root.createElement("title");
-					title.textContent = this.title;
-					node.appendChild(title);
-				}
-				for (var i=0; i<this.options.length; i++)
-				{
-					var optionNode = root.createElement(this.options[i].type);
-					if (this.options[i].type == "option")
-					{
-						optionNode.setAttribute("name",this.options[i].name);
-					} else if (this.options[i].type == "check") {
-						optionNode.setAttribute("check",this.options[i].check);
-					} else if (this.options[i].type == "scalerange") {
-						optionNode.setAttribute("min",this.options[i].min*100);
-						optionNode.setAttribute("max",this.options[i].max*100);
-					}
-					node.appendChild(optionNode);
-				}
-				for (var i=0; i<this.scale.length; i++) {
-					var scale = root.createElement("scale");
-					scale.setAttribute("position",this.scale[i][0]);
-					scale.textContent = this.scale[i][1];
-					node.appendChild(scale);
-				}
-				return node;
+				
 			};
 		};
 		
@@ -2099,50 +2074,34 @@
 			this.url = null;
 			this.id = null;
 			this.parent = null;
-			this.type = "normal";
+			this.type = null;
 			this.marker = false;
 			this.enforce = false;
 			this.gain = 1.0;
-			this.decode = function(parent,xml)
+			this.schema = null;
+			this.parent = null;
+			this.decode = function(parent,xml,schema)
 			{
-				this.url = xml.getAttribute('url');
-				this.id = xml.id;
+				this.schema = schema;
 				this.parent = parent;
-				this.type = xml.getAttribute('type');
-				var gain = xml.getAttribute('gain');
-				if (isNaN(gain) == false && gain != null)
+				var attributeMap = this.schema.getElementsByTagName('attribute');
+				for (var i=0; i<attributeMap.length; i++)
 				{
-					this.gain = decibelToLinear(Number(gain));
-				}
-				if (this.type == null) {this.type = "normal";}
-				if (this.type == 'anchor') {this.anchor = true;}
-				else {this.anchor = false;}
-				if (this.type == 'reference') {this.reference = true;}
-				else {this.reference = false;}
-				if (this.anchor == true || this.reference == true)
-				{
-					this.marker = xml.getAttribute('marker');
-					if (this.marker != undefined)
+					var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
+					var projectAttr = xml.getAttribute(attributeName);
+					projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
+					switch(typeof projectAttr)
 					{
-						this.marker = Number(this.marker);
-						if (isNaN(this.marker) == false)
-						{
-							if (this.marker > 1)
-							{	this.marker /= 100.0;}
-							if (this.marker >= 0 && this.marker <= 1)
-							{
-								this.enforce = true;
-								return;
-							} else {
-								console.log("ERROR - Marker of audioElement "+this.id+" is not between 0 and 1 (float) or 0 and 100 (integer)!");
-								console.log("ERROR - Marker not enforced!");
-							}
-						} else {
-							console.log("ERROR - Marker of audioElement "+this.id+" is not a number!");
-							console.log("ERROR - Marker not enforced!");
-						}
+					case "number":
+					case "boolean":
+						eval('this.'+attributeName+' = '+projectAttr);
+						break;
+					case "string":
+						eval('this.'+attributeName+' = "'+projectAttr+'"');
+						break;
 					}
 				}
+				
 			};
 			this.encode = function(root)
 			{
@@ -2158,99 +2117,6 @@
 				return AENode;
 			};
 		};
-		
-		this.commentQuestionNode = function(xml) {
-			this.id = null;
-			this.type = undefined;
-			this.question = undefined;
-			this.options = [];
-			this.statement = undefined;
-			
-			this.childOption = function() {
-				this.type = 'option';
-				this.name = null;
-				this.text = null;
-			};
-			this.exportXML = function(root)
-			{
-				var CQNode = root.createElement("CommentQuestion");
-				CQNode.id = this.id;
-				CQNode.setAttribute("type",this.type);
-				switch(this.type)
-				{
-				case "text":
-					CQNode.textContent = this.question;
-					break;
-				case "radio":
-					var statement = root.createElement("statement");
-					statement.textContent = this.statement;
-					CQNode.appendChild(statement);
-					for (var i=0; i<this.options.length; i++)
-					{
-						var optionNode = root.createElement("option");
-						optionNode.setAttribute("name",this.options[i].name);
-						optionNode.textContent = this.options[i].text;
-						CQNode.appendChild(optionNode);
-					}
-					break;
-				case "checkbox":
-					var statement = root.createElement("statement");
-					statement.textContent = this.statement;
-					CQNode.appendChild(statement);
-					for (var i=0; i<this.options.length; i++)
-					{
-						var optionNode = root.createElement("option");
-						optionNode.setAttribute("name",this.options[i].name);
-						optionNode.textContent = this.options[i].text;
-						CQNode.appendChild(optionNode);
-					}
-					break;
-				}
-				return CQNode;
-			};
-			this.decode = function(xml) {
-				this.id = xml.id;
-				if (xml.getAttribute('mandatory') == 'true') {this.mandatory = true;}
-				else {this.mandatory = false;}
-				this.type = xml.getAttribute('type');
-				if (this.type == undefined) {this.type = 'text';}
-				switch (this.type) {
-				case 'text':
-					this.question = xml.textContent;
-					break;
-				case 'radio':
-					var child = xml.firstElementChild;
-					this.options = [];
-					while (child != undefined) {
-						if (child.nodeName == 'statement' && this.statement == undefined) {
-							this.statement = child.textContent;
-						} else if (child.nodeName == 'option') {
-							var node = new this.childOption();
-							node.name = child.getAttribute('name');
-							node.text = child.textContent;
-							this.options.push(node);
-						}
-						child = child.nextElementSibling;
-					}
-					break;
-				case 'checkbox':
-					var child = xml.firstElementChild;
-					this.options = [];
-					while (child != undefined) {
-						if (child.nodeName == 'statement' && this.statement == undefined) {
-							this.statement = child.textContent;
-						} else if (child.nodeName == 'option') {
-							var node = new this.childOption();
-							node.name = child.getAttribute('name');
-							node.text = child.textContent;
-							this.options.push(node);
-						}
-						child = child.nextElementSibling;
-					}
-					break;
-				}
-			};
-		};
 	};
 }
 			
@@ -2259,9 +2125,9 @@
 	this.specification = specificationObject;
 	this.insertPoint = document.getElementById("topLevelBody");
 	
-	this.newPage = function(audioHolderObject)
+	this.newPage = function(audioHolderObject,store)
 	{
-		audioEngineContext.newTestPage();
+		audioEngineContext.newTestPage(store);
 		/// CHECK FOR SAMPLE RATE COMPATIBILITY
 		if (audioHolderObject.sampleRate != undefined) {
 			if (Number(audioHolderObject.sampleRate) != audioContext.sampleRate) {
@@ -2276,7 +2142,7 @@
 		audioEngineContext.audioObjects = [];
 		interfaceContext.deleteCommentBoxes();
 		interfaceContext.deleteCommentQuestions();
-		loadTest(audioHolderObject);
+		loadTest(audioHolderObject,store);
 	};
 	
 	// Bounded by interface!!
@@ -2382,7 +2248,7 @@
 		this.holder.className = 'comment-div';
 		// Create a string next to each comment asking for a comment
 		this.string = document.createElement('span');
-		this.string.innerHTML = commentQuestion.question;
+		this.string.innerHTML = commentQuestion.statement;
 		// Create the HTML5 comment box 'textarea'
 		this.textArea = document.createElement('textarea');
 		this.textArea.rows = '4';
@@ -2442,7 +2308,7 @@
 		this.span.style.marginTop = '15px';
 		
 		var optCount = commentQuestion.options.length;
-		for (var i=0; i<optCount; i++)
+		for (var optNode of commentQuestion.options)
 		{
 			var div = document.createElement('div');
 			div.style.width = '80px';
@@ -2450,7 +2316,7 @@
 			var input = document.createElement('input');
 			input.type = 'radio';
 			input.name = commentQuestion.id;
-			input.setAttribute('setvalue',commentQuestion.options[i].name);
+			input.setAttribute('setvalue',optNode.name);
 			input.className = 'comment-radio';
 			div.appendChild(input);
 			this.inputs.appendChild(div);
@@ -2461,7 +2327,7 @@
 			div.style.float = 'left';
 			div.align = 'center';
 			var span = document.createElement('span');
-			span.textContent = commentQuestion.options[i].text;
+			span.textContent = optNode.text;
 			span.className = 'comment-radio-span';
 			div.appendChild(span);
 			this.span.appendChild(div);
@@ -2662,7 +2528,7 @@
 	
 	this.createCommentQuestion = function(element) {
 		var node;
-		if (element.type == 'text') {
+		if (element.type == 'question') {
 			node = new this.commentBox(element);
 		} else if (element.type == 'radio') {
 			node = new this.radioBox(element);
@@ -2771,16 +2637,16 @@
 	// These functions will help enforce the checkers
 	this.checkHiddenAnchor = function()
 	{
-		var audioHolder = testState.currentStateMap[testState.currentIndex];
-		if (audioHolder.anchorId != null)
+		for (var ao of audioEngineContext.audioObjects)
 		{
-			var audioObject = audioEngineContext.audioObjects[audioHolder.anchorId];
-			if (audioObject.interfaceDOM.getValue() > audioObject.specification.marker && audioObject.interfaceDOM.enforce == true)
+			if (ao.specification.type == "anchor")
 			{
-				// Anchor is not set below
-				console.log('Anchor node not below marker value');
-				alert('Please keep listening');
-				return false;
+				if (ao.interfaceDOM.getValue() > ao.specification.marker && ao.interfaceDOM.enforce == true) {
+					// Anchor is not set below
+					console.log('Anchor node not below marker value');
+					alert('Please keep listening');
+					return false;
+				}
 			}
 		}
 		return true;
@@ -2788,16 +2654,16 @@
 	
 	this.checkHiddenReference = function()
 	{
-		var audioHolder = testState.currentStateMap[testState.currentIndex];
-		if (audioHolder.referenceId != null)
+		for (var ao of audioEngineContext.audioObjects)
 		{
-			var audioObject = audioEngineContext.audioObjects[audioHolder.referenceId];
-			if (audioObject.interfaceDOM.getValue() < audioObject.specification.marker && audioObject.interfaceDOM.enforce == true)
+			if (ao.specification.type == "reference")
 			{
-				// Anchor is not set below
-				console.log('Reference node not above marker value');
-				alert('Please keep listening');
-				return false;
+				if (ao.interfaceDOM.getValue() < ao.specification.marker && ao.interfaceDOM.enforce == true) {
+					// Anchor is not set below
+					console.log('Reference node not below marker value');
+					alert('Please keep listening');
+					return false;
+				}
 			}
 		}
 		return true;
@@ -2915,4 +2781,145 @@
 		console.log(str);
 		return false;
 	};
-}
\ No newline at end of file
+}
+
+function Storage()
+{
+	// Holds results in XML format until ready for collection
+	this.globalPreTest = null;
+	this.globalPostTest = null;
+	this.testPages = [];
+	this.document = document.implementation.createDocument(null,"waetresult");
+	this.root = this.document.children[0];
+	this.state = 0;
+	
+	this.initialise = function()
+	{
+		this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);
+		this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);
+	};
+	
+	this.createTestPageStore = function(specification)
+	{
+		var store = new this.pageNode(this,specification);
+		this.testPages.push(store);
+		return this.testPages[this.testPages.length-1];
+	};
+	
+	this.surveyNode = function(parent,root,specification)
+	{
+		this.specification = specification;
+		this.parent = parent;
+		this.XMLDOM = this.parent.document.createElement('survey');
+		this.XMLDOM.setAttribute('location',this.specification.location);
+		for (var optNode of this.specification.options)
+		{
+			if (optNode.type != 'statement')
+			{
+				var node = this.parent.document.createElement('surveyresult');
+				node.id = optNode.id;
+				node.setAttribute('type',optNode.type);
+				this.XMLDOM.appendChild(node);
+			}
+		}
+		root.appendChild(this.XMLDOM);
+		
+		this.postResult = function(node)
+		{
+			// From popup: node is the popupOption node containing both spec. and results
+			// ID is the position
+			if (node.specification.type == 'statement'){return;}
+			var surveyresult = this.parent.document.getElementById(node.specification.id);
+			switch(node.specification.type)
+			{
+			case "number":
+			case "question":
+				var child = this.parent.document.createElement('response');
+				child.textContent = node.response;
+				surveyresult.appendChild(child);
+				break;
+			case "radio":
+				var child = this.parent.document.createElement('response');
+				child.setAttribute('name',node.response.name);
+				child.textContent = node.response.text;
+				surveyresult.appendChild(child);
+				break;
+			case "checkbox":
+				for (var i=0; i<node.response.length; i++)
+				{
+					var checkNode = this.parent.document.createElement('response');
+					child.setAttribute('name',node.response.name);
+					child.setAttribute('checked',node.response.checked);
+					child.textContent = node.response.text;
+					surveyresult.appendChild(child);
+				}
+				break;
+			}
+		};
+	};
+	
+	this.pageNode = function(parent,specification)
+	{
+		// Create one store per test page
+		this.specification = specification;
+		this.parent = parent;
+		this.XMLDOM = this.parent.document.createElement('page');
+		this.XMLDOM.setAttribute('id',specification.id);
+		this.XMLDOM.setAttribute('presentedId',specification.presentedId);
+		this.preTest = new this.parent.surveyNode(parent,this.XMLDOM,specification.preTest);
+		this.postTest = new this.parent.surveyNode(parent,this.XMLDOM,specification.postTest);
+		
+		// Add any page metrics
+		var page_metric = this.parent.document.createElement('metric');
+		this.XMLDOM.appendChild(page_metric);
+		
+		// Add the audioelement
+		for (var element of this.specification.audioElements)
+		{
+			var aeNode = this.parent.document.createElement('audioelement');
+			aeNode.id = element.id;
+			aeNode.setAttribute('type',element.type);
+			aeNode.setAttribute('url', element.url);
+			aeNode.setAttribute('gain', element.gain);
+			if (element.type == 'anchor' || element.type == 'reference')
+			{
+				if (element.marker > 0)
+				{
+					aeNode.setAttribute('marker',element.marker);
+				}
+			}
+			var ae_metric = this.parent.document.createElement('metric');
+			aeNode.appendChild(ae_metric); 
+			this.XMLDOM.appendChild(aeNode);
+		}
+		
+		// Add any commentQuestions
+		for (var element of this.specification.commentQuestions)
+		{
+			var cqNode = this.parent.document.createElement('commentquestion');
+			cqNode.id = element.id;
+			cqNode.setAttribute('type',element.type);
+			var statement = this.parent.document.createElement('statement');
+			statement.textContent = cqNode.statement;
+			cqNode.appendChild(statement);
+			var response = this.parent.document.createElement('response');
+			cqNode.appendChild(response);
+			this.XMLDOM.appendChild(cqNode);
+		}
+		
+		this.parent.root.appendChild(this.XMLDOM);
+	};
+	this.finish = function()
+	{
+		if (this.state == 0)
+		{
+			var projectDocument = specification.projectXML;
+			projectDocument.setAttribute('file-name',url);
+			this.root.appendChild(projectDocument);
+			this.root.appendChild(returnDateNode());
+			this.root.appendChild(interfaceContext.returnNavigator());
+		}
+		this.state = 1;
+		return this.root;
+	};
+}