hedera-web/js/vn/builder.js

265 lines
5.6 KiB
JavaScript

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 : '<unknown template>';
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 [];
}
});