mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] compiler: does not modify xml doc in place
This commit is contained in:
committed by
Samuel Degueldre
parent
0e13b859d0
commit
d7850aaf7a
@@ -5,7 +5,7 @@ import { UTILS } from "./template_helpers";
|
|||||||
|
|
||||||
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
||||||
|
|
||||||
export const globalTemplates: { [key: string]: string | Node } = {};
|
export const globalTemplates: { [key: string]: string | Element } = {};
|
||||||
|
|
||||||
function parseXML(xml: string): Document {
|
function parseXML(xml: string): Document {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
@@ -67,7 +67,11 @@ export class TemplateSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) {
|
addTemplate(
|
||||||
|
name: string,
|
||||||
|
template: string | Element,
|
||||||
|
options: { allowDuplicate?: boolean } = {}
|
||||||
|
) {
|
||||||
if (name in this.rawTemplates && !options.allowDuplicate) {
|
if (name in this.rawTemplates && !options.allowDuplicate) {
|
||||||
throw new Error(`Template ${name} already defined`);
|
throw new Error(`Template ${name} already defined`);
|
||||||
}
|
}
|
||||||
@@ -82,7 +86,6 @@ export class TemplateSet {
|
|||||||
xml = xml instanceof Document ? xml : parseXML(xml);
|
xml = xml instanceof Document ? xml : parseXML(xml);
|
||||||
for (const template of xml.querySelectorAll("[t-name]")) {
|
for (const template of xml.querySelectorAll("[t-name]")) {
|
||||||
const name = template.getAttribute("t-name")!;
|
const name = template.getAttribute("t-name")!;
|
||||||
template.removeAttribute("t-name");
|
|
||||||
this.addTemplate(name, template, options);
|
this.addTemplate(name, template, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +109,7 @@ export class TemplateSet {
|
|||||||
return this.templates[name];
|
return this.templates[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
_compileTemplate(name: string, template: string | Node) {
|
_compileTemplate(name: string, template: string | Element) {
|
||||||
return compile(template, {
|
return compile(template, {
|
||||||
name,
|
name,
|
||||||
dev: this.dev,
|
dev: this.dev,
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ export type TemplateFunction = (blocks: any, utils: any) => Template;
|
|||||||
interface CompileOptions extends Config {
|
interface CompileOptions extends Config {
|
||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
export function compile(template: string | Node, options: CompileOptions = {}): TemplateFunction {
|
export function compile(
|
||||||
|
template: string | Element,
|
||||||
|
options: CompileOptions = {}
|
||||||
|
): TemplateFunction {
|
||||||
// parsing
|
// parsing
|
||||||
const ast = parse(template);
|
const ast = parse(template);
|
||||||
|
|
||||||
|
|||||||
+27
-16
@@ -180,24 +180,35 @@ export type AST =
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Parser
|
// Parser
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
const cache: WeakMap<Element, AST> = new WeakMap();
|
||||||
|
|
||||||
|
export function parse(xml: string | Element): AST {
|
||||||
|
if (typeof xml === "string") {
|
||||||
|
const elem = parseXML(`<t>${xml}</t>`).firstChild as Element;
|
||||||
|
return _parse(elem);
|
||||||
|
}
|
||||||
|
let ast = cache.get(xml);
|
||||||
|
if (!ast) {
|
||||||
|
// we clone here the xml to prevent modifying it in place
|
||||||
|
ast = _parse(xml.cloneNode(true) as Element);
|
||||||
|
cache.set(xml, ast);
|
||||||
|
}
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _parse(xml: Element): AST {
|
||||||
|
normalizeXML(xml);
|
||||||
|
const ctx = { inPreTag: false, inSVG: false };
|
||||||
|
return parseNode(xml, ctx) || { type: ASTType.Text, value: "" };
|
||||||
|
}
|
||||||
|
|
||||||
interface ParsingContext {
|
interface ParsingContext {
|
||||||
tModelInfo?: TModelInfo | null;
|
tModelInfo?: TModelInfo | null;
|
||||||
inPreTag: boolean;
|
inPreTag: boolean;
|
||||||
inSVG: boolean;
|
inSVG: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(xml: string | Node): AST {
|
function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||||
const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element);
|
|
||||||
normalizeXML(node);
|
|
||||||
const ctx = { inPreTag: false, inSVG: false };
|
|
||||||
const ast = parseNode(node, ctx);
|
|
||||||
if (!ast) {
|
|
||||||
return { type: ASTType.Text, value: "" };
|
|
||||||
}
|
|
||||||
return ast;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
|
||||||
if (!(node instanceof Element)) {
|
if (!(node instanceof Element)) {
|
||||||
return parseTextCommentNode(node, ctx);
|
return parseTextCommentNode(node, ctx);
|
||||||
}
|
}
|
||||||
@@ -237,7 +248,7 @@ function parseTNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
const lineBreakRE = /[\r\n]/;
|
const lineBreakRE = /[\r\n]/;
|
||||||
const whitespaceRE = /\s+/g;
|
const whitespaceRE = /\s+/g;
|
||||||
|
|
||||||
function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
function parseTextCommentNode(node: Node, ctx: ParsingContext): AST | null {
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
let value = node.textContent || "";
|
let value = node.textContent || "";
|
||||||
if (!ctx.inPreTag) {
|
if (!ctx.inPreTag) {
|
||||||
@@ -360,7 +371,7 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
ctx = Object.assign({}, ctx);
|
ctx = Object.assign({}, ctx);
|
||||||
ctx.tModelInfo = model;
|
ctx.tModelInfo = model;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (attr !== "t-name") {
|
||||||
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
||||||
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
||||||
}
|
}
|
||||||
@@ -801,7 +812,7 @@ function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
/**
|
/**
|
||||||
* Parse all the child nodes of a given node and return a list of ast elements
|
* Parse all the child nodes of a given node and return a list of ast elements
|
||||||
*/
|
*/
|
||||||
function parseChildren(node: Node, ctx: ParsingContext): AST[] {
|
function parseChildren(node: Element, ctx: ParsingContext): AST[] {
|
||||||
const children: AST[] = [];
|
const children: AST[] = [];
|
||||||
for (let child of node.childNodes) {
|
for (let child of node.childNodes) {
|
||||||
const childAst = parseNode(child, ctx);
|
const childAst = parseNode(child, ctx);
|
||||||
@@ -820,7 +831,7 @@ function parseChildren(node: Node, ctx: ParsingContext): AST[] {
|
|||||||
* Parse all the child nodes of a given node and return an ast if possible.
|
* Parse all the child nodes of a given node and return an ast if possible.
|
||||||
* In the case there are multiple children, they are wrapped in a astmulti.
|
* In the case there are multiple children, they are wrapped in a astmulti.
|
||||||
*/
|
*/
|
||||||
function parseChildNodes(node: Node, ctx: ParsingContext): AST | null {
|
function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
|
||||||
const children = parseChildren(node, ctx);
|
const children = parseChildren(node, ctx);
|
||||||
switch (children.length) {
|
switch (children.length) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`loading templates addTemplates does not modify its xml document in place 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = ctx['value'];
|
||||||
|
return block1([txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`loading templates can initialize qweb with a string 1`] = `
|
exports[`loading templates can initialize qweb with a string 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -24,6 +24,17 @@ describe("loading templates", () => {
|
|||||||
expect(context.renderToString("hey")).toBe("<div>jupiler</div>");
|
expect(context.renderToString("hey")).toBe("<div>jupiler</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("addTemplates does not modify its xml document in place", () => {
|
||||||
|
const data = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve"><div t-name="hey"><t t-esc="value"/></div></templates>`;
|
||||||
|
const xml = new DOMParser().parseFromString(data, "text/xml");
|
||||||
|
const context = new TestContext();
|
||||||
|
expect(xml.firstElementChild!.innerHTML).toBe(`<div t-name="hey"><t t-esc="value"/></div>`);
|
||||||
|
context.addTemplates(xml);
|
||||||
|
expect(context.renderToString("hey", { value: 123 })).toBe("<div>123</div>");
|
||||||
|
expect(xml.firstElementChild!.innerHTML).toBe(`<div t-name="hey"><t t-esc="value"/></div>`);
|
||||||
|
});
|
||||||
|
|
||||||
test("can load a few templates from a xml string", () => {
|
test("can load a few templates from a xml string", () => {
|
||||||
const data = `<?xml version="1.0" encoding="UTF-8"?>
|
const data = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<templates id="template" xml:space="preserve">
|
<templates id="template" xml:space="preserve">
|
||||||
|
|||||||
+1
-1
@@ -123,7 +123,7 @@ export function snapshotEverything() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const originalCompileTemplate = TemplateSet.prototype._compileTemplate;
|
const originalCompileTemplate = TemplateSet.prototype._compileTemplate;
|
||||||
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Node) {
|
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Element) {
|
||||||
const fn = originalCompileTemplate.call(this, "", template);
|
const fn = originalCompileTemplate.call(this, "", template);
|
||||||
if (!globalTemplateNames.has(name)) {
|
if (!globalTemplateNames.has(name)) {
|
||||||
expect(fn.toString()).toMatchSnapshot();
|
expect(fn.toString()).toMatchSnapshot();
|
||||||
|
|||||||
Reference in New Issue
Block a user