jsonblade
Version:
A powerful and modular JSON template engine with extensible filters
259 lines • 11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.initializeFilters = initializeFilters;
exports.compileJSONTemplate = compileJSONTemplate;
exports.compileJSONTemplateAsync = compileJSONTemplateAsync;
exports.evaluateExpression = evaluateExpression;
exports.evaluateExpressionAsync = evaluateExpressionAsync;
exports.getObjectPath = getObjectPath;
const filter_registry_1 = require("./filter-registry");
const string_filters_1 = require("./filters/string-filters");
const array_filters_1 = require("./filters/array-filters");
const object_filters_1 = require("./filters/object-filters");
const logic_filters_1 = require("./filters/logic-filters");
const date_filters_1 = require("./filters/date-filters");
const number_filters_1 = require("./filters/number-filters");
const validation_filters_1 = require("./filters/validation-filters");
const template_config_1 = require("./template-config");
let filtersInitialized = false;
function initializeFilters() {
if (filtersInitialized)
return;
(0, string_filters_1.registerStringFilters)();
(0, array_filters_1.registerArrayFilters)();
(0, object_filters_1.registerObjectFilters)();
(0, logic_filters_1.registerLogicFilters)();
(0, date_filters_1.registerDateFilters)();
(0, number_filters_1.registerNumberFilters)();
(0, validation_filters_1.registerValidationFilters)();
filtersInitialized = true;
}
function compileJSONTemplate(template, data, functions) {
initializeFilters();
// Handle empty template
if (!template || template.trim() === "") {
return "";
}
const stringInterpolated = template.replace(/"([^"]*)"/g, (match, content) => {
if (!content.includes("{{")) {
return match;
}
const interpolated = content.replace(/{{\s*([^}]+)\s*}}/g, (_, expr) => {
const value = evaluateExpression(expr.trim(), data, functions);
if (value == null)
return "";
const stringValue = String(value);
// Escape quotes and backslashes for JSON string context
return stringValue.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
});
return `"${interpolated}"`;
});
const fullyInterpolated = stringInterpolated.replace(/{{\s*([^}]+)\s*}}/g, (_, expr) => {
const value = evaluateExpression(expr.trim(), data, functions);
return JSON.stringify(value ?? null);
});
return JSON.parse(fullyInterpolated);
}
async function compileJSONTemplateAsync(template, data, functions) {
initializeFilters();
// Handle empty template
if (!template || template.trim() === "") {
return "";
}
const stringInterpolated = await replaceAsync(template, /"([^"]*)"/g, async (match, content) => {
if (!content.includes("{{")) {
return match;
}
const interpolated = await replaceAsync(content, /{{\s*([^}]+)\s*}}/g, async (_, expr) => {
const value = await evaluateExpressionAsync(expr.trim(), data, functions);
if (value == null)
return "";
const stringValue = String(value);
return stringValue.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
});
return `"${interpolated}"`;
});
const fullyInterpolated = await replaceAsync(stringInterpolated, /{{\s*([^}]+)\s*}}/g, async (_, expr) => {
const value = await evaluateExpressionAsync(expr.trim(), data, functions);
return JSON.stringify(value ?? null);
});
return JSON.parse(fullyInterpolated);
}
// Helper function for async string replace
async function replaceAsync(str, regex, asyncFn) {
const promises = [];
str.replace(regex, (match, ...args) => {
const promise = asyncFn(match, ...args);
promises.push(promise);
return match;
});
const data = await Promise.all(promises);
return str.replace(regex, () => data.shift());
}
function evaluateExpression(expr, data, functions) {
const parts = expr.split("|").map((p) => p.trim());
const [rawPath, ...filterParts] = parts;
// Détecter les appels de fonction dans rawPath
const functionCallMatch = rawPath.match(/^(\w+)\((.*?)\)$/);
let value;
if (functionCallMatch) {
// C'est un appel de fonction
const [, functionName, argsString] = functionCallMatch;
// Chercher la fonction dans le registre fourni
const templateFunction = functions?.find((f) => f.name === functionName);
if (templateFunction) {
// Parser les arguments
let args = [];
if (argsString.trim()) {
args = argsString.split(",").map((arg) => {
const trimmed = arg.trim();
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
return trimmed.slice(1, -1);
}
// Try to parse as number
if (!isNaN(Number(trimmed)) && trimmed !== "") {
return Number(trimmed);
}
// Try to resolve as data path first, fallback to literal string
const pathValue = getObjectPath(trimmed, data);
return pathValue !== null && pathValue !== undefined
? pathValue
: trimmed;
});
}
// Appeler la fonction
value = templateFunction.func(...args);
}
else {
// Fonction introuvable, fallback vers getObjectPath
value = getObjectPath(rawPath, data);
}
}
else {
// Path normal, utiliser getObjectPath
value = getObjectPath(rawPath, data);
}
for (const part of filterParts) {
const match = part.match(/^(\w+)(?:\((.*?)\))?$/);
if (!match)
continue;
const [, name, argsString] = match;
const fn = (0, filter_registry_1.getFilter)(name);
if (!fn) {
const config = (0, template_config_1.getTemplateConfig)();
const error = (0, template_config_1.createTemplateError)("UNKNOWN_FILTER", `Unknown filter: ${name}`, { filter: name, expression: expr });
(0, template_config_1.handleTemplateError)(error, config);
continue;
}
let args = [];
if (argsString) {
args = argsString.split(",").map((arg) => {
const trimmed = arg.trim();
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
return trimmed.slice(1, -1);
}
// Try to resolve as data path first, fallback to literal string
const pathValue = getObjectPath(trimmed, data);
return pathValue !== null && pathValue !== undefined
? pathValue
: trimmed;
});
}
value = fn(value, ...args);
}
return value;
}
async function evaluateExpressionAsync(expr, data, functions) {
const parts = expr.split("|").map((p) => p.trim());
const [rawPath, ...filterParts] = parts;
// Détecter les appels de fonction dans rawPath
const functionCallMatch = rawPath.match(/^(\w+)\((.*?)\)$/);
let value;
if (functionCallMatch) {
// C'est un appel de fonction
const [, functionName, argsString] = functionCallMatch;
// Chercher la fonction dans le registre fourni
const templateFunction = functions?.find((f) => f.name === functionName);
if (templateFunction) {
// Parser les arguments
let args = [];
if (argsString.trim()) {
args = argsString.split(",").map((arg) => {
const trimmed = arg.trim();
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
return trimmed.slice(1, -1);
}
// Try to parse as number
if (!isNaN(Number(trimmed)) && trimmed !== "") {
return Number(trimmed);
}
// Try to resolve as data path first, fallback to literal string
const pathValue = getObjectPath(trimmed, data);
return pathValue !== null && pathValue !== undefined
? pathValue
: trimmed;
});
}
// Appeler la fonction (peut être async)
value = await templateFunction.func(...args);
}
else {
// Fonction introuvable, fallback vers getObjectPath
value = getObjectPath(rawPath, data);
}
}
else {
// Path normal, utiliser getObjectPath
value = getObjectPath(rawPath, data);
}
// Note: Les filtres ne sont pas async dans cette implémentation
// Si besoin, il faudrait créer un système de filtres async séparé
for (const part of filterParts) {
const match = part.match(/^(\w+)(?:\((.*?)\))?$/);
if (!match)
continue;
const [, name, argsString] = match;
const fn = (0, filter_registry_1.getFilter)(name);
if (!fn) {
const config = (0, template_config_1.getTemplateConfig)();
const error = (0, template_config_1.createTemplateError)("UNKNOWN_FILTER", `Unknown filter: ${name}`, { filter: name, expression: expr });
(0, template_config_1.handleTemplateError)(error, config);
continue;
}
let args = [];
if (argsString) {
args = argsString.split(",").map((arg) => {
const trimmed = arg.trim();
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
return trimmed.slice(1, -1);
}
// Try to resolve as data path first, fallback to literal string
const pathValue = getObjectPath(trimmed, data);
return pathValue !== null && pathValue !== undefined
? pathValue
: trimmed;
});
}
value = fn(value, ...args);
}
return value;
}
function getObjectPath(path, data) {
// Handle special case where path is just "." meaning current item
if (path === ".") {
return data;
}
const parts = path.split(".");
let current = data;
for (const part of parts) {
if (current == null || typeof current !== "object")
return null;
current = current[part];
}
return current;
}
//# sourceMappingURL=json-template.utils.js.map