UNPKG

@itrocks/template

Version:

The W3C-valid, browser-previewable, concise, and fast HTML template engine that enables delimiter-less translations

945 lines 42.2 kB
"use strict"; 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