diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 8b20c62bd..5277be8ea 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -586,11 +586,6 @@ export class CodeGenerator { } // attributes const attrs: Attrs = {}; - const nameSpace = ast.ns || ctx.nameSpace; - if (nameSpace && isNewBlock) { - // specific namespace uri - attrs["block-ns"] = nameSpace; - } for (let key in ast.attrs) { let expr, attrName; @@ -719,7 +714,10 @@ export class CodeGenerator { attrs["block-ref"] = String(idx); } - const dom = xmlDoc.createElement(ast.tag); + const nameSpace = ast.ns || ctx.nameSpace; + const dom = nameSpace + ? xmlDoc.createElementNS(nameSpace, ast.tag) + : xmlDoc.createElement(ast.tag); for (const [attr, val] of Object.entries(attrs)) { if (!(attr === "class" && val === "")) { dom.setAttribute(attr, val); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 555835a03..3a4cbb0ed 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -213,14 +213,14 @@ export function parse(xml: string | Element): AST { function _parse(xml: Element): AST { normalizeXML(xml); - const ctx = { inPreTag: false, inSVG: false }; + const ctx = { inPreTag: false }; return parseNode(xml, ctx) || { type: ASTType.Text, value: "" }; } interface ParsingContext { tModelInfo?: TModelInfo | null; + nameSpace?: string; inPreTag: boolean; - inSVG: boolean; } function parseNode(node: Node, ctx: ParsingContext): AST | null { @@ -323,9 +323,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null { if (tagName === "pre") { ctx.inPreTag = true; } - const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG; - ctx.inSVG = ctx.inSVG || shouldAddSVGNS; - const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null; + + let ns = !ctx.nameSpace && ROOT_SVG_TAGS.has(tagName) ? "http://www.w3.org/2000/svg" : null; const ref = node.getAttribute("t-ref"); node.removeAttribute("t-ref"); @@ -389,6 +388,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null { } } else if (attr.startsWith("block-")) { throw new OwlError(`Invalid attribute: '${attr}'`); + } else if (attr === "xmlns") { + ns = value; } else if (attr !== "t-name") { if (attr.startsWith("t-") && !attr.startsWith("t-att")) { throw new OwlError(`Unknown QWeb directive: '${attr}'`); @@ -401,6 +402,9 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null { attrs[attr] = value; } } + if (ns) { + ctx.nameSpace = ns; + } const children = parseChildren(node, ctx); return { diff --git a/src/runtime/blockdom/block_compiler.ts b/src/runtime/blockdom/block_compiler.ts index cbc91e7da..13435cbbb 100644 --- a/src/runtime/blockdom/block_compiler.ts +++ b/src/runtime/blockdom/block_compiler.ts @@ -144,12 +144,7 @@ function buildTree( info.push({ type: "child", idx: index }); el = document.createTextNode(""); } - const attrs = (node as Element).attributes; - const ns = attrs.getNamedItem("block-ns"); - if (ns) { - attrs.removeNamedItem("block-ns"); - currentNS = ns.value; - } + currentNS ||= (node as Element).namespaceURI; if (!el) { el = currentNS ? document.createElementNS(currentNS, tagName) @@ -165,6 +160,7 @@ function buildTree( const fragment = document.createElement("template").content; fragment.appendChild(el); } + const attrs = (node as Element).attributes; for (let i = 0; i < attrs.length; i++) { const attrName = attrs[i].name; const attrValue = attrs[i].value; diff --git a/tests/blockdom/block.test.ts b/tests/blockdom/block.test.ts index 0576efa53..7a3551677 100644 --- a/tests/blockdom/block.test.ts +++ b/tests/blockdom/block.test.ts @@ -244,12 +244,14 @@ describe("misc", () => { }); test("namespace is not propagated to siblings", () => { - const block = createBlock(`
`); + const block = createBlock(`
`); const fixture = makeTestFixture(); mount(block(), fixture); - expect(fixture.innerHTML).toBe("
"); + expect(fixture.innerHTML).toBe( + '
' + ); expect(fixture.querySelector("svg")!.namespaceURI).toBe("someNameSpace"); expect(fixture.querySelector("g")!.namespaceURI).toBe("someNameSpace"); const allDivs = fixture.querySelectorAll("div"); diff --git a/tests/blockdom/namespace.test.ts b/tests/blockdom/namespace.test.ts index e49d83c6f..0d622ca42 100644 --- a/tests/blockdom/namespace.test.ts +++ b/tests/blockdom/namespace.test.ts @@ -30,22 +30,22 @@ describe("namespace", () => { expect(fixture.firstElementChild!.namespaceURI).toBe(XHTML_URI); }); - test("namespace can be changed with block-ns", () => { - const block = createBlock(``); + test("namespace can be changed with xmlns", () => { + const block = createBlock(``); const tree = block(); mount(tree, fixture); - expect(fixture.innerHTML).toBe(""); + expect(fixture.innerHTML).toBe(``); expect(fixture.firstElementChild!.namespaceURI).toBe(SVG_URI); }); test("namespace is kept for children", () => { const block = createBlock( - `` + `` ); const tree = block(); mount(tree, fixture); expect(fixture.innerHTML).toBe( - "" + `` ); const parent = fixture.firstElementChild!; const child1 = parent.firstElementChild!; @@ -58,10 +58,10 @@ describe("namespace", () => { }); test("various namespaces in same block", () => { - const block = createBlock(``); + const block = createBlock(``); const tree = block(); mount(tree, fixture); - expect(fixture.innerHTML).toBe(""); + expect(fixture.innerHTML).toBe(''); const none = fixture.firstElementChild!; const one = none.firstElementChild!; const two = one.nextElementSibling!; diff --git a/tests/compiler/__snapshots__/svg.test.ts.snap b/tests/compiler/__snapshots__/svg.test.ts.snap index f8c710636..b4cef8bde 100644 --- a/tests/compiler/__snapshots__/svg.test.ts.snap +++ b/tests/compiler/__snapshots__/svg.test.ts.snap @@ -5,7 +5,7 @@ exports[`properly support svg add proper namespace to g tags 1`] = ` ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let block1 = createBlock(\` \`); + let block1 = createBlock(\` \`); return function template(ctx, node, key = \\"\\") { return block1(); @@ -18,7 +18,7 @@ exports[`properly support svg add proper namespace to svg 1`] = ` ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let block1 = createBlock(\` \`); + let block1 = createBlock(\` \`); return function template(ctx, node, key = \\"\\") { return block1(); @@ -31,7 +31,7 @@ exports[`properly support svg namespace to g tags not added if already in svg na ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let block1 = createBlock(\`\`); + let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { return block1(); @@ -44,7 +44,7 @@ exports[`properly support svg namespace to svg tags added even if already in svg ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let block1 = createBlock(\`\`); + let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { return block1(); @@ -58,8 +58,8 @@ exports[`properly support svg svg creates new block if it is within html -- 2 1` let { text, createBlock, list, multi, html, toggler, comment } = bdom; let block1 = createBlock(\`
\`); - let block2 = createBlock(\`\`); - let block3 = createBlock(\`\`); + let block2 = createBlock(\`\`); + let block3 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { let b3; @@ -78,7 +78,7 @@ exports[`properly support svg svg creates new block if it is within html 1`] = ` let { text, createBlock, list, multi, html, toggler, comment } = bdom; let block1 = createBlock(\`
\`); - let block2 = createBlock(\`\`); + let block2 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { const b2 = block2(); @@ -93,7 +93,7 @@ exports[`properly support svg svg namespace added to sub templates if root tag i let { text, createBlock, list, multi, html, toggler, comment } = bdom; const callTemplate_1 = app.getTemplate(\`path\`); - let block1 = createBlock(\`\`); + let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`); @@ -107,7 +107,7 @@ exports[`properly support svg svg namespace added to sub templates if root tag i ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let block1 = createBlock(\`\`); + let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { return block1(); @@ -120,8 +120,8 @@ exports[`properly support svg svg namespace added to sub-blocks 1`] = ` ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let block1 = createBlock(\`\`); - let block2 = createBlock(\`\`); + let block1 = createBlock(\`\`); + let block2 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { let b2; diff --git a/tests/compiler/svg.test.ts b/tests/compiler/svg.test.ts index 9c5cdad20..5ead0596b 100644 --- a/tests/compiler/svg.test.ts +++ b/tests/compiler/svg.test.ts @@ -9,20 +9,20 @@ describe("properly support svg", () => { test("add proper namespace to svg", () => { const template = ` `; expect(renderToString(template)).toBe( - ` ` + ` ` ); }); test("add proper namespace to g tags", () => { const template = ` `; expect(renderToString(template)).toBe( - ` ` + ` ` ); }); test("namespace to g tags not added if already in svg namespace", () => { const template = ``; - expect(renderToString(template)).toBe(``); + expect(renderToString(template)).toBe(``); }); test("namespace to svg tags added even if already in svg namespace", () => { @@ -41,8 +41,13 @@ describe("properly support svg", () => { test("svg namespace added to sub-blocks", () => { const template = ``; - expect(renderToString(template, { path: false })).toBe(``); - expect(renderToString(template, { path: true })).toBe(``); + expect(renderToString(template, { path: false })).toBe( + `` + ); + // Because the path is its own block, it has its own xmlns attribute + expect(renderToString(template, { path: true })).toBe( + `` + ); const bdom = renderToBdom(template, { path: true }); const fixture = makeTestFixture(); diff --git a/tests/components/__snapshots__/basics.test.ts.snap b/tests/components/__snapshots__/basics.test.ts.snap index ff241b1b0..95e8b2006 100644 --- a/tests/components/__snapshots__/basics.test.ts.snap +++ b/tests/components/__snapshots__/basics.test.ts.snap @@ -1248,7 +1248,7 @@ exports[`support svg components add proper namespace to svg 1`] = ` let { text, createBlock, list, multi, html, toggler, comment } = bdom; const comp1 = app.createComponent(\`GComp\`, true, false, false, []); - let block1 = createBlock(\`\`); + let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { const b2 = comp1({}, key + \`__1\`, node, this, null); @@ -1262,7 +1262,7 @@ exports[`support svg components add proper namespace to svg 2`] = ` ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let block1 = createBlock(\`\`); + let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { return block1(); diff --git a/tests/components/basics.test.ts b/tests/components/basics.test.ts index 3dfe537c1..6ddd5d65c 100644 --- a/tests/components/basics.test.ts +++ b/tests/components/basics.test.ts @@ -1024,7 +1024,7 @@ describe("support svg components", () => { await mount(Svg, fixture); expect(fixture.innerHTML).toBe( - '' + '' ); }); });