const VnObject = require('./object'); const Scope = require('./scope'); const kebabToCamel = require('./string-util').kebabToCamel; const CompilerObject = require('./compiler-object'); const CompilerElement = require('./compiler-element'); const CompilerText = require('./compiler-text'); const regCompilers = [ CompilerText, CompilerObject, CompilerElement ]; /** * Creates a object from a XML specification. */ module.exports = new Class({ Extends: VnObject /** * Compiles an XML file. * * @param {String} path The XML path * @return {Boolean} %true on success, %false othersise */ ,compileFile(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(xmlString) { const parser = new DOMParser(); const 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(doc) { if (!doc) return false; this._preCompile(); const docElement = doc.documentElement; if (docElement.tagName !== 'vn') { this.showError('The toplevel tag should be named \'vn\''); this._contexts = null; return false; } const childs = docElement.childNodes; if (childs) for (let i = 0; i < childs.length; i++) this._compile(childs[i]); this._postCompile(); return true; } /** * Compiles a single DOM node. * * @path Node The DOM node * @return %true on success, %false othersise */ ,compileNode(node) { this._preCompile(); this._mainContext = this._compile(node).id; this._postCompile(); return true; } /** * Called before starting to compile nodes. */ ,_preCompile() { this._path = null; this._tags = {}; this._contexts = []; this._exprContexts = []; this._contextMap = {}; this._links = []; this._mainContext = null; this._compilers = []; for (regCompiler of regCompilers) this._compilers.push(new regCompiler(this)); } /** * Called after all nodes have been compiled. */ ,_postCompile() { for (const compiler of this._compilers) compiler.postCompile(this._contextMap); } /** * Compiles a node. */ ,_compile(node) { let context = null; let tagName = null; const 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; let i; const compilers = this._compilers; for (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) { const nodeId = node.getAttribute('id'); if (nodeId) { this._contextMap[kebabToCamel(nodeId)] = context.id; context.nodeId = nodeId; } let tags = this._tags[tagName]; if (!tags) this._tags[tagName] = tags = []; tags.push(context.id); } this._contexts.push(context); return context; } ,load(dstDocument, thisArg, parentScope) { if (!this._contexts) return null; const doc = dstDocument ? dstDocument : document; const objects = new Array(this._contexts.length); const exprValues = new Array(this._exprContexts.length); return new Scope(this, doc, objects, exprValues, thisArg, parentScope); } ,showError(error) { const path = this._path ? this._path : 'Node'; const logArgs = ['Vn.Builder: %s: '+ error, path]; for (let i = 1; i < arguments.length; i++) logArgs.push(arguments[i]); console.warn.apply(null, logArgs); } });