@itrocks/template 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/template.js CHANGED
@@ -3,34 +3,37 @@ import appDir from '@itrocks/app-dir';
3
3
  import { SortedArray } from '@itrocks/sorted-array';
4
4
  import { readFile } from 'node:fs/promises';
5
5
  import { normalize, sep } from 'node:path';
6
- let blockBack;
7
- let blockStack;
8
- let doHeadLinks = false;
9
- let index;
10
- let length;
11
- let source;
12
- let start;
13
- let tagName;
14
- let tagStack;
15
- let target;
16
- let targetStack;
17
- let lockLiteral;
18
- let literalPartStack;
19
- let literalParts;
20
- let inLiteral;
21
6
  export const frontScripts = new SortedArray();
22
7
  frontScripts.distinct = true;
23
- let doneLinks = new SortedArray();
24
- let headLinks = new SortedArray();
25
- let headTitle = undefined;
26
- doneLinks.distinct = true;
27
- headLinks.distinct = true;
28
8
  export { Template };
29
9
  export default class Template {
30
10
  data;
31
11
  containerData;
12
+ // block stack
13
+ blockBack = 0;
14
+ blockStack;
15
+ // parser
32
16
  doExpression = true;
17
+ index = 0;
18
+ length = 0;
19
+ source = '';
20
+ start = 0;
21
+ tagName = '';
22
+ tagStack = [];
23
+ target = '';
24
+ targetStack = [];
25
+ // literal
33
26
  doLiteral = false;
27
+ inLiteral = false;
28
+ literalParts = [];
29
+ literalPartStack = [];
30
+ lockLiteral = false;
31
+ // html head
32
+ doHeadLinks = false;
33
+ doneLinks = new SortedArray();
34
+ headLinks = new SortedArray();
35
+ headTitle;
36
+ // file
34
37
  fileName;
35
38
  filePath;
36
39
  included = false;
@@ -40,42 +43,45 @@ export default class Template {
40
43
  literalAttributes = new SortedArray('alt', 'enterkeyhint', 'label', 'lang', 'placeholder', 'srcdoc', 'title');
41
44
  // These element contents are literals.
42
45
  literalElements = new 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');
46
+ // These elements have no closing tag.
47
+ unclosingTags = new SortedArray('area', 'base', 'basefont', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track');
48
+ // Event hooks
43
49
  onAttribute;
44
50
  onTagOpen;
45
51
  onTagOpened;
46
52
  onTagClose;
53
+ // Additional parsers
47
54
  parsers = [];
48
- prefixes;
49
- // These elements have no closing tag.
50
- unclosingTags = new SortedArray('area', 'base', 'basefont', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track');
55
+ prefixes = '';
51
56
  constructor(data, containerData) {
52
57
  this.data = data;
53
58
  this.containerData = containerData;
54
- blockStack = [];
59
+ this.doneLinks.distinct = true;
60
+ this.headLinks.distinct = true;
61
+ this.blockStack = [];
55
62
  if (containerData) {
56
- blockStack.push({ blockStart: 0, collection: [], data: containerData, iteration: 0, iterations: 1 });
63
+ this.blockStack.push({ blockStart: 0, collection: [], data: containerData, iteration: 0, iterations: 1 });
57
64
  }
58
- this.prefixes = this.parsers.map(([prefix]) => prefix).join('');
59
65
  }
60
66
  applyLiterals(text, parts = []) {
61
67
  return text.replace(/\$([0-9]+)/g, (_, index) => parts[+index]);
62
68
  }
63
69
  closeTag(shouldInLiteral, targetIndex) {
64
- shouldInLiteral ||= inLiteral;
65
- ({ tagName, inLiteral } = tagStack.pop() ?? { tagName: '', inLiteral: false });
70
+ shouldInLiteral ||= this.inLiteral;
71
+ Object.assign(this, this.tagStack.pop() ?? { tagName: '', inLiteral: false });
66
72
  if (this.onTagClose)
67
- this.onTagClose.call(this, tagName);
68
- if ((tagName[0] === 'a') && (tagName === 'address')) {
69
- lockLiteral = false;
73
+ this.onTagClose.call(this, this.tagName);
74
+ if ((this.tagName[0] === 'a') && (this.tagName === 'address')) {
75
+ this.lockLiteral = false;
70
76
  }
71
- if (inLiteral && this.inlineElements.includes(tagName)) {
72
- if (this.literalElements.includes(tagName)) {
77
+ if (this.inLiteral && this.inlineElements.includes(this.tagName)) {
78
+ if (this.literalElements.includes(this.tagName)) {
73
79
  this.literalTarget(targetIndex);
74
80
  }
75
- literalParts = literalPartStack.pop();
76
- literalParts.push(target + source.substring(start, index));
77
- start = index;
78
- target = targetStack.pop() + '$' + literalParts.length;
81
+ this.literalParts = this.literalPartStack.pop();
82
+ this.literalParts.push(this.target + this.source.substring(this.start, this.index));
83
+ this.start = this.index;
84
+ this.target = this.targetStack.pop() + '$' + this.literalParts.length;
79
85
  shouldInLiteral = false;
80
86
  }
81
87
  return shouldInLiteral;
@@ -109,46 +115,51 @@ export default class Template {
109
115
  doHeadLinks: false,
110
116
  doneLinks: doneLinks,
111
117
  headLinks: headLinks,
112
- index: length,
113
- length: source.length,
114
- source: source,
115
- start: length,
116
- target: target,
117
- targetStack: [],
118
+ index: this.length,
119
+ inLiteral: this.doLiteral,
120
+ length: this.source.length,
118
121
  literalPartStack: [],
119
122
  literalParts: [],
120
- inLiteral: this.doLiteral
123
+ source: this.source,
124
+ start: this.length,
125
+ target: this.target,
126
+ targetStack: []
121
127
  };
122
128
  }
123
129
  getPosition() {
124
- return { index, start, target };
130
+ return { index: this.index, start: this.start, target: this.target };
125
131
  }
126
132
  getContext() {
127
133
  return {
128
- doHeadLinks, doneLinks, headLinks, index, length, source, start, target, targetStack,
129
- literalPartStack, literalParts, inLiteral
134
+ doHeadLinks: this.doHeadLinks,
135
+ doneLinks: this.doneLinks,
136
+ headLinks: this.headLinks,
137
+ index: this.index,
138
+ inLiteral: this.inLiteral,
139
+ length: this.length,
140
+ literalParts: this.literalParts,
141
+ literalPartStack: this.literalPartStack,
142
+ source: this.source,
143
+ start: this.start,
144
+ target: this.target,
145
+ targetStack: this.targetStack,
130
146
  };
131
147
  }
132
148
  async include(path, data) {
133
- const back = {
134
- doHeadLinks, index, length, source, start, tagName, tagStack, target, targetStack,
135
- literalParts, literalPartStack, inLiteral, lockLiteral
136
- };
137
- doHeadLinks = true;
138
- const template = new (Object.getPrototypeOf(this).constructor)(data, blockStack[0]?.data);
139
- template.included = true;
149
+ const template = new (Object.getPrototypeOf(this).constructor)(data, this.blockStack[0]?.data);
140
150
  template.doExpression = this.doExpression;
151
+ template.doHeadLinks = true;
141
152
  template.doLiteral = this.doLiteral;
153
+ template.included = true;
142
154
  template.onAttribute = this.onAttribute;
143
155
  template.onTagClose = this.onTagClose;
144
156
  template.onTagOpen = this.onTagOpen;
145
157
  template.onTagOpened = this.onTagOpened;
146
158
  template.parsers = this.parsers;
147
- const parsed = await template.parseFile(((path[0] === sep) || (path[1] === ':')) ? path : (this.filePath + sep + path));
148
- ({
149
- doHeadLinks, index, length, source, start, tagName, tagStack, target, targetStack,
150
- literalParts, literalPartStack, inLiteral, lockLiteral
151
- } = back);
159
+ template.prefixes = this.prefixes;
160
+ const parsed = await template.parseFile(((path[0] === sep) || (path[1] === ':'))
161
+ ? path
162
+ : (this.filePath + sep + path));
152
163
  return parsed.substring(parsed.indexOf('<!--BEGIN-->') + 12, parsed.indexOf('<!--END-->'));
153
164
  }
154
165
  isContextClean() {
@@ -160,151 +171,154 @@ export default class Template {
160
171
  && context.headLinks.distinct === clean.headLinks.distinct
161
172
  && context.headLinks.length === clean.headLinks.length
162
173
  && context.index === clean.index
163
- && context.length === clean.length
164
- && context.start === clean.start
165
- && context.targetStack.length === clean.targetStack.length
174
+ && context.inLiteral === clean.inLiteral
166
175
  && context.literalPartStack.length === clean.literalPartStack.length
167
176
  && context.literalParts.length === clean.literalParts.length
168
- && context.inLiteral === clean.inLiteral;
177
+ && context.length === clean.length
178
+ && context.start === clean.start
179
+ && context.targetStack.length === clean.targetStack.length;
169
180
  }
170
181
  literalTarget(index, isTitle = false) {
171
182
  let combined;
172
- if (literalParts.length) {
173
- target += source.substring(start, index);
174
- combined = this.combineLiterals(target, literalParts);
175
- target = (targetStack.pop() ?? '') + combined;
176
- literalParts = [];
183
+ if (this.literalParts.length) {
184
+ this.target += this.source.substring(this.start, index);
185
+ combined = this.combineLiterals(this.target, this.literalParts);
186
+ this.target = (this.targetStack.pop() ?? '') + combined;
187
+ this.literalParts = [];
177
188
  }
178
189
  else {
179
- combined = this.combineLiterals(source.substring(start, index));
180
- target += combined;
190
+ combined = this.combineLiterals(this.source.substring(this.start, index));
191
+ this.target += combined;
181
192
  }
182
- if (isTitle && doHeadLinks) {
183
- headTitle = combined;
193
+ if (isTitle && this.doHeadLinks) {
194
+ this.headTitle = combined;
184
195
  }
185
- start = index;
196
+ this.start = index;
186
197
  }
187
198
  async parseBuffer(buffer) {
199
+ this.prefixes = this.parsers.map(([prefix]) => prefix).join('');
188
200
  this.setSource(buffer);
189
201
  await this.parseVars();
190
- if (doHeadLinks) {
191
- return target;
192
- }
193
- if (headLinks.length) {
194
- const position = target.lastIndexOf('>', target.indexOf('</head>')) + 1;
195
- target = target.slice(0, position) + '\n\t' + headLinks.join('\n\t') + target.slice(position);
196
- doneLinks = new SortedArray;
197
- doneLinks.distinct = true;
198
- headLinks = new SortedArray;
199
- headLinks.distinct = true;
200
- }
201
- if (headTitle && !this.included) {
202
- const position = target.indexOf('>', target.indexOf('<title') + 6) + 1;
203
- target = target.slice(0, position) + headTitle + target.slice(target.indexOf('</title>', position));
204
- }
205
- return target;
202
+ if (this.doHeadLinks) {
203
+ return this.target;
204
+ }
205
+ if (this.headLinks.length) {
206
+ const position = this.target.lastIndexOf('>', this.target.indexOf('</head>')) + 1;
207
+ this.target = this.target.slice(0, position) + '\n\t' + this.headLinks.join('\n\t') + this.target.slice(position);
208
+ this.doneLinks = new SortedArray;
209
+ this.headLinks = new SortedArray;
210
+ this.doneLinks.distinct = true;
211
+ this.headLinks.distinct = true;
212
+ }
213
+ if (this.headTitle && !this.included) {
214
+ const position = this.target.indexOf('>', this.target.indexOf('<title') + 6) + 1;
215
+ this.target = this.target.slice(0, position)
216
+ + this.headTitle
217
+ + this.target.slice(this.target.indexOf('</title>', position));
218
+ }
219
+ return this.target;
206
220
  }
207
221
  async parseExpression(data, close, finalClose = '') {
208
- const indexOut = index;
209
- let open = source[index];
210
- if (inLiteral && !literalParts.length) {
211
- targetStack.push(target);
212
- target = '';
222
+ const indexOut = this.index;
223
+ let open = this.source[this.index];
224
+ if (this.inLiteral && !this.literalParts.length) {
225
+ this.targetStack.push(this.target);
226
+ this.target = '';
213
227
  }
214
228
  if (open === '<') {
215
- index += 3;
229
+ this.index += 3;
216
230
  open = '{';
217
231
  }
218
- index++;
219
- const firstChar = source[index];
220
- if ((index >= length) || !this.startsExpression(firstChar, open, close)) {
232
+ this.index++;
233
+ const firstChar = this.source[this.index];
234
+ if ((this.index >= this.length) || !this.startsExpression(firstChar, open, close)) {
221
235
  return;
222
236
  }
223
237
  let conditional = (firstChar === '?');
224
238
  const finalChar = finalClose.length ? finalClose[0] : '';
225
- let stackPos = targetStack.length;
239
+ let stackPos = this.targetStack.length;
226
240
  if (conditional) {
227
- index++;
241
+ this.index++;
228
242
  }
229
- targetStack.push(target + source.substring(start, indexOut));
230
- start = index;
231
- target = '';
232
- while (index < length) {
233
- const char = source[index];
243
+ this.targetStack.push(this.target + this.source.substring(this.start, indexOut));
244
+ this.start = this.index;
245
+ this.target = '';
246
+ while (this.index < this.length) {
247
+ const char = this.source[this.index];
234
248
  if (char === open) {
235
- targetStack.push(target + source.substring(start, index));
236
- index++;
237
- start = index;
238
- target = '';
249
+ this.targetStack.push(this.target + this.source.substring(this.start, this.index));
250
+ this.index++;
251
+ this.start = this.index;
252
+ this.target = '';
239
253
  continue;
240
254
  }
241
255
  if ((char === close)
242
- || ((char === finalChar) && (source.substring(index, index + finalClose.length) === finalClose))) {
256
+ || ((char === finalChar) && (this.source.substring(this.index, this.index + finalClose.length) === finalClose))) {
243
257
  let minus = 0;
244
- if (source[index - 1] === '?') {
258
+ if (this.source[this.index - 1] === '?') {
245
259
  conditional = true;
246
260
  minus = 1;
247
261
  }
248
- const expression = target + source.substring(start, index - minus);
249
- const lastTarget = targetStack.pop();
262
+ const expression = this.target + this.source.substring(this.start, this.index - minus);
263
+ const lastTarget = this.targetStack.pop();
250
264
  const parsed = await this.parsePath(expression, data);
251
- index += (char === close) ? 1 : finalClose.length;
252
- start = index;
253
- target = '';
265
+ this.index += (char === close) ? 1 : finalClose.length;
266
+ this.start = this.index;
267
+ this.target = '';
254
268
  if (char === finalChar)
255
- while (targetStack.length > stackPos) {
256
- target += targetStack.shift();
269
+ while (this.targetStack.length > stackPos) {
270
+ this.target += this.targetStack.shift();
257
271
  }
258
- if (inLiteral && (targetStack.length === stackPos)) {
259
- literalParts.push(parsed);
260
- target += lastTarget + '$' + literalParts.length;
272
+ if (this.inLiteral && (this.targetStack.length === stackPos)) {
273
+ this.literalParts.push(parsed);
274
+ this.target += lastTarget + '$' + this.literalParts.length;
261
275
  return conditional;
262
276
  }
263
- if (lastTarget.length || target.length) {
264
- target += lastTarget + parsed;
277
+ if (lastTarget.length || this.target.length) {
278
+ this.target += lastTarget + parsed;
265
279
  }
266
280
  else {
267
- target = parsed;
281
+ this.target = parsed;
268
282
  }
269
- if (targetStack.length === stackPos) {
270
- if (conditional && !parsed) {
271
- if ((typeof target)[0] === 's') {
272
- target = target.substring(0, target.lastIndexOf(' '));
273
- while ((index < length) && !' \n\r\t\f'.includes(source[index])) {
274
- index++;
275
- start++;
276
- }
277
- index--;
283
+ if (this.targetStack.length !== stackPos) {
284
+ continue;
285
+ }
286
+ if (conditional && !parsed) {
287
+ if ((typeof this.target)[0] === 's') {
288
+ this.target = this.target.substring(0, this.target.lastIndexOf(' '));
289
+ while ((this.index < this.length) && !' \n\r\t\f'.includes(this.source[this.index])) {
290
+ this.index++;
291
+ this.start++;
278
292
  }
279
- return conditional;
293
+ this.index--;
280
294
  }
281
295
  return conditional;
282
296
  }
283
- continue;
297
+ return conditional;
284
298
  }
285
299
  if ((char === '"') || (char === "'")) {
286
- index++;
300
+ this.index++;
287
301
  let c;
288
- while ((index < length) && ((c = source[index]) !== char)) {
302
+ while ((this.index < this.length) && ((c = this.source[this.index]) !== char)) {
289
303
  if (c === '\\')
290
- index++;
291
- index++;
304
+ this.index++;
305
+ this.index++;
292
306
  }
293
307
  }
294
- index++;
308
+ this.index++;
295
309
  }
296
310
  // bad close
297
311
  stackPos++;
298
- while (targetStack.length > stackPos) {
299
- target = targetStack.pop() + open + target;
312
+ while (this.targetStack.length > stackPos) {
313
+ this.target = this.targetStack.pop() + open + this.target;
300
314
  }
301
- target = targetStack.pop() + (finalClose.length ? '<!--' : open) + target;
315
+ this.target = this.targetStack.pop() + (finalClose.length ? '<!--' : open) + this.target;
302
316
  return conditional;
303
317
  }
304
318
  async parseFile(fileName, containerFileName) {
305
319
  if (containerFileName && !this.included) {
306
320
  const data = this.data;
307
- this.data = Object.assign({ content: () => this.include(fileName, data) }, blockStack[0]?.data);
321
+ this.data = Object.assign({ content: () => this.include(fileName, data) }, this.blockStack[0]?.data);
308
322
  return this.parseFile(normalize(containerFileName));
309
323
  }
310
324
  this.fileName = fileName.substring(fileName.lastIndexOf(sep) + 1);
@@ -318,7 +332,7 @@ export default class Template {
318
332
  if ((expression[0] === '.') && (expression.startsWith('./') || expression.startsWith('../'))) {
319
333
  return this.include(expression, data);
320
334
  }
321
- blockBack = 0;
335
+ this.blockBack = 0;
322
336
  for (const variable of expression.split('.')) {
323
337
  data = await this.parseVariable(variable, data);
324
338
  }
@@ -342,8 +356,8 @@ export default class Template {
342
356
  return variable.substring(1, variable.length - 1);
343
357
  }
344
358
  if (firstChar === '-') {
345
- blockBack++;
346
- return blockStack[blockStack.length - blockBack].data;
359
+ this.blockBack++;
360
+ return this.blockStack[this.blockStack.length - this.blockBack].data;
347
361
  }
348
362
  for (const [prefix, callback] of this.parsers) {
349
363
  if (firstChar === prefix) {
@@ -365,8 +379,8 @@ export default class Template {
365
379
  let inHead = false;
366
380
  let iteration = 0;
367
381
  let iterations = 0;
368
- while (index < length) {
369
- let char = source[index];
382
+ while (this.index < this.length) {
383
+ let char = this.source[this.index];
370
384
  // expression
371
385
  if ((char === '{') && this.doExpression) {
372
386
  await this.parseExpression(data, '}');
@@ -374,70 +388,76 @@ export default class Template {
374
388
  }
375
389
  // tag ?
376
390
  if (char !== '<') {
377
- index++;
391
+ this.index++;
378
392
  continue;
379
393
  }
380
- const tagIndex = index;
381
- char = source[++index];
394
+ const tagIndex = this.index;
395
+ char = this.source[++this.index];
382
396
  if (char === '!') {
383
- if (inLiteral) {
397
+ if (this.inLiteral) {
384
398
  this.literalTarget(tagIndex);
385
399
  }
386
- char = source[++index];
387
- index++;
400
+ char = this.source[++this.index];
401
+ this.index++;
388
402
  // comment tag
389
- if ((char === '-') && (source[index] === '-')) {
390
- index++;
403
+ if ((char === '-') && (this.source[this.index] === '-')) {
404
+ this.index++;
405
+ const firstChar = this.source[this.index];
391
406
  if (!this.doExpression
392
- || !this.startsExpression(source[index])
393
- || ((source[index] === 'B') && this.included && (source.substring(index, index + 8) === 'BEGIN-->'))
394
- || ((source[index] === 'E') && this.included && (source.substring(index, index + 6) === 'END-->'))) {
395
- index = source.indexOf('-->', index) + 3;
396
- if (index === 2)
407
+ || !this.startsExpression(firstChar)
408
+ || ((firstChar === 'B')
409
+ && this.included
410
+ && (this.source.substring(this.index, this.index + 8) === 'BEGIN-->'))
411
+ || ((firstChar === 'E')
412
+ && this.included
413
+ && (this.source.substring(this.index, this.index + 6) === 'END-->'))) {
414
+ this.index = this.source.indexOf('-->', this.index) + 3;
415
+ if (this.index === 2)
397
416
  break;
398
- if (inLiteral && (index > start)) {
417
+ if (this.inLiteral && (this.index > this.start)) {
399
418
  this.sourceToTarget();
400
419
  }
401
420
  continue;
402
421
  }
403
422
  // end condition / loop block
404
- if ('eE'.includes(source[index]) && ['end-->', 'END-->'].includes(source.substring(index, index + 6))) {
405
- target += this.trimEndLine(source.substring(start, tagIndex));
423
+ if ('eE'.includes(firstChar)
424
+ && ['end-->', 'END-->'].includes(this.source.substring(this.index, this.index + 6))) {
425
+ this.target += this.trimEndLine(this.source.substring(this.start, tagIndex));
406
426
  iteration++;
407
427
  if (iteration < iterations) {
408
428
  data = collection[iteration];
409
- index = start = blockStart;
410
- if (inLiteral && (index > start)) {
429
+ this.index = this.start = blockStart;
430
+ if (this.inLiteral && (this.index > this.start)) {
411
431
  this.sourceToTarget();
412
432
  }
413
433
  continue;
414
434
  }
415
- ({ blockStart, collection, data, iteration, iterations } = blockStack.pop()
435
+ ({ blockStart, collection, data, iteration, iterations } = this.blockStack.pop()
416
436
  ?? { blockStart: 0, collection: [], data: undefined, iteration: 0, iterations: 0 });
417
- index += 6;
418
- start = index;
419
- if (inLiteral && (index > start)) {
437
+ this.index += 6;
438
+ this.start = this.index;
439
+ if (this.inLiteral && (this.index > this.start)) {
420
440
  this.sourceToTarget();
421
441
  }
422
442
  continue;
423
443
  }
424
444
  // begin condition / loop block
425
- blockStack.push({ blockStart, collection, data, iteration, iterations });
426
- if (tagIndex > start) {
427
- target += this.trimEndLine(source.substring(start, tagIndex));
428
- start = tagIndex;
445
+ this.blockStack.push({ blockStart, collection, data, iteration, iterations });
446
+ if (tagIndex > this.start) {
447
+ this.target += this.trimEndLine(this.source.substring(this.start, tagIndex));
448
+ this.start = tagIndex;
429
449
  }
430
- const backTarget = target;
431
- const backInLiteral = inLiteral;
432
- index = tagIndex;
433
- target = '';
434
- inLiteral = false;
450
+ const backTarget = this.target;
451
+ const backInLiteral = this.inLiteral;
452
+ this.index = tagIndex;
453
+ this.target = '';
454
+ this.inLiteral = false;
435
455
  const condition = await this.parseExpression(data, '}', '-->');
436
- let blockData = condition ? (target ? data : undefined) : target;
437
- blockStart = index;
456
+ let blockData = condition ? (this.target ? data : undefined) : this.target;
457
+ blockStart = this.index;
438
458
  iteration = 0;
439
- target = backTarget;
440
- inLiteral = backInLiteral;
459
+ this.target = backTarget;
460
+ this.inLiteral = backInLiteral;
441
461
  if (Array.isArray(blockData)) {
442
462
  collection = blockData;
443
463
  data = collection[0];
@@ -452,86 +472,86 @@ export default class Template {
452
472
  this.skipBlock();
453
473
  continue;
454
474
  }
455
- if (inLiteral && (index > start)) {
475
+ if (this.inLiteral && (this.index > this.start)) {
456
476
  this.sourceToTarget();
457
477
  }
458
478
  continue;
459
479
  }
460
480
  // cdata section
461
- if ((char === '[') && (source.substring(index, index + 6) === 'CDATA[')) {
462
- index = source.indexOf(']]>', index + 6) + 3;
463
- if (index === 2)
481
+ if ((char === '[') && (this.source.substring(this.index, this.index + 6) === 'CDATA[')) {
482
+ this.index = this.source.indexOf(']]>', this.index + 6) + 3;
483
+ if (this.index === 2)
464
484
  break;
465
485
  }
466
486
  // DOCTYPE
467
487
  else {
468
- index = source.indexOf('>', index) + 1;
488
+ this.index = this.source.indexOf('>', this.index) + 1;
469
489
  }
470
- if (inLiteral) {
490
+ if (this.inLiteral) {
471
491
  this.sourceToTarget();
472
492
  }
473
493
  continue;
474
494
  }
475
495
  // tag close
476
496
  if (char === '/') {
477
- index++;
478
- const closeTagName = source.substring(index, source.indexOf('>', index));
479
- index += closeTagName.length + 1;
497
+ this.index++;
498
+ const closeTagName = this.source.substring(this.index, this.source.indexOf('>', this.index));
499
+ this.index += closeTagName.length + 1;
480
500
  if (inHead && (closeTagName[0] === 'h') && (closeTagName === 'head')) {
481
501
  inHead = false;
482
- if (!doHeadLinks) {
483
- doneLinks = headLinks;
484
- headLinks = new SortedArray;
485
- headLinks.distinct = true;
502
+ if (!this.doHeadLinks) {
503
+ this.doneLinks = this.headLinks;
504
+ this.headLinks = new SortedArray();
505
+ this.headLinks.distinct = true;
486
506
  }
487
507
  }
488
- let shouldInLiteral = inLiteral;
508
+ let shouldInLiteral = this.inLiteral;
489
509
  if (!this.unclosingTags.includes(closeTagName)) {
490
510
  do {
491
511
  shouldInLiteral = this.closeTag(shouldInLiteral, tagIndex);
492
- } while ((tagName !== closeTagName) && tagName.length);
512
+ } while ((this.tagName !== closeTagName) && this.tagName.length);
493
513
  }
494
514
  if (shouldInLiteral) {
495
- lockLiteral = false;
496
- this.literalTarget(tagIndex, (tagName[0] === 't') && (tagName === 'title'));
515
+ this.lockLiteral = false;
516
+ this.literalTarget(tagIndex, (this.tagName[0] === 't') && (this.tagName === 'title'));
497
517
  }
498
- if (inLiteral && (index > start)) {
518
+ if (this.inLiteral && (this.index > this.start)) {
499
519
  this.sourceToTarget();
500
520
  }
501
521
  continue;
502
522
  }
503
523
  // tag open
504
- while ((index < length) && !' >\n\r\t\f'.includes(source[index]))
505
- index++;
506
- tagName = source.substring(tagIndex + 1, index);
524
+ while ((this.index < this.length) && !' >\n\r\t\f'.includes(this.source[this.index]))
525
+ this.index++;
526
+ this.tagName = this.source.substring(tagIndex + 1, this.index);
507
527
  if (this.onTagOpen)
508
- this.onTagOpen.call(this, tagName);
509
- while (' \n\r\t\f'.includes(source[index]))
510
- index++;
511
- char = tagName[0];
512
- if ((char === 'h') && (tagName === 'head')) {
528
+ this.onTagOpen.call(this, this.tagName);
529
+ while (' \n\r\t\f'.includes(this.source[this.index]))
530
+ this.index++;
531
+ char = this.tagName[0];
532
+ if ((char === 'h') && (this.tagName === 'head')) {
513
533
  inHead = true;
514
534
  }
515
- const unclosingTag = this.unclosingTags.includes(tagName);
535
+ const unclosingTag = this.unclosingTags.includes(this.tagName);
516
536
  if (!unclosingTag) {
517
- tagStack.push({ tagName, inLiteral });
537
+ this.tagStack.push({ tagName: this.tagName, inLiteral: this.inLiteral });
518
538
  }
519
539
  let inlineElement = false;
520
540
  let pushedParts = false;
521
- if (inLiteral) {
522
- inlineElement = this.inlineElements.includes(tagName);
541
+ if (this.inLiteral) {
542
+ inlineElement = this.inlineElements.includes(this.tagName);
523
543
  if (inlineElement) {
524
- if (literalParts.length) {
525
- targetStack.push(target + source.substring(start, tagIndex));
544
+ if (this.literalParts.length) {
545
+ this.targetStack.push(this.target + this.source.substring(this.start, tagIndex));
526
546
  }
527
547
  else {
528
- targetStack.push(target, source.substring(start, tagIndex));
548
+ this.targetStack.push(this.target, this.source.substring(this.start, tagIndex));
529
549
  }
530
- start = tagIndex;
531
- target = '';
550
+ this.start = tagIndex;
551
+ this.target = '';
532
552
  if (!unclosingTag) {
533
- literalPartStack.push(literalParts);
534
- literalParts = [];
553
+ this.literalPartStack.push(this.literalParts);
554
+ this.literalParts = [];
535
555
  pushedParts = true;
536
556
  }
537
557
  }
@@ -539,95 +559,95 @@ export default class Template {
539
559
  this.literalTarget(tagIndex);
540
560
  }
541
561
  }
542
- const elementInLiteral = inLiteral;
562
+ const elementInLiteral = this.inLiteral;
543
563
  // attributes
544
564
  let hasTypeSubmit = false;
545
- const inInput = (char === 'i') && (tagName === 'input');
546
- const inLink = (char === 'l') && (tagName === 'link');
547
- const inScript = (char === 's') && (tagName === 'script');
565
+ const inInput = (char === 'i') && (this.tagName === 'input');
566
+ const inLink = (char === 'l') && (this.tagName === 'link');
567
+ const inScript = (char === 's') && (this.tagName === 'script');
548
568
  let targetTagIndex = -1;
549
569
  if (inHead && (inLink || inScript)) {
550
570
  this.sourceToTarget();
551
- targetTagIndex = target.lastIndexOf('<');
571
+ targetTagIndex = this.target.lastIndexOf('<');
552
572
  }
553
- while (source[index] !== '>') {
573
+ while (this.source[this.index] !== '>') {
554
574
  // attribute name
555
- const position = index;
556
- while ((index < length) && !' =>\n\r\t\f'.includes(source[index]))
557
- index++;
558
- const attributeName = source.substring(position, index);
559
- while (' \n\r\t\f'.includes(source[index]))
560
- index++;
575
+ const position = this.index;
576
+ while ((this.index < this.length) && !' =>\n\r\t\f'.includes(this.source[this.index]))
577
+ this.index++;
578
+ const attributeName = this.source.substring(position, this.index);
579
+ while (' \n\r\t\f'.includes(this.source[this.index]))
580
+ this.index++;
561
581
  // attribute value
562
- if (source[index] === '=') {
563
- index++;
564
- while (' \n\r\t\f'.includes(source[index]))
565
- index++;
582
+ if (this.source[this.index] === '=') {
583
+ this.index++;
584
+ while (' \n\r\t\f'.includes(this.source[this.index]))
585
+ this.index++;
566
586
  const attributeChar = attributeName[0];
567
587
  const [open, close] = ('afhls'.includes(attributeChar)
568
588
  && ['action', 'formaction', 'href', 'location', 'src'].includes(attributeName)) ? ['(', ')']
569
589
  : ['{', '}'];
570
- let quote = source[index];
590
+ let quote = this.source[this.index];
571
591
  if ((quote === '"') || (quote === "'")) {
572
- index++;
592
+ this.index++;
573
593
  }
574
594
  else {
575
595
  quote = ' >';
576
596
  }
577
- if ((open === '(') && (source.substring(index, index + 6) === 'app://')) {
597
+ if ((open === '(') && (this.source.substring(this.index, this.index + 6) === 'app://')) {
578
598
  this.sourceToTarget();
579
- index += 6;
580
- start = index;
599
+ this.index += 6;
600
+ this.start = this.index;
581
601
  }
582
- inLiteral = this.doLiteral && (this.literalAttributes.includes(attributeName)
602
+ this.inLiteral = this.doLiteral && (this.literalAttributes.includes(attributeName)
583
603
  || (hasTypeSubmit && (attributeChar === 'v') && (attributeName === 'value')));
584
- if (inLiteral && !pushedParts && unclosingTag && literalParts.length) {
585
- literalPartStack.push(literalParts);
586
- literalParts = [];
604
+ if (this.inLiteral && !pushedParts && unclosingTag && this.literalParts.length) {
605
+ this.literalPartStack.push(this.literalParts);
606
+ this.literalParts = [];
587
607
  pushedParts = true;
588
608
  }
589
609
  const inLinkHRef = inLink && (attributeChar === 'h') && (attributeName === 'href');
590
610
  const inScriptSrc = inScript && (attributeChar === 's') && (attributeName === 'src');
591
- if ((inLinkHRef || inScriptSrc || inLiteral) && (index > start)) {
611
+ if ((inLinkHRef || inScriptSrc || this.inLiteral) && (this.index > this.start)) {
592
612
  this.sourceToTarget();
593
613
  }
594
- const position = index;
614
+ const position = this.index;
595
615
  const shortQuote = !(quote.length - 1);
596
- while (index < length) {
597
- const char = source[index];
616
+ while (this.index < this.length) {
617
+ const char = this.source[this.index];
598
618
  // end of attribute value
599
619
  if (shortQuote ? (char === quote) : quote.includes(char)) {
600
- const attributeValue = source.substring(position, index);
601
- if (inInput) {
602
- hasTypeSubmit ||= ((attributeChar === 't') && (attributeValue[0] === 's')
603
- && (attributeName === 'type') && (attributeValue === 'submit'));
620
+ const attributeValue = this.source.substring(position, this.index);
621
+ if (inInput && (hasTypeSubmit === undefined)) {
622
+ hasTypeSubmit = (attributeChar === 't') && (attributeValue[0] === 's')
623
+ && (attributeName === 'type') && (attributeValue === 'submit');
604
624
  }
605
- if (inLiteral) {
606
- this.literalTarget(index);
625
+ if (this.inLiteral) {
626
+ this.literalTarget(this.index);
607
627
  }
608
628
  if (inLinkHRef && attributeValue.endsWith('.css')) {
609
- let frontStyle = normalize(this.filePath + sep + source.substring(start, index))
629
+ let frontStyle = normalize(this.filePath + sep + this.source.substring(this.start, this.index))
610
630
  .substring(appDir.length);
611
631
  if (sep !== '/') {
612
632
  frontStyle = frontStyle.replaceAll(sep, '/');
613
633
  }
614
- target += frontStyle;
615
- start = index;
634
+ this.target += frontStyle;
635
+ this.start = this.index;
616
636
  }
617
637
  if (inScriptSrc && attributeValue.endsWith('.js')) {
618
- let frontScript = normalize(this.filePath + sep + source.substring(start, index))
638
+ let frontScript = normalize(this.filePath + sep + this.source.substring(this.start, this.index))
619
639
  .substring(appDir.length);
620
640
  if (sep !== '/') {
621
641
  frontScript = frontScript.replaceAll(sep, '/');
622
642
  }
623
643
  frontScripts.insert(frontScript);
624
- target += frontScript;
625
- start = index;
644
+ this.target += frontScript;
645
+ this.start = this.index;
626
646
  }
627
647
  if (this.onAttribute)
628
648
  this.onAttribute(attributeName, attributeValue);
629
649
  if (char !== '>')
630
- index++;
650
+ this.index++;
631
651
  break;
632
652
  }
633
653
  // expression in attribute value
@@ -635,34 +655,34 @@ export default class Template {
635
655
  await this.parseExpression(data, close);
636
656
  continue;
637
657
  }
638
- index++;
658
+ this.index++;
639
659
  }
640
660
  }
641
661
  else if (this.onAttribute)
642
662
  this.onAttribute(attributeName, '');
643
663
  // next attribute
644
- while (' \n\r\t\f'.includes(source[index]))
645
- index++;
664
+ while (' \n\r\t\f'.includes(this.source[this.index]))
665
+ this.index++;
646
666
  }
647
- index++;
667
+ this.index++;
648
668
  if (this.onTagOpened)
649
- this.onTagOpened.call(this, tagName);
669
+ this.onTagOpened.call(this, this.tagName);
650
670
  // skip script content
651
671
  if (inScript) {
652
672
  if (this.onTagClose)
653
673
  this.onTagClose.call(this, 'script');
654
- index = source.indexOf('</script>', index) + 9;
655
- if (index === 8)
674
+ this.index = this.source.indexOf('</script>', this.index) + 9;
675
+ if (this.index === 8)
656
676
  break;
657
- if (inLiteral && (index > start)) {
677
+ if (this.inLiteral && (this.index > this.start)) {
658
678
  this.sourceToTarget();
659
679
  }
660
680
  }
661
681
  if (targetTagIndex > -1) {
662
682
  this.sourceToTarget();
663
- const headLink = target.substring(targetTagIndex);
664
- if (!doneLinks || !doneLinks.includes(headLink)) {
665
- headLinks.insert(headLink);
683
+ const headLink = this.target.substring(targetTagIndex);
684
+ if (!this.doneLinks || !this.doneLinks.includes(headLink)) {
685
+ this.headLinks.insert(headLink);
666
686
  }
667
687
  }
668
688
  if (inScript) {
@@ -670,95 +690,95 @@ export default class Template {
670
690
  }
671
691
  if (unclosingTag) {
672
692
  if (pushedParts) {
673
- literalParts = literalPartStack.pop();
693
+ this.literalParts = this.literalPartStack.pop();
674
694
  }
675
- inLiteral = elementInLiteral;
695
+ this.inLiteral = elementInLiteral;
676
696
  if (this.onTagClose)
677
- this.onTagClose.call(this, tagName);
678
- if (inLiteral) {
679
- if (index > start) {
697
+ this.onTagClose.call(this, this.tagName);
698
+ if (this.inLiteral) {
699
+ if (this.index > this.start) {
680
700
  this.sourceToTarget();
681
701
  }
682
702
  if (inlineElement) {
683
- literalParts.push(target);
684
- target = targetStack.pop() + '$' + literalParts.length;
703
+ this.literalParts.push(this.target);
704
+ this.target = this.targetStack.pop() + '$' + this.literalParts.length;
685
705
  }
686
706
  }
687
707
  }
688
708
  else {
689
- lockLiteral ||= (tagName[0] === 'a') && (tagName === 'address');
690
- inLiteral = this.doLiteral && !lockLiteral && this.literalElements.includes(tagName);
691
- if (inLiteral && (index > start)) {
709
+ this.lockLiteral ||= (this.tagName[0] === 'a') && (this.tagName === 'address');
710
+ this.inLiteral = this.doLiteral && !this.lockLiteral && this.literalElements.includes(this.tagName);
711
+ if (this.inLiteral && (this.index > this.start)) {
692
712
  this.sourceToTarget();
693
713
  }
694
714
  }
695
715
  }
696
- if (tagStack.length) {
697
- let shouldInLiteral = inLiteral;
698
- while (tagStack.length) {
699
- shouldInLiteral = this.closeTag(shouldInLiteral, length);
716
+ if (this.tagStack.length) {
717
+ let shouldInLiteral = this.inLiteral;
718
+ while (this.tagStack.length) {
719
+ shouldInLiteral = this.closeTag(shouldInLiteral, this.length);
700
720
  }
701
721
  if (shouldInLiteral) {
702
- this.literalTarget(length);
722
+ this.literalTarget(this.length);
703
723
  }
704
- return target;
724
+ return this.target;
705
725
  }
706
- if (inLiteral) {
707
- this.literalTarget(index);
726
+ if (this.inLiteral) {
727
+ this.literalTarget(this.index);
708
728
  }
709
- if (start < length) {
710
- target += source.substring(start);
711
- start = length;
729
+ if (this.start < this.length) {
730
+ this.target += this.source.substring(this.start);
731
+ this.start = this.length;
712
732
  }
713
- return target;
733
+ return this.target;
714
734
  }
715
- setSource(setSource, setIndex = 0, setStart, setTarget = '') {
716
- index = setIndex;
717
- length = setSource.length;
718
- source = setSource;
719
- start = setStart ?? index;
720
- tagName = '';
721
- tagStack = [];
722
- target = setTarget;
723
- inLiteral = this.doLiteral;
724
- literalPartStack = [];
725
- literalParts = [];
726
- lockLiteral = false;
727
- targetStack = [];
735
+ setSource(source, index = 0, start, target = '') {
736
+ this.index = index;
737
+ this.length = source.length;
738
+ this.source = source;
739
+ this.start = start ?? index;
740
+ this.tagName = '';
741
+ this.tagStack = [];
742
+ this.target = target;
743
+ this.inLiteral = this.doLiteral;
744
+ this.literalPartStack = [];
745
+ this.literalParts = [];
746
+ this.lockLiteral = false;
747
+ this.targetStack = [];
728
748
  }
729
749
  skipBlock() {
730
- if (index > start) {
750
+ if (this.index > this.start) {
731
751
  this.sourceToTarget();
732
752
  }
733
753
  let depth = 1;
734
754
  while (depth) {
735
- index = source.indexOf('<!--', index);
736
- if (index < 0) {
755
+ this.index = this.source.indexOf('<!--', this.index);
756
+ if (this.index < 0) {
737
757
  break;
738
758
  }
739
- index += 4;
740
- const char = source[index];
759
+ this.index += 4;
760
+ const char = this.source[this.index];
741
761
  if (!this.startsExpression(char)) {
742
762
  continue;
743
763
  }
744
- if ((char === 'e') && (source.substring(index, index + 6) === 'end-->')) {
764
+ if ((char === 'e') && (this.source.substring(this.index, this.index + 6) === 'end-->')) {
745
765
  depth--;
746
766
  continue;
747
767
  }
748
768
  depth++;
749
769
  }
750
- index -= 4;
751
- if (index < 0) {
752
- index = length;
770
+ this.index -= 4;
771
+ if (this.index < 0) {
772
+ this.index = this.length;
753
773
  }
754
- start = index;
774
+ this.start = this.index;
755
775
  }
756
776
  sourceToTarget() {
757
- target += source.substring(start, index);
758
- start = index;
777
+ this.target += this.source.substring(this.start, this.index);
778
+ this.start = this.index;
759
779
  }
760
780
  startsExpression(char, open = '{', close = '}') {
761
- return RegExp('[a-z0-9"%*.?@\'' + open + close + '-]', 'i').test(char);
781
+ return RegExp('[a-z0-9"*.?\'' + open + close + this.prefixes + '-]', 'i').test(char);
762
782
  }
763
783
  trimEndLine(string) {
764
784
  let index = string.length;