This commit is contained in:
Géry Debongnie
2021-10-11 15:03:56 +02:00
parent 82ab18ad83
commit 1a33d3d8e8
7 changed files with 173 additions and 147 deletions
+54 -34
View File
@@ -44,22 +44,17 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio
} else {
exprID = `scope.${value.id}`;
}
if (ctx.parentTextNode) {
ctx.addIf(`${exprID} != null`);
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
ctx.closeIf();
} else if (ctx.parentNode) {
ctx.rootContext.shouldDefineUtils = true;
ctx.addLine(`insertValue(c${ctx.parentNode}, ${exprID})`);
} else {
ctx.addIf(`${exprID} != null`);
if (ctx.escaping) {
let protectID;
if (value.hasBody) {
ctx.rootContext.shouldDefineUtils = true;
protectID = ctx.startProtectScope();
ctx.addLine(
`${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
);
}
if (ctx.parentTextNode) {
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
} else if (ctx.parentNode) {
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
} else {
let nodeID = ctx.generateID();
ctx.rootContext.rootNode = nodeID;
ctx.rootContext.parentTextNode = nodeID;
@@ -67,27 +62,52 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio
if (ctx.rootContext.shouldDefineResult) {
ctx.addLine(`result = vn${nodeID}`);
}
}
if (value.hasBody) {
ctx.stopProtectScope(protectID);
}
} else {
ctx.rootContext.shouldDefineUtils = true;
if (value.hasBody) {
ctx.addLine(
`const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});`
);
ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
} else {
ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
}
}
if (node.childNodes.length) {
ctx.addElse();
qweb._compileChildren(node, ctx);
}
ctx.closeIf();
}
// ctx.addIf(`${exprID} != null`);
// if (ctx.escaping) {
// let protectID;
// if (value.hasBody) {
// ctx.rootContext.shouldDefineUtils = true;
// protectID = ctx.startProtectScope();
// ctx.addLine(
// `${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
// );
// }
// if (ctx.parentTextNode) {
// ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
// } else if (ctx.parentNode) {
// ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
// } else {
// let nodeID = ctx.generateID();
// ctx.rootContext.rootNode = nodeID;
// ctx.rootContext.parentTextNode = nodeID;
// ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
// if (ctx.rootContext.shouldDefineResult) {
// ctx.addLine(`result = vn${nodeID}`);
// }
// }
// if (value.hasBody) {
// ctx.stopProtectScope(protectID);
// }
// } else {
// ctx.rootContext.shouldDefineUtils = true;
// if (value.hasBody) {
// ctx.addLine(
// `const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});`
// );
// ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
// } else {
// ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
// }
// }
// if (node.childNodes.length) {
// ctx.addElse();
// qweb._compileChildren(node, ctx);
// }
// ctx.closeIf();
}
QWeb.addDirective({
+1
View File
@@ -84,6 +84,7 @@ export class CompilationContext {
this.code.unshift(" let QWeb = this.constructor;");
}
if (this.shouldDefineUtils) {
this.code.unshift(" let insertValue = utils.insertValue;");
this.code.unshift(" let utils = this.constructor.utils;");
}
return this.code;
+17 -2
View File
@@ -1,8 +1,9 @@
import { EventBus } from "../core/event_bus";
import { h, patch, VNode } from "../vdom/index";
import { CompilationContext } from "./compilation_context";
import { shallowEqual, escape } from "../utils";
import { shallowEqual, escape, _Markup } from "../utils";
import { addNS } from "../vdom/vdom";
import { htmlToVDOM } from "../vdom/html_to_vdom";
/**
* Owl QWeb Engine
@@ -89,6 +90,16 @@ function isComponent(obj): boolean {
return obj && obj.hasOwnProperty("__owl__");
}
function insertValue(children: any[], value: any) {
if (value != null) {
if (value instanceof _Markup) {
children.push(...htmlToVDOM(value as any));
} else {
children.push({ text: value });
}
}
}
class VDomArray extends Array {
toString() {
return vDomToString(this);
@@ -111,6 +122,7 @@ function vDomToString(vdom: VNode[]): string {
const UTILS: Utils = {
zero: Symbol("zero"),
insertValue,
toClassObj(expr) {
const result = {};
if (typeof expr === "string") {
@@ -599,7 +611,10 @@ export class QWeb extends EventBus {
if (!(dName in QWeb.DIRECTIVE_NAMES)) {
throw new Error(`Unknown QWeb directive: '${attrName}'`);
}
if (node.tagName !== "t" && (attrName === "t-esc" || attrName === "t-out" || attrName === "t-raw")) {
if (
node.tagName !== "t" &&
(attrName === "t-esc" || attrName === "t-out" || attrName === "t-raw")
) {
const tNode = document.implementation.createDocument(
"http://www.w3.org/1999/xhtml",
"t",
+41 -46
View File
@@ -102,68 +102,63 @@ export function shallowEqual(p1, p2): boolean {
return true;
}
const escapeMethod = Symbol('html')
// notable issues:
// * objects can't be negative in JS, so !!"" -> false but
// !!(new String) -> true, likewise markup
// TODO (?)
// * Markup.join / Markup#join => escapes items and returns a Markup
// * Markup#replace => automatically escapes the replacements (difficult impl)
class _Markup extends String {
[escapeMethod]() {
return this;
}
}
export class _Markup extends String {}
/**
* Returns a markup object, which acts like a String but is considered safe by
* `_.escape`, and will therefore be injected as-is (without additional
* escaping) in templates. Can be used to inject dynamic HTML in templates
* (where the template itself can't), see first example.
*
* Can also be used as a *template tag*, in which case the literal content
* won't be escaped but the substitutions which are not already markup objects
* will be.
*
* ## WARNINGS:
* * A markup object is a `String` (boxed) but not a `string` (primitive), they
* typecheck differently which can be relevant.
* * To strip out the "markupness", just call `String(markup)`.
* * Most string operations (e.g. concatenation, `String#replace`, ...) will
* also strip out markupness
* * If the input is empty, returns a regular string (that way boolean tests
* work as expected).
*
* @returns a markup object
*
* @example regular function
* let h;
* if (someTest) {
* h = Markup(_t("This is a <strong>success</strong>"));
* } else {
* h = Markup(_t("Things did <strong>not</strong> work out"));
* }
* qweb.render("some_template", { message: h });
*
* @example template tag
* const escaped = "<some> text";
* const asis = Markup`some <b>text</b>`;
* const h = Markup`Regular strings get ${escaped} but markup is injected ${asis}`;
*/
* Returns a markup object, which acts like a String but is considered safe by
* `_.escape`, and will therefore be injected as-is (without additional
* escaping) in templates. Can be used to inject dynamic HTML in templates
* (where the template itself can't), see first example.
*
* Can also be used as a *template tag*, in which case the literal content
* won't be escaped but the substitutions which are not already markup objects
* will be.
*
* ## WARNINGS:
* * A markup object is a `String` (boxed) but not a `string` (primitive), they
* typecheck differently which can be relevant.
* * To strip out the "markupness", just call `String(markup)`.
* * Most string operations (e.g. concatenation, `String#replace`, ...) will
* also strip out markupness
* * If the input is empty, returns a regular string (that way boolean tests
* work as expected).
*
* @returns a markup object
*
* @example regular function
* let h;
* if (someTest) {
* h = Markup(_t("This is a <strong>success</strong>"));
* } else {
* h = Markup(_t("Things did <strong>not</strong> work out"));
* }
* qweb.render("some_template", { message: h });
*
* @example template tag
* const escaped = "<some> text";
* const asis = Markup`some <b>text</b>`;
* const h = Markup`Regular strings get ${escaped} but markup is injected ${asis}`;
*/
export function Markup(v, ...exprs) {
if (!(v instanceof Array)) {
return v ? new _Markup(v) : '';
return v ? new _Markup(v) : "";
}
const elements = [];
let i = 0;
for(; i < exprs.length; ++i) {
for (; i < exprs.length; ++i) {
elements.push(v[i], escape(exprs[i]));
}
elements.push(v[i]);
const s = elements.join('');
if (!s) { return '' }
const s = elements.join("");
if (!s) {
return "";
}
return new _Markup(s);
}
+21 -25
View File
@@ -3064,14 +3064,14 @@ exports[`t-out (formerly t-raw tests) not escaping 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _2 = scope['var'];
if (_2 != null) {
c1.push({text: _2});
}
insertValue(c1, _2)
return vn1;
}"
`;
@@ -3181,14 +3181,14 @@ exports[`t-out escaping 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = scope['var'];
if (_2 != null) {
c1.push({text: _2});
}
insertValue(c1, _2)
return vn1;
}"
`;
@@ -3197,14 +3197,14 @@ exports[`t-out escaping on a node 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = 'ok';
if (_2 != null) {
c1.push({text: _2});
}
insertValue(c1, _2)
return vn1;
}"
`;
@@ -3213,16 +3213,14 @@ exports[`t-out escaping on a node with a body 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = 'ok';
if (_2 != null) {
c1.push({text: _2});
} else {
c1.push({text: \`nope\`});
}
insertValue(c1, _2)
return vn1;
}"
`;
@@ -3231,16 +3229,14 @@ exports[`t-out escaping on a node with a body, as a default 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = scope['var'];
if (_2 != null) {
c1.push({text: _2});
} else {
c1.push({text: \`nope\`});
}
insertValue(c1, _2)
return vn1;
}"
`;
@@ -3249,14 +3245,14 @@ exports[`t-out literal 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = 'ok';
if (_2 != null) {
c1.push({text: _2});
}
insertValue(c1, _2)
return vn1;
}"
`;
@@ -3357,14 +3353,14 @@ exports[`t-out variable 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let insertValue = utils.insertValue;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _2 = scope['var'];
if (_2 != null) {
c1.push({text: _2});
}
insertValue(c1, _2)
return vn1;
}"
`;
+2 -2
View File
@@ -150,7 +150,7 @@ describe("error handling", () => {
});
});
describe("t-out", () => {
describe.only("t-out", () => {
test("literal", () => {
qweb.addTemplate("test", `<span><t t-out="'ok'"/></span>`);
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
@@ -178,7 +178,7 @@ describe("t-out", () => {
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
});
test("escaping on a node with a body, as a default", () => {
test.only("escaping on a node with a body, as a default", () => {
qweb.addTemplate("test", `<span t-out="var">nope</span>`);
expect(renderToString(qweb, "test")).toBe("<span>nope</span>");
});
+1 -2
View File
@@ -32,7 +32,6 @@ describe("old t-esc directive", () => {
"<span>&amp;lt;ok&amp;gt;abc&amp;lt;/ok&amp;gt;</span>"
);
});
});
describe("old t-raw directive", () => {
@@ -64,4 +63,4 @@ describe("old t-raw directive", () => {
"<span><p>text<!-- top secret --></p></span>"
);
});
});
});