summaryrefslogtreecommitdiff
path: root/addons/web/static/lib/qweb
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
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/lib/qweb')
-rw-r--r--addons/web/static/lib/qweb/qweb-benchmark.html57
-rw-r--r--addons/web/static/lib/qweb/qweb-benchmark.xml58
-rw-r--r--addons/web/static/lib/qweb/qweb-test-attributes.xml83
-rw-r--r--addons/web/static/lib/qweb/qweb-test-call.xml63
-rw-r--r--addons/web/static/lib/qweb/qweb-test-conditionals.xml68
-rw-r--r--addons/web/static/lib/qweb/qweb-test-extend.xml62
-rw-r--r--addons/web/static/lib/qweb/qweb-test-foreach.xml46
-rw-r--r--addons/web/static/lib/qweb/qweb-test-global.xml54
-rw-r--r--addons/web/static/lib/qweb/qweb-test-output.xml42
-rw-r--r--addons/web/static/lib/qweb/qweb-test-set.xml53
-rw-r--r--addons/web/static/lib/qweb/qweb-test-utf8tags.xml8
-rw-r--r--addons/web/static/lib/qweb/qweb-test-widgets.xml26
-rw-r--r--addons/web/static/lib/qweb/qweb-test.js.html73
-rw-r--r--addons/web/static/lib/qweb/qweb.js435
-rw-r--r--addons/web/static/lib/qweb/qweb2.js861
15 files changed, 1989 insertions, 0 deletions
diff --git a/addons/web/static/lib/qweb/qweb-benchmark.html b/addons/web/static/lib/qweb/qweb-benchmark.html
new file mode 100644
index 00000000..4f7fb56a
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-benchmark.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <script type="text/javascript" src="qweb.js"></script>
+ <script type="text/javascript" src="qweb2.js"></script>
+ <script type="text/javascript">
+ (function (c) {
+ if (c.time) { return; }
+ var d = {};
+ c.time = function (key) {
+ d[key] = Date.now();
+ };
+ c.timeEnd = function (key) {
+ var end = Date.now(),
+ origin = d[key];
+ delete d[key];
+ if (!origin) { return; }
+ console.log(key + ': ' + (end - origin) + 'ms');
+ };
+ })(window.console);
+ var dict = {
+ session : true,
+ testing : 'yes',
+ name : 'AGR'
+ };
+ console.time("Load template with QWeb");
+ QWeb.add_template("qweb-benchmark.xml");
+ console.timeEnd("Load template with QWeb");
+
+ console.time("Load template with QWeb2");
+ var engine = new QWeb2.Engine("qweb-benchmark.xml")
+ engine.debug = true;
+ console.timeEnd("Load template with QWeb2")
+
+ var iter = 1000;
+ console.log("Rendering...");
+ console.time("Render " + iter + " templates with QWeb");
+ for (var i = 0; i < iter; i++) {
+ var qweb = QWeb.render('benchmark', dict);
+ }
+ console.timeEnd("Render " + iter + " templates with QWeb");
+
+ console.time("Render " + iter + " templates with QWeb2");
+ for (var i = 0; i < iter; i++) {
+ var qweb2 = engine.render('benchmark', dict);
+ }
+ console.timeEnd("Render " + iter + " templates with QWeb2");
+ </script>
+</head>
+
+<body>
+Please, check your console for results
+</body>
+
+</html>
+
diff --git a/addons/web/static/lib/qweb/qweb-benchmark.xml b/addons/web/static/lib/qweb/qweb-benchmark.xml
new file mode 100644
index 00000000..a499b4f1
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-benchmark.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template">
+<t t-name="benchmark"><div id="oe_notification" class="oe_notification">
+ <div id="oe_notification_default">
+ <a class="ui-notify-cross ui-notify-close" href="#">x</a>
+ <h1>title</h1>
+ <p>text</p>
+ </div>
+ <div id="oe_notification_alert" class="ui-state-error">
+ <a class="ui-notify-cross ui-notify-close" href="#">x</a>
+ <span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert"></span>
+ <h1>title</h1>
+ <p>text</p>
+ </div>
+ </div>
+ <t t-js="d">
+ d.iter = 'one,two,three,four,five'.split(',')
+ </t>
+ <t t-foreach="iter" t-as="i">
+ <t t-call="benchmark_call">
+ + <t t-esc="i"/>
+ </t>
+ </t>
+ <t t-set="enplus">1</t>
+ <t t-set="novar">true</t>
+ <div t-attf-class="id_#{enplus}"/>
+ <div t-if="testing || true" t-att-class="novar || 'yes'" style="display: none">
+ <t t-set="novar"></t>
+ <t t-set="style">height: 200px; border: 1px solid red;</t>
+ <div t-att="{ 'style' : style, 'disabled' : 'false', 'readonly' : novar or undefined }"/>
+ <t t-foreach="{'my': 'first', 'my2': 'second' }" t-as="v">
+ * <t t-esc="v"/> : <t t-esc="v_value"/>
+ </t>
+ Ok this is good <t t-esc="name"/>!
+ <t t-set="myvar">Hi there !</t>
+ [<t t-raw="myvar"/>]
+ <t t-set="myvar2" t-value="'a,b,c,d,e'.split(',')"/>
+ <t t-foreach="myvar2" t-as="i">
+ (<t t-esc="i"/>)
+ </t>
+ </div>
+ <div id="oe_notification" class="oe_notification">
+ <div id="oe_notification_default">
+ <a class="ui-notify-cross ui-notify-close" href="#">x</a>
+ <h1>title</h1>
+ <p>text</p>
+ </div>
+ </div>
+</t>
+<t t-name="benchmark_call">
+ <div id="oe_notification_alert" class="ui-state-error">
+ <a class="ui-notify-cross ui-notify-close" href="#">x</a>
+ <span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert"></span>
+ <h1>Here's your value : (<t t-esc="0"/>) !!</h1>
+ </div>
+</t>
+</templates>
+
diff --git a/addons/web/static/lib/qweb/qweb-test-attributes.xml b/addons/web/static/lib/qweb/qweb-test-attributes.xml
new file mode 100644
index 00000000..b599b76b
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-attributes.xml
@@ -0,0 +1,83 @@
+<templates>
+ <t t-name="static">
+ <div foo="a" bar="b" baz="c"/>
+ </t>
+ <result id="static"><![CDATA[<div foo="a" bar="b" baz="c"></div>]]></result>
+
+ <t t-name="static-void">
+ <img src="/test.jpg" alt="Test"/>
+ </t>
+ <result id="static-void"><![CDATA[<img src="/test.jpg" alt="Test"/>]]></result>
+
+ <t t-name="fixed-literal">
+ <div t-att-foo="'bar'"/>
+ </t>
+ <result id="fixed-literal"><![CDATA[<div foo="bar"></div>]]></result>
+
+ <t t-name="fixed-variable">
+ <div t-att-foo="value"/>
+ </t>
+ <params id="fixed-variable">{"value": "ok"}</params>
+ <result id="fixed-variable"><![CDATA[<div foo="ok"></div>]]></result>
+
+ <t t-name="tuple-literal">
+ <div t-att="['foo', 'bar']"/>
+ </t>
+ <result id="tuple-literal"><![CDATA[<div foo="bar"></div>]]></result>
+
+ <t t-name="tuple-variable">
+ <div t-att="value"/>
+ </t>
+ <params id="tuple-variable">{"value": ["foo", "bar"]}</params>
+ <result id="tuple-variable"><![CDATA[<div foo="bar"></div>]]></result>
+
+ <t t-name="object">
+ <div t-att="value"/>
+ </t>
+ <params id="object">{"value": {"a": 1, "b": 2, "c": 3}}</params>
+ <result id="object"><![CDATA[<div a="1" b="2" c="3"></div>]]></result>
+
+ <t t-name="format-literal">
+ <div t-attf-foo="bar"/>
+ </t>
+ <result id="format-literal"><![CDATA[<div foo="bar"></div>]]></result>
+
+ <t t-name="format-value">
+ <div t-attf-foo="b{{value}}r"/>
+ </t>
+ <params id="format-value">{"value": "a"}</params>
+ <result id="format-value"><![CDATA[<div foo="bar"></div>]]></result>
+
+ <t t-name="format-expression">
+ <div t-attf-foo="{{value + 37}}"/>
+ </t>
+ <params id="format-expression">{"value": 5}</params>
+ <result id="format-expression"><![CDATA[<div foo="42"></div>]]></result>
+
+ <t t-name="format-multiple">
+ <div t-attf-foo="a {{value1}} is {{value2}} of {{value3}} ]"/>
+ </t>
+ <params id="format-multiple">{
+ "value1": 0,
+ "value2": 1,
+ "value3": 2
+ }</params>
+ <result id="format-multiple"><![CDATA[
+ <div foo="a 0 is 1 of 2 ]"></div>
+ ]]></result>
+
+ <t t-name="various-escapes">
+ <div foo="&lt;foo"
+ t-att-bar="bar"
+ t-attf-baz="&lt;{{baz}}&gt;"
+ t-att="qux"/>
+ </t>
+ <params id="various-escapes"><![CDATA[{
+ "bar": "<bar>",
+ "baz": "\"<baz>\"",
+ "qux": {"qux": "<>"}
+ }]]></params>
+ <result id="various-escapes"><![CDATA[
+ <div foo="&lt;foo" bar="&lt;bar&gt;" baz="&lt;&quot;&lt;baz&gt;&quot;&gt;" qux="&lt;&gt;"></div>
+ ]]></result>
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-call.xml b/addons/web/static/lib/qweb/qweb-test-call.xml
new file mode 100644
index 00000000..8cf1d1d6
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-call.xml
@@ -0,0 +1,63 @@
+<templates>
+ <t t-name="_basic-callee">ok</t>
+ <t t-name="_callee-printsbody"><t t-esc="0"/></t>
+ <t t-name="_callee-uses-foo"><t t-esc="foo"/></t>
+
+ <t t-name="basic-caller">
+ <t t-call="_basic-callee"/>
+ </t>
+ <result id="basic-caller">ok</result>
+
+ <t t-name="with-unused-body">
+ <t t-call="_basic-callee">WHEEE</t>
+ </t>
+ <result id="with-unused-body">ok</result>
+
+ <t t-name="with-unused-setbody">
+ <t t-call="_basic-callee">
+ <t t-set="qux" t-value="3"/>
+ </t>
+ </t>
+ <result id="with-unused-setbody">ok</result>
+
+ <t t-name="with-used-body">
+ <t t-call="_callee-printsbody">ok</t>
+ </t>
+ <result id="with-used-body">ok</result>
+
+ <t t-name="with-used-setbody">
+ <t t-call="_callee-uses-foo">
+ <t t-set="foo" t-value="'ok'"/>
+ </t>
+ </t>
+ <result id="with-used-setbody">ok</result>
+
+ <!--
+ postfix to call removed because Python impl appends all whitespace
+ following called template's root to template result (+= element.tail)
+ -> ends up with bunch of extra whitespace in the middle of the
+ generated content. Could normalize, not sure current impl can be
+ fixed as-is
+ -->
+ <t t-name="inherit-context">
+ <t t-set="foo" t-value="1"/>
+ <t t-call="_callee-uses-foo"/><!-- - <t t-esc="foo"/> -->
+ </t>
+ <result id="inherit-context">1<!-- - 1 --></result>
+
+ <t t-name="scoped-parameter">
+ <t t-call="_basic-callee">
+ <t t-set="foo" t-value="42"/>
+ </t>
+ <!-- should not print anything -->
+ <t t-esc="foo"/>
+ </t>
+ <result id="scoped-parameter">
+ ok
+ </result>
+
+ <t t-name="expression-caller">
+ <t t-call="{{True and '_basic-callee' or 'other'}}"/>
+ </t>
+ <result id="expression-caller">ok</result>
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-conditionals.xml b/addons/web/static/lib/qweb/qweb-test-conditionals.xml
new file mode 100644
index 00000000..f4b95b69
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-conditionals.xml
@@ -0,0 +1,68 @@
+<templates>
+ <t t-name="boolean-value-condition">
+ <t t-if="condition">ok</t>
+ </t>
+ <params id="boolean-value-condition">{"condition": true}</params>
+ <result id="boolean-value-condition">ok</result>
+
+ <t t-name="boolean-value-condition-false">
+ <t t-if="condition">fail</t>
+ </t>
+ <params id="boolean-value-condition-false">{"condition": false}</params>
+ <result id="boolean-value-condition-false"/>
+
+ <t t-name="boolean-value-condition-missing">
+ <t t-if="condition">fail</t>
+ </t>
+ <result id="boolean-value-condition-missing"/>
+
+ <t t-name="boolean-value-condition-elif">
+ <t t-if="color == 'black'">black pearl</t>
+ <t t-elif="color == 'yellow'">yellow submarine</t>
+ <t t-elif="color == 'red'">red is dead</t>
+ <t t-else="">beer</t>
+ </t>
+ <params id="boolean-value-condition-elif">{"color": "red"}</params>
+ <result id="boolean-value-condition-elif">red is dead</result>
+
+ <t t-name="boolean-value-condition-else">
+ <div><span>begin</span><t t-if="condition">ok</t>
+ <t t-else="">ok-else</t><span>end</span></div>
+ </t>
+ <params id="boolean-value-condition-else">{"condition": true}</params>
+ <result id="boolean-value-condition-else"><![CDATA[<div><span>begin</span>ok<span>end</span></div>]]></result>
+
+ <t t-name="boolean-value-condition-false-else">
+ <div><span>begin</span><t t-if="condition">fail</t>
+ <t t-else="">fail-else</t><span>end</span></div>
+ </t>
+ <params id="boolean-value-condition-false-else">{"condition": false}</params>
+ <result id="boolean-value-condition-false-else"><![CDATA[<div><span>begin</span>fail-else<span>end</span></div>]]></result>
+
+ <t t-name="comment-branching">
+ <t t-if="condition == 'if'">if</t>
+ <t t-elif="condition == 'elif1'">elif1</t>
+ <!-- Comment ignored PART OF THE TEST !!! -->
+ <t t-elif="condition == 'elif2'">elif2</t>
+ <t t-else="">else</t>
+ </t>
+ <params id="comment-branching">{"condition": "elif1"}</params>
+ <result id="comment-branching"><![CDATA[elif1]]></result>
+
+ <t t-name="comment-branching-1">
+ <t t-if="condition == 'if'">if</t>
+ <t t-elif="condition == 'elif1'">elif1</t>
+ <!-- Comment ignored PART OF THE TEST !!! -->
+ <t t-elif="condition == 'elif2'">elif2</t>
+ <t t-else="">else</t>
+ </t>
+ <params id="comment-branching-1">{"condition": "elif2"}</params>
+ <result id="comment-branching-1"><![CDATA[elif2]]></result>
+
+ <t t-name="comment-branching-2">
+ <div t-if="condition == 'if'">if</div><!-- Comment ignored PART OF THE TEST !!! --><div>sometext</div>
+ </t>
+ <params id="comment-branching-2">{"condition": "if"}</params>
+ <result id="comment-branching-2"><![CDATA[<div>if</div><div>sometext</div>]]></result>
+
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-extend.xml b/addons/web/static/lib/qweb/qweb-test-extend.xml
new file mode 100644
index 00000000..f8a24c4e
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-extend.xml
@@ -0,0 +1,62 @@
+<templates>
+ <!-- js-only -->
+ <t t-name="jquery-extend">
+ <ul><li>one</li></ul>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul" t-operation="append"><li>3</li></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul li:first-child" t-operation="replace"><li>2</li></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul" t-operation="prepend"><li>1</li></t>
+ <t t-jquery="ul" t-operation="before"><hr/></t>
+ <t t-jquery="ul" t-operation="after"><hr/></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul">this.attr('class', 'main');</t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul" t-operation="attributes"><attribute name="title" value="Main Title" /></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul" t-operation="attributes"><attribute name="name">main-ul</attribute></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="hr:eq(1)" t-operation="replace"><footer></footer></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="footer" t-operation="inner"><b>[[end]]</b></t>
+ </t>
+ <result id="jquery-extend"><![CDATA[
+ <hr/><ul class="main" title="Main Title" name="main-ul"><li>1</li><li>2</li><li>3</li></ul><footer><b>[[end]]</b></footer>
+]]></result>
+
+ <t t-name="jquery-extend-clone" t-extend="jquery-extend">
+ <t t-jquery="ul" t-operation="append"><li>[[cloned template]]</li></t>
+ </t>
+ <result id="jquery-extend-clone"><![CDATA[
+ <hr/><ul class="main" title="Main Title" name="main-ul"><li>1</li><li>2</li><li>3</li><li>[[cloned template]]</li></ul><footer><b>[[end]]</b></footer>
+]]></result>
+
+
+ <t t-name="a">
+ <div><span>Hi</span></div>
+ </t>
+ <t t-name="b" t-extend="a">
+ <t t-jquery="span" t-operation="after"><i>World</i></t>
+ </t>
+ <t t-name="c" t-extend="b">
+ <t t-jquery="span" t-operation="replace"><span>Hello</span></t>
+ </t>
+ <result id="a"><![CDATA[
+ <div><span>Hi</span></div>
+]]></result>
+ <result id="b"><![CDATA[
+ <div><span>Hi</span><i>World</i></div>
+]]></result>
+ <result id="c"><![CDATA[
+ <div><span>Hello</span><i>World</i></div>
+]]></result>
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-foreach.xml b/addons/web/static/lib/qweb/qweb-test-foreach.xml
new file mode 100644
index 00000000..9a1e8ecb
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-foreach.xml
@@ -0,0 +1,46 @@
+<templates xml:space="preserve">
+ <t t-name="iter-items">
+ <t t-foreach="[3, 2, 1]" t-as="item">
+[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
+ </t>
+ <result id="iter-items">
+[0: 3 3]
+[1: 2 2]
+[2: 1 1]
+ </result>
+
+ <t t-name="iter-position">
+ <t t-foreach="5" t-as="item">
+-<t t-if="item_first"> first</t><t t-if="item_last"> last</t> (<t t-esc="item_parity"/>)</t>
+ </t>
+ <result id="iter-position">
+- first (even)
+- (odd)
+- (even)
+- (odd)
+- last (even)
+ </result>
+
+ <!-- test integer param -->
+ <t t-name="iter-int">
+ <t t-foreach="3" t-as="item">
+[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
+ </t>
+ <result id="iter-int">
+[0: 0 0]
+[1: 1 1]
+[2: 2 2]
+ </result>
+
+ <!-- test dict param -->
+ <t t-name="iter-dict">
+ <t t-foreach="value" t-as="item">
+[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/> - <t t-esc="item_parity"/>]</t>
+ </t>
+ <params id="iter-dict">{"value": {"a": 1, "b": 2, "c": 3}}</params>
+ <result id="iter-dict">
+[0: a 1 - even]
+[1: b 2 - odd]
+[2: c 3 - even]
+ </result>
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-global.xml b/addons/web/static/lib/qweb/qweb-test-global.xml
new file mode 100644
index 00000000..8bdefc6a
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-global.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<templates>
+ <t t-name="_callee-asc"><Año t-att-falló="'agüero'" t-raw="0"/></t>
+ <t t-name="_callee-uses-foo"><span t-esc="foo">foo default</span></t>
+ <t t-name="_callee-asc-toto"><div t-raw="toto">toto default</div></t>
+
+ <t t-name="caller">
+ <t t-foreach="[4,5,6]" t-as="value">
+ <span t-esc="value"/>
+ <t t-call="_callee-asc">
+ <t t-call="_callee-uses-foo">
+ <t t-set="foo" t-value="'aaa'"/>
+ </t>
+ <t t-call="_callee-uses-foo"/>
+ <t t-set="foo" t-value="'bbb'"/>
+ <t t-call="_callee-uses-foo"/>
+ </t>
+ </t>
+ <t t-call="_callee-asc-toto"/>
+ <t t-set="toto"><t t-set="truc" t-value="'bbb'"/><i t-att-notruc="not truc or None" t-att-truc="bool(truc)">i</i></t>
+ <t t-call="_callee-asc-toto"/>
+ </t>
+
+ <result id="caller"><![CDATA[
+ <span>4</span>
+ <Año falló="agüero">
+ <span>aaa</span>
+ <span>foo default</span>
+
+ <span>bbb</span>
+ </Año>
+
+ <span>5</span>
+ <Año falló="agüero">
+ <span>aaa</span>
+ <span>foo default</span>
+
+ <span>bbb</span>
+ </Año>
+
+ <span>6</span>
+ <Año falló="agüero">
+ <span>aaa</span>
+ <span>foo default</span>
+
+ <span>bbb</span>
+ </Año>
+
+ <div>toto default</div>
+
+ <div><i truc="True">i</i></div>
+ ]]></result>
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-output.xml b/addons/web/static/lib/qweb/qweb-test-output.xml
new file mode 100644
index 00000000..cbe7f051
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-output.xml
@@ -0,0 +1,42 @@
+<templates>
+ <!-- esc, evaluates and returns @t-esc after having xml-escaped it -->
+ <t t-name="esc-literal">
+ <t t-esc="'ok'"/>
+ </t>
+ <result id="esc-literal">ok</result>
+
+ <t t-name="esc-variable">
+ <t t-esc="var"/>
+ </t>
+ <params id="esc-variable">{"var": "ok"}</params>
+ <result id="esc-variable">ok</result>
+
+ <t t-name="esc-toescape">
+ <t t-esc="var"/>
+ </t>
+ <params id="esc-toescape"><![CDATA[{"var": "<ok>"}]]></params>
+ <result id="esc-toescape"><![CDATA[&lt;ok&gt;]]></result>
+ <t t-name="esc-node">
+ <span t-esc="'ok'"/>
+ </t>
+ <result id="esc-node"><![CDATA[<span>ok</span>]]></result>
+
+
+ <!-- raw, evaluates and returns @t-raw directly (no escaping) -->
+ <t t-name="raw-literal">
+ <t t-raw="'ok'"/>
+ </t>
+ <result id="raw-literal">ok</result>
+
+ <t t-name="raw-variable">
+ <t t-raw="var"/>
+ </t>
+ <params id="raw-variable">{"var": "ok"}</params>
+ <result id="raw-variable">ok</result>
+
+ <t t-name="raw-notescaped">
+ <t t-raw="var"/>
+ </t>
+ <params id="raw-notescaped"><![CDATA[{"var": "<ok>"}]]></params>
+ <result id="raw-notescaped"><![CDATA[<ok>]]></result>
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-set.xml b/addons/web/static/lib/qweb/qweb-test-set.xml
new file mode 100644
index 00000000..945fd4a2
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-set.xml
@@ -0,0 +1,53 @@
+<templates>
+ <t t-name="set-from-attribute-literal">
+ <t t-set="value" t-value="'ok'"/>
+ <t t-esc="value"/>
+ </t>
+ <result id="set-from-attribute-literal">
+ ok
+ </result>
+
+ <t t-name="set-from-body-literal">
+ <t t-set="value">ok</t>
+ <t t-esc="value"/>
+ </t>
+ <result id="set-from-body-literal">
+ ok
+ </result>
+
+ <t t-name="set-from-attribute-lookup">
+ <t t-set="stuff" t-value="value"/>
+ <t t-esc="stuff"/>
+ </t>
+ <params id="set-from-attribute-lookup">
+ {"value": "ok"}
+ </params>
+ <result id="set-from-attribute-lookup">
+ ok
+ </result>
+
+ <t t-name="set-from-body-lookup">
+ <t t-set="stuff">
+ <t t-esc="value"/>
+ </t>
+ <t t-esc="stuff"/>
+ </t>
+ <params id="set-from-body-lookup">
+ {"value": "ok"}
+ </params>
+ <result id="set-from-body-lookup">
+ ok
+ </result>
+
+ <t t-name="set-empty-body">
+ <t t-set="stuff"/>
+ <t t-esc="stuff"/>
+ </t>
+ <result id="set-empty-body"/>
+
+ <t t-name="t-value-priority">
+ <t t-set="value" t-value="1">2</t>
+ <t t-esc="value"/>
+ </t>
+ <result id="t-value-priority">1</result>
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-utf8tags.xml b/addons/web/static/lib/qweb/qweb-test-utf8tags.xml
new file mode 100644
index 00000000..dd51635b
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-utf8tags.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<templates>
+ <t t-name="fixed-literal">
+ <Año t-att-falló="'agüero'"/>
+ </t>
+ <result id="fixed-literal"><![CDATA[<Año falló="agüero"></Año>]]></result>
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-widgets.xml b/addons/web/static/lib/qweb/qweb-test-widgets.xml
new file mode 100644
index 00000000..503880bc
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test-widgets.xml
@@ -0,0 +1,26 @@
+<templates>
+ <t t-name="date-simple"><t t-esc='value' /></t>
+ <params id="date-simple">{"value": "1988-09-16"}</params>
+ <result id="date-simple">1988-09-16</result>
+
+ <t t-name="datetime-simple"><t t-esc='value' /></t>
+ <params id="datetime-simple">{"value": "1988-09-16 14:00:00"}</params>
+ <result id="datetime-simple">1988-09-16 14:00:00</result>
+
+ <t t-name="datetime-widget-datetime"><t t-esc='value' t-options="{'widget': 'datetime'}" /></t>
+ <params id="datetime-widget-datetime">{"value": "1988-09-16 14:00:00"}</params>
+ <result id="datetime-widget-datetime">09/16/1988 16:00:00</result>
+
+ <t t-name="datetime-widget-date"><t t-esc='value' t-options="{'widget': 'date'}" /></t>
+ <params id="datetime-widget-date">{"value": "1988-09-16 14:00:00"}</params>
+ <result id="datetime-widget-date">09/16/1988</result>
+
+ <t t-name="datetime-widget-date-tz2"><t t-esc='value' t-options="{'widget': 'date'}" /></t>
+ <params id="datetime-widget-date-tz2">{"value": "1988-09-16 01:00:00"}</params>
+ <result id="datetime-widget-date-tz2">09/16/1988</result>
+
+ <t t-name="datetime-widget-date-tz"><t t-esc='value' t-options="{'widget': 'date'}" /></t>
+ <params id="datetime-widget-date-tz">{"value": "1988-09-16 23:00:00"}</params>
+ <result id="datetime-widget-date-tz">09/17/1988</result>
+
+</templates>
diff --git a/addons/web/static/lib/qweb/qweb-test.js.html b/addons/web/static/lib/qweb/qweb-test.js.html
new file mode 100644
index 00000000..0ee5f27c
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test.js.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+<head>
+ <script src="/web/static/lib/jquery/jquery.js"></script>
+ <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css" type="text/css" media="screen"/>
+ <script type="text/javascript" src="/web/static/lib/qunit/qunit.js"></script>
+
+ <script type="text/javascript" src="qweb2.js"></script>
+
+ <script>
+ QWeb = new QWeb2.Engine();
+ function trim(s) {
+ return s.replace(/(^\s+|\s+$)/g, '');
+ }
+ function render(template, context) {
+ return trim(QWeb.render(template, context)).toLowerCase();
+ }
+
+ /**
+ * Loads the template file, and executes all the test template in a
+ * qunit module $title
+ */
+ function test(title, template) {
+ QUnit.module(title, {
+ setup: function () {
+ var self = this;
+ this.qweb = new QWeb2.Engine();
+ QUnit.stop();
+ this.qweb.add_template(template, function (_, doc) {
+ self.doc = doc;
+ QUnit.start();
+ })
+ }
+ });
+ QUnit.test('autotest', function (assert) {
+ var templates = this.qweb.templates;
+ for (var template in templates) {
+ if (!templates.hasOwnProperty(template)) { continue; }
+ // ignore templates whose name starts with _, they're
+ // helpers/internal
+ if (/^_/.test(template)) { continue; }
+
+ var params = this.doc.querySelector('params#' + template);
+ var args = params ? JSON.parse(params.textContent) : {};
+
+ var results = this.doc.querySelector('result#' + template);
+ assert.equal(
+ trim(this.qweb.render(template, args)),
+ trim(results.textContent),
+ template);
+ }
+ });
+ }
+ $(document).ready(function() {
+ test("Output", 'qweb-test-output.xml');
+ test("Context-setting", 'qweb-test-set.xml');
+ test("Conditionals", 'qweb-test-conditionals.xml');
+ test("Attributes manipulation", 'qweb-test-attributes.xml');
+ test("Template calling (to the faraway pages)",
+ 'qweb-test-call.xml');
+ test("Foreach", 'qweb-test-foreach.xml');
+ test("Global", 'qweb-test-global.xml');
+
+ test('Template inheritance', 'qweb-test-extend.xml');
+ });
+ </script>
+
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/addons/web/static/lib/qweb/qweb.js b/addons/web/static/lib/qweb/qweb.js
new file mode 100644
index 00000000..cd652485
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb.js
@@ -0,0 +1,435 @@
+/*
+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.
+*/
+
+//---------------------------------------------------------
+// QWeb javascript
+//---------------------------------------------------------
+
+/*
+ TODO
+
+ String parsing
+ if (window.DOMParser) {
+ parser=new DOMParser();
+ xmlDoc=parser.parseFromString(text,"text/xml");
+ } else {
+ xmlDoc=new ActiveXObject("Msxml2.DOMDocument.4.0");
+ xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
+ Which versions to try, it's confusing...
+ xmlDoc.async="false";
+ xmlDoc.async=false;
+ xmlDoc.preserveWhiteSpace=true;
+ xmlDoc.load("f.xml");
+ xmlDoc.loadXML(text); ?
+ }
+
+ Support space in IE by reparsing the responseText
+ xmlhttp.responseXML.loadXML(xmlhttp.responseText); ?
+
+ Preprocess: (nice optimization)
+ preprocess by flattening all non t- element to a TEXT_NODE.
+ count the number of "\n" in text nodes to give an aproximate LINE NUMBER on elements for error reporting
+ if from IE HTMLDOM use if(a[i].specified) to avoid 88 empty attributes per element during the preprocess,
+
+ implement t-trim 'left' 'right' 'both', is it needed ? inner=render_trim(l_inner.join(), t_att)
+
+ Ruby/python: to backport from javascript to python/ruby render_node to use regexp, factorize foreach %var, t-att test for tuple(attname,value)
+
+ DONE
+ we reintroduced t-att-id, no more t-esc-id because of the new convention t-att="["id","val"]"
+*/
+
+var QWeb = {
+ templates:{},
+ prefix:"t",
+ reg:new RegExp(),
+ tag:{},
+ att:{},
+ ValueException: function (value, message) {
+ this.value = value;
+ this.message = message;
+ },
+ eval_object:function(e, v) {
+ // TODO: Currently this will also replace and, or, ... in strings. Try
+ // 'hi boys and girls' != '' and 1 == 1 -- will be replaced to : 'hi boys && girls' != '' && 1 == 1
+ // try to find a solution without tokenizing
+ e = '(' + e + ')';
+ e = e.replace(/\band\b/g, " && ");
+ e = e.replace(/\bor\b/g, " || ");
+ e = e.replace(/\bgt\b/g, " > ");
+ e = e.replace(/\bgte\b/g, " >= ");
+ e = e.replace(/\blt\b/g, " < ");
+ e = e.replace(/\blte\b/g, " <= ");
+ if (v[e] != undefined) {
+ return v[e];
+ } else {
+ with (v) return eval(e);
+ }
+ },
+ eval_str:function(e, v) {
+ var r = this.eval_object(e, v);
+ r = (typeof(r) == "undefined" || r == null) ? "" : r.toString();
+ return e == "0" ? v["0"] : r;
+ },
+ eval_format:function(e, v) {
+ var m, src = e.split(/#/), r = src[0];
+ for (var i = 1; i < src.length; i++) {
+ if (m = src[i].match(/^{(.*)}(.*)/)) {
+ r += this.eval_str(m[1], v) + m[2];
+ } else {
+ r += "#" + src[i];
+ }
+ }
+ return r;
+ },
+ eval_bool:function(e, v) {
+ return !!this.eval_object(e, v);
+ },
+ trim : function(v, mode) {
+ if (!v || !mode) return v;
+ switch (mode) {
+ case 'both':
+ return v.replace(/^\s*|\s*$/g, "");
+ case "left":
+ return v.replace(/^\s*/, "");
+ case "right":
+ return v.replace(/\s*$/, "");
+ }
+ throw new QWeb.ValueException(
+ mode, "unknown trimming mode, trim mode must follow the pattern '[inner] (left|right|both)'");
+ },
+ escape_text:function(s) {
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+ },
+ escape_att:function(s) {
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
+ },
+ render_node : function(e, v, inner_trim) {
+ if (e.nodeType == 3) {
+ return inner_trim ? this.trim(e.data, inner_trim) : e.data;
+ }
+ if (e.nodeType == 1) {
+ var g_att = {};
+ var t_att = {};
+ var t_render = null;
+ var a = e.attributes;
+ for (var i = 0; i < a.length; i++) {
+ var an = a[i].name,av = a[i].value;
+ var m;
+ if (m = an.match(this.reg)) {
+ var n = m[1];
+ if (n == "eval") {
+ n = m[2].substring(1);
+ av = this.eval_str(av, v);
+ }
+ var f;
+ if (f = this.att[n]) {
+ this[f](e, t_att, g_att, v, m[2], av);
+ } else if (f = this.tag[n]) {
+ t_render = f;
+ }
+ t_att[n] = av;
+ } else {
+ g_att[an] = av;
+ }
+ }
+ if (inner_trim && !t_att["trim"]) {
+ t_att["trim"] = "inner " + inner_trim;
+ }
+ if (t_render) {
+ return this[t_render](e, t_att, g_att, v);
+ }
+ return this.render_element(e, t_att, g_att, v);
+ }
+ return "";
+ },
+ render_element:function(e, t_att, g_att, v) {
+ var inner = "", ec = e.childNodes, trim = t_att["trim"], inner_trim;
+ if (trim) {
+ if (/\binner\b/.test(trim)) {
+ inner_trim = true;
+ if (trim == 'inner') {
+ trim = "both";
+ }
+ }
+ var tm = /\b(both|left|right)\b/.exec(trim);
+ if (tm) trim = tm[1];
+ }
+ for (var i = 0; i < ec.length; i++) {
+ inner += inner_trim ? this.trim(this.render_node(ec[i], v, inner_trim ? trim : null), trim) : this.render_node(ec[i], v, inner_trim ? trim : null);
+ }
+ if (trim && !inner_trim) {
+ inner = this.trim(inner, trim);
+ }
+ if (e.tagName == this.prefix) {
+ return inner;
+ }
+ var att = "";
+ for (var an in g_att) {
+ att += " " + an + '="' + this.escape_att(g_att[an]) + '"';
+ }
+ // Some IE versions have problems with closed tags
+ var opentag = !!t_att['opentag'] && this.eval_bool(t_att["opentag"], v);
+ return inner.length || opentag ? "<" + e.tagName + att + ">" + inner + "</" + e.tagName + ">" : "<" + e.tagName + att + "/>";
+ },
+ render_att_att:function(e, t_att, g_att, v, ext, av) {
+ if (ext) {
+ var attv = this.eval_object(av, v);
+ if (attv != null) {
+ g_att[ext.substring(1)] = attv.toString();
+ }
+ } else {
+ var o = this.eval_object(av, v);
+ if (o != null) {
+ // TODO: http://bonsaiden.github.com/JavaScript-Garden/#types.typeof
+ if (o.constructor == Array && o.length > 1 && o[1] != null) {
+ g_att[o[0]] = new String(o[1]);
+ } else if (o.constructor == Object) {
+ for (var i in o) {
+ if(o[i]!=null) {
+ g_att[i] = new String(o[i]);
+ }
+ }
+ }
+ }
+ }
+ },
+ render_att_attf:function(e, t_att, g_att, v, ext, av) {
+ g_att[ext.substring(1)] = this.eval_format(av, v);
+ },
+ render_tag_raw:function(e, t_att, g_att, v) {
+ return this.eval_str(t_att["raw"], v);
+ },
+ render_tag_rawf:function(e, t_att, g_att, v) {
+ return this.eval_format(t_att["rawf"], v);
+ },
+ /*
+ * Idea: if the name of the tag != t render the tag around the value <a name="a" t-esc="label"/>
+ */
+ render_tag_esc:function(e, t_att, g_att, v) {
+ return this.escape_text(this.eval_str(t_att["esc"], v));
+ },
+ render_tag_escf:function(e, t_att, g_att, v) {
+ return this.escape_text(this.eval_format(t_att["escf"], v));
+ },
+ render_tag_if:function(e, t_att, g_att, v) {
+ return this.eval_bool(t_att["if"], v) ? this.render_element(e, t_att, g_att, v) : "";
+ },
+ render_tag_set:function(e, t_att, g_att, v) {
+ var ev = t_att["value"];
+ if (ev && ev.constructor != Function) {
+ v[t_att["set"]] = this.eval_object(ev, v);
+ } else {
+ v[t_att["set"]] = this.render_element(e, t_att, g_att, v);
+ }
+ return "";
+ },
+ render_tag_call:function(e, t_att, g_att, v) {
+ var d = v;
+ if (!t_att["import"]) {
+ d = {};
+ for (var i in v) {
+ d[i] = v[i];
+ }
+ }
+ d["0"] = this.render_element(e, t_att, g_att, d);
+ return this.render(t_att["call"], d);
+ },
+ render_tag_js:function(e, t_att, g_att, v) {
+ var dict_name = t_att["js"] || "dict";
+ v[dict_name] = v;
+ var r = this.eval_str(this.render_element(e, t_att, g_att, v), v);
+ delete(v[dict_name]);
+ return r || '';
+ },
+ /**
+ * Renders a foreach loop (@t-foreach).
+ *
+ * Adds the following elements to its context, where <code>${name}</code>
+ * is specified via <code>@t-as</code>:
+ * * <code>${name}</code> The current element itself
+ * * <code>${name}_value</code> Same as <code>${name}</code>
+ * * <code>${name}_index</code> The 0-based index of the current element
+ * * <code>${name}_first</code> Whether the current element is the first one
+ * * <code>${name}_parity</code> odd|even (as strings)
+ * * <code>${name}_all</code> The iterated collection itself
+ *
+ * If the collection being iterated is an array, also adds:
+ * * <code>${name}_last</code> Whether the current element is the last one
+ * * All members of the current object
+ *
+ * If the collection being iterated is an object, the value is actually the object's key
+ *
+ * @param e ?
+ * @param t_att attributes of the element being <code>t-foreach</code>'d
+ * @param g_att ?
+ * @param old_context the context in which the foreach is evaluated
+ */
+ render_tag_foreach:function(e, t_att, g_att, old_context) {
+ var expr = t_att["foreach"];
+ var enu = this.eval_object(expr, old_context);
+ var ru = [];
+ if (enu) {
+ var val = t_att['as'] || expr.replace(/[^a-zA-Z0-9]/g, '_');
+ var context = {};
+ for (var i in old_context) {
+ context[i] = old_context[i];
+ }
+ context[val + "_all"] = enu;
+ var val_value = val + "_value",
+ val_index = val + "_index",
+ val_first = val + "_first",
+ val_last = val + "_last",
+ val_parity = val + "_parity";
+ var size = enu.length;
+ if (size) {
+ context[val + "_size"] = size;
+ for (var j = 0; j < size; j++) {
+ var cur = enu[j];
+ context[val_value] = cur;
+ context[val_index] = j;
+ context[val_first] = j == 0;
+ context[val_last] = j + 1 == size;
+ context[val_parity] = (j % 2 == 1 ? 'odd' : 'even');
+ if (cur.constructor == Object) {
+ for (var k in cur) {
+ context[k] = cur[k];
+ }
+ }
+ context[val] = cur;
+ var r = this.render_element(e, t_att, g_att, context);
+ ru.push(r);
+ }
+ } else {
+ var index = 0;
+ for (cur in enu) {
+ context[val_value] = cur;
+ context[val_index] = index;
+ context[val_first] = index == 0;
+ context[val_parity] = (index % 2 == 1 ? 'odd' : 'even');
+ context[val] = cur;
+ ru.push(this.render_element(e, t_att, g_att, context));
+ index += 1;
+ }
+ }
+ return ru.join("");
+ } else {
+ return "qweb: foreach " + expr + " not found.";
+ }
+ },
+ hash:function() {
+ var l = [], m;
+ for (var i in this) {
+ if (m = i.match(/render_tag_(.*)/)) {
+ this.tag[m[1]] = i;
+ l.push(m[1]);
+ } else if (m = i.match(/render_att_(.*)/)) {
+ this.att[m[1]] = i;
+ l.push(m[1]);
+ }
+ }
+ l.sort(function(a, b) {
+ return a.length > b.length ? -1 : 1;
+ });
+ var s = "^" + this.prefix + "-(eval|" + l.join("|") + "|.*)(.*)$";
+ this.reg = new RegExp(s);
+ },
+ /**
+ * returns the correct XMLHttpRequest instance for the browser, or null if
+ * it was not able to build any XHR instance.
+ *
+ * @returns XMLHttpRequest|MSXML2.XMLHTTP.3.0|null
+ */
+ get_xhr:function () {
+ if (window.XMLHttpRequest) {
+ return new window.XMLHttpRequest();
+ }
+ try {
+ return new ActiveXObject('MSXML2.XMLHTTP.3.0');
+ } catch(e) {
+ return null;
+ }
+ },
+ load_xml:function(s) {
+ var xml;
+ if (s[0] == "<") {
+ /*
+ manque ca pour sarrisa
+ if(window.DOMParser){
+ mozilla
+ if(!window.DOMParser){
+ var doc = Sarissa.getDomDocument();
+ doc.loadXML(sXml);
+ return doc;
+ };
+ };
+ */
+ } else {
+ var req = this.get_xhr();
+ if (req) {
+ req.open("GET", s, false);
+ req.send(null);
+ //if ie r.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
+ xml = req.responseXML;
+ /*
+ TODO
+ if intsernetexploror
+ getdomimplmentation() for try catch
+ responseXML.getImplet
+ d=domimple()
+ d.preserverWhitespace=1
+ d.loadXML()
+
+ xml.preserverWhitespace=1
+ xml.loadXML(r.reponseText)
+ */
+ return xml;
+ }
+ }
+ },
+ add_template:function(e) {
+ // TODO: keep sources so we can implement reload()
+ this.hash();
+ if (e.constructor == String) {
+ e = this.load_xml(e);
+ }
+
+ var ec = e.documentElement ? e.documentElement.childNodes : ( e.childNodes ? e.childNodes : [] );
+
+ for (var i = 0; i < ec.length; i++) {
+ var n = ec[i];
+ if (n.nodeType == 1) {
+ var name = n.getAttribute(this.prefix + "-name");
+ this.templates[name] = n;
+ }
+ }
+ },
+ render:function(name, v) {
+ var e;
+ if (e = this.templates[name]) {
+ return this.render_node(e, v);
+ }
+ return "template " + name + " not found";
+ }
+};
+
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;
+})();