@itrocks/template 0.2.1 → 0.2.2

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 DELETED
@@ -1,946 +0,0 @@
1
- import { appDir } from '@itrocks/app-dir';
2
- import { Str } from '@itrocks/rename';
3
- import { SortedArray } from '@itrocks/sorted-array';
4
- import { readFile } from 'node:fs/promises';
5
- import { normalize } from 'node:path';
6
- import { sep } from 'node:path';
7
- export const depends = {
8
- toString: async (value) => '' + value
9
- };
10
- const DEBUG = false;
11
- const done = { done: true };
12
- export const frontScripts = new SortedArray();
13
- frontScripts.distinct = true;
14
- export function templateDependsOn(dependencies) {
15
- Object.assign(depends, dependencies);
16
- }
17
- export class HtmlResponse {
18
- html;
19
- dependencies;
20
- constructor(html, ...dependencies) {
21
- this.html = html;
22
- this.dependencies = dependencies;
23
- }
24
- toString() { return this.html; }
25
- }
26
- export class Template {
27
- data;
28
- containerData;
29
- // block stack
30
- blockBack = 0;
31
- blockStack = [];
32
- // parser
33
- doExpression = true;
34
- index = 0;
35
- length = 0;
36
- source = '';
37
- start = 0;
38
- tagName = '';
39
- tagStack = [];
40
- target = '';
41
- targetReplace = '';
42
- targetStack = [];
43
- // literal
44
- doLiteral = false;
45
- inLiteral = false;
46
- literalParts = [];
47
- literalPartStack = [];
48
- lockLiteral = false;
49
- // html head
50
- addLinks = new SortedArray();
51
- doneLinks = new SortedArray();
52
- headTitle;
53
- // file
54
- fileName;
55
- filePath;
56
- included = false;
57
- // Inline elements are replaced by $1 when in literal.
58
- inlineElements = new 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');
59
- // These attribute values are literals.
60
- literalAttributes = new SortedArray('alt', 'enterkeyhint', 'label', 'lang', 'placeholder', 'srcdoc', 'title');
61
- // These element contents are literals.
62
- 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');
63
- // These elements have no closing tag.
64
- unclosingTags = new SortedArray('area', 'base', 'basefont', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track');
65
- // Event hooks
66
- onAttribute;
67
- onTagOpen;
68
- onTagOpened;
69
- onTagClose;
70
- // Additional parsers
71
- parsers = [];
72
- prefixes = '';
73
- constructor(data, containerData) {
74
- this.data = data;
75
- this.containerData = containerData;
76
- this.addLinks.distinct = true;
77
- this.doneLinks.distinct = true;
78
- if (containerData) {
79
- this.blockStack.push({ blockStart: 0, data: containerData, iteration: done });
80
- }
81
- }
82
- applyLiterals(text, parts = []) {
83
- return text.replace(/\$([0-9]+)/g, (_, index) => parts[+index]);
84
- }
85
- closeTag(shouldInLiteral, targetIndex) {
86
- shouldInLiteral ||= this.inLiteral;
87
- Object.assign(this, this.tagStack.pop() ?? { tagName: '', inLiteral: false });
88
- if (this.onTagClose)
89
- this.onTagClose.call(this, this.tagName);
90
- if ((this.tagName[0] === 'a') && (this.tagName === 'address')) {
91
- this.lockLiteral = false;
92
- }
93
- if (this.inLiteral && this.inlineElements.includes(this.tagName)) {
94
- if (this.literalElements.includes(this.tagName)) {
95
- this.literalTarget(targetIndex);
96
- }
97
- this.literalParts = this.literalPartStack.pop();
98
- this.literalParts.push(this.target + this.source.substring(this.start, this.index));
99
- this.start = this.index;
100
- this.target = this.targetStack.pop() + '$' + this.literalParts.length;
101
- shouldInLiteral = false;
102
- }
103
- return shouldInLiteral;
104
- }
105
- combineLiterals(text, parts) {
106
- const original = text;
107
- text = text.trimEnd();
108
- const right = text.length;
109
- let left = text.length;
110
- text = text.trimStart();
111
- left -= text.length;
112
- if (text !== '') {
113
- text = (parts && /^(\$[1-9][0-9]*)+$/.test(text))
114
- ? parts.join('')
115
- : this.applyLiterals(text, parts?.map(part => (((typeof part)[0] === 's') && (part[0] !== '<')) ? this.applyLiterals(part) : part));
116
- }
117
- return original.substring(0, left) + text + original.substring(right);
118
- }
119
- debugEvents() {
120
- this.onAttribute = (name, value) => console.log('attribute', name, '=', value);
121
- this.onTagOpen = (name) => console.log('tag.open =', name);
122
- this.onTagOpened = (name) => console.log('tag.opened =', name);
123
- this.onTagClose = (name) => console.log('tag.closed =', name);
124
- }
125
- embedHtmlResponse(htmlResponse) {
126
- for (let dependency of htmlResponse.dependencies) {
127
- if (dependency[0] === '<') {
128
- const script = dependency.match(/<script[^>]*\bsrc=["']([^"']+)["']/i)?.[1];
129
- if (script) {
130
- frontScripts.push(script);
131
- }
132
- if (DEBUG)
133
- console.log('addLink(', dependency, ')');
134
- this.addLinks.push(dependency);
135
- continue;
136
- }
137
- dependency = normalize(dependency).slice(appDir.length);
138
- switch (dependency.slice(dependency.lastIndexOf('.') + 1)) {
139
- case 'css':
140
- if (DEBUG)
141
- console.log('addLink(', '<link href="' + dependency + '" rel="stylesheet">', ')');
142
- this.addLinks.push('<link href="' + dependency + '" rel="stylesheet">');
143
- continue;
144
- case 'js':
145
- frontScripts.push(dependency);
146
- if (DEBUG)
147
- console.log('addLink(', '<script src="' + dependency + '" type="module"></script>', ')');
148
- this.addLinks.push('<script src="' + dependency + '" type="module"></script>');
149
- continue;
150
- }
151
- }
152
- }
153
- getCleanContext() {
154
- const addLinks = new SortedArray;
155
- const doneLinks = new SortedArray;
156
- addLinks.distinct = true;
157
- doneLinks.distinct = true;
158
- return {
159
- addLinks,
160
- doneLinks,
161
- included: false,
162
- index: this.length,
163
- inLiteral: this.doLiteral,
164
- length: this.source.length,
165
- literalPartStack: [],
166
- literalParts: [],
167
- source: this.source,
168
- start: this.length,
169
- target: this.target,
170
- targetStack: []
171
- };
172
- }
173
- getPosition() {
174
- return { index: this.index, start: this.start, target: this.target };
175
- }
176
- getContext() {
177
- return {
178
- addLinks: this.addLinks,
179
- doneLinks: this.doneLinks,
180
- included: this.included,
181
- index: this.index,
182
- inLiteral: this.inLiteral,
183
- length: this.length,
184
- literalParts: this.literalParts,
185
- literalPartStack: this.literalPartStack,
186
- source: this.source,
187
- start: this.start,
188
- target: this.target,
189
- targetStack: this.targetStack,
190
- };
191
- }
192
- async include(path, data) {
193
- const template = new (Object.getPrototypeOf(this).constructor)(data, this.blockStack[0]?.data);
194
- template.addLinks = this.addLinks;
195
- template.doExpression = this.doExpression;
196
- template.doLiteral = this.doLiteral;
197
- template.doneLinks = this.doneLinks;
198
- template.included = true;
199
- template.onAttribute = this.onAttribute;
200
- template.onTagClose = this.onTagClose;
201
- template.onTagOpen = this.onTagOpen;
202
- template.onTagOpened = this.onTagOpened;
203
- template.parsers = this.parsers;
204
- const parsed = await template.parseFile(((path[0] === sep) || (path[1] === ':'))
205
- ? path
206
- : (this.filePath + sep + path));
207
- this.headTitle = template.headTitle;
208
- const beginPosition = parsed.indexOf('<!--BEGIN-->');
209
- const endPosition = parsed.lastIndexOf('<!--END-->');
210
- if ((beginPosition < 0) && (parsed[1] === '!') && parsed.startsWith('<!DOCTYPE html>')) {
211
- if (this.targetReplace === '') {
212
- if (DEBUG)
213
- console.log('! targetReplace', path);
214
- this.targetReplace = parsed;
215
- }
216
- return '';
217
- }
218
- return (beginPosition > -1)
219
- ? parsed.slice(beginPosition + 12, (endPosition > -1) ? endPosition : parsed.length)
220
- : parsed;
221
- }
222
- includePath(filePath) {
223
- return (filePath[0] === '/')
224
- ? (appDir + ((filePath[1] === '@') ? '/node_modules' : '') + filePath)
225
- : filePath;
226
- }
227
- isContextClean() {
228
- const clean = this.getCleanContext();
229
- const context = this.getContext();
230
- return context.addLinks.distinct === clean.addLinks.distinct
231
- && context.addLinks.length === clean.addLinks.length
232
- && context.doneLinks.distinct === clean.doneLinks.distinct
233
- && context.included === clean.included
234
- && context.index === clean.index
235
- && context.inLiteral === clean.inLiteral
236
- && context.literalPartStack.length === clean.literalPartStack.length
237
- && context.literalParts.length === clean.literalParts.length
238
- && context.length === clean.length
239
- && context.start === clean.start
240
- && context.targetStack.length === clean.targetStack.length;
241
- }
242
- literalTarget(index, isTitle = false) {
243
- let combined;
244
- if (this.literalParts.length) {
245
- this.target += this.source.substring(this.start, index);
246
- combined = this.combineLiterals(this.target, this.literalParts);
247
- this.target = (this.targetStack.pop() ?? '') + combined;
248
- this.literalParts = [];
249
- }
250
- else {
251
- combined = this.combineLiterals(this.source.substring(this.start, index));
252
- this.target += combined;
253
- }
254
- if (isTitle && this.included) {
255
- this.headTitle = combined;
256
- }
257
- this.start = index;
258
- }
259
- normalizeLink(link) {
260
- if (link[0] === '/')
261
- return link;
262
- const result = normalize(this.filePath + sep + link).substring(appDir.length);
263
- return (((sep === '/') ? result : result.replaceAll(sep, '/')))
264
- .replace(/^\/node_modules\/@itrocks\//, '/@itrocks/')
265
- .replace(/^\/node_modules\//, '/lib/');
266
- }
267
- async parseBuffer(buffer) {
268
- this.prefixes = this.parsers.map(([prefix]) => prefix).join('');
269
- this.setSource(buffer);
270
- await this.parseVars();
271
- if (this.included) {
272
- return this.target;
273
- }
274
- if (this.addLinks.length) {
275
- let addLink;
276
- const addLinks = new Array;
277
- while (addLink = this.addLinks.shift()) {
278
- for (const attribute of ['href', 'src']) {
279
- let start = addLink.indexOf(attribute + '=');
280
- if (start < 0)
281
- continue;
282
- start += attribute.length;
283
- while ((addLink[start] !== '"') && (addLink[start] !== "'")) {
284
- start++;
285
- }
286
- const quote = addLink[start++];
287
- const stop = addLink.indexOf(quote, start);
288
- const link = addLink.substring(start, stop);
289
- if (DEBUG)
290
- console.log('check(', link, ')');
291
- if (!this.doneLinks.includes(link)) {
292
- if (DEBUG)
293
- console.log('+ addLink(', addLink, ')');
294
- if (DEBUG)
295
- console.log('+ doneLink(', link, ')');
296
- addLinks.push(addLink);
297
- this.doneLinks.push(link);
298
- }
299
- }
300
- }
301
- if (addLinks.length) {
302
- const position = this.target.lastIndexOf('>', this.target.indexOf('</head>')) + 1;
303
- this.target = this.target.slice(0, position) + '\n\t' + addLinks.join('\n\t') + this.target.slice(position);
304
- }
305
- }
306
- return (this.targetReplace !== '') ? this.targetReplace : this.target;
307
- }
308
- async parseExpression(data, open, close, finalClose = '') {
309
- const indexOut = this.index;
310
- if (this.inLiteral && !this.literalParts.length) {
311
- this.targetStack.push(this.target);
312
- this.target = '';
313
- }
314
- if (open === '<') {
315
- this.index += 3;
316
- open = '{';
317
- }
318
- this.index++;
319
- const firstChar = this.source[this.index];
320
- if ((this.index >= this.length) || !this.startsExpression(firstChar, open, close)) {
321
- return;
322
- }
323
- let conditional = (firstChar === '?');
324
- const finalChar = finalClose.length ? finalClose[0] : '';
325
- let stackPos = this.targetStack.length;
326
- if (conditional) {
327
- this.index++;
328
- }
329
- this.targetStack.push(this.target + this.source.substring(this.start, indexOut));
330
- this.start = this.index;
331
- this.target = '';
332
- while (this.index < this.length) {
333
- const char = this.source[this.index];
334
- if (char === open) {
335
- this.targetStack.push(this.target + this.source.substring(this.start, this.index));
336
- this.index++;
337
- this.start = this.index;
338
- this.target = '';
339
- continue;
340
- }
341
- if ((char === close)
342
- || ((char === finalChar) && (this.source.substring(this.index, this.index + finalClose.length) === finalClose))) {
343
- let minus = 0;
344
- if (this.source[this.index - 1] === '?') {
345
- conditional = true;
346
- minus = 1;
347
- }
348
- const expression = this.target + this.source.substring(this.start, this.index - minus);
349
- const lastTarget = this.targetStack.pop();
350
- const parsed = await this.parsePath(expression, data);
351
- this.index += (char === close) ? 1 : finalClose.length;
352
- this.start = this.index;
353
- this.target = '';
354
- if (char === finalChar)
355
- while (this.targetStack.length > stackPos) {
356
- this.target += this.targetStack.shift();
357
- }
358
- if (this.inLiteral && (this.targetStack.length === stackPos)) {
359
- this.literalParts.push(parsed);
360
- this.target += lastTarget + '$' + this.literalParts.length;
361
- return conditional;
362
- }
363
- if (lastTarget.length || this.target.length) {
364
- this.target += lastTarget + parsed;
365
- }
366
- else {
367
- this.target = parsed;
368
- }
369
- if (this.targetStack.length !== stackPos) {
370
- continue;
371
- }
372
- if (conditional && !parsed) {
373
- if ((typeof this.target)[0] === 's') {
374
- this.target = this.target.substring(0, this.target.lastIndexOf(' '));
375
- while ((this.index < this.length) && !' >\n\r\t\f'.includes(this.source[this.index])) {
376
- this.index++;
377
- this.start++;
378
- }
379
- this.index--;
380
- }
381
- return conditional;
382
- }
383
- return conditional;
384
- }
385
- if ((char === '"') || (char === "'")) {
386
- this.index++;
387
- let c;
388
- while ((this.index < this.length) && ((c = this.source[this.index]) !== char)) {
389
- if (c === '\\')
390
- this.index++;
391
- this.index++;
392
- }
393
- }
394
- this.index++;
395
- }
396
- // bad close
397
- stackPos++;
398
- while (this.targetStack.length > stackPos) {
399
- this.target = this.targetStack.pop() + open + this.target;
400
- }
401
- this.target = this.targetStack.pop() + (finalClose.length ? '<!--' : open) + this.target;
402
- return conditional;
403
- }
404
- async parseFile(fileName, containerFileName) {
405
- if (DEBUG)
406
- console.log('----- parseFile', containerFileName ? 'contained' : 'fetched', this.included ? 'included' : 'final', fileName);
407
- if (containerFileName) {
408
- const data = this.data;
409
- this.data = Object.assign({ content: () => this.include(fileName, data) }, this.blockStack[0]?.data);
410
- return this.parseFile(normalize(containerFileName));
411
- }
412
- this.fileName = fileName.substring(fileName.lastIndexOf(sep) + 1);
413
- this.filePath = fileName.substring(0, fileName.lastIndexOf(sep));
414
- let target = await this.parseBuffer(await readFile(fileName, 'utf-8'));
415
- if (containerFileName && this.headTitle) {
416
- const position = target.indexOf('>', target.indexOf('<title') + 6) + 1;
417
- target = target.slice(0, position)
418
- + this.headTitle
419
- + target.slice(target.indexOf('</title>', position));
420
- }
421
- return target;
422
- }
423
- async parsePath(expression, data) {
424
- if (DEBUG)
425
- console.log('parsePath', expression);
426
- if (expression === '') {
427
- return undefined;
428
- }
429
- if (((expression[0] === '.') && ((expression[1] === '/') || ((expression[1] === '.') && (expression[2] === '/'))))
430
- || (expression[0] === '/')) {
431
- let expressionEnd = expression.length - 1;
432
- if (expression[expressionEnd] === '-') {
433
- let blockBack = 1;
434
- expressionEnd--;
435
- while (expression[expressionEnd] === '-') {
436
- blockBack++;
437
- expressionEnd--;
438
- }
439
- const blockStack = this.blockStack;
440
- return this.include(this.includePath(expression.slice(0, expressionEnd)), blockStack[blockStack.length - blockBack].data);
441
- }
442
- if (expression[expressionEnd] === ')') {
443
- const openPosition = expression.lastIndexOf('(');
444
- return this.include(this.includePath(expression.slice(0, openPosition)), await this.parsePath(expression.slice(openPosition + 1, expression.length - 1), data));
445
- }
446
- return this.include(this.includePath(expression), data);
447
- }
448
- let onlyDots = true;
449
- for (const c of expression) {
450
- if (c === '.')
451
- continue;
452
- onlyDots = false;
453
- break;
454
- }
455
- if (onlyDots) {
456
- if (expression.length <= 1) {
457
- return (((typeof data)[0] === 'f') && ((data + '')[0] !== 'c'))
458
- ? data.call()
459
- : data;
460
- }
461
- expression = expression.slice(2);
462
- }
463
- this.blockBack = 0;
464
- for (const variable of expression.split('.')) {
465
- data = await this.parseVariable(variable, data);
466
- }
467
- if (data instanceof HtmlResponse) {
468
- this.embedHtmlResponse(data);
469
- }
470
- return data;
471
- }
472
- async parseVariable(variable, data) {
473
- if (DEBUG)
474
- console.log('parseVariable', variable, 'in', data);
475
- if (variable === '') {
476
- let dataBack;
477
- do {
478
- this.blockBack++;
479
- dataBack = this.blockStack[this.blockStack.length - this.blockBack];
480
- } while (dataBack.condition);
481
- return dataBack.data;
482
- }
483
- if (variable === '*') {
484
- return (typeof data === 'object') ? Object.values(data) : data;
485
- }
486
- const firstChar = variable[0];
487
- if ((firstChar === 'B') && (variable === 'BEGIN')) {
488
- return data;
489
- }
490
- if (((firstChar === '"') && (variable[variable.length - 1] === '"'))
491
- || ((firstChar === "'") && (variable[variable.length - 1] === "'"))) {
492
- return variable.substring(1, variable.length - 1);
493
- }
494
- for (const [prefix, callback] of this.parsers) {
495
- if (firstChar === prefix) {
496
- return await callback(variable, data);
497
- }
498
- }
499
- if (((typeof data)[0] === 'o') ? !(variable in data) : (variable[data] === undefined)) {
500
- const asStr = new Str(await depends.toString(data));
501
- if (DEBUG)
502
- console.log('is', variable, 'in', asStr, '?');
503
- if (variable in asStr) {
504
- data = asStr;
505
- }
506
- }
507
- let value = data[variable];
508
- return (((typeof value)[0] === 'f') && ((value + '')[0] !== 'c'))
509
- ? value.call(data)
510
- : value;
511
- }
512
- async parseVars() {
513
- let blockStart = 0;
514
- let data = this.data;
515
- let inHead = false;
516
- let iteration = done;
517
- let iterator;
518
- while (this.index < this.length) {
519
- let char = this.source[this.index];
520
- // expression
521
- if ((char === '{') && this.doExpression) {
522
- await this.parseExpression(data, char, '}');
523
- continue;
524
- }
525
- // tag ?
526
- if (char !== '<') {
527
- this.index++;
528
- continue;
529
- }
530
- const tagIndex = this.index;
531
- char = this.source[++this.index];
532
- if (char === '!') {
533
- if (this.inLiteral) {
534
- this.literalTarget(tagIndex);
535
- }
536
- char = this.source[++this.index];
537
- this.index++;
538
- // comment tag
539
- if ((char === '-') && (this.source[this.index] === '-')) {
540
- this.index++;
541
- const firstChar = this.source[this.index];
542
- if (!this.doExpression
543
- || !this.startsExpression(firstChar)
544
- || ((firstChar === 'B') && (this.source.substring(this.index, this.index + 8) === 'BEGIN-->'))
545
- || ((firstChar === 'E') && (this.source.substring(this.index, this.index + 6) === 'END-->'))) {
546
- this.index = this.source.indexOf('-->', this.index) + 3;
547
- if (this.index < 3)
548
- break;
549
- if (this.inLiteral && (this.index > this.start)) {
550
- this.sourceToTarget();
551
- }
552
- continue;
553
- }
554
- // end condition / loop block
555
- if ((firstChar === 'e') && (this.source.substring(this.index, this.index + 6) === 'end-->')) {
556
- this.target += this.trimEndLine(this.source.substring(this.start, tagIndex));
557
- iteration = iterator?.next() ?? done;
558
- if (!iteration.done) {
559
- data = iteration.value;
560
- this.index = this.start = blockStart;
561
- if (this.inLiteral && (this.index > this.start)) {
562
- this.sourceToTarget();
563
- }
564
- continue;
565
- }
566
- ({ blockStart, data, iteration, iterator } = this.blockStack.pop()
567
- ?? { blockStart: 0, data: undefined, iteration: done });
568
- this.index += 6;
569
- this.start = this.index;
570
- if (this.inLiteral && (this.index > this.start)) {
571
- this.sourceToTarget();
572
- }
573
- continue;
574
- }
575
- // begin condition / loop block
576
- if (tagIndex > this.start) {
577
- this.target += this.trimEndLine(this.source.substring(this.start, tagIndex));
578
- this.start = tagIndex;
579
- }
580
- const backTarget = this.target;
581
- const backInLiteral = this.inLiteral;
582
- this.index = tagIndex;
583
- this.target = '';
584
- this.inLiteral = false;
585
- const condition = await this.parseExpression(data, '<', '}', '-->');
586
- this.blockStack.push({ blockStart, condition, data, iteration, iterator });
587
- let blockData = condition ? (this.target ? data : undefined) : this.target;
588
- blockStart = this.index;
589
- this.target = backTarget;
590
- this.inLiteral = backInLiteral;
591
- if (blockData && blockData[Symbol.iterator]) {
592
- iterator = blockData[Symbol.iterator]();
593
- iteration = iterator?.next() ?? done;
594
- data = iteration.value;
595
- }
596
- else {
597
- data = blockData;
598
- iteration = { done: !data, value: data };
599
- iterator = undefined;
600
- }
601
- if (iteration.done) {
602
- this.skipBlock();
603
- continue;
604
- }
605
- if (this.inLiteral && (this.index > this.start)) {
606
- this.sourceToTarget();
607
- }
608
- continue;
609
- }
610
- // cdata section
611
- if ((char === '[') && (this.source.substring(this.index, this.index + 6) === 'CDATA[')) {
612
- this.index = this.source.indexOf(']]>', this.index + 6) + 3;
613
- if (this.index < 3)
614
- break;
615
- }
616
- // DOCTYPE
617
- else {
618
- this.index = this.source.indexOf('>', this.index) + 1;
619
- }
620
- if (this.inLiteral) {
621
- this.sourceToTarget();
622
- }
623
- continue;
624
- }
625
- // tag close
626
- if (char === '/') {
627
- this.index++;
628
- const closeTagName = this.source.substring(this.index, this.source.indexOf('>', this.index));
629
- this.index += closeTagName.length + 1;
630
- if (inHead && (closeTagName[0] === 'h') && (closeTagName === 'head')) {
631
- inHead = false;
632
- }
633
- let shouldInLiteral = this.inLiteral;
634
- if (!this.unclosingTags.includes(closeTagName)) {
635
- do {
636
- shouldInLiteral = this.closeTag(shouldInLiteral, tagIndex);
637
- } while ((this.tagName !== closeTagName) && this.tagName.length);
638
- }
639
- if (shouldInLiteral) {
640
- this.lockLiteral = false;
641
- this.literalTarget(tagIndex, (this.tagName[0] === 't') && (this.tagName === 'title'));
642
- }
643
- if (this.inLiteral && (this.index > this.start)) {
644
- this.sourceToTarget();
645
- }
646
- continue;
647
- }
648
- // tag open
649
- while ((this.index < this.length) && !' >\n\r\t\f'.includes(this.source[this.index]))
650
- this.index++;
651
- this.tagName = this.source.substring(tagIndex + 1, this.index);
652
- if (this.onTagOpen)
653
- this.onTagOpen.call(this, this.tagName);
654
- while (' \n\r\t\f'.includes(this.source[this.index]))
655
- this.index++;
656
- char = this.tagName[0];
657
- if ((char === 'h') && (this.tagName === 'head')) {
658
- inHead = true;
659
- }
660
- const unclosingTag = this.unclosingTags.includes(this.tagName);
661
- if (!unclosingTag) {
662
- this.tagStack.push({ tagName: this.tagName, inLiteral: this.inLiteral });
663
- }
664
- let inlineElement = false;
665
- let pushedParts = false;
666
- if (this.inLiteral) {
667
- inlineElement = this.inlineElements.includes(this.tagName);
668
- if (inlineElement) {
669
- if (this.literalParts.length) {
670
- this.targetStack.push(this.target + this.source.substring(this.start, tagIndex));
671
- }
672
- else {
673
- this.targetStack.push(this.target, this.source.substring(this.start, tagIndex));
674
- }
675
- this.start = tagIndex;
676
- this.target = '';
677
- if (!unclosingTag) {
678
- this.literalPartStack.push(this.literalParts);
679
- this.literalParts = [];
680
- pushedParts = true;
681
- }
682
- }
683
- else {
684
- this.literalTarget(tagIndex);
685
- }
686
- }
687
- const elementInLiteral = this.inLiteral;
688
- // attributes
689
- let hasTypeSubmit = false;
690
- const inInput = (char === 'i') && (this.tagName === 'input');
691
- const inLink = (char === 'l') && (this.tagName === 'link');
692
- const inScript = (char === 's') && (this.tagName === 'script');
693
- let targetTagIndex = -1;
694
- if (inHead && (inLink || inScript)) {
695
- this.sourceToTarget();
696
- targetTagIndex = this.target.lastIndexOf('<');
697
- }
698
- while (this.source[this.index] !== '>') {
699
- // attribute name
700
- const attributePosition = this.index;
701
- while ((this.index < this.length) && !' =>\n\r\t\f'.includes(this.source[this.index]))
702
- this.index++;
703
- const attributeName = this.source.substring(attributePosition, this.index);
704
- while (' \n\r\t\f'.includes(this.source[this.index]))
705
- this.index++;
706
- let attributeBlock = (attributeName[0] === 'd') && (attributeName === 'data-if') ? '' : undefined;
707
- // attribute value
708
- if (this.source[this.index] === '=') {
709
- this.index++;
710
- while (' \n\r\t\f'.includes(this.source[this.index]))
711
- this.index++;
712
- const attributeChar = attributeName[0];
713
- const [open, close] = ('afhls'.includes(attributeChar)
714
- && ['action', 'formaction', 'href', 'location', 'src'].includes(attributeName)) ? ['(', ')']
715
- : ['{', '}'];
716
- let quote = this.source[this.index];
717
- if ((quote === '"') || (quote === "'")) {
718
- this.index++;
719
- }
720
- else {
721
- quote = ' >';
722
- }
723
- if ((open === '(') && (this.source.substring(this.index, this.index + 6) === 'app://')) {
724
- this.sourceToTarget();
725
- this.index += 6;
726
- this.start = this.index;
727
- }
728
- this.inLiteral = this.doLiteral && (this.literalAttributes.includes(attributeName)
729
- || (hasTypeSubmit && (attributeChar === 'v') && (attributeName === 'value')));
730
- if (this.inLiteral && !pushedParts && unclosingTag && this.literalParts.length) {
731
- this.literalPartStack.push(this.literalParts);
732
- this.literalParts = [];
733
- pushedParts = true;
734
- }
735
- const inLinkHRef = inLink && (attributeChar === 'h') && (attributeName === 'href');
736
- const inScriptSrc = inScript && (attributeChar === 's') && (attributeName === 'src');
737
- if ((inLinkHRef || inScriptSrc || this.inLiteral) && (this.index > this.start)) {
738
- this.sourceToTarget();
739
- }
740
- const valuePosition = this.index;
741
- const shortQuote = !(quote.length - 1);
742
- if (shortQuote && (attributeBlock !== undefined)) {
743
- attributeBlock = this.target + this.source.substring(this.start, attributePosition);
744
- this.start = this.index;
745
- this.target = '';
746
- }
747
- while (this.index < this.length) {
748
- const char = this.source[this.index];
749
- // end of attribute value
750
- if (shortQuote ? (char === quote) : quote.includes(char)) {
751
- const attributeValue = this.source.substring(valuePosition, this.index);
752
- if (inInput && !hasTypeSubmit) {
753
- hasTypeSubmit = (attributeChar === 't') && (attributeValue[0] === 's')
754
- && (attributeName === 'type') && (attributeValue === 'submit');
755
- }
756
- if (this.inLiteral) {
757
- this.literalTarget(this.index);
758
- }
759
- if (inLinkHRef && attributeValue.endsWith('.css')) {
760
- let frontStyle = this.normalizeLink(this.source.substring(this.start, this.index));
761
- this.target += frontStyle;
762
- this.start = this.index;
763
- if (!(inHead && this.included)) {
764
- if (DEBUG)
765
- console.log('doneLink(', frontStyle, ')');
766
- this.doneLinks.push(frontStyle);
767
- }
768
- }
769
- if (inScriptSrc && attributeValue.endsWith('.js')) {
770
- let frontScript = this.normalizeLink(this.source.substring(this.start, this.index));
771
- frontScripts.push(frontScript);
772
- this.target += frontScript;
773
- this.start = this.index;
774
- if (!(inHead && this.included)) {
775
- if (DEBUG)
776
- console.log('doneLink(', frontScript, ')');
777
- this.doneLinks.push(frontScript);
778
- }
779
- }
780
- if (this.onAttribute)
781
- this.onAttribute(attributeName, attributeValue);
782
- if (char !== '>')
783
- this.index++;
784
- break;
785
- }
786
- // expression in attribute value
787
- if ((char === open) && this.doExpression) {
788
- await this.parseExpression(data, open, close);
789
- continue;
790
- }
791
- this.index++;
792
- }
793
- }
794
- else {
795
- if (this.onAttribute)
796
- this.onAttribute(attributeName, '');
797
- if ((attributeName[0] === 'd') && (attributeName === 'data-end')) {
798
- this.index = attributePosition;
799
- this.sourceToTarget();
800
- this.index += attributeName.length;
801
- this.start = this.index;
802
- }
803
- }
804
- // next attribute
805
- while (' \n\r\t\f'.includes(this.source[this.index]))
806
- this.index++;
807
- if (attributeBlock !== undefined) {
808
- if (!this.target) {
809
- this.index = this.source.indexOf('data-end', this.index) + 8;
810
- if (this.index < 8) {
811
- throw 'Missing data-end matching data-if at position ' + attributePosition
812
- + ' into template file ' + this.filePath + sep + this.fileName;
813
- }
814
- }
815
- this.start = this.index;
816
- this.target = attributeBlock;
817
- }
818
- }
819
- this.index++;
820
- if (this.onTagOpened)
821
- this.onTagOpened.call(this, this.tagName);
822
- // skip script content
823
- if (inScript) {
824
- if (this.onTagClose)
825
- this.onTagClose.call(this, 'script');
826
- this.index = this.source.indexOf('</script>', this.index) + 9;
827
- if (this.index < 9)
828
- break;
829
- if (this.inLiteral && (this.index > this.start)) {
830
- this.sourceToTarget();
831
- }
832
- }
833
- if ((targetTagIndex > -1) && this.included) {
834
- this.sourceToTarget();
835
- const headLink = this.target.substring(targetTagIndex);
836
- if (DEBUG)
837
- console.log('addLink(', headLink, ')');
838
- this.addLinks.push(headLink);
839
- }
840
- if (inScript) {
841
- continue;
842
- }
843
- if (unclosingTag) {
844
- if (pushedParts) {
845
- this.literalParts = this.literalPartStack.pop();
846
- }
847
- this.inLiteral = elementInLiteral;
848
- if (this.onTagClose)
849
- this.onTagClose.call(this, this.tagName);
850
- if (this.inLiteral) {
851
- if (this.index > this.start) {
852
- this.sourceToTarget();
853
- }
854
- if (inlineElement) {
855
- this.literalParts.push(this.target);
856
- this.target = this.targetStack.pop() + '$' + this.literalParts.length;
857
- }
858
- }
859
- }
860
- else {
861
- this.lockLiteral ||= (this.tagName[0] === 'a') && (this.tagName === 'address');
862
- this.inLiteral = this.doLiteral && !this.lockLiteral && this.literalElements.includes(this.tagName);
863
- if (this.inLiteral && (this.index > this.start)) {
864
- this.sourceToTarget();
865
- }
866
- }
867
- }
868
- if (this.tagStack.length) {
869
- let shouldInLiteral = this.inLiteral;
870
- while (this.tagStack.length) {
871
- shouldInLiteral = this.closeTag(shouldInLiteral, this.length);
872
- }
873
- if (shouldInLiteral) {
874
- this.literalTarget(this.length);
875
- }
876
- return this.target;
877
- }
878
- if (this.inLiteral) {
879
- this.literalTarget(this.index);
880
- }
881
- if (this.start < this.length) {
882
- this.target += this.source.substring(this.start);
883
- this.start = this.length;
884
- }
885
- return this.target;
886
- }
887
- setSource(source, index = 0, start, target = '') {
888
- this.index = index;
889
- this.length = source.length;
890
- this.source = source;
891
- this.start = start ?? index;
892
- this.tagName = '';
893
- this.tagStack = [];
894
- this.target = target;
895
- this.inLiteral = this.doLiteral;
896
- this.literalPartStack = [];
897
- this.literalParts = [];
898
- this.lockLiteral = false;
899
- this.targetStack = [];
900
- }
901
- skipBlock() {
902
- if (this.index > this.start) {
903
- this.sourceToTarget();
904
- }
905
- let depth = 1;
906
- while (depth) {
907
- this.index = this.source.indexOf('<!--', this.index);
908
- if (this.index < 0) {
909
- break;
910
- }
911
- this.index += 4;
912
- const char = this.source[this.index];
913
- if (!this.startsExpression(char)) {
914
- continue;
915
- }
916
- if ((char === 'e') && (this.source.substring(this.index, this.index + 6) === 'end-->')) {
917
- depth--;
918
- continue;
919
- }
920
- depth++;
921
- }
922
- this.index -= 4;
923
- if (this.index < 0) {
924
- this.index = this.length;
925
- }
926
- this.start = this.index;
927
- }
928
- sourceToTarget() {
929
- this.target += this.source.substring(this.start, this.index);
930
- this.start = this.index;
931
- }
932
- startsExpression(char, open = '{', close = '}') {
933
- return RegExp(`[a-z0-9"'*./?` + open + close + this.prefixes + ']', 'i').test(char);
934
- }
935
- trimEndLine(string) {
936
- let index = string.length;
937
- while ((index > 0) && ' \n\r\t\f'.includes(string[index - 1])) {
938
- index--;
939
- if (string[index] === '\n') {
940
- break;
941
- }
942
- }
943
- return string.substring(0, index);
944
- }
945
- }
946
- //# sourceMappingURL=template.js.map