summaryrefslogtreecommitdiff
path: root/addons/web/static/lib/qweb/qweb2.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/lib/qweb/qweb2.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/lib/qweb/qweb2.js')
-rw-r--r--addons/web/static/lib/qweb/qweb2.js861
1 files changed, 861 insertions, 0 deletions
diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js
new file mode 100644
index 00000000..d2a367f1
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb2.js
@@ -0,0 +1,861 @@
+/*
+Copyright (c) 2013, Fabien Meghazi
+
+Released under the MIT license
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+// TODO: trim support
+// TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650
+// TODO: templates orverwritten could be called by t-call="__super__" ?
+// TODO: t-set + t-value + children node == scoped variable ?
+var QWeb2 = {
+ expressions_cache: { },
+ RESERVED_WORDS: 'true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date'.split(','),
+ ACTIONS_PRECEDENCE: 'foreach,if,elif,else,call,set,tag,esc,raw,js,debug,log'.split(','),
+ WORD_REPLACEMENT: {
+ 'and': '&&',
+ 'or': '||',
+ 'gt': '>',
+ 'gte': '>=',
+ 'lt': '<',
+ 'lte': '<='
+ },
+ VOID_ELEMENTS: 'area,base,br,col,embed,hr,img,input,keygen,link,menuitem,meta,param,source,track,wbr'.split(','),
+ tools: {
+ exception: function(message, context) {
+ context = context || {};
+ var prefix = 'QWeb2';
+ if (context.template) {
+ prefix += " - template['" + context.template + "']";
+ }
+ throw new Error(prefix + ": " + message);
+ },
+ warning : function(message) {
+ if (typeof(window) !== 'undefined' && window.console) {
+ window.console.warn(message);
+ }
+ },
+ 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, "");
+ }
+ },
+ js_escape: function(s, noquotes) {
+ return (noquotes ? '' : "'") + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + (noquotes ? '' : "'");
+ },
+ html_escape: function(s, attribute) {
+ if (s == null) {
+ return '';
+ }
+ s = String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ if (attribute) {
+ s = s.replace(/"/g, '&quot;');
+ }
+ return s;
+ },
+ gen_attribute: function(o) {
+ if (o !== null && o !== undefined) {
+ if (o.constructor === Array) {
+ if (o[1] !== null && o[1] !== undefined) {
+ return this.format_attribute(o[0], o[1]);
+ }
+ } 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 '';
+ },
+ format_attribute: function(name, value) {
+ return ' ' + name + '="' + this.html_escape(value, true) + '"';
+ },
+ extend: function(dst, src, exclude) {
+ for (var p in src) {
+ if (src.hasOwnProperty(p) && !(exclude && this.arrayIndexOf(exclude, p) !== -1)) {
+ dst[p] = src[p];
+ }
+ }
+ return dst;
+ },
+ arrayIndexOf : function(array, item) {
+ for (var i = 0, ilen = array.length; i < ilen; i++) {
+ if (array[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ get_element_sibling: function(node, dom_attr) {
+ // This helper keeps support for IE8 which does not
+ // implement DOMNode.(previous|next)ElementSibling
+ var sibling = node[dom_attr];
+ while (sibling && sibling.nodeType !== 1) {
+ sibling = sibling[dom_attr];
+ }
+ return sibling;
+ },
+ 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 {
+ // avoid XMLSerializer with text node for IE
+ if (node.nodeType == 3) {
+ return node.data;
+ }
+ if (typeof XMLSerializer !== 'undefined') {
+ return (new XMLSerializer()).serializeToString(node);
+ } else {
+ switch(node.nodeType) {
+ case 1: return node.outerHTML;
+ case 4: return '<![CDATA[' + node.data + ']]>';
+ case 8: return '<!-- ' + node.data + '-->';
+ }
+ throw new Error('Unknown node type ' + node.nodeType);
+ }
+ }
+ },
+ call: function(context, template, old_dict, _import, callback) {
+ var new_dict = this.extend({}, old_dict);
+ new_dict['__caller__'] = old_dict['__template__'];
+ if (callback) {
+ new_dict[0] = callback(context, new_dict);
+ }
+ return context.engine._render(template, new_dict);
+ },
+ foreach: function(context, enu, as, old_dict, callback) {
+ if (enu != null) {
+ var index, jlen, cur;
+ var 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 (enu instanceof Array) {
+ var size = enu.length;
+ new_dict[as + "_size"] = size;
+ for (index = 0, jlen = enu.length; index < jlen; index++) {
+ cur = enu[index];
+ new_dict[as_value] = cur;
+ new_dict[as_index] = index;
+ new_dict[as_first] = index === 0;
+ new_dict[as_last] = index + 1 === size;
+ new_dict[as_parity] = (index % 2 == 1 ? 'odd' : 'even');
+ if (cur && 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);
+ }
+ this.foreach(context, _enu, as, old_dict, callback);
+ } else {
+ index = 0;
+ for (var k in enu) {
+ if (enu.hasOwnProperty(k)) {
+ cur = enu[k];
+ new_dict[as_value] = cur;
+ new_dict[as_index] = index;
+ new_dict[as_first] = index === 0;
+ new_dict[as_parity] = (index % 2 == 1 ? 'odd' : 'even');
+ new_dict[as] = k;
+ callback(context, new_dict);
+ index += 1;
+ }
+ }
+ }
+
+ _.each(Object.keys(old_dict), function(z) {
+ old_dict[z] = new_dict[z];
+ });
+ } else {
+ this.exception("No enumerator given to foreach", context);
+ }
+ }
+ }
+};
+
+QWeb2.Engine = (function() {
+ function Engine() {
+ // TODO: handle prefix at template level : t-prefix="x", don't forget to lowercase it
+ this.prefix = 't';
+ this.debug = false;
+ this.templates_resources = []; // TODO: implement this.reload()
+ this.templates = {};
+ this.compiled_templates = {};
+ this.extend_templates = {};
+ this.default_dict = {};
+ this.tools = QWeb2.tools;
+ this.jQuery = window.jQuery;
+ this.reserved_words = QWeb2.RESERVED_WORDS.slice(0);
+ this.actions_precedence = QWeb2.ACTIONS_PRECEDENCE.slice(0);
+ this.void_elements = QWeb2.VOID_ELEMENTS.slice(0);
+ this.word_replacement = QWeb2.tools.extend({}, QWeb2.WORD_REPLACEMENT);
+ this.preprocess_node = null;
+ for (var i = 0; i < arguments.length; i++) {
+ this.add_template(arguments[i]);
+ }
+ }
+
+ QWeb2.tools.extend(Engine.prototype, {
+ /**
+ * Add a template to the engine
+ *
+ * @param {String|Document} template Template as string or url or DOM Document
+ * @param {Function} [callback] Called when the template is loaded, force async request
+ */
+ add_template : function(template, callback) {
+ var self = this;
+ this.templates_resources.push(template);
+ if (template.constructor === String) {
+ return this.load_xml(template, function (err, xDoc) {
+ if (err) {
+ if (callback) {
+ return callback(err);
+ } else {
+ throw err;
+ }
+ }
+ self.add_template(xDoc, callback);
+ });
+ }
+ template = this.preprocess(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) {
+ var name = node.getAttribute(this.prefix + '-name');
+ var extend = node.getAttribute(this.prefix + '-extend');
+ 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);
+ this.extend_templates[name] = (this.extend_templates[extend] || []).slice();
+ extend = name;
+ name = undefined;
+ }
+ if (name) {
+ this.templates[name] = node;
+ this.compiled_templates[name] = null;
+ } 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];
+ }
+ }
+ }
+ }
+ if (callback) {
+ callback(null, template);
+ }
+ return true;
+ },
+ preprocess: function(doc) {
+ /**
+ * Preprocess a template's document at load time.
+ * This method is mostly used for template sanitization but could
+ * also be overloaded for extended features such as translations, ...
+ * Throws an exception if a template is invalid.
+ *
+ * @param {Document} doc Document containg the loaded templates
+ * @return {Document} Returns the pre-processed/sanitized template
+ */
+ var self = this;
+ var childs = (doc.documentElement && doc.documentElement.childNodes) || doc.childNodes || [];
+
+ // Check for load errors
+ for (var i = 0; i < childs.length; i++) {
+ var node = childs[i];
+ if (node.nodeType === 1 && node.nodeName == 'parsererror') {
+ return this.tools.exception(node.innerText);
+ }
+ }
+
+ // Sanitize t-elif and t-else directives
+ var tbranch = doc.querySelectorAll('[t-elif], [t-else]');
+ for (var i = 0, ilen = tbranch.length; i < ilen; i++) {
+ var node = tbranch[i];
+ var prev_elem = self.tools.get_element_sibling(node, 'previousSibling');
+ var pattr = function(name) { return prev_elem.getAttribute(name); }
+ var nattr = function(name) { return +!!node.getAttribute(name); }
+ if (prev_elem && (pattr('t-if') || pattr('t-elif'))) {
+ if (pattr('t-foreach')) {
+ return self.tools.exception("Error: t-if cannot stay at the same level as t-foreach when using t-elif or t-else");
+ }
+ if (['t-if', 't-elif', 't-else'].map(nattr).reduce(function(a, b) { return a + b; }) > 1) {
+ return self.tools.exception("Error: only one conditional branching directive is allowed per node");
+ }
+ // All text nodes between branch nodes are removed
+ var text_node;
+ while ((text_node = node.previousSibling) !== prev_elem) {
+ if (text_node.nodeType !== 8 && self.tools.trim(text_node.nodeValue)) {
+ return self.tools.exception("Error: text is not allowed between branching directives");
+ }
+ // IE <= 11.0 doesn't support ChildNode.remove
+ text_node.parentNode.removeChild(text_node);
+ }
+ } else {
+ return self.tools.exception("Error: t-elif and t-else directives must be preceded by a t-if or t-elif directive");
+ }
+ }
+
+ return doc;
+ },
+ load_xml : function(s, callback) {
+ var self = this;
+ var async = !!callback;
+ s = this.tools.trim(s);
+ if (s.charAt(0) === '<') {
+ var tpl = this.load_xml_string(s);
+ if (callback) {
+ callback(null, tpl);
+ }
+ return tpl;
+ } else {
+ var req = this.get_xhr();
+ if (this.debug) {
+ s += '?debug=' + (new Date()).getTime(); // TODO fme: do it properly in case there's already url parameters
+ }
+ req.open('GET', s, async);
+ if (async) {
+ req.addEventListener("load", function() {
+ // 0, not being a valid HTTP status code, is used by browsers
+ // to indicate success for a non-http xhr response
+ // (for example, using the file:// protocol)
+ // https://developer.mozilla.org/fr/docs/Web/API/XMLHttpRequest
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=331610
+ if (req.status == 200 || req.status == 0) {
+ callback(null, self._parse_from_request(req));
+ } else {
+ callback(new Error("Can't load template " + s + ", http status " + req.status));
+ }
+ });
+ }
+ req.send(null);
+ if (!async) {
+ return this._parse_from_request(req);
+ }
+ }
+ },
+ _parse_from_request: function(req) {
+ var xDoc = req.responseXML;
+ if (xDoc) {
+ if (!xDoc.documentElement) {
+ throw new Error("QWeb2: This xml document has no root document : " + xDoc.responseText);
+ }
+ if (xDoc.documentElement.nodeName == "parsererror") {
+ throw new Error("QWeb2: Could not parse document :" + xDoc.documentElement.childNodes[0].nodeValue);
+ }
+ return xDoc;
+ } else {
+ return this.load_xml_string(req.responseText);
+ }
+ },
+ 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') {
+ throw new Error("QWeb2: Could not parse document :" + r.body.innerText);
+ }
+ return r;
+ }
+ var xDoc;
+ try {
+ xDoc = new ActiveXObject("MSXML2.DOMDocument");
+ } catch (e) {
+ throw new Error("Could not find a DOM Parser: " + e.message);
+ }
+ xDoc.async = false;
+ xDoc.preserveWhiteSpace = true;
+ xDoc.loadXML(s);
+ return xDoc;
+ },
+ has_template : function(template) {
+ return !!this.templates[template];
+ },
+ get_xhr : function() {
+ if (window.XMLHttpRequest) {
+ return new window.XMLHttpRequest();
+ }
+ try {
+ return new ActiveXObject('MSXML2.XMLHTTP.3.0');
+ } catch (e) {
+ throw new Error("Could not get XHR");
+ }
+ },
+ compile : function(node) {
+ var e = new QWeb2.Element(this, node);
+ var template = node.getAttribute(this.prefix + '-name');
+ return " /* 'this' refers to Qweb2.Engine instance */\n" +
+ " var context = { engine : this, template : " + (this.tools.js_escape(template)) + " };\n" +
+ " dict = dict || {};\n" +
+ " dict['__template__'] = '" + template + "';\n" +
+ " var r = [];\n" +
+ " /* START TEMPLATE */" +
+ (this.debug ? "" : " try {\n") +
+ (e.compile()) + "\n" +
+ " /* END OF TEMPLATE */" +
+ (this.debug ? "" : " } catch(error) {\n" +
+ " if (console && console.exception) console.exception(error);\n" +
+ " context.engine.tools.exception('Runtime Error: ' + error, context);\n") +
+ (this.debug ? "" : " }\n") +
+ " return r.join('');";
+ },
+ render : function(template, dict) {
+ dict = dict || {};
+ QWeb2.tools.extend(dict, this.default_dict);
+ /*if (this.debug && window['console'] !== undefined) {
+ console.time("QWeb render template " + template);
+ }*/
+ var r = this._render(template, dict);
+ /*if (this.debug && window['console'] !== undefined) {
+ console.timeEnd("QWeb render template " + template);
+ }*/
+ return r;
+ },
+ _render : function(template, dict) {
+ if (this.compiled_templates[template]) {
+ return this.compiled_templates[template].apply(this, [dict || {}]);
+ } else if (this.templates[template]) {
+ var ext;
+ if (ext = this.extend_templates[template]) {
+ var extend_node;
+ while (extend_node = ext.shift()) {
+ this.extend(template, extend_node);
+ }
+ }
+ var code = this.compile(this.templates[template]), tcompiled;
+ try {
+ tcompiled = new Function(['dict'], code);
+ } catch (error) {
+ if (this.debug && window.console) {
+ console.log(code);
+ }
+ this.tools.exception("Error evaluating template: " + error, { template: template });
+ }
+ if (!tcompiled) {
+ this.tools.exception("Error evaluating template: (IE?)" + error, { template: template });
+ }
+ this.compiled_templates[template] = tcompiled;
+ return this.render(template, dict);
+ } else {
+ return this.tools.exception("Template '" + template + "' not found");
+ }
+ },
+ extend : function(template, extend_node) {
+ var jQuery = this.jQuery;
+ if (!jQuery) {
+ return this.tools.exception("Can't extend template " + template + " without jQuery");
+ }
+ var template_dest = this.templates[template];
+ for (var i = 0, ilen = extend_node.childNodes.length; i < ilen; i++) {
+ var child = extend_node.childNodes[i];
+ if (child.nodeType === 1) {
+ var jquery = child.getAttribute(this.prefix + '-jquery'),
+ operation = child.getAttribute(this.prefix + '-operation'),
+ target,
+ error_msg = "Error while extending template '" + template;
+ if (jquery) {
+ target = jQuery(jquery, template_dest);
+ if (!target.length && window.console) {
+ console.debug('Can\'t find "'+jquery+'" when extending template '+template);
+ }
+ } else {
+ this.tools.exception(error_msg + "No expression given");
+ }
+ error_msg += "' (expression='" + jquery + "') : ";
+ if (operation) {
+ var allowed_operations = "append,prepend,before,after,replace,inner,attributes".split(',');
+ if (this.tools.arrayIndexOf(allowed_operations, operation) == -1) {
+ this.tools.exception(error_msg + "Invalid operation : '" + operation + "'");
+ }
+ operation = {'replace' : 'replaceWith', 'inner' : 'html'}[operation] || operation;
+ if (operation === 'attributes') {
+ jQuery('attribute', child).each(function () {
+ var attrib = jQuery(this);
+ target.attr(attrib.attr('name'), attrib.text() || attrib.attr('value'));
+ });
+ } else {
+ target[operation](child.cloneNode(true).childNodes);
+ }
+ } else {
+ try {
+ var f = new Function(['$', 'document'], this.tools.xml_node_to_string(child, true));
+ } catch(error) {
+ return this.tools.exception("Parse " + error_msg + error);
+ }
+ try {
+ f.apply(target, [jQuery, template_dest.ownerDocument]);
+ } catch(error) {
+ return this.tools.exception("Runtime " + error_msg + error);
+ }
+ }
+ }
+ }
+ }
+ });
+ return Engine;
+})();
+
+QWeb2.Element = (function() {
+ function Element(engine, node) {
+ this.engine = engine;
+ this.node = node;
+ this.tag = node.tagName;
+ this.actions = {tag: this.tag};
+ this.actions_done = [];
+ this.attributes = {};
+ this.children = [];
+ this._top = [];
+ this._bottom = [];
+ this._indent = 1;
+ this.process_children = true;
+ this.is_void_element = ~QWeb2.tools.arrayIndexOf(this.engine.void_elements, this.tag);
+ 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;
+ var m = name.match(new RegExp("^" + this.engine.prefix + "-(.+)"));
+ if (m) {
+ name = m[1];
+ if (name === 'name') {
+ continue;
+ }
+ if (name.match(/^attf?(-.*)?/)) {
+ this.attributes[m[0]] = attr.value;
+ } else {
+ this.actions[name] = attr.value;
+ }
+ } else {
+ this.attributes[name] = attr.value;
+ }
+ }
+ }
+ if (this.engine.preprocess_node) {
+ this.engine.preprocess_node.call(this);
+ }
+ }
+
+ QWeb2.tools.extend(Element.prototype, {
+ compile : function() {
+ 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() {
+ switch (this.node.nodeType) {
+ case 3:
+ case 4:
+ this.top_string(this.node.data);
+ break;
+ case 1:
+ this.compile_element();
+ }
+ var r = this._top.join('');
+ if (this.process_children) {
+ for (var i = 0, ilen = this.children.length; i < ilen; i++) {
+ var child = this.children[i];
+ child._indent = this._indent;
+ r += child._compile();
+ }
+ }
+ r += this._bottom.join('');
+ 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(this.engine.reserved_words, invar) < 0) {
+ invar = this.engine.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;
+ },
+ format_str: function (e) {
+ if (e == '0') {
+ return 'dict[0]';
+ }
+ return this.format_expression(e);
+ },
+ string_interpolation : function(s) {
+ var _this = this;
+ if (!s) {
+ return "''";
+ }
+ function append_literal(s) {
+ s && r.push(_this.engine.tools.js_escape(s));
+ }
+
+ var re = /(?:#{(.+?)}|{{(.+?)}})/g, start = 0, r = [], m;
+ while (m = re.exec(s)) {
+ // extract literal string between previous and current match
+ append_literal(s.slice(start, re.lastIndex - m[0].length));
+ // extract matched expression
+ r.push('(' + this.format_str(m[2] || m[1]) + ')');
+ // update position of new matching
+ start = re.lastIndex;
+ }
+ // remaining text after last expression
+ append_literal(s.slice(start));
+
+ 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) {
+ return this._top.push(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
+ },
+ bottom : function(s) {
+ return this._bottom.unshift(this.get_indent() + s + '\n');
+ },
+ bottom_string : function(s) {
+ return this._bottom.unshift(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
+ },
+ compile_element : function() {
+ for (var i = 0, ilen = this.engine.actions_precedence.length; i < ilen; i++) {
+ var a = this.engine.actions_precedence[i];
+ if (a in this.actions) {
+ var value = this.actions[a];
+ var key = 'compile_action_' + a;
+ if (this[key]) {
+ this[key](value);
+ } else if (this.engine[key]) {
+ this.engine[key].call(this, value);
+ } else {
+ this.engine.tools.exception("No handler method for action '" + a + "'");
+ }
+ }
+ }
+ },
+ compile_action_tag : function() {
+ if (this.tag.toLowerCase() !== this.engine.prefix) {
+ this.top_string("<" + this.tag);
+ for (var a in this.attributes) {
+ var v = this.attributes[a];
+ var d = a.split('-');
+ if (d[0] === this.engine.prefix && d.length > 1) {
+ if (d.length === 2) {
+ this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(v)) + "));");
+ } else {
+ this.top("r.push(context.engine.tools.gen_attribute(['" + d.slice(2).join('-') + "', (" +
+ (d[1] === 'att' ? this.format_expression(v) : this.string_interpolation(v)) + ")]));");
+ }
+ } else {
+ this.top_string(this.engine.tools.gen_attribute([a, v]));
+ }
+ }
+
+ if (this.actions.opentag === 'true' || (!this.children.length && this.is_void_element)) {
+ // We do not enforce empty content on void elements
+ // because QWeb rendering is not necessarily html.
+ this.top_string("/>");
+ } else {
+ this.top_string(">");
+ this.bottom_string("</" + this.tag + ">");
+ }
+ }
+ },
+ compile_action_if : function(value) {
+ this.top("if (" + (this.format_expression(value)) + ") {");
+ this.bottom("}");
+ this.indent();
+ },
+ compile_action_elif : function(value) {
+ this.top("else if (" + (this.format_expression(value)) + ") {");
+ this.bottom("}");
+ this.indent();
+ },
+ compile_action_else : function(value) {
+ this.top("else {");
+ 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) {
+ if (this.children.length === 0) {
+ return this.top("r.push(context.engine.tools.call(context, " + (this.string_interpolation(value)) + ", dict));");
+ } else {
+ this.top("r.push(context.engine.tools.call(context, " + (this.string_interpolation(value)) + ", dict, null, function(context, dict) {");
+ this.bottom("}));");
+ this.indent();
+ this.top("var r = [];");
+ return this.bottom("return r.join('');");
+ }
+ },
+ compile_action_set : function(value) {
+ var variable = this.format_expression(value);
+ if (this.actions['value']) {
+ if (this.children.length) {
+ this.engine.tools.warning("@set with @value plus node chidren found. Children are ignored.");
+ }
+ this.top(variable + " = (" + (this.format_expression(this.actions['value'])) + ");");
+ this.process_children = false;
+ } else {
+ if (this.children.length === 0) {
+ this.top(variable + " = '';");
+ } else if (this.children.length === 1 && this.children[0].node.nodeType === 3) {
+ this.top(variable + " = " + (this.engine.tools.js_escape(this.children[0].node.data)) + ";");
+ this.process_children = false;
+ } else {
+ this.top(variable + " = (function(dict) {");
+ this.bottom("})(dict);");
+ this.indent();
+ this.top("var r = [];");
+ this.bottom("return r.join('');");
+ }
+ }
+ },
+ compile_action_esc : function(value) {
+ this.top("var t = " + this.format_str(value) + ";");
+ this.top("if (t != null) r.push(context.engine.tools.html_escape(t));");
+ this.top("else {");
+ this.bottom("}");
+ this.indent();
+ },
+ compile_action_raw : function(value) {
+ this.top("var t = " + this.format_str(value) + ";");
+ this.top("if (t != null) r.push(t);");
+ this.top("else {");
+ this.bottom("}");
+ this.indent();
+ },
+ compile_action_js : function(value) {
+ this.top("(function(" + value + ") {");
+ this.bottom("})(dict);");
+ this.indent();
+ var lines = this.engine.tools.xml_node_to_string(this.node, true).split(/\r?\n/);
+ for (var i = 0, ilen = lines.length; i < ilen; i++) {
+ this.top(lines[i]);
+ }
+ 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;
+})();