nicholas@2690: /* globals document, console, DOMParser */ nicholas@2224: function Specification() { nicholas@2690: var schemaRoot; nicholas@2690: var schemaString; nicholas@2538: // Handles the decoding of the project specification XML into a simple JavaScript Object. nicholas@2538: nicholas@2378: // attributes nicholas@2688: this.interface = undefined; nicholas@2688: this.projectReturn = undefined; nicholas@2688: this.returnURL = undefined; nicholas@2688: this.randomiseOrder = undefined; nicholas@2688: this.poolSize = undefined; nicholas@2688: this.loudness = undefined; nicholas@2688: this.sampleRate = undefined; nicholas@2688: this.crossFade = undefined; nicholas@2688: this.preSilence = undefined; nicholas@2688: this.postSilence = undefined; nicholas@2688: this.playOne = undefined; nicholas@2822: this.minNumberPlays = undefined; nicholas@2822: this.maxNumberPlays = undefined; nicholas@3062: this.randomiseAxisOrder = undefined; nicholas@2538: nicholas@2378: // nodes nicholas@2854: this.metrics = new metricNode(); nicholas@2857: this.preTest = new surveyNode(this); nicholas@2857: this.postTest = new surveyNode(this); nicholas@2866: this.preTest.location = "pre"; nicholas@2866: this.postTest.location = "post"; nicholas@2538: this.pages = []; nicholas@2854: this.interfaces = new interfaceNode(this); nicholas@2538: this.errors = []; nicholas@2224: this.exitText = "Thank you."; nicholas@2538: nicholas@2857: // Creators nicholas@2857: this.createNewPage = function () { nicholas@2857: var newpage = new page(this); nicholas@2857: this.pages.push(newpage); nicholas@2857: return newpage; nicholas@2859: }; nicholas@2857: nicholas@2687: var processAttribute = function (attribute, schema) { nicholas@2538: // attribute is the string returned from getAttribute on the XML nicholas@2538: // schema is the node nicholas@2687: if (schema.getAttribute('name') === null && schema.getAttribute('ref') !== undefined) { nicholas@2852: schema = schemaRoot.querySelector("[name=" + schema.getAttribute('ref') + "]"); nicholas@2538: } nicholas@2538: var defaultOpt = schema.getAttribute('default'); nicholas@2684: if (attribute === null) { nicholas@2538: attribute = defaultOpt; nicholas@2538: } nicholas@2538: var dataType = schema.getAttribute('type'); nicholas@2538: if (typeof dataType == "string") { nicholas@2538: dataType = dataType.substr(3); nicholas@2538: } else { nicholas@2852: var rest = schema.querySelectorAll("restriction,enumeration"); nicholas@2224: if (rest.length > 0) { nicholas@2224: dataType = rest[0].getAttribute("base"); nicholas@2224: if (typeof dataType == "string") { nicholas@2224: dataType = dataType.substr(3); nicholas@2224: } else { nicholas@2224: dataType = "string"; nicholas@2224: } nicholas@2224: } else { nicholas@2224: dataType = "string"; nicholas@2224: } nicholas@2224: } nicholas@2684: if (attribute === null) { nicholas@2538: return attribute; nicholas@2538: } nicholas@2538: switch (dataType) { nicholas@2538: case "boolean": nicholas@2538: if (attribute == 'true') { nicholas@2538: attribute = true; nicholas@2538: } else { nicholas@2538: attribute = false; nicholas@2538: } nicholas@2538: break; nicholas@2538: case "negativeInteger": nicholas@2538: case "positiveInteger": nicholas@2538: case "nonNegativeInteger": nicholas@2538: case "nonPositiveInteger": nicholas@2538: case "integer": nicholas@2538: case "decimal": nicholas@2538: case "short": nicholas@2538: attribute = Number(attribute); nicholas@2538: break; nicholas@2538: default: nicholas@2538: attribute = String(attribute); nicholas@2538: break; nicholas@2538: } nicholas@2538: return attribute; nicholas@2538: }; nicholas@2538: nicholas@2687: this.processSchema = function (schemaXSD) { nicholas@2687: if (schemaRoot === undefined) { nicholas@2687: schemaString = schemaXSD; nicholas@2687: var parse = new DOMParser(); nicholas@2687: schemaRoot = parse.parseFromString(schemaString, 'text/xml'); nicholas@2687: Object.defineProperties(this, { nicholas@2687: 'schema': { nicholas@2687: 'value': schemaRoot nicholas@2687: }, nicholas@2687: 'schemaString': { nicholas@2687: 'value': schemaString nicholas@2687: } nicholas@2687: }); nicholas@2687: } nicholas@2690: }; nicholas@2687: this.getSchema = function () { nicholas@2687: return schemaRoot; nicholas@2690: }; nicholas@2687: this.getSchemaString = function () { nicholas@2687: return schemaString; nicholas@2690: }; nicholas@2687: nicholas@2538: this.decode = function (projectXML) { nicholas@2687: schemaRoot = this.schema; nicholas@2538: this.errors = []; nicholas@2538: // projectXML - DOM Parsed document nicholas@2538: this.projectXML = projectXML.childNodes[0]; nicholas@2538: var setupNode = projectXML.getElementsByTagName('setup')[0]; nicholas@2852: var schemaSetup = schemaRoot.querySelector('[name=setup]'); nicholas@2538: // First decode the attributes nicholas@2852: var attributes = schemaSetup.querySelectorAll('attribute'); nicholas@2684: var i; nicholas@2684: for (i = 0; i < attributes.length; i++) { nicholas@2538: var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref'); nicholas@2538: var projectAttr = setupNode.getAttribute(attributeName); nicholas@2692: projectAttr = processAttribute(projectAttr, attributes[i]); nicholas@2687: if (projectAttr !== null) { nicholas@2687: this[attributeName] = projectAttr; nicholas@2538: } nicholas@2538: } n@3103: if (projectXML.getElementsByTagName('calibration').length > 0) { n@3103: this.calibration.decode(this, projectXML.getElementsByTagName('calibration')[0]); n@3103: } nicholas@2538: nicholas@2224: var exitTextNode = setupNode.getElementsByTagName('exitText'); nicholas@2224: if (exitTextNode.length == 1) { nicholas@2224: this.exitText = exitTextNode[0].textContent; nicholas@2224: } nicholas@2538: nicholas@2538: this.metrics.decode(this, setupNode.getElementsByTagName('metric')[0]); nicholas@2538: nicholas@2538: // Now process the survey node options nicholas@2538: var survey = setupNode.getElementsByTagName('survey'); nicholas@2684: for (i = 0; i < survey.length; i++) { nicholas@2538: var location = survey[i].getAttribute('location'); nicholas@2538: switch (location) { nicholas@2257: case 'pre': nicholas@2257: case 'before': nicholas@2538: this.preTest.decode(this, survey[i]); nicholas@2257: break; nicholas@2257: case 'post': nicholas@2257: case 'after': nicholas@2538: this.postTest.decode(this, survey[i]); nicholas@2257: break; nicholas@2257: } nicholas@2538: } nicholas@2538: nicholas@2538: var interfaceNode = setupNode.getElementsByTagName('interface'); nicholas@2538: if (interfaceNode.length > 1) { nicholas@2538: this.errors.push("Only one node in the node allowed! Others except first ingnored!"); nicholas@2538: } nicholas@2854: nicholas@2684: if (interfaceNode.length !== 0) { nicholas@2538: interfaceNode = interfaceNode[0]; nicholas@2852: this.interfaces.decode(this, interfaceNode, this.schema.querySelectorAll('[name=interface]')[1]); nicholas@2538: } nicholas@2538: nicholas@2538: // Page tags nicholas@2538: var pageTags = projectXML.getElementsByTagName('page'); nicholas@2852: var pageSchema = this.schema.querySelector('[name=page]'); nicholas@2684: for (i = 0; i < pageTags.length; i++) { nicholas@2854: var node = new page(this); nicholas@2538: node.decode(this, pageTags[i], pageSchema); nicholas@2538: this.pages.push(node); nicholas@2941: for (var r = 0; r < node.repeatCount; r++) { nicholas@2941: var repeat = new page(this); nicholas@2941: repeat.decode(this, pageTags[i], pageSchema); nicholas@2941: repeat.id += "-repeat-" + r; nicholas@2941: this.pages.push(repeat); nicholas@2941: } nicholas@2538: } nicholas@2538: }; nicholas@2538: nicholas@2538: this.encode = function () { nicholas@2538: var RootDocument = document.implementation.createDocument(null, "waet"); nicholas@2538: var root = RootDocument.firstChild; nicholas@2538: root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); nicholas@2538: root.setAttribute("xsi:noNamespaceSchemaLocation", "test-schema.xsd"); nicholas@2538: // Build setup node nicholas@2224: var setup = RootDocument.createElement("setup"); nicholas@2852: var schemaSetup = schemaRoot.querySelector('[name=setup]'); nicholas@2224: // First decode the attributes nicholas@2852: var attributes = schemaSetup.querySelectorAll('attribute'); nicholas@2538: for (var i = 0; i < attributes.length; i++) { nicholas@2224: var name = attributes[i].getAttribute("name"); nicholas@2895: if (name === null) { nicholas@2224: name = attributes[i].getAttribute("ref"); nicholas@2224: } nicholas@2685: if (this[name] !== undefined || attributes[i].getAttribute("use") == "required") { nicholas@2685: setup.setAttribute(name, this[name]); nicholas@2224: } nicholas@2224: } nicholas@2224: root.appendChild(setup); nicholas@2224: // Survey node nicholas@2684: if (this.exitText !== null) { nicholas@2224: var exitTextNode = RootDocument.createElement('exitText'); nicholas@2224: exitTextNode.textContent = this.exitText; nicholas@2224: setup.appendChild(exitTextNode); nicholas@2224: } nicholas@3100: setup.appendChild(this.calibration.encode(RootDocument)); nicholas@2224: setup.appendChild(this.preTest.encode(RootDocument)); nicholas@2224: setup.appendChild(this.postTest.encode(RootDocument)); nicholas@2224: setup.appendChild(this.metrics.encode(RootDocument)); nicholas@2224: setup.appendChild(this.interfaces.encode(RootDocument)); nicholas@2684: this.pages.forEach(function (page) { nicholas@2224: root.appendChild(page.encode(RootDocument)); nicholas@2684: }); nicholas@2538: return RootDocument; nicholas@2538: }; nicholas@2538: nicholas@3100: this.calibration = (function () { nicholas@3100: var frequencies = false, nicholas@3100: levels = false, nicholas@3100: channels = false, nicholas@3100: schema = undefined; nicholas@3100: var Calibration = {}; nicholas@3100: Object.defineProperties(Calibration, { nicholas@3100: "parent": { nicholas@3100: "value": this nicholas@3100: }, nicholas@3100: "schema": { nicholas@3100: "get": function () { nicholas@3100: return schema; nicholas@3100: }, nicholas@3100: "set": function (t) { nicholas@3100: if (schema === undefined) { nicholas@3100: schema = t; nicholas@3100: } else { nicholas@3100: throw ("Cannot set readonly"); nicholas@3100: } nicholas@3100: } nicholas@3100: }, nicholas@3100: "checkFrequencies": { nicholas@3100: "get": function () { nicholas@3100: return frequencies; nicholas@3100: }, nicholas@3100: "set": function (t) { nicholas@3100: frequencies = (t === true); nicholas@3100: } nicholas@3100: }, nicholas@3100: "checkLevels": { nicholas@3100: "get": function () { nicholas@3100: return levels; nicholas@3100: }, nicholas@3100: "set": function (t) { nicholas@3100: levels = (t === true); nicholas@3100: } nicholas@3100: }, nicholas@3100: "checkChannels": { nicholas@3100: "get": function () { nicholas@3100: return channels; nicholas@3100: }, nicholas@3100: "set": function (t) { nicholas@3100: channels = (t === true); nicholas@3100: } nicholas@3100: }, nicholas@3100: "encode": { nicholas@3100: "value": function (doc) { nicholas@3100: var node = doc.createElement("calibration"); nicholas@3100: node.setAttribute("checkFrequencies", frequencies); nicholas@3100: node.setAttribute("checkLevels", levels); nicholas@3100: node.setAttribute("checkChannels", channels); nicholas@3100: return node; nicholas@3100: } nicholas@3100: }, nicholas@3100: "decode": { nicholas@3100: "value": function (parent, xml) { nicholas@3100: this.schema = schemaRoot.querySelector("[name=calibration]"); nicholas@3100: var attributeMap = this.schema.querySelectorAll('attribute'), nicholas@3100: i; nicholas@3100: for (i in attributeMap) { nicholas@3100: if (isNaN(Number(i)) === true) { nicholas@3100: break; nicholas@3100: } nicholas@3100: var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref'); nicholas@3100: var projectAttr = xml.getAttribute(attributeName); nicholas@3100: projectAttr = processAttribute(projectAttr, attributeMap[i]); nicholas@3100: if (projectAttr !== null) { nicholas@3100: this[attributeName] = projectAttr; nicholas@3100: } nicholas@3100: } nicholas@3100: } nicholas@3100: } nicholas@3100: }); nicholas@3100: return Calibration; nicholas@3100: })(); nicholas@3100: nicholas@2854: function surveyNode(specification) { nicholas@2688: this.location = undefined; nicholas@2538: this.options = []; nicholas@2688: this.parent = undefined; nicholas@3020: this.showBackButton = true; nicholas@2224: this.specification = specification; nicholas@2538: nicholas@2857: this.addOption = function () { nicholas@2857: var node = new this.OptionNode(this.specification); nicholas@2857: this.options.push(node); nicholas@2857: return node; nicholas@2859: }; nicholas@2857: nicholas@2538: this.OptionNode = function (specification) { nicholas@2538: this.type = undefined; n@2582: this.schema = undefined; nicholas@2538: this.id = undefined; nicholas@2224: this.name = undefined; nicholas@2538: this.mandatory = undefined; nicholas@2538: this.statement = undefined; nicholas@2538: this.boxsize = undefined; nicholas@2538: this.options = []; nicholas@2538: this.min = undefined; nicholas@2538: this.max = undefined; nicholas@2774: this.minWait = undefined; nicholas@2538: this.step = undefined; nicholas@2464: this.conditions = []; nicholas@2538: nicholas@2538: this.decode = function (parent, child) { nicholas@2852: this.schema = schemaRoot.querySelector("[name=" + child.nodeName + "]"); nicholas@2852: var attributeMap = this.schema.querySelectorAll('attribute'); nicholas@2684: var i; nicholas@2684: for (i in attributeMap) { nicholas@2684: if (isNaN(Number(i)) === true) { nicholas@2538: break; nicholas@2538: } nicholas@2538: var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref'); nicholas@2538: var projectAttr = child.getAttribute(attributeName); nicholas@2692: projectAttr = processAttribute(projectAttr, attributeMap[i]); nicholas@2687: if (projectAttr !== null) { nicholas@2687: this[attributeName] = projectAttr; nicholas@2538: } nicholas@2538: } n@2582: if (child.nodeName == 'surveyentry') { n@2582: console.log("NOTE - Use of is now deprecated. Whilst these will still work, newer nodes and tighter error checking will not be enforced"); n@2582: console.log("Please use the newer, type specifc nodes"); n@2582: if (!this.type) { n@2582: throw ("Type not specified"); n@2582: } n@2582: } else { n@2582: this.type = child.nodeName.split('survey')[1]; n@2582: } nicholas@2538: this.statement = child.getElementsByTagName('statement')[0].textContent; nicholas@2538: if (this.type == "checkbox" || this.type == "radio") { nicholas@2538: var children = child.getElementsByTagName('option'); nicholas@2684: if (children.length === null) { nicholas@2538: console.log('Malformed' + child.nodeName + 'entry'); nicholas@2538: this.statement = 'Malformed' + child.nodeName + 'entry'; nicholas@2538: this.type = 'statement'; nicholas@2538: } else { nicholas@2538: this.options = []; nicholas@2684: for (i = 0; i < children.length; i++) { nicholas@2538: this.options.push({ nicholas@2538: name: children[i].getAttribute('name'), nicholas@2538: text: children[i].textContent nicholas@2538: }); nicholas@2538: } nicholas@2538: } n@2582: } else if (this.type == "slider") { n@2582: this.leftText = ""; n@2582: this.rightText = ""; n@2582: var minText = child.getElementsByTagName("minText"); n@2582: var maxText = child.getElementsByTagName("maxText"); n@2582: if (minText.length > 0) { n@2582: this.leftText = minText[0].textContent; n@2582: } n@2582: if (maxText.length > 0) { n@2582: this.rightText = maxText[0].textContent; n@2582: } nicholas@2538: } nicholas@2464: var conditionElements = child.getElementsByTagName("conditional"); nicholas@2684: for (i = 0; i < conditionElements.length; i++) { nicholas@2464: var condition = conditionElements[i]; nicholas@2464: var obj = { nicholas@2464: check: condition.getAttribute("check"), nicholas@2464: value: condition.getAttribute("value"), nicholas@2464: jumpToOnPass: condition.getAttribute("jumpToOnPass"), nicholas@2464: jumpToOnFail: condition.getAttribute("jumpToOnFail") nicholas@2684: }; nicholas@2464: this.conditions.push(obj); nicholas@2464: } nicholas@2538: }; nicholas@2538: nicholas@2538: this.exportXML = function (doc) { n@2583: var node = doc.createElement('survey' + this.type); nicholas@2538: var statement = doc.createElement('statement'); nicholas@2538: statement.textContent = this.statement; nicholas@2538: node.appendChild(statement); nicholas@2224: node.id = this.id; nicholas@2684: if (this.name !== undefined) { nicholas@2538: node.setAttribute("name", this.name); nicholas@2538: } nicholas@2684: if (this.mandatory !== undefined) { nicholas@2538: node.setAttribute("mandatory", this.mandatory); nicholas@2538: } nicholas@2224: node.id = this.id; nicholas@2684: if (this.name !== undefined) { nicholas@2538: node.setAttribute("name", this.name); nicholas@2538: } nicholas@2538: switch (this.type) { nicholas@2224: case "checkbox": nicholas@2684: if (this.min !== undefined) { nicholas@2566: node.setAttribute("min", this.min); nicholas@2566: } else { nicholas@2566: node.setAttribute("min", "0"); nicholas@2566: } nicholas@2684: if (this.max !== undefined) { nicholas@2566: node.setAttribute("max", this.max); nicholas@2566: } else { nicholas@2573: node.setAttribute("max", "undefined"); nicholas@2686: } /* falls through */ nicholas@2224: case "radio": nicholas@2538: for (var i = 0; i < this.options.length; i++) { nicholas@2224: var option = this.options[i]; nicholas@2224: var optionNode = doc.createElement("option"); nicholas@2538: optionNode.setAttribute("name", option.name); nicholas@2224: optionNode.textContent = option.text; nicholas@2224: node.appendChild(optionNode); nicholas@2224: } nicholas@2562: break; nicholas@2224: case "number": nicholas@2684: if (this.min !== undefined) { nicholas@2538: node.setAttribute("min", this.min); nicholas@2538: } nicholas@2684: if (this.max !== undefined) { nicholas@2538: node.setAttribute("max", this.max); nicholas@2538: } nicholas@2562: break; nicholas@2224: case "question": nicholas@2684: if (this.boxsize !== undefined) { nicholas@2538: node.setAttribute("boxsize", this.boxsize); nicholas@2538: } nicholas@2684: if (this.mandatory !== undefined) { nicholas@2538: node.setAttribute("mandatory", this.mandatory); nicholas@2538: } nicholas@2562: break; nicholas@2562: case "video": nicholas@2684: if (this.mandatory !== undefined) { nicholas@2573: node.setAttribute("mandatory", this.mandatory); nicholas@2686: } /* falls through */ nicholas@2562: case "youtube": nicholas@2684: if (this.url !== undefined) { nicholas@2573: node.setAttribute("url", this.url); nicholas@2573: } nicholas@2562: break; n@2583: case "slider": n@2583: node.setAttribute("min", this.min); n@2583: node.setAttribute("max", this.max); n@2583: if (this.leftText) { n@2583: var minText = doc.createElement("minText"); n@2583: minText.textContent = this.leftText; n@2583: node.appendChild(minText); n@2583: } n@2583: if (this.rightText) { n@2583: var maxText = doc.createElement("maxText"); n@2583: maxText.textContent = this.rightText; n@2583: node.appendChild(maxText); n@2583: } nicholas@2686: break; nicholas@2224: default: nicholas@2224: break; nicholas@2224: } nicholas@2684: this.conditions.forEach(function (condition) { nicholas@2465: var conditionDOM = doc.createElement("conditional"); nicholas@2538: conditionDOM.setAttribute("check", condition.check); nicholas@2538: conditionDOM.setAttribute("value", condition.value); nicholas@2538: conditionDOM.setAttribute("jumpToOnPass", condition.jumpToOnPass); nicholas@2538: conditionDOM.setAttribute("jumpToOnFail", condition.jumpToOnFail); nicholas@2465: node.appendChild(conditionDOM); nicholas@2684: }); nicholas@2538: return node; nicholas@2538: }; nicholas@2538: }; nicholas@2538: this.decode = function (parent, xml) { nicholas@2857: this.schema = schemaRoot.querySelector('[name=survey]'); nicholas@2224: this.parent = parent; nicholas@2538: this.location = xml.getAttribute('location'); nicholas@2538: if (this.location == 'before') { nicholas@2538: this.location = 'pre'; nicholas@2538: } else if (this.location == 'after') { nicholas@2538: this.location = 'post'; nicholas@2538: } nicholas@3020: this.showBackButton = xml.getAttribute("showBackButton"); nicholas@3020: if (this.showBackButton == "false") { nicholas@3020: this.showBackButton = false; nicholas@3020: } else { nicholas@3020: this.showBackButton = true; nicholas@3020: } nicholas@2684: var child = xml.firstElementChild; n@2582: while (child) { nicholas@2538: var node = new this.OptionNode(this.specification); n@2582: node.decode(parent, child); nicholas@2538: this.options.push(node); n@2582: child = child.nextElementSibling; nicholas@2538: } nicholas@2684: if (this.options.length === 0) { nicholas@2257: console.log("Empty survey node"); nicholas@2257: console.log(this); nicholas@2257: } nicholas@2538: }; nicholas@2538: this.encode = function (doc) { nicholas@2538: var node = doc.createElement('survey'); nicholas@2538: node.setAttribute('location', this.location); nicholas@3021: node.setAttribute('showBackButton', this.showBackButton); nicholas@2538: for (var i = 0; i < this.options.length; i++) { nicholas@2538: node.appendChild(this.options[i].exportXML(doc)); nicholas@2538: } nicholas@2538: return node; nicholas@2538: }; nicholas@2859: } nicholas@2538: nicholas@2854: function interfaceNode(specification) { nicholas@2688: this.title = undefined; nicholas@2688: this.name = undefined; nicholas@2777: this.image = undefined; nicholas@2538: this.options = []; nicholas@2538: this.scales = []; nicholas@2854: this.schema = undefined; nicholas@2538: nicholas@2538: this.decode = function (parent, xml) { nicholas@2854: this.schema = schemaRoot.querySelectorAll('[name=interface]')[1]; nicholas@2538: this.name = xml.getAttribute('name'); nicholas@2538: var titleNode = xml.getElementsByTagName('title'); nicholas@2538: if (titleNode.length == 1) { nicholas@2538: this.title = titleNode[0].textContent; nicholas@2538: } nicholas@2538: var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption'); nicholas@2538: // Extract interfaceoption node schema nicholas@2852: var interfaceOptionNodeSchema = this.schema.querySelector('[name=interfaceoption]'); nicholas@2852: var attributeMap = interfaceOptionNodeSchema.querySelectorAll('attribute'); nicholas@2684: var i, j; nicholas@2684: for (i = 0; i < interfaceOptionNodes.length; i++) { nicholas@2538: var ioNode = interfaceOptionNodes[i]; nicholas@2538: var option = {}; nicholas@2684: for (j = 0; j < attributeMap.length; j++) { nicholas@2538: var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref'); nicholas@2538: var projectAttr = ioNode.getAttribute(attributeName); nicholas@2692: projectAttr = processAttribute(projectAttr, attributeMap[j]); nicholas@2687: if (projectAttr !== null) { nicholas@2688: option[attributeName] = projectAttr; nicholas@2538: } nicholas@2538: } n@2787: if (option.type == "check" && ioNode.firstElementChild) { n@2787: option.errorMessage = ioNode.firstElementChild.textContent; n@2787: } nicholas@2538: this.options.push(option); nicholas@2538: } nicholas@2777: // Get the image node nicholas@2777: var imageNode = xml.getElementsByTagName("image"); nicholas@2777: if (imageNode.length == 1) { nicholas@2777: this.image = imageNode[0].getAttribute("src"); nicholas@2777: } nicholas@2538: // Now the scales nodes nicholas@2538: var scaleParent = xml.getElementsByTagName('scales'); nicholas@2538: if (scaleParent.length == 1) { nicholas@2538: scaleParent = scaleParent[0]; nicholas@2852: var scalelabels = scaleParent.querySelectorAll('scalelabel'); nicholas@2684: for (i = 0; i < scalelabels.length; i++) { nicholas@2538: this.scales.push({ nicholas@2538: text: scalelabels[i].textContent, nicholas@2538: position: Number(scalelabels[i].getAttribute('position')) nicholas@2538: }); nicholas@2538: } nicholas@2538: } nicholas@2538: }; nicholas@2538: nicholas@2538: this.encode = function (doc) { nicholas@2538: var node = doc.createElement("interface"); nicholas@2684: if (typeof this.name == "string" && this.name.length > 0) nicholas@2538: node.setAttribute("name", this.name); nicholas@2243: if (typeof this.title == "string") { nicholas@2243: var titleNode = doc.createElement("title"); nicholas@2243: titleNode.textContent = this.title; nicholas@2243: node.appendChild(titleNode); nicholas@2243: } nicholas@2684: this.options.forEach(function (option) { nicholas@2224: var child = doc.createElement("interfaceoption"); nicholas@3136: Object.keys(option).forEach(function(key) { nicholas@3136: child.setAttribute(key, option[key]); nicholas@3136: }); n@2788: if (option.type == "check" && option.errorMessage !== undefined) { n@2788: var errorMessage = doc.createElement("errormessage"); n@2788: errorMessage.textContent = option.errorMessage; n@2788: child.appendChild(errorMessage); n@2788: } nicholas@2224: node.appendChild(child); nicholas@2684: }); nicholas@2780: if (typeof this.image == "string" && this.image.length !== 0) { nicholas@2780: var imgNode = doc.createElement("image"); nicholas@2780: imgNode.setAttribute("src", this.image); nicholas@2780: node.appendChild(imgNode); nicholas@2780: } nicholas@2684: if (this.scales.length !== 0) { nicholas@2224: var scales = doc.createElement("scales"); nicholas@2684: this.scales.forEach(function (scale) { nicholas@2224: var child = doc.createElement("scalelabel"); nicholas@2538: child.setAttribute("position", scale.position); nicholas@2224: child.textContent = scale.text; nicholas@2224: scales.appendChild(child); nicholas@2684: }); nicholas@2224: node.appendChild(scales); nicholas@2224: } nicholas@2224: return node; nicholas@2538: }; nicholas@2859: } nicholas@2538: nicholas@2854: function metricNode() { nicholas@2224: this.enabled = []; nicholas@2538: this.decode = function (parent, xml) { nicholas@2224: var children = xml.getElementsByTagName('metricenable'); nicholas@2538: for (var i in children) { nicholas@2684: if (isNaN(Number(i)) === true) { nicholas@2538: break; nicholas@2538: } nicholas@2224: this.enabled.push(children[i].textContent); nicholas@2224: } nicholas@2684: }; nicholas@2538: this.encode = function (doc) { nicholas@2224: var node = doc.createElement('metric'); nicholas@2538: for (var i in this.enabled) { nicholas@2684: if (isNaN(Number(i)) === true) { nicholas@2538: break; nicholas@2538: } nicholas@2224: var child = doc.createElement('metricenable'); nicholas@2224: child.textContent = this.enabled[i]; nicholas@2224: node.appendChild(child); nicholas@2224: } nicholas@2224: return node; nicholas@2684: }; nicholas@2859: } nicholas@2538: nicholas@2854: function page(specification) { nicholas@2538: this.presentedId = undefined; nicholas@2538: this.id = undefined; nicholas@2470: this.title = undefined; nicholas@2538: this.hostURL = undefined; nicholas@2538: this.randomiseOrder = undefined; nicholas@2538: this.loop = undefined; nicholas@2688: this.outsideReference = undefined; nicholas@2688: this.loudness = undefined; nicholas@2688: this.label = undefined; nicholas@2688: this.labelStart = undefined; nicholas@2857: this.preTest = new surveyNode(specification); nicholas@2857: this.postTest = new surveyNode(specification); nicholas@2866: this.preTest.location = "pre"; nicholas@2866: this.postTest.location = "post"; nicholas@2538: this.interfaces = []; nicholas@2688: this.playOne = undefined; nicholas@2688: this.restrictMovement = undefined; n@2713: this.position = undefined; nicholas@2538: this.commentBoxPrefix = "Comment on track"; nicholas@2822: this.minNumberPlays = undefined; nicholas@2822: this.maxNumberPlays = undefined; nicholas@3062: this.randomiseAxisOrder = undefined; nicholas@2538: this.audioElements = []; nicholas@2538: this.commentQuestions = []; nicholas@2852: this.schema = schemaRoot.querySelector("[name=page]"); nicholas@2224: this.specification = specification; nicholas@2688: this.parent = undefined; nicholas@2538: nicholas@2859: this.addInterface = function () { nicholas@2859: var node = new interfaceNode(specification); nicholas@2859: this.interfaces.push(node); nicholas@2859: return node; nicholas@2859: }; nicholas@2859: this.addCommentQuestion = function () { nicholas@2859: var node = new commentQuestionNode(specification); nicholas@2859: this.commentQuestions.push(node); nicholas@2859: return node; nicholas@2859: }; nicholas@2859: this.addAudioElement = function () { nicholas@2859: var node = new audioElementNode(specification); nicholas@2859: this.audioElements.push(node); nicholas@2859: return node; nicholas@2859: }; nicholas@2859: nicholas@2538: this.decode = function (parent, xml) { nicholas@2224: this.parent = parent; nicholas@2852: var attributeMap = this.schema.querySelectorAll('attribute'); nicholas@2684: var i, node; nicholas@2684: for (i = 0; i < attributeMap.length; i++) { nicholas@2538: var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref'); nicholas@2538: var projectAttr = xml.getAttribute(attributeName); nicholas@2692: projectAttr = processAttribute(projectAttr, attributeMap[i]); nicholas@2687: if (projectAttr !== null) { nicholas@2687: this[attributeName] = projectAttr; nicholas@2538: } nicholas@2538: } nicholas@2538: nicholas@2470: // Get the title nicholas@2470: var title = xml.getElementsByTagName('title'); nicholas@2684: if (title.length !== 0 && title[0].parentElement == xml) { nicholas@2470: this.title = title[0].textContent; nicholas@2470: } nicholas@2538: nicholas@2538: // Get the Comment Box Prefix nicholas@2538: var CBP = xml.getElementsByTagName('commentboxprefix'); nicholas@2684: if (CBP.length !== 0 && CBP[0].parentElement == xml) { nicholas@2538: this.commentBoxPrefix = CBP[0].textContent; nicholas@2538: } nicholas@2538: nicholas@2538: // Now decode the interfaces nicholas@2854: var interfaceNodes = xml.getElementsByTagName('interface'); nicholas@2854: for (i = 0; i < interfaceNodes.length; i++) { nicholas@2854: node = new interfaceNode(this.specification); nicholas@2854: node.decode(this, interfaceNodes[i], parent.schema.querySelectorAll('[name=interface]')[1]); nicholas@2538: this.interfaces.push(node); nicholas@2538: } nicholas@2538: nicholas@2538: // Now process the survey node options nicholas@2538: var survey = xml.getElementsByTagName('survey'); nicholas@2852: var surveySchema = parent.schema.querySelector('[name=survey]'); nicholas@2684: for (i = 0; i < survey.length; i++) { nicholas@2538: var location = survey[i].getAttribute('location'); nicholas@2538: if (location == 'pre' || location == 'before') { nicholas@2857: if (this.preTest.options.length !== 0) { nicholas@2538: this.errors.push("Already a pre/before test survey defined! Ignoring second!!"); nicholas@2538: } else { nicholas@2538: this.preTest.decode(parent, survey[i], surveySchema); nicholas@2538: } nicholas@2538: } else if (location == 'post' || location == 'after') { nicholas@2857: if (this.postTest.options.length !== 0) { nicholas@2538: this.errors.push("Already a post/after test survey defined! Ignoring second!!"); nicholas@2538: } else { nicholas@2538: this.postTest.decode(parent, survey[i], surveySchema); nicholas@2538: } nicholas@2538: } nicholas@2538: } nicholas@2538: nicholas@2538: // Now process the audioelement tags nicholas@2538: var audioElements = xml.getElementsByTagName('audioelement'); nicholas@2684: for (i = 0; i < audioElements.length; i++) { nicholas@2854: var audioNode = new audioElementNode(this.specification); nicholas@2684: audioNode.decode(this, audioElements[i]); nicholas@2684: this.audioElements.push(audioNode); nicholas@2538: } nicholas@2538: nicholas@2538: // Now decode the commentquestions n@2579: var cqNode = xml.getElementsByTagName('commentquestions'); nicholas@2684: if (cqNode.length !== 0) { n@2579: cqNode = cqNode[0]; nicholas@2644: var commentQuestion = cqNode.firstElementChild; nicholas@2644: while (commentQuestion) { nicholas@2854: node = new commentQuestionNode(this.specification); nicholas@2644: node.decode(parent, commentQuestion); n@2579: this.commentQuestions.push(node); nicholas@2644: commentQuestion = commentQuestion.nextElementSibling; n@2579: } nicholas@2538: } nicholas@2538: }; nicholas@2538: nicholas@2538: this.encode = function (root) { nicholas@2538: var AHNode = root.createElement("page"); nicholas@2224: // First decode the attributes nicholas@2852: var attributes = this.schema.querySelectorAll('attribute'); nicholas@2684: var i; nicholas@2684: for (i = 0; i < attributes.length; i++) { nicholas@2224: var name = attributes[i].getAttribute("name"); nicholas@2688: if (name === null) { nicholas@2224: name = attributes[i].getAttribute("ref"); nicholas@2224: } nicholas@2685: if (this[name] !== undefined || attributes[i].getAttribute("use") == "required") { nicholas@2685: AHNode.setAttribute(name, this[name]); nicholas@2224: } nicholas@2224: } nicholas@2224: // nicholas@2224: var commentboxprefix = root.createElement("commentboxprefix"); nicholas@2224: commentboxprefix.textContent = this.commentBoxPrefix; nicholas@2224: AHNode.appendChild(commentboxprefix); nicholas@2538: nicholas@2684: for (i = 0; i < this.interfaces.length; i++) { nicholas@2538: AHNode.appendChild(this.interfaces[i].encode(root)); nicholas@2538: } nicholas@2538: nicholas@2684: for (i = 0; i < this.audioElements.length; i++) { nicholas@2538: AHNode.appendChild(this.audioElements[i].encode(root)); nicholas@2538: } nicholas@2538: // Create nicholas@3036: if (this.commentQuestions.length > 0) { nicholas@3036: var node = root.createElement("commentquestions"); nicholas@3036: for (i = 0; i < this.commentQuestions.length; i++) { nicholas@3036: node.appendChild(this.commentQuestions[i].encode(root)); nicholas@3036: } nicholas@3036: AHNode.appendChild(node); nicholas@2538: } nicholas@2538: nicholas@2538: AHNode.appendChild(this.preTest.encode(root)); nicholas@2224: AHNode.appendChild(this.postTest.encode(root)); nicholas@2538: return AHNode; nicholas@2538: }; nicholas@2538: nicholas@2854: function commentQuestionNode(specification) { nicholas@2688: this.id = undefined; nicholas@2224: this.name = undefined; nicholas@2538: this.type = undefined; nicholas@2538: this.statement = undefined; nicholas@3034: this.mandatory = undefined; nicholas@2852: this.schema = schemaRoot.querySelector('[name=commentquestion]'); nicholas@2538: this.decode = function (parent, xml) { nicholas@2538: this.id = xml.id; nicholas@2224: this.name = xml.getAttribute('name'); nicholas@3034: this.mandatory = xml.getAttribute("mandatory") == "true"; nicholas@2688: if (this.name === null) { nicholas@2688: this.name = undefined; nicholas@2688: } n@2579: switch (xml.nodeName) { n@2579: case "commentradio": n@2579: this.type = "radio"; n@2579: this.options = []; n@2579: break; n@2579: case "commentcheckbox": n@2579: this.type = "checkbox"; n@2579: this.options = []; n@2579: break; n@2579: case "commentslider": n@2579: this.type = "slider"; n@2579: this.min = undefined; n@2579: this.max = undefined; n@2579: this.step = undefined; n@2579: break; n@2579: case "commentquestion": n@2579: this.type = "question"; n@2579: break; nicholas@2686: default: nicholas@2686: throw ("Unknown comment type " + xml.nodeName); n@2579: } nicholas@2538: this.statement = xml.getElementsByTagName('statement')[0].textContent; n@2579: if (this.type == "radio" || this.type == "checkbox") { n@2579: var optNodes = xml.getElementsByTagName('option'); n@2579: for (var i = 0; i < optNodes.length; i++) { n@2579: var optNode = optNodes[i]; n@2579: this.options.push({ n@2579: name: optNode.getAttribute('name'), n@2579: text: optNode.textContent n@2579: }); n@2579: } n@2579: } n@2579: if (this.type == "slider") { n@2579: this.min = Number(xml.getAttribute("min")); n@2579: this.max = Number(xml.getAttribute("max")); n@2579: this.step = Number(xml.getAttribute("step")); nicholas@2684: if (this.step === undefined) { n@2579: this.step = 1; n@2579: } n@2579: this.value = Number(xml.getAttribute("value")); nicholas@2684: if (this.value === undefined) { nicholas@2684: this.value = this.min; n@2579: } n@2580: this.leftText = xml.getElementsByTagName("minText"); n@2580: if (this.leftText && this.leftText.length > 0) { n@2580: this.leftText = this.leftText[0].textContent; n@2580: } else { n@2580: this.leftText = ""; n@2580: } n@2580: this.rightText = xml.getElementsByTagName("maxText"); n@2580: if (this.rightText && this.rightText.length > 0) { n@2580: this.rightText = this.rightText[0].textContent; n@2580: } else { n@2580: this.rightText = ""; n@2580: } nicholas@2538: } nicholas@2538: }; nicholas@2538: nicholas@2538: this.encode = function (root) { n@2579: var node; n@2579: switch (this.type) { n@2579: case "radio": n@2579: node = root.createElement("commentradio"); n@2579: break; n@2579: case "checkbox": n@2579: node = root.createElement("commentcheckbox"); n@2579: break; n@2579: case "slider": n@2579: node = root.createElement("commentslider"); n@2579: break; n@2579: case "question": n@2579: node = root.createElement("commentquestion"); n@2579: break; nicholas@2686: default: nicholas@2686: throw ("Unknown type " + this.type); n@2579: } nicholas@2224: node.id = this.id; nicholas@3036: node.setAttribute("mandatory", this.mandatory); nicholas@2538: node.setAttribute("type", this.type); nicholas@2684: if (this.name !== undefined) { nicholas@2538: node.setAttribute("name", this.name); nicholas@2538: } nicholas@2224: var statement = root.createElement("statement"); nicholas@2224: statement.textContent = this.statement; nicholas@2224: node.appendChild(statement); n@2579: if (this.type == "radio" || this.type == "checkbox") { nicholas@2684: this.options.forEach(function (option) { n@2579: var child = root.createElement("option"); n@2579: child.setAttribute("name", option.name); n@2579: child.textContent = option.text; n@2579: node.appendChild(child); nicholas@2684: }); n@2579: } n@2579: if (this.type == "slider") { n@2579: node.setAttribute("min", this.min); n@2579: node.setAttribute("max", this.max); n@2579: if (this.step !== 1) { n@2579: node.setAttribute("step", this.step); n@2579: } n@2579: if (this.value !== this.min) { n@2579: node.setAttribute("value", this.value); n@2579: } n@2580: if (this.leftText.length > 0) { n@2580: var leftText = root.createElement("minText"); n@2580: leftText.textContent = this.leftText; n@2580: node.appendChild(leftText); n@2580: } n@2580: if (this.rightText.length > 0) { n@2580: var rightText = root.createElement("maxText"); n@2580: rightText.textContent = this.rightText; n@2580: node.appendChild(rightText); n@2580: } nicholas@2224: } nicholas@2224: return node; nicholas@2538: }; nicholas@2859: } nicholas@2538: nicholas@2854: function audioElementNode(specification) { nicholas@2688: this.url = undefined; nicholas@2688: this.id = undefined; nicholas@2688: this.name = undefined; nicholas@2688: this.parent = undefined; nicholas@2688: this.type = undefined; nicholas@2688: this.marker = undefined; nicholas@2538: this.enforce = false; nicholas@2538: this.gain = 0.0; nicholas@2688: this.label = undefined; nicholas@2460: this.startTime = undefined; nicholas@2460: this.stopTime = undefined; nicholas@2614: this.sampleRate = undefined; nicholas@2777: this.image = undefined; nicholas@2822: this.minNumberPlays = undefined; nicholas@2822: this.maxNumberPlays = undefined; nicholas@2614: this.alternatives = []; nicholas@2852: this.schema = schemaRoot.querySelector('[name=audioelement]'); nicholas@2688: this.parent = undefined; nicholas@2538: this.decode = function (parent, xml) { nicholas@2538: this.parent = parent; nicholas@2852: var attributeMap = this.schema.querySelectorAll('attribute'); nicholas@2538: for (var i = 0; i < attributeMap.length; i++) { nicholas@2538: var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref'); nicholas@2538: var projectAttr = xml.getAttribute(attributeName); nicholas@2692: projectAttr = processAttribute(projectAttr, attributeMap[i]); nicholas@2687: if (projectAttr !== null) { nicholas@2687: this[attributeName] = projectAttr; nicholas@2538: } nicholas@2538: } nicholas@2614: // Get the alternative nodes nicholas@2614: var child = xml.firstElementChild; nicholas@2614: while (child) { nicholas@2614: if (child.nodeName == "alternative") { nicholas@2614: this.alternatives.push({ nicholas@2614: 'url': child.getAttribute("url"), nicholas@2614: 'sampleRate': child.getAttribute("sampleRate") nicholas@2614: }); nicholas@2614: } nicholas@2614: child = child.nextElementSibling; nicholas@2614: } nicholas@2538: nicholas@2538: }; nicholas@2538: this.encode = function (root) { nicholas@2538: var AENode = root.createElement("audioelement"); nicholas@2852: var attributes = this.schema.querySelectorAll('attribute'); nicholas@2538: for (var i = 0; i < attributes.length; i++) { nicholas@2224: var name = attributes[i].getAttribute("name"); nicholas@2688: if (name === null) { nicholas@2224: name = attributes[i].getAttribute("ref"); nicholas@2224: } nicholas@2685: if (this[name] !== undefined || attributes[i].getAttribute("use") == "required") { nicholas@2685: AENode.setAttribute(name, this[name]); nicholas@2224: } nicholas@2224: } nicholas@2614: this.alternatives.forEach(function (alt) { nicholas@2614: var node = root.createElement("alternative"); nicholas@2614: node.setAttribute("url", alt.url); nicholas@2614: node.setAttribute("sampleRate", alt.sampleRate); nicholas@2614: AENode.appendChild(node); nicholas@2614: }); nicholas@2538: return AENode; nicholas@2538: }; nicholas@2859: } nicholas@2859: } b@2437: }