[CLEAN] qweb, kanban: removed forgotten console.log + unnecessary void line
[odoo/odoo.git] / addons / web / static / lib / qweb / qweb2.js
1 /*
2 Copyright (c) 2013, Fabien Meghazi
3
4 Released under the MIT license
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy of
7 this software and associated documentation files (the "Software"), to deal in
8 the Software without restriction, including without limitation the rights to use,
9 copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 Software, and to permit persons to whom the Software is furnished to do so,
11 subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
20 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 // TODO: trim support
25 // TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650
26 // TODO: templates orverwritten could be called by t-call="__super__" ?
27 // TODO: t-set + t-value + children node == scoped variable ?
28 var QWeb2 = {
29     expressions_cache: {},
30     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(','),
31     ACTIONS_PRECEDENCE: 'foreach,if,call,set,esc,escf,raw,rawf,js,debug,log'.split(','),
32     WORD_REPLACEMENT: {
33         'and': '&&',
34         'or': '||',
35         'gt': '>',
36         'gte': '>=',
37         'lt': '<',
38         'lte': '<='
39     },
40     tools: {
41         exception: function(message, context) {
42             context = context || {};
43             var prefix = 'QWeb2';
44             if (context.template) {
45                 prefix += " - template['" + context.template + "']";
46             }
47             throw new Error(prefix + ": " + message);
48         },
49         warning : function(message) {
50             if (typeof(window) !== 'undefined' && window.console) {
51                 window.console.warn(message);
52             }
53         },
54         trim: function(s, mode) {
55             switch (mode) {
56                 case "left":
57                     return s.replace(/^\s*/, "");
58                 case "right":
59                     return s.replace(/\s*$/, "");
60                 default:
61                     return s.replace(/^\s*|\s*$/g, "");
62             }
63         },
64         js_escape: function(s, noquotes) {
65             return (noquotes ? '' : "'") + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + (noquotes ? '' : "'");
66         },
67         html_escape: function(s, attribute) {
68             if (s == null) {
69                 return '';
70             }
71             s = String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
72             if (attribute) {
73                 s = s.replace(/"/g, '&quot;');
74             }
75             return s;
76         },
77         gen_attribute: function(o) {
78             if (o !== null && o !== undefined) {
79                 if (o.constructor === Array) {
80                     if (o[1] !== null && o[1] !== undefined) {
81                         return this.format_attribute(o[0], o[1]);
82                     }
83                 } else if (typeof o === 'object') {
84                     var r = '';
85                     for (var k in o) {
86                         if (o.hasOwnProperty(k)) {
87                             r += this.gen_attribute([k, o[k]]);
88                         }
89                     }
90                     return r;
91                 }
92             }
93             return '';
94         },
95         format_attribute: function(name, value) {
96             return ' ' + name + '="' + this.html_escape(value, true) + '"';
97         },
98         extend: function(dst, src, exclude) {
99             for (var p in src) {
100                 if (src.hasOwnProperty(p) && !(exclude && this.arrayIndexOf(exclude, p) !== -1)) {
101                     dst[p] = src[p];
102                 }
103             }
104             return dst;
105         },
106         arrayIndexOf : function(array, item) {
107             for (var i = 0, ilen = array.length; i < ilen; i++) {
108                 if (array[i] === item) {
109                     return i;
110                 }
111             }
112             return -1;
113         },
114         xml_node_to_string : function(node, childs_only) {
115             if (childs_only) {
116                 var childs = node.childNodes, r = [];
117                 for (var i = 0, ilen = childs.length; i < ilen; i++) {
118                     r.push(this.xml_node_to_string(childs[i]));
119                 }
120                 return r.join('');
121             } else {
122                 if (typeof XMLSerializer !== 'undefined') {
123                     return (new XMLSerializer()).serializeToString(node);
124                 } else {
125                     switch(node.nodeType) {
126                     case 1: return node.outerHTML;
127                     case 3: return node.data;
128                     case 4: return '<![CDATA[' + node.data + ']]>';
129                     case 8: return '<!-- ' + node.data + '-->';
130                     }
131                     throw new Error('Unknown node type ' + node.nodeType);
132                 }
133             }
134         },
135         call: function(context, template, old_dict, _import, callback) {
136             var new_dict = this.extend({}, old_dict);
137             new_dict['__caller__'] = old_dict['__template__'];
138             if (callback) {
139                 new_dict['__content__'] = callback(context, new_dict);
140             }
141             var r = context.engine._render(template, new_dict);
142             if (_import) {
143                 if (_import === '*') {
144                     this.extend(old_dict, new_dict, ['__caller__', '__template__']);
145                 } else {
146                     _import = _import.split(',');
147                     for (var i = 0, ilen = _import.length; i < ilen; i++) {
148                         var v = _import[i];
149                         old_dict[v] = new_dict[v];
150                     }
151                 }
152             }
153             return r;
154         },
155         foreach: function(context, enu, as, old_dict, callback) {
156             if (enu != null) {
157                 var size, new_dict = this.extend({}, old_dict);
158                 new_dict[as + "_all"] = enu;
159                 var as_value = as + "_value",
160                     as_index = as + "_index",
161                     as_first = as + "_first",
162                     as_last = as + "_last",
163                     as_parity = as + "_parity";
164                 if (size = enu.length) {
165                     new_dict[as + "_size"] = size;
166                     for (var j = 0, jlen = enu.length; j < jlen; j++) {
167                         var cur = enu[j];
168                         new_dict[as_value] = cur;
169                         new_dict[as_index] = j;
170                         new_dict[as_first] = j === 0;
171                         new_dict[as_last] = j + 1 === size;
172                         new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
173                         if (cur.constructor === Object) {
174                             this.extend(new_dict, cur);
175                         }
176                         new_dict[as] = cur;
177                         callback(context, new_dict);
178                     }
179                 } else if (enu.constructor == Number) {
180                     var _enu = [];
181                     for (var i = 0; i < enu; i++) {
182                         _enu.push(i);
183                     }
184                     this.foreach(context, _enu, as, old_dict, callback);
185                 } else {
186                     var index = 0;
187                     for (var k in enu) {
188                         if (enu.hasOwnProperty(k)) {
189                             var v = enu[k];
190                             new_dict[as_value] = v;
191                             new_dict[as_index] = index;
192                             new_dict[as_first] = index === 0;
193                             new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
194                             new_dict[as] = k;
195                             callback(context, new_dict);
196                             index += 1;
197                         }
198                       }
199                 }
200             } else {
201                 this.exception("No enumerator given to foreach", context);
202             }
203         }
204     }
205 };
206
207 QWeb2.Engine = (function() {
208     function Engine() {
209         // TODO: handle prefix at template level : t-prefix="x", don't forget to lowercase it
210         this.prefix = 't';
211         this.debug = false;
212         this.templates_resources = []; // TODO: implement this.reload()
213         this.templates = {};
214         this.compiled_templates = {};
215         this.extend_templates = {};
216         this.default_dict = {};
217         this.tools = QWeb2.tools;
218         this.jQuery = window.jQuery;
219         this.reserved_words = QWeb2.RESERVED_WORDS.slice(0);
220         this.actions_precedence = QWeb2.ACTIONS_PRECEDENCE.slice(0);
221         this.word_replacement = QWeb2.tools.extend({}, QWeb2.WORD_REPLACEMENT);
222         this.preprocess_node = null;
223         for (var i = 0; i < arguments.length; i++) {
224             this.add_template(arguments[i]);
225         }
226     }
227
228     QWeb2.tools.extend(Engine.prototype, {
229         add_template : function(template) {
230             this.templates_resources.push(template);
231             if (template.constructor === String) {
232                 template = this.load_xml(template);
233             }
234             var ec = (template.documentElement && template.documentElement.childNodes) || template.childNodes || [];
235             for (var i = 0; i < ec.length; i++) {
236                 var node = ec[i];
237                 if (node.nodeType === 1) {
238                     if (node.nodeName == 'parsererror') {
239                         return this.tools.exception(node.innerText);
240                     }
241                     var name = node.getAttribute(this.prefix + '-name');
242                     var extend = node.getAttribute(this.prefix + '-extend');
243                     if (name && extend) {
244                         // Clone template and extend it
245                         if (!this.templates[extend]) {
246                             return this.tools.exception("Can't clone undefined template " + extend);
247                         }
248                         this.templates[name] = this.templates[extend].cloneNode(true);
249                         extend = name;
250                         name = undefined;
251                     }
252                     if (name) {
253                         this.templates[name] = node;
254                         this.compiled_templates[name] = null;
255                     } else if (extend) {
256                         delete(this.compiled_templates[extend]);
257                         if (this.extend_templates[extend]) {
258                             this.extend_templates[extend].push(node);
259                         } else {
260                             this.extend_templates[extend] = [node];
261                         }
262                     }
263                 }
264             }
265             return true;
266         },
267         load_xml : function(s) {
268             s = this.tools.trim(s);
269             if (s.charAt(0) === '<') {
270                 return this.load_xml_string(s);
271             } else {
272                 var req = this.get_xhr();
273                 if (req) {
274                     // TODO: third parameter is async : https://developer.mozilla.org/en/XMLHttpRequest#open()
275                     // do an on_ready in QWeb2{} that could be passed to add_template
276                     if (this.debug) {
277                         s += '?debug=' + (new Date()).getTime(); // TODO fme: do it properly in case there's already url parameters
278                     }
279                     req.open('GET', s, false);
280                     req.send(null);
281                     var xDoc = req.responseXML;
282                     if (xDoc) {
283                         if (!xDoc.documentElement) {
284                             throw new Error("QWeb2: This xml document has no root document : " + xDoc.responseText);
285                         }
286                         if (xDoc.documentElement.nodeName == "parsererror") {
287                             return this.tools.exception(xDoc.documentElement.childNodes[0].nodeValue);
288                         }
289                         return xDoc;
290                     } else {
291                         return this.load_xml_string(req.responseText);
292                     }
293                 }
294             }
295         },
296         load_xml_string : function(s) {
297             if (window.DOMParser) {
298                 var dp = new DOMParser();
299                 var r = dp.parseFromString(s, "text/xml");
300                 if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') {
301                     return this.tools.exception(r.body.innerText);
302                 }
303                 return r;
304             }
305             var xDoc;
306             try {
307                 // new ActiveXObject("Msxml2.DOMDocument.4.0");
308                 xDoc = new ActiveXObject("MSXML2.DOMDocument");
309             } catch (e) {
310                 return this.tools.exception(
311                     "Could not find a DOM Parser: " + e.message);
312             }
313             xDoc.async = false;
314             xDoc.preserveWhiteSpace = true;
315             xDoc.loadXML(s);
316             return xDoc;
317         },
318         has_template : function(template) {
319             return !!this.templates[template];
320         },
321         get_xhr : function() {
322             if (window.XMLHttpRequest) {
323                 return new window.XMLHttpRequest();
324             }
325             try {
326                 return new ActiveXObject('MSXML2.XMLHTTP.3.0');
327             } catch (e) {
328                 return null;
329             }
330         },
331         compile : function(node) {
332             var e = new QWeb2.Element(this, node);
333             var template = node.getAttribute(this.prefix + '-name');
334             return  "   /* 'this' refers to Qweb2.Engine instance */\n" +
335                     "   var context = { engine : this, template : " + (this.tools.js_escape(template)) + " };\n" +
336                     "   dict = dict || {};\n" +
337                     "   dict['__template__'] = '" + template + "';\n" +
338                     "   var r = [];\n" +
339                     "   /* START TEMPLATE */" +
340                     (this.debug ? "" : " try {\n") +
341                     (e.compile()) + "\n" +
342                     "   /* END OF TEMPLATE */" +
343                     (this.debug ? "" : " } catch(error) {\n" +
344                     "       if (console && console.exception) console.exception(error);\n" +
345                     "       context.engine.tools.exception('Runtime Error: ' + error, context);\n") +
346                     (this.debug ? "" : "   }\n") +
347                     "   return r.join('');";
348         },
349         render : function(template, dict) {
350             dict = dict || {};
351             QWeb2.tools.extend(dict, this.default_dict);
352             /*if (this.debug && window['console'] !== undefined) {
353                 console.time("QWeb render template " + template);
354             }*/
355             var r = this._render(template, dict);
356             /*if (this.debug && window['console'] !== undefined) {
357                 console.timeEnd("QWeb render template " + template);
358             }*/
359             return r;
360         },
361         _render : function(template, dict) {
362             if (this.compiled_templates[template]) {
363                 return this.compiled_templates[template].apply(this, [dict || {}]);
364             } else if (this.templates[template]) {
365                 var ext;
366                 if (ext = this.extend_templates[template]) {
367                     var extend_node;
368                     while (extend_node = ext.shift()) {
369                         this.extend(template, extend_node);
370                     }
371                 }
372                 var code = this.compile(this.templates[template]), tcompiled;
373                 try {
374                     tcompiled = new Function(['dict'], code);
375                 } catch (error) {
376                     if (this.debug && window.console) {
377                         console.log(code);
378                     }
379                     this.tools.exception("Error evaluating template: " + error, { template: name });
380                 }
381                 if (!tcompiled) {
382                     this.tools.exception("Error evaluating template: (IE?)" + error, { template: name });
383                 }
384                 this.compiled_templates[template] = tcompiled;
385                 return this.render(template, dict);
386             } else {
387                 return this.tools.exception("Template '" + template + "' not found");
388             }
389         },
390         extend : function(template, extend_node) {
391             if (!this.jQuery) {
392                 return this.tools.exception("Can't extend template " + template + " without jQuery");
393             }
394             var template_dest = this.templates[template];
395             for (var i = 0, ilen = extend_node.childNodes.length; i < ilen; i++) {
396                 var child = extend_node.childNodes[i];
397                 if (child.nodeType === 1) {
398                     var jquery = child.getAttribute(this.prefix + '-jquery'),
399                         operation = child.getAttribute(this.prefix + '-operation'),
400                         target,
401                         error_msg = "Error while extending template '" + template;
402                     if (jquery) {
403                         target = this.jQuery(jquery, template_dest);
404                     } else {
405                         this.tools.exception(error_msg + "No expression given");
406                     }
407                     error_msg += "' (expression='" + jquery + "') : ";
408                     if (operation) {
409                         var allowed_operations = "append,prepend,before,after,replace,inner".split(',');
410                         if (this.tools.arrayIndexOf(allowed_operations, operation) == -1) {
411                             this.tools.exception(error_msg + "Invalid operation : '" + operation + "'");
412                         }
413                         operation = {'replace' : 'replaceWith', 'inner' : 'html'}[operation] || operation;
414                         target[operation](child.cloneNode(true).childNodes);
415                     } else {
416                         try {
417                             var f = new Function(['$', 'document'], this.tools.xml_node_to_string(child, true));
418                         } catch(error) {
419                             return this.tools.exception("Parse " + error_msg + error);
420                         }
421                         try {
422                             f.apply(target, [this.jQuery, template_dest.ownerDocument]);
423                         } catch(error) {
424                             return this.tools.exception("Runtime " + error_msg + error);
425                         }
426                     }
427                 }
428             }
429         }
430     });
431     return Engine;
432 })();
433
434 QWeb2.Element = (function() {
435     function Element(engine, node) {
436         this.engine = engine;
437         this.node = node;
438         this.tag = node.tagName;
439         this.actions = {};
440         this.actions_done = [];
441         this.attributes = {};
442         this.children = [];
443         this._top = [];
444         this._bottom = [];
445         this._indent = 1;
446         this.process_children = true;
447         var childs = this.node.childNodes;
448         if (childs) {
449             for (var i = 0, ilen = childs.length; i < ilen; i++) {
450                 this.children.push(new QWeb2.Element(this.engine, childs[i]));
451             }
452         }
453         var attrs = this.node.attributes;
454         if (attrs) {
455             for (var j = 0, jlen = attrs.length; j < jlen; j++) {
456                 var attr = attrs[j];
457                 var name = attr.name;
458                 var m = name.match(new RegExp("^" + this.engine.prefix + "-(.+)"));
459                 if (m) {
460                     name = m[1];
461                     if (name === 'name') {
462                         continue;
463                     }
464                     this.actions[name] = attr.value;
465                 } else {
466                     this.attributes[name] = attr.value;
467                 }
468             }
469         }
470         if (this.engine.preprocess_node) {
471             this.engine.preprocess_node.call(this);
472         }
473     }
474
475     QWeb2.tools.extend(Element.prototype, {
476         compile : function() {
477             var r = [],
478                 instring = false,
479                 lines = this._compile().split('\n');
480             for (var i = 0, ilen = lines.length; i < ilen; i++) {
481                 var m, line = lines[i];
482                 if (m = line.match(/^(\s*)\/\/@string=(.*)/)) {
483                     if (instring) {
484                         if (this.engine.debug) {
485                             // Split string lines in indented r.push arguments
486                             r.push((m[2].indexOf("\\n") != -1 ? "',\n\t" + m[1] + "'" : '') + m[2]);
487                         } else {
488                             r.push(m[2]);
489                         }
490                     } else {
491                         r.push(m[1] + "r.push('" + m[2]);
492                         instring = true;
493                     }
494                 } else {
495                     if (instring) {
496                         r.push("');\n");
497                     }
498                     instring = false;
499                     r.push(line + '\n');
500                 }
501             }
502             return r.join('');
503         },
504         _compile : function() {
505             switch (this.node.nodeType) {
506                 case 3:
507                 case 4:
508                     this.top_string(this.node.data);
509                 break;
510                 case 1:
511                     this.compile_element();
512             }
513             var r = this._top.join('');
514             if (this.process_children) {
515                 for (var i = 0, ilen = this.children.length; i < ilen; i++) {
516                     var child = this.children[i];
517                     child._indent = this._indent;
518                     r += child._compile();
519                 }
520             }
521             r += this._bottom.join('');
522             return r;
523         },
524         format_expression : function(e) {
525             /* Naive format expression builder. Replace reserved words and variables to dict[variable]
526              * Does not handle spaces before dot yet, and causes problems for anonymous functions. Use t-js="" for that */
527             if (QWeb2.expressions_cache[e]) {
528               return QWeb2.expressions_cache[e];
529             }
530             var chars = e.split(''),
531                 instring = '',
532                 invar = '',
533                 invar_pos = 0,
534                 r = '';
535             chars.push(' ');
536             for (var i = 0, ilen = chars.length; i < ilen; i++) {
537                 var c = chars[i];
538                 if (instring.length) {
539                     if (c === instring && chars[i - 1] !== "\\") {
540                         instring = '';
541                     }
542                 } else if (c === '"' || c === "'") {
543                     instring = c;
544                 } else if (c.match(/[a-zA-Z_\$]/) && !invar.length) {
545                     invar = c;
546                     invar_pos = i;
547                     continue;
548                 } else if (c.match(/\W/) && invar.length) {
549                     // TODO: Should check for possible spaces before dot
550                     if (chars[invar_pos - 1] !== '.' && QWeb2.tools.arrayIndexOf(this.engine.reserved_words, invar) < 0) {
551                         invar = this.engine.word_replacement[invar] || ("dict['" + invar + "']");
552                     }
553                     r += invar;
554                     invar = '';
555                 } else if (invar.length) {
556                     invar += c;
557                     continue;
558                 }
559                 r += c;
560             }
561             r = r.slice(0, -1);
562             QWeb2.expressions_cache[e] = r;
563             return r;
564         },
565         string_interpolation : function(s) {
566             if (!s) {
567               return "''";
568             }
569             var regex = /^{(.*)}(.*)/,
570                 src = s.split(/#/),
571                 r = [];
572             for (var i = 0, ilen = src.length; i < ilen; i++) {
573                 var val = src[i],
574                     m = val.match(regex);
575                 if (m) {
576                     r.push("(" + this.format_expression(m[1]) + ")");
577                     if (m[2]) {
578                         r.push(this.engine.tools.js_escape(m[2]));
579                     }
580                 } else if (!(i === 0 && val === '')) {
581                     r.push(this.engine.tools.js_escape((i === 0 ? '' : '#') + val));
582                 }
583             }
584             return r.join(' + ');
585         },
586         indent : function() {
587             return this._indent++;
588         },
589         dedent : function() {
590             if (this._indent !== 0) {
591                 return this._indent--;
592             }
593         },
594         get_indent : function() {
595             return new Array(this._indent + 1).join("\t");
596         },
597         top : function(s) {
598             return this._top.push(this.get_indent() + s + '\n');
599         },
600         top_string : function(s) {
601             return this._top.push(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
602         },
603         bottom : function(s) {
604             return this._bottom.unshift(this.get_indent() + s + '\n');
605         },
606         bottom_string : function(s) {
607             return this._bottom.unshift(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
608         },
609         compile_element : function() {
610             for (var i = 0, ilen = this.engine.actions_precedence.length; i < ilen; i++) {
611                 var a = this.engine.actions_precedence[i];
612                 if (a in this.actions) {
613                     var value = this.actions[a];
614                     var key = 'compile_action_' + a;
615                     if (this[key]) {
616                         this[key](value);
617                     } else if (this.engine[key]) {
618                         this.engine[key].call(this, value);
619                     } else {
620                         this.engine.tools.exception("No handler method for action '" + a + "'");
621                     }
622                 }
623             }
624             if (this.tag.toLowerCase() !== this.engine.prefix) {
625                 var tag = "<" + this.tag;
626                 for (var a in this.attributes) {
627                     tag += this.engine.tools.gen_attribute([a, this.attributes[a]]);
628                 }
629                 this.top_string(tag);
630                 if (this.actions.att) {
631                     this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(this.actions.att)) + "));");
632                 }
633                 for (var a in this.actions) {
634                     var v = this.actions[a];
635                     var m = a.match(/att-(.+)/);
636                     if (m) {
637                         this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.format_expression(v)) + ")]));");
638                     }
639                     var m = a.match(/attf-(.+)/);
640                     if (m) {
641                         this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.string_interpolation(v)) + ")]));");
642                     }
643                 }
644                 if (this.children.length || this.actions.opentag === 'true') {
645                     this.top_string(">");
646                     this.bottom_string("</" + this.tag + ">");
647                 } else {
648                     this.top_string("/>");
649                 }
650             }
651         },
652         compile_action_if : function(value) {
653             this.top("if (" + (this.format_expression(value)) + ") {");
654             this.bottom("}");
655             this.indent();
656         },
657         compile_action_foreach : function(value) {
658             var as = this.actions['as'] || value.replace(/[^a-zA-Z0-9]/g, '_');
659             //TODO: exception if t-as not valid
660             this.top("context.engine.tools.foreach(context, " + (this.format_expression(value)) + ", " + (this.engine.tools.js_escape(as)) + ", dict, function(context, dict) {");
661             this.bottom("});");
662             this.indent();
663         },
664         compile_action_call : function(value) {
665             var _import = this.actions['import'] || '';
666             if (this.children.length === 0) {
667                 return this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + "));");
668             } else {
669                 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) {");
670                 this.bottom("}));");
671                 this.indent();
672                 this.top("var r = [];");
673                 return this.bottom("return r.join('');");
674             }
675         },
676         compile_action_set : function(value) {
677             var variable = this.format_expression(value);
678             if (this.actions['value']) {
679                 if (this.children.length) {
680                     this.engine.tools.warning("@set with @value plus node chidren found. Children are ignored.");
681                 }
682                 this.top(variable + " = (" + (this.format_expression(this.actions['value'])) + ");");
683                 this.process_children = false;
684             } else {
685                 if (this.children.length === 0) {
686                     this.top(variable + " = '';");
687                 } else if (this.children.length === 1 && this.children[0].node.nodeType === 3) {
688                     this.top(variable + " = " + (this.engine.tools.js_escape(this.children[0].node.data)) + ";");
689                     this.process_children = false;
690                 } else {
691                     this.top(variable + " = (function(dict) {");
692                     this.bottom("})(dict);");
693                     this.indent();
694                     this.top("var r = [];");
695                     this.bottom("return r.join('');");
696                 }
697             }
698         },
699         compile_action_esc : function(value) {
700             this.top("r.push(context.engine.tools.html_escape(" + (this.format_expression(value)) + "));");
701         },
702         compile_action_escf : function(value) {
703             this.top("r.push(context.engine.tools.html_escape(" + (this.string_interpolation(value)) + "));");
704         },
705         compile_action_raw : function(value) {
706             this.top("r.push(" + (this.format_expression(value)) + ");");
707         },
708         compile_action_rawf : function(value) {
709             this.top("r.push(" + (this.string_interpolation(value)) + ");");
710         },
711         compile_action_js : function(value) {
712             this.top("(function(" + value + ") {");
713             this.bottom("})(dict);");
714             this.indent();
715             var lines = this.engine.tools.xml_node_to_string(this.node, true).split(/\r?\n/);
716             for (var i = 0, ilen = lines.length; i < ilen; i++) {
717                 this.top(lines[i]);
718             }
719             this.process_children = false;
720         },
721         compile_action_debug : function(value) {
722             this.top("debugger;");
723         },
724         compile_action_log : function(value) {
725             this.top("console.log(" + this.format_expression(value) + ");");
726         }
727     });
728     return Element;
729 })();