@itrocks/template 0.0.4 → 0.0.5

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