/** * Creates a object from a XML specification. **/ Vn.Builder = new Class ({ Extends: Vn.Object ,addedMap: {} ,add: function (id, object) { this.addedMap[id] = object; } ,setParent: function (parentBuilder) { this.parentBuilder = parentBuilder; if (parentBuilder && !this.signalData) this.signalData = parentBuilder.builder.signalData; } ,getMain: function (result) { return result.objects[this.mainContext.id]; } ,getById: function (objectId, result) { var index = this.contextMap[objectId]; if (index !== undefined) return result.objects[index]; else if (this.addedMap[objectId]) return this.addedMap[objectId]; else if (this.parentBuilder) this.parentBuilder.getById (objectId); return null; } ,getByTagName: function (tagName, result) { var tags = this.tags[tagName]; if (tags) { var arr = new Array (tags.length); for (var i = 0; i < tags.length; i++) arr[i] = result.objects[tags[i]]; return arr; } return []; } ,loadXml: function (path, dstDocument) { var xmlDoc = Vn.getXml (path); if (!xmlDoc) return false; var docElement = xmlDoc.documentElement; if (docElement.tagName !== 'vn') return false; this._compileInit (dstDocument); var childs = docElement.childNodes; if (childs) for (var i = 0; i < childs.length; i++) this._compileNode (childs[i]); this._compileEnd (); return true; } ,loadXmlFromNode: function (node, dstDocument) { this._compileInit (dstDocument); this.mainContext = this._compileNode (node); this._compileEnd (); } ,load: function () { var contexts = this.contexts; var len = contexts.length; var objects = new Array (len); for (var i = 0; i < len; i++) { var context = contexts[i]; objects[i] = this.textInstantiate (context) || this.objectInstantiate (context) || this.elementInstantiate (context); } var res = new BuilderResult (this, objects); var addedObject; for (var i = this.propLinks.length - 1; i >= 0; i--) { var l = this.propLinks[i]; if (addedObject = this.addedMap[l.value]) objects[l.context.id][l.prop] = addedObject; } for (var i = this.childLinks.length - 1; i >= 0; i--) { var l = this.childLinks[i]; if (addedObject = this.addedMap[l.value]) objects[l.context.id].appendChild (addedObject); } for (var i = 0; i < len; i++) { var context = contexts[i]; var object = objects[i]; this.objectLink (context, object, objects, res) || this.elementLink (context, object, objects, res); } return res; } ,_compileInit: function (dstDocument) { this.tags = {}; this.contexts = []; this.contextMap = {}; this.propLinks = []; this.childLinks = []; this.mainContext = null; this.document = dstDocument ? dstDocument : document; } ,_compileEnd: function (node) { var contextId; for (var i = this.propLinks.length - 1; i >= 0; i--) { var l = this.propLinks[i]; if (contextId = this.contextMap[l.value]) { l.context.objectProps[l.prop] = contextId; this.propLinks.splice (i, 1); } else if (this.parentBuilder) { var object = this.parentBuilder.getById (l.value); if (object) pl.context.props[pl.prop] = object; } } for (var i = this.childLinks.length - 1; i >= 0; i--) { var l = this.childLinks[i]; if (contextId = this.contextMap[l.value]) { l.context.childs.push (contextId); this.childLinks.splice (i, 1); } } } ,_compileNode: function (node) { var context = null; var tagName = null; if (node.nodeType === Node.ELEMENT_NODE) tagName = node.tagName.toLowerCase (); else if (node.nodeType !== Node.TEXT_NODE || /^\s*$/.test (node.textContent)) return null; var context = this.textCompile (node, tagName) || this.objectCompile (node, tagName) || this.elementCompile (node, tagName); context.id = this.contexts.length; if (tagName) { var nodeId = node.getAttribute ('id'); if (nodeId) this.contextMap[nodeId] = context.id; var tags = this.tags[tagName]; if (!tags) this.tags[tagName] = tags = []; tags.push (context.id); } this.contexts.push (context); return context; } /** * Creates a text node context. **/ ,textCompile: function (node, tagName) { if (!tagName) var text = node.textContent; else if (tagName === 't') var text = _(node.firstChild.textContent); else return null; return {text: text}; } ,textInstantiate: function (context) { if (!context.text) return null; return this.document.createTextNode (context.text); } /** * Creates a object context. **/ ,objectCompile: function (node, tagName) { var handler; var props = {}; var objectProps = {}; var childs = []; var events = {}; var klass = Vn.customTags[tagName]; if (!klass) return null; var context = { klass: klass, props: props, events: events, objectProps: objectProps, childs: childs }; var a = node.attributes; for (var i = 0; i < a.length; i++) { var attribute = a[i].nodeName; var value = a[i].nodeValue; if ((handler = this._getEventHandler (attribute, value))) { events[attribute.substr (3)] = handler; } else if (!/^(id|property)$/.test (attribute)) { this.propCompile (context, klass, props, node, attribute, value); } } var childNodes = node.childNodes; if (childNodes) for (var i = 0; i < childNodes.length; i++) { var child = childNodes[i]; var childTagName = null; if (child.tagName) childTagName = child.tagName.toLowerCase (); if (childTagName === 'pointer') { this.childLinks.push ({ context: context, objectId: child.getAttribute ('object') }); } else if (childTagName === 'custom') { context.custom = child; } else { var childContext = this._compileNode (child); if (!childContext) continue; var prop = null; if (child.getAttribute) prop = child.getAttribute ('property'); if (prop) objectProps[prop] = childContext.id; else childs.push (childContext.id); } } return context; } ,propCompile: function (context, klass, props, node, attribute, value) { var newValue = null; var propName = attribute.replace (/-./g, this._replaceFunc); var propInfo = klass.Properties[propName]; if (!propInfo) { console.warn ('Vn.Builder: Attribute \'%s\' not valid for tag \'%s\'', attribute, node.tagName); return; } if (!value) { console.warn ('Vn.Builder: Attribute \'%s\' empty on tag \'%s\'', attribute, node.tagName); return; } switch (propInfo.type) { case Boolean: newValue = (/^(true|1)$/i).test (value); break; case Number: newValue = 0 + new Number (value); break; case String: newValue = this._translateValue (value); break; case Function: newValue = this._getMethod (value); break; default: if (propInfo.enumType) newValue = propInfo.enumType[value]; break; } if (newValue !== null && newValue !== undefined) { props[propName] = newValue; } else if (propInfo.type instanceof Function) { this.propLinks.push ({ context: context, prop: attribute, value: value }); } else console.warn ('Vn.Builder: Attribute \'%s\' invalid for tag \'%s\'', attribute, node.tagName); } ,objectInstantiate: function (context) { if (!context.klass) return null; return new context.klass (context.props); } ,objectLink: function (context, object, objects, res) { if (!context.klass) return null; var objectProps = context.objectProps; for (var prop in objectProps) object[prop] = objects[objectProps[prop]]; var childs = context.childs; for (var i = 0; i < childs.length; i++) object.appendChild (objects[childs[i]]); var events = context.events; for (var event in events) object.on (event, events[event].bind (this.signalData)); if (context.custom) object.loadXml (res, context.custom); } /** * Creates a HTML node context. **/ ,elementCompile: function (node, tagName) { var handler; var events = {}; var childs = []; var htmlNode = this.document.createElement (tagName); var a = node.attributes; for (var i = 0; i < a.length; i++) { var attribute = a[i].nodeName; var value = a[i].nodeValue; if ((handler = this._getEventHandler (attribute, value))) { events[attribute.substr (3)] = handler; } else if (attribute !== 'id') htmlNode.setAttribute (attribute, this._translateValue (value)); } var childNodes = node.childNodes; if (childNodes) for (var i = 0; i < childNodes.length; i++) { var childContext = this._compileNode (childNodes[i]); if (childContext) childs.push (childContext.id); } return { node: htmlNode, events: events, childs: childs }; } ,elementInstantiate: function (context) { if (!context.node) return null; return context.node.cloneNode (false); } ,elementLink: function (context, object, objects) { if (!context.node) return null; var childs = context.childs; for (var i = 0; i < childs.length; i++) { var child = objects[childs[i]]; if (child instanceof Htk.Widget) object.appendChild (child.getNode ()); else if (child instanceof Node) object.appendChild (child); } var events = context.events; for (var event in events) object.addEventListener (event, events[event].bind (this.signalData)); } ,_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; var method; try { method = eval (methodName); } catch (e) { method = null; } if (method == null) console.warn ('Vn.Builder: Function \'%s\' not found', value); return method; } ,_getEventHandler: function (attribute, value) { if (!(/^on-\w+/.test (attribute))) return null; return this._getMethod (value); } ,_replaceFunc: function (token) { return token.charAt(1).toUpperCase (); } }); var BuilderResult = new Class ({ Extends: Vn.Object ,initialize: function (builder, objects) { this.builder = builder; this.objects = objects; } ,getMain: function () { return this.builder.getMain (this); } ,$: function (objectId) { return this.builder.getById (objectId, this); } ,getById: function (objectId) { return this.builder.getById (objectId, this); } ,getByTagName: function (tagName) { return this.builder.getByTagName (tagName, this); } ,_destroy: function () { var objects = this.objects; for (var i = 0; i < objects.length; i++) if (objects[i].unref) objects[i].unref (); this.parent (); } });