/** * Creates a object from a XML specification. **/ Vn.Builder = new Class ({ objectMap: {} ,tags: {} ,destroy: function () { for (var key in this.objectMap) { var object = this.objectMap[key]; if (object.destroy instanceof Function) this.objectMap[key].destroy (); } } ,loadXml: function (xmlDoc) { if (!xmlDoc) return false; var docElement = xmlDoc.documentElement; if (docElement.tagName !== 'vn') return false; this.contexts = []; var childs = docElement.childNodes; if (childs) for (var i = 0; i < childs.length; i++) this.loadNode (childs[i], null); this.resolveProperties (); delete this.contexts; return true; } ,loadXmlFromNode: function (node) { this.contexts = []; var mainNode = this.loadNode (node); this.resolveProperties (); delete this.contexts; return mainNode; } ,add: function (id, object) { this.objectMap[id] = object; } ,loadNode: function (node) { var customNode; var htmlNode = null; var tagName = null; if (node.tagName) tagName = node.tagName.toLowerCase (); if (tagName === 't') { htmlNode = document.createTextNode (_(node.firstChild.textContent)); } else if (!tagName) { htmlNode = document.importNode (node, false); } else if ((customNode = this.loadCustomNode (node, null))) { if (customNode instanceof Htk.Widget) htmlNode = customNode.getNode (); } else { htmlNode = document.createElement (tagName); var a = node.attributes; for (var i = 0; i < a.length; i++) { var nodeName = a[i].nodeName; var nodeValue = a[i].nodeValue; if (/on-\w+/.test (nodeName)) { var method = this.getMethod (nodeValue); htmlNode.addEventListener ( nodeName.substr (3), method.bind (this.signalData)); } else if (nodeName === 'id') { this.objectMap[nodeValue] = htmlNode; } else htmlNode.setAttribute (nodeName, this.translateValue (nodeValue)); } var childs = node.childNodes; if (childs) for (var i = 0; i < childs.length; i++) { var htmlChild = this.loadNode (childs[i]); if (htmlChild) htmlNode.appendChild (htmlChild); } } return htmlNode; } ,loadCustomNode: function (node, parentContext) { if (!node.tagName) return null; var tagName = node.tagName.toLowerCase (); var klass = Vn.customTags[tagName]; if (!klass) return null; var customNode = new klass (); if (!this.tags[tagName]) this.tags[tagName] = []; this.tags[tagName].push (customNode); var context = { node: node ,parent: parentContext ,object: customNode ,klass: klass }; this.contexts.push (context); var nodeId = node.getAttribute ('id'); if (nodeId) this.objectMap[nodeId] = customNode; var childs = node.childNodes; if (childs) for (var i = 0; i < childs.length; i++) this.loadCustomNode (childs[i], context); return customNode; } ,translateValue: function (value) { var chr = value.charAt (0); if (chr == '_') return _(value.substr (1)); else if (chr == '\\' && value.charAt (1) == '_') return value.substr (1); return value; } ,getMethod: function (value) { if (this.signalData) var methodName = 'this.signalData.'+ value; else var methodName = value; return eval (methodName); } ,replaceFunc: function (token) { return token.charAt(1).toUpperCase (); } ,resolveProperties: function () { for (var i = 0; i < this.contexts.length; i++) { var c = this.contexts[i]; var a = c.node.attributes; for (var j = 0; j < a.length; j++) { var nodeName = a[j].nodeName; var nodeValue = a[j].nodeValue; if (/on-\w+/.test (nodeName)) { var method = this.getMethod (nodeValue); c.object.on (nodeName.substr (3), method, this.signalData); } else if (!/^(id|property)$/.test (nodeName)) { var prop = nodeName.replace (/-./g, this.replaceFunc); this.setProperty (c, prop, nodeValue); } } if (c.parent) { var parentProperty = c.node.getAttribute ('property'); if (!parentProperty) parentProperty = c.parent.klass.Child; if (parentProperty) this.setProperty (c.parent, parentProperty, c.object); if (c.klass.Parent) this.setProperty (c, c.klass.Parent, c.parent.object); } c.object.loadXml (this, c.node); } } ,setProperty: function (c, propName, value) { var prop = c.klass.Properties[propName]; if (!prop) { console.warn ('Htk.Builder: Attribute \'%s\' not valid for tag \'%s\'', propName, c.node.tagName); return; } if (!value) return; switch (prop.type) { case Boolean: value = (/^(true|1)$/i).test (value); break; case Number: value = 0 + new Number (value); break; case String: value = this.translateValue (value); break; case Function: { var method = this.getMethod (value); value = method ? method.bind (this.signalData) : null; break; } default: if (prop.type instanceof Function) { if (typeof value == 'string') value = this.get (value); if (!(value instanceof prop.type)) return; } else if (prop.enumType) value = prop.enumType[value]; } if (value !== undefined) c.object[propName] = value; } ,setParent: function (parentBuilder) { this.parentBuilder = parentBuilder; if (parentBuilder) this.signalData = parentBuilder.signalData; } ,$: function (objectId) { return this.get (objectId); } ,get: function (objectId) { var object = this.objectMap[objectId]; if (object) return object; if (this.parentBuilder) return this.parentBuilder.get (objectId); return null; } ,getObjects: function (tagName) { if (this.tags[tagName]) return this.tags[tagName]; return []; } });