@itrocks/template
Version:
The W3C-valid, browser-previewable, concise, and fast HTML template engine that enables delimiter-less translations
945 lines • 42.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Template = exports.HtmlResponse = exports.frontScripts = exports.depends = void 0;
exports.templateDependsOn = templateDependsOn;
const app_dir_1 = require("@itrocks/app-dir");
const rename_1 = require("@itrocks/rename");
const sorted_array_1 = require("@itrocks/sorted-array");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const node_path_2 = require("node:path");
exports.depends = {
toString: async (value) => '' + value
};
const DEBUG = false;
const done = { done: true };
exports.frontScripts = new sorted_array_1.SortedArray();
exports.frontScripts.distinct = true;
function templateDependsOn(dependencies) {
Object.assign(exports.depends, dependencies);
}
class HtmlResponse {
html;
dependencies;
constructor(html, ...dependencies) {
this.html = html;
this.dependencies = dependencies;
}
toString() { return this.html; }
}
exports.HtmlResponse = HtmlResponse;
class Template {
data;
containerData;
// block stack
blockBack = 0;
blockStack = [];
// parser
doExpression = true;
index = 0;
length = 0;
source = '';
start = 0;
tagName = '';
tagStack = [];
target = '';
targetReplace = '';
targetStack = [];
// literal
doLiteral = false;
inLiteral = false;
literalParts = [];
literalPartStack = [];
lockLiteral = false;
// html head
addLinks = new sorted_array_1.SortedArray();
doneLinks = new sorted_array_1.SortedArray();
headTitle;
// file
fileName;
filePath;
included = false;
// Inline elements are replaced by $1 when in literal.
inlineElements = new sorted_array_1.SortedArray('a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'button', 'cite', 'code', 'data', 'del', 'dfn', 'em', 'font', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'mark', 'meter', 'object', 'optgroup', 'option', 'output', 'picture', 'q', 'rt', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'time', 'tspan', 'tt', 'u', 'var', 'wbr');
// These attribute values are literals.
literalAttributes = new sorted_array_1.SortedArray('alt', 'enterkeyhint', 'label', 'lang', 'placeholder', 'srcdoc', 'title');
// These element contents are literals.
literalElements = new sorted_array_1.SortedArray('a', 'abbr', 'acronym', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'caption', 'center', 'cite', 'data', 'datalist', 'dd', 'del', 'desc', 'details', 'dfn', 'dialog', 'div', 'dt', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'i', 'iframe', 'ins', 'keygen', 'label', 'legend', 'li', 'main', 'mark', 'menuitem', 'meter', 'nav', 'noframes', 'noscript', 'optgroup', 'option', 'p', 'pre', 'q', 'rb', 's', 'section', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'td', 'template', 'text', 'textarea', 'textpath', 'th', 'time', 'title', 'tspan', 'u', 'wbr');
// These elements have no closing tag.
unclosingTags = new sorted_array_1.SortedArray('area', 'base', 'basefont', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track');
// Event hooks
onAttribute;
onTagOpen;
onTagOpened;
onTagClose;
// Additional parsers
parsers = [];
prefixes = '';
constructor(data, containerData) {
this.data = data;
this.containerData = containerData;
this.addLinks.distinct = true;
this.doneLinks.distinct = true;
if (containerData) {
this.blockStack.push({ blockStart: 0, data: containerData, iteration: done });
}
}
applyLiterals(text, parts = []) {
return text.replace(/\$([0-9]+)/g, (_, index) => parts[+index]);
}
closeTag(shouldInLiteral, targetIndex) {
shouldInLiteral ||= this.inLiteral;
Object.assign(this, this.tagStack.pop() ?? { tagName: '', inLiteral: false });
if (this.onTagClose)
this.onTagClose.call(this, this.tagName);
if ((this.tagName[0] === 'a') && (this.tagName === 'address')) {
this.lockLiteral = false;
}
if (this.inLiteral && this.inlineElements.includes(this.tagName)) {
if (this.literalElements.includes(this.tagName)) {
this.literalTarget(targetIndex);
}
this.literalParts = this.literalPartStack.pop();
this.literalParts.push(this.target + this.source.substring(this.start, this.index));
this.start = this.index;
this.target = this.targetStack.pop() + '$' + this.literalParts.length;
shouldInLiteral = false;
}
return shouldInLiteral;
}
combineLiterals(text, parts) {
const original = text;
text = text.trimEnd();
const right = text.length;
let left = text.length;
text = text.trimStart();
left -= text.length;
if (text !== '') {
text = (parts && /^(\$[1-9][0-9]*)+$/.test(text))
? parts.join('')
: this.applyLiterals(text, parts?.map(part => (((typeof part)[0] === 's') && (part[0] !== '<')) ? this.applyLiterals(part) : part));
}
return original.substring(0, left) + text + original.substring(right);
}
debugEvents() {
this.onAttribute = (name, value) => console.log('attribute', name, '=', value);
this.onTagOpen = (name) => console.log('tag.open =', name);
this.onTagOpened = (name) => console.log('tag.opened =', name);
this.onTagClose = (name) => console.log('tag.closed =', name);
}
embedHtmlResponse(htmlResponse) {
for (let dependency of htmlResponse.dependencies) {
if (dependency[0] === '<') {
const script = dependency.match(/<script[^>]*\bsrc=["']([^"']+)["']/i)?.[1];
if (script) {
exports.frontScripts.push(script);
}
if (DEBUG)
console.log('addLink(', dependency, ')');
this.addLinks.push(dependency);
continue;
}
dependency = (0, node_path_1.normalize)(dependency).slice(app_dir_1.appDir.length);
switch (dependency.slice(dependency.lastIndexOf('.') + 1)) {
case 'css':
if (DEBUG)
console.log('addLink(', '<link href="' + dependency + '" rel="stylesheet">', ')');
this.addLinks.push('<link href="' + dependency + '" rel="stylesheet">');
continue;
case 'js':
exports.frontScripts.push(dependency);
if (DEBUG)
console.log('addLink(', '<script src="' + dependency + '" type="module"></script>', ')');
this.addLinks.push('<script src="' + dependency + '" type="module"></script>');
continue;
}
}
}
getCleanContext() {
const addLinks = new sorted_array_1.SortedArray;
const doneLinks = new sorted_array_1.SortedArray;
addLinks.distinct = true;
doneLinks.distinct = true;
return {
addLinks,
doneLinks,
included: false,
index: this.length,
inLiteral: this.doLiteral,
length: this.source.length,
literalPartStack: [],
literalParts: [],
source: this.source,
start: this.length,
target: this.target,
targetStack: []
};
}
getPosition() {
return { index: this.index, start: this.start, target: this.target };
}
getContext() {
return {
addLinks: this.addLinks,
doneLinks: this.doneLinks,
included: this.included,
index: this.index,
inLiteral: this.inLiteral,
length: this.length,
literalParts: this.literalParts,
literalPartStack: this.literalPartStack,
source: this.source,
start: this.start,
target: this.target,
targetStack: this.targetStack,
};
}
async include(path, data) {
const template = new (Object.getPrototypeOf(this).constructor)(data, this.blockStack[0]?.data);
template.addLinks = this.addLinks;
template.doExpression = this.doExpression;
template.doLiteral = this.doLiteral;
template.doneLinks = this.doneLinks;
template.included = true;
template.onAttribute = this.onAttribute;
template.onTagClose = this.onTagClose;
template.onTagOpen = this.onTagOpen;
template.onTagOpened = this.onTagOpened;
template.parsers = this.parsers;
const parsed = await template.parseFile(((path[0] === node_path_2.sep) || (path[1] === ':'))
? path
: (this.filePath + node_path_2.sep + path));
this.headTitle = template.headTitle;
const beginPosition = parsed.indexOf('<!--BEGIN-->');
const endPosition = parsed.lastIndexOf('<!--END-->');
if ((beginPosition < 0) && (parsed[1] === '!') && parsed.startsWith('<!DOCTYPE html>')) {
if (this.targetReplace === '') {
if (DEBUG)
console.log('! targetReplace', path);
this.targetReplace = parsed;
}
return '';
}
return (beginPosition > -1)
? parsed.slice(beginPosition + 12, (endPosition > -1) ? endPosition : parsed.length)
: parsed;
}
includePath(filePath) {
return (filePath[0] === '/')
? (app_dir_1.appDir + ((filePath[1] === '@') ? '/node_modules' : '') + filePath)
: filePath;
}
isContextClean() {
const clean = this.getCleanContext();
const context = this.getContext();
return context.addLinks.distinct === clean.addLinks.distinct
&& context.addLinks.length === clean.addLinks.length
&& context.doneLinks.distinct === clean.doneLinks.distinct
&& context.included === clean.included
&& context.index === clean.index
&& context.inLiteral === clean.inLiteral
&& context.literalPartStack.length === clean.literalPartStack.length
&& context.literalParts.length === clean.literalParts.length
&& context.length === clean.length
&& context.start === clean.start
&& context.targetStack.length === clean.targetStack.length;
}
literalTarget(index, isTitle = false) {
let combined;
if (this.literalParts.length) {
this.target += this.source.substring(this.start, index);
combined = this.combineLiterals(this.target, this.literalParts);
this.target = (this.targetStack.pop() ?? '') + combined;
this.literalParts = [];
}
else {
combined = this.combineLiterals(this.source.substring(this.start, index));
this.target += combined;
}
if (isTitle && this.included) {
this.headTitle = combined;
}
this.start = index;
}
normalizeLink(link) {
if (link[0] === '/')
return link;
const result = (0, node_path_1.normalize)(this.filePath + node_path_2.sep + link).substring(app_dir_1.appDir.length);
return (node_path_2.sep === '/') ? result : result.replaceAll(node_path_2.sep, '/');
}
async parseBuffer(buffer) {
this.prefixes = this.parsers.map(([prefix]) => prefix).join('');
this.setSource(buffer);
await this.parseVars();
if (this.included) {
return this.target;
}
if (this.addLinks.length) {
let addLink;
const addLinks = new Array;
while (addLink = this.addLinks.shift()) {
for (const attribute of ['href', 'src']) {
let start = addLink.indexOf(attribute + '=');
if (start < 0)
continue;
start += attribute.length;
while ((addLink[start] !== '"') && (addLink[start] !== "'")) {
start++;
}
const quote = addLink[start++];
const stop = addLink.indexOf(quote, start);
const link = addLink.substring(start, stop);
if (DEBUG)
console.log('check(', link, ')');
if (!this.doneLinks.includes(link)) {
if (DEBUG)
console.log('+ addLink(', addLink, ')');
if (DEBUG)
console.log('+ doneLink(', link, ')');
addLinks.push(addLink);
this.doneLinks.push(link);
}
}
}
if (addLinks.length) {
const position = this.target.lastIndexOf('>', this.target.indexOf('</head>')) + 1;
this.target = this.target.slice(0, position) + '\n\t' + addLinks.join('\n\t') + this.target.slice(position);
}
}
return (this.targetReplace !== '') ? this.targetReplace : this.target;
}
async parseExpression(data, open, close, finalClose = '') {
const indexOut = this.index;
if (this.inLiteral && !this.literalParts.length) {
this.targetStack.push(this.target);
this.target = '';
}
if (open === '<') {
this.index += 3;
open = '{';
}
this.index++;
const firstChar = this.source[this.index];
if ((this.index >= this.length) || !this.startsExpression(firstChar, open, close)) {
return;
}
let conditional = (firstChar === '?');
const finalChar = finalClose.length ? finalClose[0] : '';
let stackPos = this.targetStack.length;
if (conditional) {
this.index++;
}
this.targetStack.push(this.target + this.source.substring(this.start, indexOut));
this.start = this.index;
this.target = '';
while (this.index < this.length) {
const char = this.source[this.index];
if (char === open) {
this.targetStack.push(this.target + this.source.substring(this.start, this.index));
this.index++;
this.start = this.index;
this.target = '';
continue;
}
if ((char === close)
|| ((char === finalChar) && (this.source.substring(this.index, this.index + finalClose.length) === finalClose))) {
let minus = 0;
if (this.source[this.index - 1] === '?') {
conditional = true;
minus = 1;
}
const expression = this.target + this.source.substring(this.start, this.index - minus);
const lastTarget = this.targetStack.pop();
const parsed = await this.parsePath(expression, data);
this.index += (char === close) ? 1 : finalClose.length;
this.start = this.index;
this.target = '';
if (char === finalChar)
while (this.targetStack.length > stackPos) {
this.target += this.targetStack.shift();
}
if (this.inLiteral && (this.targetStack.length === stackPos)) {
this.literalParts.push(parsed);
this.target += lastTarget + '$' + this.literalParts.length;
return conditional;
}
if (lastTarget.length || this.target.length) {
this.target += lastTarget + parsed;
}
else {
this.target = parsed;
}
if (this.targetStack.length !== stackPos) {
continue;
}
if (conditional && !parsed) {
if ((typeof this.target)[0] === 's') {
this.target = this.target.substring(0, this.target.lastIndexOf(' '));
while ((this.index < this.length) && !' >\n\r\t\f'.includes(this.source[this.index])) {
this.index++;
this.start++;
}
this.index--;
}
return conditional;
}
return conditional;
}
if ((char === '"') || (char === "'")) {
this.index++;
let c;
while ((this.index < this.length) && ((c = this.source[this.index]) !== char)) {
if (c === '\\')
this.index++;
this.index++;
}
}
this.index++;
}
// bad close
stackPos++;
while (this.targetStack.length > stackPos) {
this.target = this.targetStack.pop() + open + this.target;
}
this.target = this.targetStack.pop() + (finalClose.length ? '<!--' : open) + this.target;
return conditional;
}
async parseFile(fileName, containerFileName) {
if (DEBUG)
console.log('----- parseFile', containerFileName ? 'contained' : 'fetched', this.included ? 'included' : 'final', fileName);
if (containerFileName) {
const data = this.data;
this.data = Object.assign({ content: () => this.include(fileName, data) }, this.blockStack[0]?.data);
return this.parseFile((0, node_path_1.normalize)(containerFileName));
}
this.fileName = fileName.substring(fileName.lastIndexOf(node_path_2.sep) + 1);
this.filePath = fileName.substring(0, fileName.lastIndexOf(node_path_2.sep));
let target = await this.parseBuffer(await (0, promises_1.readFile)(fileName, 'utf-8'));
if (containerFileName && this.headTitle) {
const position = target.indexOf('>', target.indexOf('<title') + 6) + 1;
target = target.slice(0, position)
+ this.headTitle
+ target.slice(target.indexOf('</title>', position));
}
return target;
}
async parsePath(expression, data) {
if (DEBUG)
console.log('parsePath', expression);
if (expression === '') {
return undefined;
}
if (((expression[0] === '.') && ((expression[1] === '/') || ((expression[1] === '.') && (expression[2] === '/'))))
|| (expression[0] === '/')) {
let expressionEnd = expression.length - 1;
if (expression[expressionEnd] === '-') {
let blockBack = 1;
expressionEnd--;
while (expression[expressionEnd] === '-') {
blockBack++;
expressionEnd--;
}
const blockStack = this.blockStack;
return this.include(this.includePath(expression.slice(0, expressionEnd)), blockStack[blockStack.length - blockBack].data);
}
if (expression[expressionEnd] === ')') {
const openPosition = expression.lastIndexOf('(');
return this.include(this.includePath(expression.slice(0, openPosition)), await this.parsePath(expression.slice(openPosition + 1, expression.length - 1), data));
}
return this.include(this.includePath(expression), data);
}
let onlyDots = true;
for (const c of expression) {
if (c === '.')
continue;
onlyDots = false;
break;
}
if (onlyDots) {
if (expression.length <= 1) {
return (((typeof data)[0] === 'f') && ((data + '')[0] !== 'c'))
? data.call()
: data;
}
expression = expression.slice(2);
}
this.blockBack = 0;
for (const variable of expression.split('.')) {
data = await this.parseVariable(variable, data);
}
if (data instanceof HtmlResponse) {
this.embedHtmlResponse(data);
}
return data;
}
async parseVariable(variable, data) {
if (DEBUG)
console.log('parseVariable', variable, 'in', data);
if (variable === '') {
let dataBack;
do {
this.blockBack++;
dataBack = this.blockStack[this.blockStack.length - this.blockBack];
} while (dataBack.condition);
return dataBack.data;
}
if (variable === '*') {
return (typeof data === 'object') ? Object.values(data) : data;
}
const firstChar = variable[0];
if ((firstChar === 'B') && (variable === 'BEGIN')) {
return data;
}
if (((firstChar === '"') && (variable[variable.length - 1] === '"'))
|| ((firstChar === "'") && (variable[variable.length - 1] === "'"))) {
return variable.substring(1, variable.length - 1);
}
for (const [prefix, callback] of this.parsers) {
if (firstChar === prefix) {
return await callback(variable, data);
}
}
if (data[variable] === undefined) {
data = new rename_1.Str(await exports.depends.toString(data));
}
let value = data[variable];
return (((typeof value)[0] === 'f') && ((value + '')[0] !== 'c'))
? value.call(data)
: value;
}
async parseVars() {
let blockStart = 0;
let data = this.data;
let inHead = false;
let iteration = done;
let iterator;
while (this.index < this.length) {
let char = this.source[this.index];
// expression
if ((char === '{') && this.doExpression) {
await this.parseExpression(data, char, '}');
continue;
}
// tag ?
if (char !== '<') {
this.index++;
continue;
}
const tagIndex = this.index;
char = this.source[++this.index];
if (char === '!') {
if (this.inLiteral) {
this.literalTarget(tagIndex);
}
char = this.source[++this.index];
this.index++;
// comment tag
if ((char === '-') && (this.source[this.index] === '-')) {
this.index++;
const firstChar = this.source[this.index];
if (!this.doExpression
|| !this.startsExpression(firstChar)
|| ((firstChar === 'B') && (this.source.substring(this.index, this.index + 8) === 'BEGIN-->'))
|| ((firstChar === 'E') && (this.source.substring(this.index, this.index + 6) === 'END-->'))) {
this.index = this.source.indexOf('-->', this.index) + 3;
if (this.index < 3)
break;
if (this.inLiteral && (this.index > this.start)) {
this.sourceToTarget();
}
continue;
}
// end condition / loop block
if ((firstChar === 'e') && (this.source.substring(this.index, this.index + 6) === 'end-->')) {
this.target += this.trimEndLine(this.source.substring(this.start, tagIndex));
iteration = iterator?.next() ?? done;
if (!iteration.done) {
data = iteration.value;
this.index = this.start = blockStart;
if (this.inLiteral && (this.index > this.start)) {
this.sourceToTarget();
}
continue;
}
({ blockStart, data, iteration, iterator } = this.blockStack.pop()
?? { blockStart: 0, data: undefined, iteration: done });
this.index += 6;
this.start = this.index;
if (this.inLiteral && (this.index > this.start)) {
this.sourceToTarget();
}
continue;
}
// begin condition / loop block
if (tagIndex > this.start) {
this.target += this.trimEndLine(this.source.substring(this.start, tagIndex));
this.start = tagIndex;
}
const backTarget = this.target;
const backInLiteral = this.inLiteral;
this.index = tagIndex;
this.target = '';
this.inLiteral = false;
const condition = await this.parseExpression(data, '<', '}', '-->');
this.blockStack.push({ blockStart, condition, data, iteration, iterator });
let blockData = condition ? (this.target ? data : undefined) : this.target;
blockStart = this.index;
this.target = backTarget;
this.inLiteral = backInLiteral;
if (blockData && blockData[Symbol.iterator]) {
iterator = blockData[Symbol.iterator]();
iteration = iterator?.next() ?? done;
data = iteration.value;
}
else {
data = blockData;
iteration = { done: !data, value: data };
iterator = undefined;
}
if (iteration.done) {
this.skipBlock();
continue;
}
if (this.inLiteral && (this.index > this.start)) {
this.sourceToTarget();
}
continue;
}
// cdata section
if ((char === '[') && (this.source.substring(this.index, this.index + 6) === 'CDATA[')) {
this.index = this.source.indexOf(']]>', this.index + 6) + 3;
if (this.index < 3)
break;
}
// DOCTYPE
else {
this.index = this.source.indexOf('>', this.index) + 1;
}
if (this.inLiteral) {
this.sourceToTarget();
}
continue;
}
// tag close
if (char === '/') {
this.index++;
const closeTagName = this.source.substring(this.index, this.source.indexOf('>', this.index));
this.index += closeTagName.length + 1;
if (inHead && (closeTagName[0] === 'h') && (closeTagName === 'head')) {
inHead = false;
}
let shouldInLiteral = this.inLiteral;
if (!this.unclosingTags.includes(closeTagName)) {
do {
shouldInLiteral = this.closeTag(shouldInLiteral, tagIndex);
} while ((this.tagName !== closeTagName) && this.tagName.length);
}
if (shouldInLiteral) {
this.lockLiteral = false;
this.literalTarget(tagIndex, (this.tagName[0] === 't') && (this.tagName === 'title'));
}
if (this.inLiteral && (this.index > this.start)) {
this.sourceToTarget();
}
continue;
}
// tag open
while ((this.index < this.length) && !' >\n\r\t\f'.includes(this.source[this.index]))
this.index++;
this.tagName = this.source.substring(tagIndex + 1, this.index);
if (this.onTagOpen)
this.onTagOpen.call(this, this.tagName);
while (' \n\r\t\f'.includes(this.source[this.index]))
this.index++;
char = this.tagName[0];
if ((char === 'h') && (this.tagName === 'head')) {
inHead = true;
}
const unclosingTag = this.unclosingTags.includes(this.tagName);
if (!unclosingTag) {
this.tagStack.push({ tagName: this.tagName, inLiteral: this.inLiteral });
}
let inlineElement = false;
let pushedParts = false;
if (this.inLiteral) {
inlineElement = this.inlineElements.includes(this.tagName);
if (inlineElement) {
if (this.literalParts.length) {
this.targetStack.push(this.target + this.source.substring(this.start, tagIndex));
}
else {
this.targetStack.push(this.target, this.source.substring(this.start, tagIndex));
}
this.start = tagIndex;
this.target = '';
if (!unclosingTag) {
this.literalPartStack.push(this.literalParts);
this.literalParts = [];
pushedParts = true;
}
}
else {
this.literalTarget(tagIndex);
}
}
const elementInLiteral = this.inLiteral;
// attributes
let hasTypeSubmit = false;
const inInput = (char === 'i') && (this.tagName === 'input');
const inLink = (char === 'l') && (this.tagName === 'link');
const inScript = (char === 's') && (this.tagName === 'script');
let targetTagIndex = -1;
if (inHead && (inLink || inScript)) {
this.sourceToTarget();
targetTagIndex = this.target.lastIndexOf('<');
}
while (this.source[this.index] !== '>') {
// attribute name
const attributePosition = this.index;
while ((this.index < this.length) && !' =>\n\r\t\f'.includes(this.source[this.index]))
this.index++;
const attributeName = this.source.substring(attributePosition, this.index);
while (' \n\r\t\f'.includes(this.source[this.index]))
this.index++;
let attributeBlock = (attributeName[0] === 'd') && (attributeName === 'data-if') ? '' : undefined;
// attribute value
if (this.source[this.index] === '=') {
this.index++;
while (' \n\r\t\f'.includes(this.source[this.index]))
this.index++;
const attributeChar = attributeName[0];
const [open, close] = ('afhls'.includes(attributeChar)
&& ['action', 'formaction', 'href', 'location', 'src'].includes(attributeName)) ? ['(', ')']
: ['{', '}'];
let quote = this.source[this.index];
if ((quote === '"') || (quote === "'")) {
this.index++;
}
else {
quote = ' >';
}
if ((open === '(') && (this.source.substring(this.index, this.index + 6) === 'app://')) {
this.sourceToTarget();
this.index += 6;
this.start = this.index;
}
this.inLiteral = this.doLiteral && (this.literalAttributes.includes(attributeName)
|| (hasTypeSubmit && (attributeChar === 'v') && (attributeName === 'value')));
if (this.inLiteral && !pushedParts && unclosingTag && this.literalParts.length) {
this.literalPartStack.push(this.literalParts);
this.literalParts = [];
pushedParts = true;
}
const inLinkHRef = inLink && (attributeChar === 'h') && (attributeName === 'href');
const inScriptSrc = inScript && (attributeChar === 's') && (attributeName === 'src');
if ((inLinkHRef || inScriptSrc || this.inLiteral) && (this.index > this.start)) {
this.sourceToTarget();
}
const valuePosition = this.index;
const shortQuote = !(quote.length - 1);
if (shortQuote && (attributeBlock !== undefined)) {
attributeBlock = this.target + this.source.substring(this.start, attributePosition);
this.start = this.index;
this.target = '';
}
while (this.index < this.length) {
const char = this.source[this.index];
// end of attribute value
if (shortQuote ? (char === quote) : quote.includes(char)) {
const attributeValue = this.source.substring(valuePosition, this.index);
if (inInput && !hasTypeSubmit) {
hasTypeSubmit = (attributeChar === 't') && (attributeValue[0] === 's')
&& (attributeName === 'type') && (attributeValue === 'submit');
}
if (this.inLiteral) {
this.literalTarget(this.index);
}
if (inLinkHRef && attributeValue.endsWith('.css')) {
let frontStyle = this.normalizeLink(this.source.substring(this.start, this.index));
this.target += frontStyle;
this.start = this.index;
if (!(inHead && this.included)) {
if (DEBUG)
console.log('doneLink(', frontStyle, ')');
this.doneLinks.push(frontStyle);
}
}
if (inScriptSrc && attributeValue.endsWith('.js')) {
let frontScript = this.normalizeLink(this.source.substring(this.start, this.index));
exports.frontScripts.push(frontScript);
this.target += frontScript;
this.start = this.index;
if (!(inHead && this.included)) {
if (DEBUG)
console.log('doneLink(', frontScript, ')');
this.doneLinks.push(frontScript);
}
}
if (this.onAttribute)
this.onAttribute(attributeName, attributeValue);
if (char !== '>')
this.index++;
break;
}
// expression in attribute value
if ((char === open) && this.doExpression) {
await this.parseExpression(data, open, close);
continue;
}
this.index++;
}
}
else {
if (this.onAttribute)
this.onAttribute(attributeName, '');
if ((attributeName[0] === 'd') && (attributeName === 'data-end')) {
this.index = attributePosition;
this.sourceToTarget();
this.index += attributeName.length;
this.start = this.index;
}
}
// next attribute
while (' \n\r\t\f'.includes(this.source[this.index]))
this.index++;
if (attributeBlock !== undefined) {
if (!this.target) {
this.index = this.source.indexOf('data-end', this.index) + 8;
if (this.index < 8) {
throw 'Missing data-end matching data-if at position ' + attributePosition
+ ' into template file ' + this.filePath + node_path_2.sep + this.fileName;
}
}
this.start = this.index;
this.target = attributeBlock;
}
}
this.index++;
if (this.onTagOpened)
this.onTagOpened.call(this, this.tagName);
// skip script content
if (inScript) {
if (this.onTagClose)
this.onTagClose.call(this, 'script');
this.index = this.source.indexOf('</script>', this.index) + 9;
if (this.index < 9)
break;
if (this.inLiteral && (this.index > this.start)) {
this.sourceToTarget();
}
}
if ((targetTagIndex > -1) && this.included) {
this.sourceToTarget();
const headLink = this.target.substring(targetTagIndex);
if (DEBUG)
console.log('addLink(', headLink, ')');
this.addLinks.push(headLink);
}
if (inScript) {
continue;
}
if (unclosingTag) {
if (pushedParts) {
this.literalParts = this.literalPartStack.pop();
}
this.inLiteral = elementInLiteral;
if (this.onTagClose)
this.onTagClose.call(this, this.tagName);
if (this.inLiteral) {
if (this.index > this.start) {
this.sourceToTarget();
}
if (inlineElement) {
this.literalParts.push(this.target);
this.target = this.targetStack.pop() + '$' + this.literalParts.length;
}
}
}
else {
this.lockLiteral ||= (this.tagName[0] === 'a') && (this.tagName === 'address');
this.inLiteral = this.doLiteral && !this.lockLiteral && this.literalElements.includes(this.tagName);
if (this.inLiteral && (this.index > this.start)) {
this.sourceToTarget();
}
}
}
if (this.tagStack.length) {
let shouldInLiteral = this.inLiteral;
while (this.tagStack.length) {
shouldInLiteral = this.closeTag(shouldInLiteral, this.length);
}
if (shouldInLiteral) {
this.literalTarget(this.length);
}
return this.target;
}
if (this.inLiteral) {
this.literalTarget(this.index);
}
if (this.start < this.length) {
this.target += this.source.substring(this.start);
this.start = this.length;
}
return this.target;
}
setSource(source, index = 0, start, target = '') {
this.index = index;
this.length = source.length;
this.source = source;
this.start = start ?? index;
this.tagName = '';
this.tagStack = [];
this.target = target;
this.inLiteral = this.doLiteral;
this.literalPartStack = [];
this.literalParts = [];
this.lockLiteral = false;
this.targetStack = [];
}
skipBlock() {
if (this.index > this.start) {
this.sourceToTarget();
}
let depth = 1;
while (depth) {
this.index = this.source.indexOf('<!--', this.index);
if (this.index < 0) {
break;
}
this.index += 4;
const char = this.source[this.index];
if (!this.startsExpression(char)) {
continue;
}
if ((char === 'e') && (this.source.substring(this.index, this.index + 6) === 'end-->')) {
depth--;
continue;
}
depth++;
}
this.index -= 4;
if (this.index < 0) {
this.index = this.length;
}
this.start = this.index;
}
sourceToTarget() {
this.target += this.source.substring(this.start, this.index);
this.start = this.index;
}
startsExpression(char, open = '{', close = '}') {
return RegExp(`[a-z0-9"'*./?` + open + close + this.prefixes + ']', 'i').test(char);
}
trimEndLine(string) {
let index = string.length;
while ((index > 0) && ' \n\r\t\f'.includes(string[index - 1])) {
index--;
if (string[index] === '\n') {
break;
}
}
return string.substring(0, index);
}
}
exports.Template = Template;
//# sourceMappingURL=template.js.map