Files
Reservair/assets/js/templating/RsvTemplateEngine.js
T
2026-06-22 11:20:28 +02:00

95 lines
2.5 KiB
JavaScript

import { RsvTemplateRegistry } from './RsvTemplateRegistry.js';
function esc_html(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
export class RsvTemplateEngine {
constructor(registry = null) {
this.registry = registry ?? new RsvTemplateRegistry();
}
render(source, data = {}) {
const interpolated = this.interpolate(source, data);
return this.expand_elements(interpolated, data);
}
interpolate(source, data) {
return source.replace(/{{\s*([^}]+?)\s*}}/g, (match, path) => {
const value = this.resolve(path.trim(), data);
if (value === null || value === undefined || typeof value === 'object') {
return ''; // null and non-scalar (arrays, objects) render empty
}
return esc_html(String(value));
});
}
/**
*
* @param {*} data Submitted & computed data for the form
* @param {*} element The element to build the symbol table for
* @returns {Object} The symbol table for the element
*/
build_symbol_table(data, element) {
const attrs = {};
for (const attr of element.attributes) {
attrs[attr.name] = attr.value;
}
return { ...data, ...attrs };
}
wrap(element) {
const elWrap = document.createElement('div');
elWrap.innerHTML = element;
return elWrap;
}
expand_elements(html, data) {
html = html.replace(/<([\w-]+)([^>]*?)\/>/g, '<$1$2></$1>');
let doc = document.createElement('div');
doc.innerHTML = html;
[...doc.querySelectorAll('*')]
.filter(el => this.registry.has(el.tagName.toLowerCase()))
.forEach(el => {
const renderer = this.registry.get(el.tagName.toLowerCase());
el.replaceWith(this.wrap(renderer(this.build_symbol_table(data, el))));
});
const wrapper = document.createElement('div');
wrapper.appendChild(doc);
return wrapper.innerHTML;
}
resolve(path, data) {
const tokens = this.tokens(path);
let current = data;
for (const token of tokens) {
if (typeof current !== 'object' || current === null || !(token in current)) {
return null;
}
current = current[token];
}
return current;
}
tokens(path) {
const raw = path.split(/[\.\[\]]+/).filter(Boolean);
const result = [];
for (const token of raw) {
if (token === '$') continue;
result.push(token.replace(/^['"]|['"]$/g, ''));
}
return result;
}
}