UNPKG

jsonblade

Version:

A powerful and modular JSON template engine with extensible filters

305 lines 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processCommentsAsync = processCommentsAsync; exports.processConditionalsAsync = processConditionalsAsync; exports.processIfElseAsync = processIfElseAsync; exports.processLoopsAsync = processLoopsAsync; exports.processVariablesAsync = processVariablesAsync; exports.processUnlessAsync = processUnlessAsync; exports.compileAdvancedTemplateAsync = compileAdvancedTemplateAsync; const json_template_utils_1 = require("./json-template.utils"); async function processCommentsAsync(template) { return template.replace(/\{\{!.*?\}\}/gs, ""); } async function processConditionalsAsync(template, data) { const conditionalRegex = /\{\{#if\s+([^}]+)\}\}(.*?)\{\{\/if\}\}/gs; let result = template; const matches = Array.from(template.matchAll(conditionalRegex)); for (const match of matches) { const [fullMatch, condition, content] = match; const conditionResult = await evaluateConditionAsync(condition.trim(), data); const replacement = conditionResult ? content : ""; result = result.replace(fullMatch, replacement); } return result; } async function processIfElseAsync(template, data) { let result = template; // Keep processing until no more if/else patterns are found let hasMatches = true; while (hasMatches) { hasMatches = false; // Find all if statements and their matching else/endif with proper nesting const ifMatches = []; const ifRegex = /\{\{#if\s+([^}]+)\}\}/g; let match; while ((match = ifRegex.exec(result)) !== null) { const startPos = match.index; const condition = match[1]; // Find the matching {{#else}} and {{/if}} considering nesting let nestLevel = 1; let pos = match.index + match[0].length; let elsePos = -1; let endPos = -1; while (pos < result.length && nestLevel > 0) { // Look for next if, else, or endif const nextIf = result.indexOf("{{#if", pos); const nextElse = result.indexOf("{{#else}}", pos); const nextEndIf = result.indexOf("{{/if}}", pos); // Find the earliest occurrence let nextPos = result.length; let nextType = ""; if (nextIf !== -1 && nextIf < nextPos) { nextPos = nextIf; nextType = "if"; } if (nextElse !== -1 && nextElse < nextPos && nestLevel === 1 && elsePos === -1) { nextPos = nextElse; nextType = "else"; } if (nextEndIf !== -1 && nextEndIf < nextPos) { nextPos = nextEndIf; nextType = "endif"; } if (nextType === "if") { nestLevel++; pos = nextPos + 5; // Skip "{{#if" } else if (nextType === "else" && nestLevel === 1) { elsePos = nextPos; pos = nextPos + 9; // Skip "{{#else}}" } else if (nextType === "endif") { nestLevel--; if (nestLevel === 0) { endPos = nextPos; } pos = nextPos + 7; // Skip "{{/if}}" } else { break; // No more matches found } } if (elsePos !== -1 && endPos !== -1) { const ifContent = result.substring(startPos + match[0].length, elsePos); const elseContent = result.substring(elsePos + 9, endPos); const fullMatch = result.substring(startPos, endPos + 7); ifMatches.push({ fullMatch, condition, ifContent, elseContent, startPos, length: fullMatch.length, }); } } if (ifMatches.length > 0) { // Process the innermost (shortest) match first ifMatches.sort((a, b) => a.length - b.length); const shortestMatch = ifMatches[0]; const conditionResult = await evaluateConditionAsync(shortestMatch.condition.trim(), data); const replacement = conditionResult ? shortestMatch.ifContent : shortestMatch.elseContent; result = result.replace(shortestMatch.fullMatch, replacement); hasMatches = true; } } return result; } async function processLoopsAsync(template, data) { const loopRegex = /\{\{#each\s+([^}]+)\}\}(.*?)\{\{\/each\}\}/gs; let result = template; const matches = Array.from(template.matchAll(loopRegex)); for (const match of matches) { const [fullMatch, arrayPath, content] = match; const array = (0, json_template_utils_1.getObjectPath)(arrayPath.trim(), data); if (!Array.isArray(array)) { result = result.replace(fullMatch, ""); continue; } const iterations = []; for (let i = 0; i < array.length; i++) { const item = array[i]; const loopData = { ...data, ...item, ".": item, // Add the current item as "." for filters "@index": i, "@first": i === 0, "@last": i === array.length - 1, }; let iterationContent = content; // Process variables within the loop context first const loopVariables = await extractVariablesAsync(iterationContent, loopData); const enrichedLoopData = { ...loopData, ...loopVariables }; iterationContent = replaceVariablesAsync(iterationContent, loopVariables); // Process conditionals within the loop iterationContent = await processIfElseAsync(iterationContent, enrichedLoopData); iterationContent = await processConditionalsAsync(iterationContent, enrichedLoopData); iterationContent = await processUnlessAsync(iterationContent, enrichedLoopData); // Process variable interpolations const { compileJSONTemplateAsync, } = require("./async-json-template.utils"); iterationContent = await compileJSONTemplateAsync(iterationContent, enrichedLoopData); iterations.push(iterationContent); } result = result.replace(fullMatch, iterations.join("")); } return result; } async function processVariablesAsync(template, data) { const variables = await extractVariablesAsync(template, data); const templateWithoutSets = template.replace(/\{\{#set\s+(\w+)\s*=\s*([^}]+)\}\}/g, ""); let result = templateWithoutSets; Object.keys(variables).forEach((varName) => { // Use negative lookahead to avoid matching property references like {{userData.active}} const regex = new RegExp(`\\{\\{${varName}(?!\\.)\\}\\}`, "g"); const value = variables[varName]; result = result.replace(regex, typeof value === "string" ? value : JSON.stringify(value)); }); return result; } async function processUnlessAsync(template, data) { const unlessRegex = /\{\{#unless\s+([^}]+)\}\}(.*?)\{\{\/unless\}\}/gs; let result = template; const matches = Array.from(template.matchAll(unlessRegex)); for (const match of matches) { const [fullMatch, condition, content] = match; const conditionResult = await evaluateConditionAsync(condition.trim(), data); const replacement = !conditionResult ? content : ""; result = result.replace(fullMatch, replacement); } return result; } async function evaluateConditionAsync(condition, data) { const { evaluateExpressionAsync: evalExprAsync, } = require("./async-json-template.utils"); const result = await evalExprAsync(condition, data); if (typeof result === "boolean") return result; if (typeof result === "number") return result !== 0; if (typeof result === "string") return result.length > 0; if (Array.isArray(result)) return result.length > 0; if (result && typeof result === "object") return Object.keys(result).length > 0; return !!result; } async function extractVariablesAsync(template, data) { const { evaluateExpressionAsync: evalExprAsync, } = require("./async-json-template.utils"); const variables = {}; // First, find all {{#each}}...{{/each}} blocks to avoid extracting variables inside them const eachBlocks = []; const eachRegex = /\{\{#each\s+[^}]+\}\}/g; const eachEndRegex = /\{\{\/each\}\}/g; let eachMatch; while ((eachMatch = eachRegex.exec(template)) !== null) { const startPos = eachMatch.index; // Find matching {{/each}} let nestLevel = 1; let pos = eachMatch.index + eachMatch[0].length; while (pos < template.length && nestLevel > 0) { const nextEach = template.indexOf("{{#each", pos); const nextEndEach = template.indexOf("{{/each}}", pos); if (nextEndEach === -1) break; if (nextEach !== -1 && nextEach < nextEndEach) { nestLevel++; pos = nextEach + 7; // length of '{{#each' } else { nestLevel--; if (nestLevel === 0) { eachBlocks.push({ start: startPos, end: nextEndEach + 9 }); // +9 for '{{/each}}' } pos = nextEndEach + 9; } } } // Function to check if a position is inside any each block const isInsideEachBlock = (pos) => { return eachBlocks.some((block) => pos >= block.start && pos <= block.end); }; const setRegex = /\{\{#set\s+(\w+)\s*=\s*([^}]+)\}\}/g; const matches = Array.from(template.matchAll(setRegex)); for (const match of matches) { // Skip if this variable definition is inside an each block if (isInsideEachBlock(match.index)) { continue; } const [, varName, expression] = match; const result = await evalExprAsync(expression.trim(), { ...data, ...variables, }); variables[varName] = result; } return variables; } function replaceVariablesAsync(template, variables) { // Find all {{#each}}...{{/each}} blocks to avoid removing {{#set}} inside them const eachBlocks = []; const eachRegex = /\{\{#each\s+[^}]+\}\}/g; let eachMatch; while ((eachMatch = eachRegex.exec(template)) !== null) { const startPos = eachMatch.index; // Find matching {{/each}} let nestLevel = 1; let pos = eachMatch.index + eachMatch[0].length; while (pos < template.length && nestLevel > 0) { const nextEach = template.indexOf("{{#each", pos); const nextEndEach = template.indexOf("{{/each}}", pos); if (nextEndEach === -1) break; if (nextEach !== -1 && nextEach < nextEndEach) { nestLevel++; pos = nextEach + 7; // length of '{{#each' } else { nestLevel--; if (nestLevel === 0) { eachBlocks.push({ start: startPos, end: nextEndEach + 9 }); // +9 for '{{/each}}' } pos = nextEndEach + 9; } } } // Function to check if a position is inside any each block const isInsideEachBlock = (pos) => { return eachBlocks.some((block) => pos >= block.start && pos <= block.end); }; // Remove {{#set}} declarations that are NOT inside each blocks let result = template.replace(/\{\{#set\s+(\w+)\s*=\s*([^}]+)\}\}/g, (match, varName, expression, offset) => { if (isInsideEachBlock(offset)) { return match; // Keep the {{#set}} if it's inside an each block } return ""; // Remove it if it's global }); Object.keys(variables).forEach((varName) => { // Use negative lookahead to avoid matching property references like {{userData.active}} const regex = new RegExp(`\\{\\{${varName}(?!\\.)\\}\\}`, "g"); const value = variables[varName]; result = result.replace(regex, typeof value === "string" ? value : JSON.stringify(value)); }); return result; } async function compileAdvancedTemplateAsync(template, data) { const { compileJSONTemplateAsync } = require("./async-json-template.utils"); let result = template; result = await processCommentsAsync(result); const variables = await extractVariablesAsync(result, data); const enrichedData = { ...data, ...variables }; result = replaceVariablesAsync(result, variables); result = await processLoopsAsync(result, enrichedData); result = await processIfElseAsync(result, enrichedData); result = await processConditionalsAsync(result, enrichedData); result = await processUnlessAsync(result, enrichedData); result = await compileJSONTemplateAsync(result, enrichedData); return result; } //# sourceMappingURL=async-advanced-templating.js.map