// vim:set et fdm=syntax fdl=0 fdc=3 fdn=2: //--------------------------------------------------------- // 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, "&").replace(//g, ">"); }, escape_att:function(s) { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); }, 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 + 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 */ 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 ${name} * is specified via @t-as: * * ${name} The current element itself * * ${name}_value Same as ${name} * * ${name}_index The 0-based index of the current element * * ${name}_first Whether the current element is the first one * * ${name}_parity odd|even (as strings) * * ${name}_all The iterated collection itself * * If the collection being iterated is an array, also adds: * * ${name}_last 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 t-foreach'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"; } };