mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
|
|
import { compile, Template } from "../compiler";
|
|
import { component } from "../component/component_node";
|
|
import { UTILS } from "./template_helpers";
|
|
|
|
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
|
|
|
export const globalTemplates: { [key: string]: string | Node } = {};
|
|
|
|
function parseXML(xml: string): Document {
|
|
const parser = new DOMParser();
|
|
|
|
const doc = parser.parseFromString(xml, "text/xml");
|
|
if (doc.getElementsByTagName("parsererror").length) {
|
|
let msg = "Invalid XML in template.";
|
|
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
|
if (parsererrorText) {
|
|
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
|
const re = /\d+/g;
|
|
const firstMatch = re.exec(parsererrorText);
|
|
if (firstMatch) {
|
|
const lineNumber = Number(firstMatch[0]);
|
|
const line = xml.split("\n")[lineNumber - 1];
|
|
const secondMatch = re.exec(parsererrorText);
|
|
if (line && secondMatch) {
|
|
const columnIndex = Number(secondMatch[0]) - 1;
|
|
if (line[columnIndex]) {
|
|
msg +=
|
|
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
|
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
throw new Error(msg);
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
export interface TemplateSetConfig {
|
|
dev?: boolean;
|
|
translatableAttributes?: string[];
|
|
translateFn?: (s: string) => string;
|
|
templates?: string | Document;
|
|
}
|
|
|
|
export class TemplateSet {
|
|
dev: boolean;
|
|
rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
|
|
templates: { [name: string]: Template } = {};
|
|
translateFn?: (s: string) => string;
|
|
translatableAttributes?: string[];
|
|
utils: typeof UTILS = Object.assign({}, UTILS, {
|
|
call: (owner: any, subTemplate: string, ctx: any, parent: any) => {
|
|
const template = this.getTemplate(subTemplate);
|
|
return toggler(subTemplate, template.call(owner, ctx, parent));
|
|
},
|
|
getTemplate: (name: string) => this.getTemplate(name),
|
|
});
|
|
|
|
constructor(config: TemplateSetConfig = {}) {
|
|
this.dev = config.dev || false;
|
|
this.translateFn = config.translateFn;
|
|
this.translatableAttributes = config.translatableAttributes;
|
|
if (config.templates) {
|
|
this.addTemplates(config.templates);
|
|
}
|
|
}
|
|
|
|
addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) {
|
|
if (name in this.rawTemplates && !options.allowDuplicate) {
|
|
throw new Error(`Template ${name} already defined`);
|
|
}
|
|
this.rawTemplates[name] = template;
|
|
}
|
|
|
|
addTemplates(xml: string | Document, options: { allowDuplicate?: boolean } = {}) {
|
|
if (!xml) {
|
|
// empty string
|
|
return;
|
|
}
|
|
xml = xml instanceof Document ? xml : parseXML(xml);
|
|
for (const template of xml.querySelectorAll("[t-name]")) {
|
|
const name = template.getAttribute("t-name")!;
|
|
template.removeAttribute("t-name");
|
|
this.addTemplate(name, template, options);
|
|
}
|
|
}
|
|
|
|
getTemplate(name: string): Template {
|
|
if (!(name in this.templates)) {
|
|
const rawTemplate = this.rawTemplates[name];
|
|
if (rawTemplate === undefined) {
|
|
throw new Error(`Missing template: "${name}"`);
|
|
}
|
|
const templateFn = this._compileTemplate(name, rawTemplate);
|
|
// first add a function to lazily get the template, in case there is a
|
|
// recursive call to the template name
|
|
this.templates[name] = (context, parent) => this.templates[name](context, parent);
|
|
const template = templateFn(bdom, this.utils);
|
|
this.templates[name] = template;
|
|
}
|
|
return this.templates[name];
|
|
}
|
|
|
|
_compileTemplate(name: string, template: string | Node) {
|
|
return compile(template, {
|
|
name,
|
|
dev: this.dev,
|
|
translateFn: this.translateFn,
|
|
translatableAttributes: this.translatableAttributes,
|
|
});
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// xml tag helper
|
|
// -----------------------------------------------------------------------------
|
|
export function xml(...args: Parameters<typeof String.raw>) {
|
|
const name = `__template__${xml.nextId++}`;
|
|
const value = String.raw(...args);
|
|
globalTemplates[name] = value;
|
|
return name;
|
|
}
|
|
|
|
xml.nextId = 1;
|