2011-05-04 09:29:26 +00:00
|
|
|
// TODO: trim support
|
2011-05-17 10:46:28 +00:00
|
|
|
// TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650
|
2011-05-04 09:29:26 +00:00
|
|
|
var QWeb2 = {
|
|
|
|
expressions_cache: {},
|
2011-05-26 09:57:48 +00:00
|
|
|
reserved_words: 'true,false,NaN,null,undefined,debugger,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object,Date'.split(','),
|
2011-05-04 09:42:19 +00:00
|
|
|
actions_precedence: 'foreach,if,call,set,esc,escf,raw,rawf,js,debug,log'.split(','),
|
2011-05-04 09:29:26 +00:00
|
|
|
word_replacement: {
|
|
|
|
'and': '&&',
|
|
|
|
'or': '||',
|
|
|
|
'gt': '>',
|
|
|
|
'gte': '>=',
|
|
|
|
'lt': '<',
|
|
|
|
'lte': '<='
|
|
|
|
},
|
|
|
|
tools: {
|
|
|
|
exception: function(message, context) {
|
|
|
|
context = context || {};
|
|
|
|
var prefix = 'QWeb2';
|
|
|
|
if (context.template) {
|
|
|
|
prefix += " - template['" + context.template + "']";
|
|
|
|
}
|
|
|
|
throw prefix + ": " + message;
|
|
|
|
},
|
2011-05-05 09:38:53 +00:00
|
|
|
trim: function(s, mode) {
|
|
|
|
switch (mode) {
|
|
|
|
case "left":
|
|
|
|
return s.replace(/^\s*/, "");
|
|
|
|
case "right":
|
|
|
|
return s.replace(/\s*$/, "");
|
|
|
|
default:
|
|
|
|
return s.replace(/^\s*|\s*$/g, "");
|
|
|
|
}
|
|
|
|
},
|
2011-05-04 14:38:40 +00:00
|
|
|
js_escape: function(s, noquotes) {
|
|
|
|
return (noquotes ? '' : "'") + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + (noquotes ? '' : "'");
|
2011-05-04 09:29:26 +00:00
|
|
|
},
|
|
|
|
html_escape: function(s, attribute) {
|
2011-06-21 09:18:36 +00:00
|
|
|
if (s == null) {
|
2011-05-04 09:29:26 +00:00
|
|
|
return '';
|
|
|
|
}
|
2011-06-21 09:18:36 +00:00
|
|
|
s = String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
2011-05-04 09:29:26 +00:00
|
|
|
if (attribute) {
|
|
|
|
s = s.replace(/"/g, '"');
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
},
|
|
|
|
gen_attribute: function(o) {
|
|
|
|
if (o !== null && o !== undefined) {
|
|
|
|
if (o.constructor === Array) {
|
|
|
|
if (o[1] !== null && o[1] !== undefined) {
|
2011-06-22 10:06:15 +00:00
|
|
|
return ' ' + o[0] + '="' + this.html_escape(o[1], true) + '"';
|
2011-05-04 09:29:26 +00:00
|
|
|
}
|
|
|
|
} else if (typeof o === 'object') {
|
|
|
|
var r = '';
|
|
|
|
for (var k in o) {
|
|
|
|
if (o.hasOwnProperty(k)) {
|
|
|
|
r += this.gen_attribute([k, o[k]]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
},
|
|
|
|
extend: function(dst, src, exclude) {
|
|
|
|
for (var p in src) {
|
2011-05-04 10:43:57 +00:00
|
|
|
if (src.hasOwnProperty(p) && !(exclude && this.arrayIndexOf(exclude, p) !== -1)) {
|
2011-05-04 09:29:26 +00:00
|
|
|
dst[p] = src[p];
|
|
|
|
}
|
|
|
|
}
|
2011-05-04 09:49:31 +00:00
|
|
|
return dst;
|
2011-05-04 09:29:26 +00:00
|
|
|
},
|
|
|
|
arrayIndexOf : function(array, item) {
|
|
|
|
for (var i = 0, ilen = array.length; i < ilen; i++) {
|
|
|
|
if (array[i] === item) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
},
|
2011-05-09 10:04:45 +00:00
|
|
|
xml_node_to_string : function(node, childs_only) {
|
|
|
|
if (childs_only) {
|
|
|
|
var childs = node.childNodes, r = [];
|
|
|
|
for (var i = 0, ilen = childs.length; i < ilen; i++) {
|
|
|
|
r.push(this.xml_node_to_string(childs[i]));
|
|
|
|
}
|
|
|
|
return r.join('');
|
|
|
|
} else {
|
|
|
|
if (node.xml !== undefined) {
|
|
|
|
return node.xml;
|
|
|
|
} else {
|
|
|
|
return (new XMLSerializer()).serializeToString(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
xpath_selectNodes : function(node, expr) {
|
|
|
|
if (node.selectNodes !== undefined) {
|
|
|
|
return node.selectNodes(expr);
|
|
|
|
} else {
|
|
|
|
var xpath = new XPathEvaluator();
|
|
|
|
try {
|
|
|
|
var result = xpath.evaluate(expr, node, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
|
|
|
} catch(error) {
|
|
|
|
this.exception("Error with XPath expression '" + expr + "' : " + error);
|
|
|
|
}
|
|
|
|
var r = [];
|
|
|
|
if (result != null) {
|
|
|
|
var element;
|
|
|
|
while (element = result.iterateNext()) {
|
|
|
|
r.push(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
},
|
2011-05-04 09:29:26 +00:00
|
|
|
call: function(context, template, old_dict, _import, callback) {
|
|
|
|
var new_dict = this.extend({}, old_dict);
|
|
|
|
new_dict['__caller__'] = old_dict['__template__'];
|
|
|
|
if (callback) {
|
2011-05-10 13:52:43 +00:00
|
|
|
new_dict['__content__'] = callback(context, new_dict);
|
2011-05-04 09:29:26 +00:00
|
|
|
}
|
|
|
|
var r = context.engine._render(template, new_dict);
|
|
|
|
if (_import) {
|
|
|
|
if (_import === '*') {
|
|
|
|
this.extend(old_dict, new_dict, ['__caller__', '__template__']);
|
|
|
|
} else {
|
|
|
|
_import = _import.split(',');
|
|
|
|
for (var i = 0, ilen = _import.length; i < ilen; i++) {
|
|
|
|
var v = _import[i];
|
|
|
|
old_dict[v] = new_dict[v];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
},
|
|
|
|
foreach: function(context, enu, as, old_dict, callback) {
|
|
|
|
if (enu != null) {
|
|
|
|
var size, new_dict = this.extend({}, old_dict);
|
|
|
|
new_dict[as + "_all"] = enu;
|
|
|
|
var as_value = as + "_value",
|
|
|
|
as_index = as + "_index",
|
|
|
|
as_first = as + "_first",
|
|
|
|
as_last = as + "_last",
|
|
|
|
as_parity = as + "_parity";
|
|
|
|
if (size = enu.length) {
|
|
|
|
new_dict[as + "_size"] = size;
|
|
|
|
for (var j = 0, jlen = enu.length; j < jlen; j++) {
|
|
|
|
var cur = enu[j];
|
|
|
|
new_dict[as_value] = cur;
|
|
|
|
new_dict[as_index] = j;
|
|
|
|
new_dict[as_first] = j === 0;
|
|
|
|
new_dict[as_last] = j + 1 === size;
|
|
|
|
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
|
|
|
|
if (cur.constructor === Object) {
|
|
|
|
this.extend(new_dict, cur);
|
|
|
|
}
|
|
|
|
new_dict[as] = cur;
|
|
|
|
callback(context, new_dict);
|
|
|
|
}
|
|
|
|
} else if (enu.constructor == Number) {
|
|
|
|
var _enu = [];
|
|
|
|
for (var i = 0; i < enu; i++) {
|
|
|
|
_enu.push(i);
|
|
|
|
}
|
2011-05-04 09:49:31 +00:00
|
|
|
this.foreach(context, enu, as, old_dict, callback);
|
2011-05-04 09:29:26 +00:00
|
|
|
} else {
|
|
|
|
var index = 0;
|
|
|
|
for (var k in enu) {
|
|
|
|
if (enu.hasOwnProperty(k)) {
|
|
|
|
var v = enu[k];
|
|
|
|
new_dict[as_value] = v;
|
|
|
|
new_dict[as_index] = index;
|
|
|
|
new_dict[as_first] = index === 0;
|
|
|
|
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
|
2011-05-04 12:36:53 +00:00
|
|
|
new_dict[as] = k;
|
2011-05-04 09:29:26 +00:00
|
|
|
callback(context, new_dict);
|
2011-05-04 09:49:31 +00:00
|
|
|
index += 1;
|
2011-05-04 09:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.exception("No enumerator given to foreach", context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
QWeb2.Engine = (function() {
|
|
|
|
function Engine() {
|
|
|
|
// TODO: handle prefix at template level : t-prefix="x"
|
|
|
|
this.prefix = 't';
|
2011-05-04 14:38:40 +00:00
|
|
|
this.debug = false;
|
2011-05-04 09:29:26 +00:00
|
|
|
this.templates_resources = []; // TODO: implement this.reload()
|
|
|
|
this.templates = {};
|
|
|
|
this.compiled_templates = {};
|
2011-05-09 10:04:45 +00:00
|
|
|
this.extend_templates = {};
|
2011-05-04 09:29:26 +00:00
|
|
|
this.dict = {};
|
|
|
|
this.tools = QWeb2.tools;
|
2011-05-09 10:04:45 +00:00
|
|
|
this.jQuery = window.jQuery;
|
2011-05-04 09:29:26 +00:00
|
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
|
|
this.add_template(arguments[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QWeb2.tools.extend(Engine.prototype, {
|
|
|
|
add_template : function(template) {
|
|
|
|
this.templates_resources.push(template);
|
|
|
|
if (template.constructor === String) {
|
|
|
|
template = this.load_xml(template);
|
|
|
|
}
|
|
|
|
var ec = (template.documentElement && template.documentElement.childNodes) || template.childNodes || [];
|
|
|
|
for (var i = 0; i < ec.length; i++) {
|
|
|
|
var node = ec[i];
|
|
|
|
if (node.nodeType === 1) {
|
|
|
|
if (node.nodeName == 'parsererror') {
|
|
|
|
return this.tools.exception(node.innerText);
|
|
|
|
}
|
|
|
|
var name = node.getAttribute(this.prefix + '-name');
|
2011-05-09 10:04:45 +00:00
|
|
|
var extend = node.getAttribute(this.prefix + '-extend');
|
2011-05-09 12:19:01 +00:00
|
|
|
if (name && extend) {
|
|
|
|
// Clone template and extend it
|
|
|
|
if (!this.templates[extend]) {
|
|
|
|
return this.tools.exception("Can't clone undefined template " + extend);
|
|
|
|
}
|
|
|
|
this.templates[name] = this.templates[extend].cloneNode(true);
|
|
|
|
extend = name;
|
|
|
|
name = undefined;
|
|
|
|
}
|
2011-05-04 09:29:26 +00:00
|
|
|
if (name) {
|
|
|
|
this.templates[name] = node;
|
2011-05-09 10:04:45 +00:00
|
|
|
} else if (extend) {
|
|
|
|
delete(this.compiled_templates[extend]);
|
|
|
|
if (this.extend_templates[extend]) {
|
|
|
|
this.extend_templates[extend].push(node);
|
|
|
|
} else {
|
|
|
|
this.extend_templates[extend] = [node];
|
|
|
|
}
|
2011-05-04 09:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
load_xml : function(s) {
|
|
|
|
s = s.replace(/^\s*|\s*$/g, '');
|
2011-05-05 09:38:53 +00:00
|
|
|
if (this.tools.trim(s)[0] === '<') {
|
2011-05-04 09:29:26 +00:00
|
|
|
return this.load_xml_string(s);
|
|
|
|
} else {
|
|
|
|
var req = this.get_xhr();
|
|
|
|
if (req) {
|
|
|
|
// TODO: third parameter is async : https://developer.mozilla.org/en/XMLHttpRequest#open()
|
|
|
|
// do an on_ready in QWeb2{} that could be passed to add_template
|
|
|
|
req.open('GET', s, false);
|
|
|
|
req.send(null);
|
|
|
|
if (req.responseXML) {
|
|
|
|
if (req.responseXML.documentElement.nodeName == "parsererror") {
|
|
|
|
return this.tools.exception(req.responseXML.documentElement.childNodes[0].nodeValue);
|
|
|
|
}
|
|
|
|
return req.responseXML;
|
|
|
|
} else {
|
2011-05-04 09:49:31 +00:00
|
|
|
return this.load_xml_string(req.responseText);
|
2011-05-04 09:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
load_xml_string : function(s) {
|
|
|
|
if (window.DOMParser) {
|
|
|
|
var dp = new DOMParser();
|
|
|
|
var r = dp.parseFromString(s, "text/xml");
|
|
|
|
if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') {
|
|
|
|
return this.tools.exception(r.body.innerText);
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
var xDoc;
|
|
|
|
try {
|
|
|
|
// new ActiveXObject("Msxml2.DOMDocument.4.0");
|
|
|
|
xDoc = new ActiveXObject("MSXML2.DOMDocument");
|
|
|
|
} catch (e) {
|
|
|
|
return this.tools.exception("Could not find a DOM Parser");
|
|
|
|
}
|
|
|
|
xDoc.async = false;
|
|
|
|
xDoc.preserveWhiteSpace = true;
|
|
|
|
xDoc.loadXML(s);
|
|
|
|
return xDoc;
|
|
|
|
},
|
|
|
|
get_xhr : function() {
|
|
|
|
if (window.XMLHttpRequest) {
|
|
|
|
return new window.XMLHttpRequest();
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
return new ActiveXObject('MSXML2.XMLHTTP.3.0');
|
|
|
|
} catch (e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
compile : function(node) {
|
|
|
|
var e = new QWeb2.Element(this, node);
|
2011-05-04 09:49:31 +00:00
|
|
|
var template = node.getAttribute(this.prefix + '-name');
|
2011-05-17 10:46:28 +00:00
|
|
|
return " /* 'this' refers to Qweb2.Engine instance */\n" +
|
2011-05-04 09:29:26 +00:00
|
|
|
" var context = { engine : this, template : " + (this.tools.js_escape(template)) + " };\n" +
|
|
|
|
" dict = dict || {};\n" +
|
|
|
|
" dict['__template__'] = '" + template + "';\n" +
|
|
|
|
" var r = [];\n" +
|
|
|
|
" /* START TEMPLATE */ try {\n" +
|
|
|
|
(e.compile()) + "\n" +
|
|
|
|
" /* END OF TEMPLATE */ } catch(error) {\n" +
|
|
|
|
" context.engine.tools.exception('Runtime Error: ' + error, context);\n" +
|
|
|
|
" }\n" +
|
2011-05-17 10:46:28 +00:00
|
|
|
" return r.join('');";
|
2011-05-04 09:29:26 +00:00
|
|
|
},
|
|
|
|
render : function(template, dict) {
|
2011-05-09 10:04:45 +00:00
|
|
|
if (this.debug && window['console'] !== undefined) {
|
|
|
|
console.time("QWeb render template " + template);
|
|
|
|
}
|
2011-05-04 09:29:26 +00:00
|
|
|
var r = this._render(template, dict);
|
2011-05-09 10:04:45 +00:00
|
|
|
if (this.debug && window['console'] !== undefined) {
|
|
|
|
console.timeEnd("QWeb render template " + template);
|
|
|
|
}
|
2011-05-04 09:29:26 +00:00
|
|
|
return r;
|
|
|
|
},
|
|
|
|
_render : function(template, dict) {
|
|
|
|
if (this.compiled_templates[template]) {
|
|
|
|
return this.compiled_templates[template].apply(this, [dict || {}]);
|
|
|
|
} else if (this.templates[template]) {
|
2011-05-09 10:04:45 +00:00
|
|
|
var ext;
|
|
|
|
if (ext = this.extend_templates[template]) {
|
|
|
|
var extend_node;
|
|
|
|
while (extend_node = ext.shift()) {
|
|
|
|
this.extend(template, extend_node);
|
|
|
|
}
|
|
|
|
}
|
2011-05-04 09:29:26 +00:00
|
|
|
var code = this.compile(this.templates[template]), tcompiled;
|
|
|
|
try {
|
2011-05-17 10:46:28 +00:00
|
|
|
tcompiled = new Function(['dict'], code);
|
2011-05-04 09:29:26 +00:00
|
|
|
} catch (error) {
|
2011-05-17 10:46:28 +00:00
|
|
|
if (this.debug && window.console) {
|
|
|
|
console.log(code);
|
|
|
|
}
|
2011-05-04 09:29:26 +00:00
|
|
|
this.tools.exception("Error evaluating template: " + error, { template: name });
|
|
|
|
}
|
2011-05-17 10:46:28 +00:00
|
|
|
if (!tcompiled) {
|
|
|
|
this.tools.exception("Error evaluating template: (IE?)" + error, { template: name });
|
|
|
|
}
|
2011-05-04 09:29:26 +00:00
|
|
|
this.compiled_templates[template] = tcompiled;
|
|
|
|
return this.render(template, dict);
|
|
|
|
} else {
|
|
|
|
return this.tools.exception("Template '" + template + "' not found");
|
|
|
|
}
|
2011-05-09 10:04:45 +00:00
|
|
|
},
|
|
|
|
extend : function(template, extend_node) {
|
|
|
|
if (!this.jQuery) {
|
|
|
|
return this.tools.exception("Can't extend template " + template + " without jQuery");
|
|
|
|
}
|
|
|
|
for (var i = 0, ilen = extend_node.childNodes.length; i < ilen; i++) {
|
|
|
|
var child = extend_node.childNodes[i];
|
|
|
|
if (child.nodeType === 1) {
|
|
|
|
var xpath = child.getAttribute(this.prefix + '-xpath'),
|
|
|
|
jquery = child.getAttribute(this.prefix + '-jquery'),
|
|
|
|
operation = child.getAttribute(this.prefix + '-operation'),
|
|
|
|
target,
|
|
|
|
error_msg = "Error while extending template '" + template;
|
|
|
|
if (jquery) {
|
|
|
|
target = this.jQuery(jquery, this.templates[template]);
|
|
|
|
} else if (xpath) {
|
|
|
|
// NOTE: due to the XPath implementation, extending a template will only work once
|
|
|
|
// when using XPath because XPathResult won't match objects with other constructor than 'Element'
|
|
|
|
// As jQuery will create HTMLElements, xpath will ignore them then causing problems when a
|
|
|
|
// template has already been extended. XPath selectors will probably be removed in QWeb.
|
|
|
|
target = this.jQuery(this.tools.xpath_selectNodes(this.templates[template], xpath));
|
|
|
|
} else {
|
|
|
|
this.tools.exception(error_msg + "No expression given");
|
|
|
|
}
|
|
|
|
error_msg += "' (expression='" + (jquery || xpath) + "') : ";
|
|
|
|
var inner = this.tools.xml_node_to_string(child, true);
|
|
|
|
if (operation) {
|
|
|
|
var allowed_operations = "append,prepend,before,after,replace,inner".split(',');
|
|
|
|
if (this.tools.arrayIndexOf(allowed_operations, operation) == -1) {
|
|
|
|
this.tools.exception(error_msg + "Invalid operation : '" + operation + "'");
|
|
|
|
}
|
|
|
|
operation = {'replace' : 'replaceWith', 'inner' : 'html'}[operation] || operation;
|
2011-05-26 09:34:52 +00:00
|
|
|
target[operation](this.jQuery(inner));
|
2011-05-09 10:04:45 +00:00
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
var f = new Function(['$'], inner);
|
|
|
|
} catch(error) {
|
|
|
|
return this.tools.exception("Parse " + error_msg + error);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
f.apply(target, [this.jQuery]);
|
|
|
|
} catch(error) {
|
|
|
|
return this.tools.exception("Runtime " + error_msg + error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-05-04 09:29:26 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return Engine;
|
|
|
|
})();
|
|
|
|
|
|
|
|
QWeb2.Element = (function() {
|
|
|
|
function Element(engine, node) {
|
|
|
|
this.engine = engine;
|
|
|
|
this.node = node;
|
|
|
|
this.tag = node.tagName;
|
|
|
|
this.actions = {};
|
|
|
|
this.actions_done = [];
|
|
|
|
this.attributes = {};
|
|
|
|
this.children = [];
|
|
|
|
this._top = [];
|
|
|
|
this._bottom = [];
|
|
|
|
this._indent = 1;
|
|
|
|
this.process_children = true;
|
|
|
|
var childs = this.node.childNodes;
|
|
|
|
if (childs) {
|
|
|
|
for (var i = 0, ilen = childs.length; i < ilen; i++) {
|
|
|
|
this.children.push(new QWeb2.Element(this.engine, childs[i]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var attrs = this.node.attributes;
|
|
|
|
if (attrs) {
|
|
|
|
for (var j = 0, jlen = attrs.length; j < jlen; j++) {
|
|
|
|
var attr = attrs[j];
|
|
|
|
var name = attr.name;
|
2011-05-04 09:49:31 +00:00
|
|
|
var m = name.match(new RegExp("^" + this.engine.prefix + "-(.+)"));
|
2011-05-04 09:29:26 +00:00
|
|
|
if (m) {
|
|
|
|
name = m[1];
|
|
|
|
if (name === 'name') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.actions[name] = attr.value;
|
|
|
|
} else {
|
|
|
|
this.attributes[name] = attr.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QWeb2.tools.extend(Element.prototype, {
|
|
|
|
compile : function() {
|
2011-05-04 14:38:40 +00:00
|
|
|
var r = [],
|
|
|
|
instring = false,
|
|
|
|
lines = this._compile().split('\n');
|
|
|
|
for (var i = 0, ilen = lines.length; i < ilen; i++) {
|
|
|
|
var m, line = lines[i];
|
|
|
|
if (m = line.match(/^(\s*)\/\/@string=(.*)/)) {
|
|
|
|
if (instring) {
|
|
|
|
if (this.engine.debug) {
|
|
|
|
// Split string lines in indented r.push arguments
|
|
|
|
r.push((m[2].indexOf("\\n") != -1 ? "',\n\t" + m[1] + "'" : '') + m[2]);
|
|
|
|
} else {
|
|
|
|
r.push(m[2]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
r.push(m[1] + "r.push('" + m[2]);
|
|
|
|
instring = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (instring) {
|
|
|
|
r.push("');\n");
|
|
|
|
}
|
|
|
|
instring = false;
|
|
|
|
r.push(line + '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r.join('');
|
|
|
|
},
|
|
|
|
_compile : function() {
|
2011-05-04 09:29:26 +00:00
|
|
|
switch (this.node.nodeType) {
|
|
|
|
case 3:
|
|
|
|
case 4:
|
|
|
|
this.top_string(this.node.data);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
this.compile_element();
|
|
|
|
}
|
2011-05-04 14:38:40 +00:00
|
|
|
var r = this._top.join('');
|
2011-05-04 09:29:26 +00:00
|
|
|
if (this.process_children) {
|
|
|
|
for (var i = 0, ilen = this.children.length; i < ilen; i++) {
|
|
|
|
var child = this.children[i];
|
|
|
|
child._indent = this._indent;
|
2011-05-04 14:38:40 +00:00
|
|
|
r += child._compile();
|
2011-05-04 09:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
2011-05-04 14:38:40 +00:00
|
|
|
r += this._bottom.join('');
|
2011-05-04 09:29:26 +00:00
|
|
|
return r;
|
|
|
|
},
|
|
|
|
format_expression : function(e) {
|
|
|
|
/* Naive format expression builder. Replace reserved words and variables to dict[variable]
|
|
|
|
* Does not handle spaces before dot yet, and causes problems for anonymous functions. Use t-js="" for that */
|
|
|
|
if (QWeb2.expressions_cache[e]) {
|
|
|
|
return QWeb2.expressions_cache[e];
|
|
|
|
}
|
|
|
|
var chars = e.split(''),
|
|
|
|
instring = '',
|
|
|
|
invar = '',
|
|
|
|
invar_pos = 0,
|
|
|
|
r = '';
|
|
|
|
chars.push(' ');
|
|
|
|
for (var i = 0, ilen = chars.length; i < ilen; i++) {
|
|
|
|
var c = chars[i];
|
|
|
|
if (instring.length) {
|
|
|
|
if (c === instring && chars[i - 1] !== "\\") {
|
|
|
|
instring = '';
|
|
|
|
}
|
|
|
|
} else if (c === '"' || c === "'") {
|
|
|
|
instring = c;
|
|
|
|
} else if (c.match(/[a-zA-Z_\$]/) && !invar.length) {
|
|
|
|
invar = c;
|
|
|
|
invar_pos = i;
|
|
|
|
continue;
|
|
|
|
} else if (c.match(/\W/) && invar.length) {
|
|
|
|
// TODO: Should check for possible spaces before dot
|
|
|
|
if (chars[invar_pos - 1] !== '.' && QWeb2.tools.arrayIndexOf(QWeb2.reserved_words, invar) < 0) {
|
|
|
|
invar = QWeb2.word_replacement[invar] || ("dict['" + invar + "']");
|
|
|
|
}
|
|
|
|
r += invar;
|
|
|
|
invar = '';
|
|
|
|
} else if (invar.length) {
|
|
|
|
invar += c;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
r += c;
|
|
|
|
}
|
|
|
|
r = r.slice(0, -1);
|
|
|
|
QWeb2.expressions_cache[e] = r;
|
|
|
|
return r;
|
|
|
|
},
|
|
|
|
string_interpolation : function(s) {
|
|
|
|
if (!s) {
|
|
|
|
return "''";
|
|
|
|
}
|
|
|
|
var regex = /^{(.*)}(.*)/,
|
2011-05-04 09:49:31 +00:00
|
|
|
src = s.split(/#/),
|
2011-05-04 09:29:26 +00:00
|
|
|
r = [];
|
|
|
|
for (var i = 0, ilen = src.length; i < ilen; i++) {
|
|
|
|
var val = src[i],
|
2011-05-04 09:49:31 +00:00
|
|
|
m = val.match(regex);
|
2011-05-04 09:29:26 +00:00
|
|
|
if (m) {
|
|
|
|
r.push("(" + this.format_expression(m[1]) + ")");
|
|
|
|
if (m[2]) {
|
|
|
|
r.push(this.engine.tools.js_escape(m[2]));
|
|
|
|
}
|
|
|
|
} else if (!(i === 0 && val === '')) {
|
|
|
|
r.push(this.engine.tools.js_escape((i === 0 ? '' : '#') + val));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r.join(' + ');
|
|
|
|
},
|
|
|
|
indent : function() {
|
|
|
|
return this._indent++;
|
|
|
|
},
|
|
|
|
dedent : function() {
|
|
|
|
if (this._indent !== 0) {
|
|
|
|
return this._indent--;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
get_indent : function() {
|
|
|
|
return new Array(this._indent + 1).join("\t");
|
|
|
|
},
|
|
|
|
top : function(s) {
|
|
|
|
return this._top.push(this.get_indent() + s + '\n');
|
|
|
|
},
|
|
|
|
top_string : function(s) {
|
2011-05-04 14:38:40 +00:00
|
|
|
return this._top.push(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
|
2011-05-04 09:29:26 +00:00
|
|
|
},
|
2011-05-04 09:49:31 +00:00
|
|
|
bottom : function(s) {
|
2011-05-04 09:29:26 +00:00
|
|
|
return this._bottom.unshift(this.get_indent() + s + '\n');
|
|
|
|
},
|
|
|
|
bottom_string : function(s) {
|
2011-05-04 14:38:40 +00:00
|
|
|
return this._bottom.unshift(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
|
2011-05-04 09:29:26 +00:00
|
|
|
},
|
|
|
|
compile_element : function() {
|
|
|
|
for (var i = 0, ilen = QWeb2.actions_precedence.length; i < ilen; i++) {
|
|
|
|
var a = QWeb2.actions_precedence[i];
|
|
|
|
if (a in this.actions) {
|
|
|
|
var value = this.actions[a];
|
|
|
|
var key = 'compile_action_' + a;
|
|
|
|
if (!this[key]) {
|
|
|
|
this.engine.tools.exception("No handler method for action '" + a + "'");
|
|
|
|
}
|
|
|
|
this[key](value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.tag !== this.engine.prefix) {
|
|
|
|
var tag = "<" + this.tag;
|
|
|
|
for (var a in this.attributes) {
|
|
|
|
tag += this.engine.tools.gen_attribute([a, this.attributes[a]]);
|
|
|
|
}
|
|
|
|
this.top_string(tag);
|
|
|
|
if (this.actions.att) {
|
|
|
|
this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(this.actions.att)) + "));");
|
|
|
|
}
|
|
|
|
for (var a in this.actions) {
|
|
|
|
var v = this.actions[a];
|
|
|
|
var m = a.match(/att-(.+)/);
|
|
|
|
if (m) {
|
|
|
|
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.format_expression(v)) + ")]));");
|
|
|
|
}
|
|
|
|
var m = a.match(/attf-(.+)/);
|
|
|
|
if (m) {
|
|
|
|
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.string_interpolation(v)) + ")]));");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.children.length || this.actions.opentag === 'true') {
|
|
|
|
this.top_string(">");
|
|
|
|
this.bottom_string("</" + this.tag + ">");
|
|
|
|
} else {
|
|
|
|
this.top_string("/>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
compile_action_if : function(value) {
|
|
|
|
this.top("if (" + (this.format_expression(value)) + ") {");
|
|
|
|
this.bottom("}");
|
|
|
|
this.indent();
|
|
|
|
},
|
|
|
|
compile_action_foreach : function(value) {
|
|
|
|
var as = this.actions['as'] || value.replace(/[^a-zA-Z0-9]/g, '_');
|
|
|
|
//TODO: exception if t-as not valid
|
|
|
|
this.top("context.engine.tools.foreach(context, " + (this.format_expression(value)) + ", " + (this.engine.tools.js_escape(as)) + ", dict, function(context, dict) {");
|
|
|
|
this.bottom("});");
|
|
|
|
this.indent();
|
|
|
|
},
|
|
|
|
compile_action_call : function(value) {
|
2011-05-04 09:49:31 +00:00
|
|
|
var _import = this.actions['import'] || '';
|
2011-05-04 09:29:26 +00:00
|
|
|
if (this.children.length === 0) {
|
|
|
|
return this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + "));");
|
|
|
|
} else {
|
|
|
|
this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + ", function(context, dict) {");
|
|
|
|
this.bottom("}));");
|
|
|
|
this.indent();
|
|
|
|
this.top("var r = [];");
|
|
|
|
return this.bottom("return r.join('');");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
compile_action_set : function(value) {
|
2011-05-26 09:57:48 +00:00
|
|
|
var variable = this.format_expression(value);
|
2011-05-04 09:29:26 +00:00
|
|
|
if (this.actions['value']) {
|
2011-05-26 09:57:48 +00:00
|
|
|
this.top(variable + " = (" + (this.format_expression(this.actions['value'])) + ");");
|
2011-05-04 09:29:26 +00:00
|
|
|
this.process_children = false;
|
|
|
|
} else {
|
|
|
|
if (this.children.length === 0) {
|
2011-05-26 09:57:48 +00:00
|
|
|
this.top(variable + " = '';");
|
2011-05-04 09:29:26 +00:00
|
|
|
} else if (this.children.length === 1 && this.children[0].node.nodeType === 3) {
|
2011-05-26 09:57:48 +00:00
|
|
|
this.top(variable + " = " + (this.engine.tools.js_escape(this.children[0].node.data)) + ";");
|
2011-05-04 09:29:26 +00:00
|
|
|
this.process_children = false;
|
|
|
|
} else {
|
2011-05-26 09:57:48 +00:00
|
|
|
this.top(variable + " = (function(dict) {");
|
2011-05-04 09:29:26 +00:00
|
|
|
this.bottom("})(dict);");
|
|
|
|
this.indent();
|
|
|
|
this.top("var r = [];");
|
|
|
|
this.bottom("return r.join('');");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
compile_action_esc : function(value) {
|
|
|
|
this.top("r.push(context.engine.tools.html_escape(" + (this.format_expression(value)) + "));");
|
|
|
|
},
|
|
|
|
compile_action_escf : function(value) {
|
|
|
|
this.top("r.push(context.engine.tools.html_escape(" + (this.string_interpolation(value)) + "));");
|
|
|
|
},
|
|
|
|
compile_action_raw : function(value) {
|
|
|
|
this.top("r.push(" + (this.format_expression(value)) + ");");
|
|
|
|
},
|
|
|
|
compile_action_rawf : function(value) {
|
|
|
|
this.top("r.push(" + (this.string_interpolation(value)) + ");");
|
|
|
|
},
|
|
|
|
compile_action_js : function(value) {
|
|
|
|
this.top("(function(" + value + ") {");
|
|
|
|
this.bottom("})(dict);");
|
|
|
|
this.indent();
|
|
|
|
if (this.children.length === 1) {
|
|
|
|
var lines = this.children[0].node.data.split(/\r?\n/);
|
|
|
|
for (var i = 0, ilen = lines.length; i < ilen; i++) {
|
|
|
|
this.top(lines[i]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.engine.tools.exception("'js' code block contains " + this.children.length + " nodes instead of 1");
|
|
|
|
}
|
|
|
|
// Maybe I could handle the children ?
|
|
|
|
this.process_children = false;
|
|
|
|
},
|
|
|
|
compile_action_debug : function(value) {
|
|
|
|
this.top("debugger;");
|
|
|
|
},
|
|
|
|
compile_action_log : function(value) {
|
|
|
|
this.top("console.log(" + this.format_expression(value) + "});");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return Element;
|
|
|
|
})();
|