var VnObject = require ('./object'); var Scope = require ('./scope'); var kebabToCamel = require ('./string-util').kebabToCamel; var CompilerObject = require ('./compiler-object'); var CompilerElement = require ('./compiler-element'); var CompilerText = require ('./compiler-text'); var regCompilers = [ CompilerObject, CompilerElement, CompilerText ]; /** * Creates a object from a XML specification. */ module.exports = new Class ({ Extends: VnObject ,_contexts: null /** * Compiles an XML file. * * @param {String} path The XML path * @return {Boolean} %true on success, %false othersise */ ,compileFile: function (path) { this._path = path; return this.compileDocument (Vn.getXml (path)); } /** * Compiles an XML string. * * @param {String} xmlString The XML string * @return {Boolean} %true on success, %false othersise */ ,compileString: function (xmlString) { var parser = new DOMParser (); var doc = parser.parseFromString (xmlString, 'text/xml'); return this.compileDocument (doc); } /** * Compiles a XML document. * * @param {Document} doc The DOM document * @return {Boolean} %true on success, %false othersise */ ,compileDocument: function (doc) { if (!doc) return false; this._preCompile (); var docElement = doc.documentElement; if (docElement.tagName !== 'vn') { this.showError ('The toplevel tag should be named \'vn\''); this._contexts = null; return false; } var childs = docElement.childNodes; if (childs) for (var i = 0; i < childs.length; i++) this._compile (childs[i]); this._postCompile (); return true; } /** * Compiles a single DOM node. * * @param {Node} path The DOM node * @return {Boolean} %true on success, %false othersise */ ,compileNode: function (node) { this._preCompile (); this._mainContext = this._compile (node).id; this._postCompile (); return true; } /** * Called before starting to compile nodes. */ ,_preCompile: function () { this._path = null; this._tags = {}; this._contexts = []; this._contextMap = {}; this._mainContext = null; var compilers = []; this._compilers = compilers; for (var i = 0; i < regCompilers.length; i++) compilers.push (new regCompilers[i] (this)); } /** * Called after all nodes have been compiled. */ ,_postCompile: function () { var compilers = this._compilers; for (var i = 0; i < compilers.length; i++) compilers[i].postCompile (this._contextMap); } /** * Compiles a node. */ ,_compile: function (node) { var context = null; var tagName = null; var isElement = node.nodeType === Node.ELEMENT_NODE; if (isElement) tagName = node.tagName.toLowerCase (); else if (node.nodeType !== Node.TEXT_NODE || /^[\n\r\t]*$/.test (node.textContent)) return null; var compilers = this._compilers; for (var i = 0; i < compilers.length && context === null; i++) context = compilers[i].compile (this, node, tagName); context.id = this._contexts.length; context.compiler = compilers[i - 1]; if (isElement) { var nodeId = node.getAttribute ('id'); if (nodeId) { this._contextMap[kebabToCamel (nodeId)] = context.id; context.nodeId = nodeId; } var tags = this._tags[tagName]; if (!tags) this._tags[tagName] = tags = []; tags.push (context.id); } this._contexts.push (context); return context; } /** * Creates a new scope from a compiled XML tree. * * @param {Document} dstDocument The document used to create the nodes * @param {Object} signalData The object where to bind methods * @param {Scope} parentScope The parent scope or %null for no parent * @param {Lot} lot The default lot * @return {Scope} The created scope */ ,load: function (dstDocument, signalData, parentScope, extraObjects, lot) { if (this._contexts === null) return null; var doc = dstDocument ? dstDocument : document; var contexts = this._contexts; var len = contexts.length; var objects = new Array (len); var scope = new Scope (this, objects, signalData, parentScope, lot) for (var i = 0; i < len; i++) { var context = contexts[i]; objects[i] = context.compiler.instantiate (doc, context, scope); } scope.init (extraObjects); return scope; } /** * Links all scope objects and connects it's events. */ ,link: function (scope) { var contexts = this._contexts; var objects = scope.objects; var compilers = this._compilers; for (var i = 0; i < compilers.length; i++) compilers[i].preLink (scope); for (var i = 0; i < contexts.length; i++) { var context = contexts[i]; context.compiler.link (context, objects[i], objects, scope); } for (var i = 0; i < contexts.length; i++) { var context = contexts[i]; context.compiler.connect (context, objects[i], objects, scope); } for (var i = 0; i < compilers.length; i++) compilers[i].postLink (scope); } /** * Logs an error parsing the node. * * @param {String} error The error message template * @param {...} varArgs The message template arguments */ ,showError: function (error) { var path = this._path ? this._path : ''; var logArgs = ['%s: '+ error, path]; for (var i = 1; i < arguments.length; i++) logArgs.push (arguments[i]); console.warn.apply (console, logArgs); } ,getMain: function (result) { return result.objects[this._mainContext]; } ,getByTagName: function (scope, tagName) { var tags = this._tags[tagName]; if (tags) { var arr = new Array (tags.length); for (var i = 0; i < tags.length; i++) arr[i] = scope.objects[tags[i]]; return arr; } return []; } });