"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ Object.defineProperty(exports, "__esModule", { value: true }); exports.template = exports.templateParser = void 0; const source_map_1 = require("source-map"); // Matches <%= expr %>. This does not support structural JavaScript (for/if/...). const kInterpolateRe = /<%=([\s\S]+?)%>/g; // Matches <%# text %>. It's a comment and will be entirely ignored. const kCommentRe = /<%#([\s\S]+?)%>/g; // Used to match template delimiters. // <%- expr %>: HTML escape the value. // <% ... %>: Structural template code. const kEscapeRe = /<%-([\s\S]+?)%>/g; const kEvaluateRe = /<%([\s\S]+?)%>/g; /** Used to map characters to HTML entities. */ const kHtmlEscapes = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`', }; // Used to match HTML entities and HTML characters. const reUnescapedHtml = new RegExp(`[${Object.keys(kHtmlEscapes).join('')}]`, 'g'); function _positionFor(content, offset) { let line = 1; let column = 0; for (let i = 0; i < offset - 1; i++) { if (content[i] == '\n') { line++; column = 0; } else { column++; } } return { line, column, }; } /** * Given a source text (and a fileName), returns a TemplateAst. */ function templateParser(sourceText, fileName) { const children = []; // Compile the regexp to match each delimiter. const reExpressions = [kEscapeRe, kCommentRe, kInterpolateRe, kEvaluateRe]; const reDelimiters = RegExp(reExpressions.map((x) => x.source).join('|') + '|$', 'g'); const parsed = sourceText.split(reDelimiters); let offset = 0; // Optimization that uses the fact that the end of a node is always the beginning of the next // node, so we keep the positioning of the nodes in memory. let start = _positionFor(sourceText, offset); let end; const increment = reExpressions.length + 1; for (let i = 0; i < parsed.length; i += increment) { const [content, escape, comment, interpolate, evaluate] = parsed.slice(i, i + increment); if (content) { end = _positionFor(sourceText, offset + content.length); offset += content.length; children.push({ kind: 'content', content, start, end }); start = end; } if (escape) { end = _positionFor(sourceText, offset + escape.length + 5); offset += escape.length + 5; children.push({ kind: 'escape', expression: escape, start, end }); start = end; } if (comment) { end = _positionFor(sourceText, offset + comment.length + 5); offset += comment.length + 5; children.push({ kind: 'comment', text: comment, start, end }); start = end; } if (interpolate) { end = _positionFor(sourceText, offset + interpolate.length + 5); offset += interpolate.length + 5; children.push({ kind: 'interpolate', expression: interpolate, start, end, }); start = end; } if (evaluate) { end = _positionFor(sourceText, offset + evaluate.length + 5); offset += evaluate.length + 5; children.push({ kind: 'evaluate', expression: evaluate, start, end }); start = end; } } return { fileName, content: sourceText, children, }; } exports.templateParser = templateParser; /** * Fastest implementation of the templating algorithm. It only add strings and does not bother * with source maps. */ function templateFast(ast, options) { const module = options && options.module ? 'module.exports.default =' : ''; const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, "\\\\\\'"); return ` return ${module} function(obj) { obj || (obj = {}); let __t; let __p = ''; const __escapes = ${JSON.stringify(kHtmlEscapes)}; const __escapesre = new RegExp('${reHtmlEscape}', 'g'); const __e = function(s) { return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : ''; }; with (obj) { ${ast.children .map((node) => { switch (node.kind) { case 'content': return `__p += ${JSON.stringify(node.content)};`; case 'interpolate': return `__p += ((__t = (${node.expression})) == null) ? '' : __t;`; case 'escape': return `__p += __e(${node.expression});`; case 'evaluate': return node.expression; } }) .join('\n')} } return __p; }; `; } /** * Templating algorithm with source map support. The map is outputted as //# sourceMapUrl=... */ function templateWithSourceMap(ast, options) { const sourceUrl = ast.fileName; const module = options && options.module ? 'module.exports.default =' : ''; const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, "\\\\\\'"); const preamble = new source_map_1.SourceNode(1, 0, sourceUrl, '').add(new source_map_1.SourceNode(1, 0, sourceUrl, [ `return ${module} function(obj) {\n`, ' obj || (obj = {});\n', ' let __t;\n', ' let __p = "";\n', ` const __escapes = ${JSON.stringify(kHtmlEscapes)};\n`, ` const __escapesre = new RegExp('${reHtmlEscape}', 'g');\n`, `\n`, ` const __e = function(s) { `, ` return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : '';`, ` };\n`, ` with (obj) {\n`, ])); const end = ast.children.length ? ast.children[ast.children.length - 1].end : { line: 0, column: 0 }; const nodes = ast.children .reduce((chunk, node) => { let code = ''; switch (node.kind) { case 'content': code = [ new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p = __p'), ...node.content.split('\n').map((line, i, arr) => { return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, '\n + ' + JSON.stringify(line + (i == arr.length - 1 ? '' : '\n'))); }), new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ';\n'), ]; break; case 'interpolate': code = [ new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p += ((__t = '), ...node.expression.split('\n').map((line, i, arr) => { return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + (i == arr.length - 1 ? '' : '\n')); }), new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ') == null ? "" : __t);\n'), ]; break; case 'escape': code = [ new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p += __e('), ...node.expression.split('\n').map((line, i, arr) => { return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + (i == arr.length - 1 ? '' : '\n')); }), new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ');\n'), ]; break; case 'evaluate': code = [ ...node.expression.split('\n').map((line, i, arr) => { return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + (i == arr.length - 1 ? '' : '\n')); }), new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, '\n'), ]; break; } return chunk.add(new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, code)); }, preamble) .add(new source_map_1.SourceNode(end.line, end.column, sourceUrl, [' };\n', '\n', ' return __p;\n', '}\n'])); const code = nodes.toStringWithSourceMap({ file: sourceUrl, sourceRoot: (options && options.sourceRoot) || '.', }); // Set the source content in the source map, otherwise the sourceUrl is not enough // to find the content. code.map.setSourceContent(sourceUrl, ast.content); return (code.code + '\n//# sourceMappingURL=data:application/json;base64,' + Buffer.from(code.map.toString()).toString('base64')); } /** * An equivalent of EJS templates, which is based on John Resig's `tmpl` implementation * (http://ejohn.org/blog/javascript-micro-templating/) and Laura Doktorova's doT.js * (https://github.com/olado/doT). * * This version differs from lodash by removing support from ES6 quasi-literals, and making the * code slightly simpler to follow. It also does not depend on any third party, which is nice. * * Finally, it supports SourceMap, if you ever need to debug, which is super nice. * * @param content The template content. * @param options Optional Options. See TemplateOptions for more description. * @return {(input: T) => string} A function that accept an input object and returns the content * of the template with the input applied. */ function template(content, options) { const sourceUrl = (options && options.sourceURL) || 'ejs'; const ast = templateParser(content, sourceUrl); let source; // If there's no need for source map support, we revert back to the fast implementation. if (options && options.sourceMap) { source = templateWithSourceMap(ast, options); } else { source = templateFast(ast, options); } // We pass a dummy module in case the module option is passed. If `module: true` is passed, we // need to only use the source, not the function itself. Otherwise expect a module object to be // passed, and we use that one. const fn = Function('module', source); const module = options && options.module ? (options.module === true ? { exports: {} } : options.module) : null; const result = fn(module); // Provide the compiled function's source by its `toString` method or // the `source` property as a convenience for inlining compiled templates. result.source = source; return result; } exports.template = template;