@itrocks/template 0.2.0 → 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/package.json CHANGED
@@ -14,20 +14,17 @@
14
14
  "@types/node": "^24.10",
15
15
  "jest": "^30.2",
16
16
  "ts-jest": "^29.4",
17
- "typescript": "~5.9"
17
+ "typescript": "^6.0"
18
18
  },
19
19
  "engines": {
20
- "node": ">=18"
21
- },
22
- "exports": {
23
- "import": "./esm/template.js",
24
- "require": "./cjs/template.js"
20
+ "node": ">=24"
25
21
  },
22
+ "exports": "./cjs/template.js",
26
23
  "files": [
27
24
  "LICENSE",
28
25
  "README.md",
29
- "*/*.d.ts",
30
- "*/*.js"
26
+ "cjs",
27
+ "!*.map"
31
28
  ],
32
29
  "homepage": "it.rocks",
33
30
  "jest": {
@@ -63,11 +60,8 @@
63
60
  "url": "git+https://github.com/itrocks-ts/template.git"
64
61
  },
65
62
  "scripts": {
66
- "build": "npm run build:cjs && npm run build:esm",
67
- "build:cjs": "tsc -p tsconfig.cjs.json",
68
- "build:esm": "tsc -p tsconfig.esm.json",
63
+ "build": "tsc",
69
64
  "test": "test/test"
70
65
  },
71
- "types": "./esm/template.d.ts",
72
- "version": "0.2.0"
66
+ "version": "0.2.2"
73
67
  }
package/esm/template.d.ts DELETED
@@ -1,123 +0,0 @@
1
- import { SortedArray } from '@itrocks/sorted-array';
2
- export type Dependencies = {
3
- toString: (value: any) => Promise<string>;
4
- };
5
- export declare const depends: Dependencies;
6
- type BlockStackEntry = {
7
- blockStart: number;
8
- condition?: boolean;
9
- data: any;
10
- iteration: IteratorResult<any> | {
11
- done: boolean;
12
- value?: any;
13
- };
14
- iterator?: Iterator<any>;
15
- };
16
- type Close = ')' | '}';
17
- type Final = '' | '-->';
18
- type Open = '(' | '{';
19
- export type VariableParser = [parser: string, (variable: string, data: any) => any];
20
- export declare const frontScripts: SortedArray<string>;
21
- export declare function templateDependsOn(dependencies: Partial<Dependencies>): void;
22
- export declare class HtmlResponse {
23
- html: string;
24
- dependencies: string[];
25
- constructor(html: string, ...dependencies: string[]);
26
- toString(): string;
27
- }
28
- export declare class Template {
29
- data?: any | undefined;
30
- containerData?: any | undefined;
31
- blockBack: number;
32
- blockStack: BlockStackEntry[];
33
- doExpression: boolean;
34
- index: number;
35
- length: number;
36
- source: string;
37
- start: number;
38
- tagName: string;
39
- tagStack: {
40
- tagName: string;
41
- inLiteral: boolean;
42
- }[];
43
- target: string;
44
- targetReplace: string;
45
- targetStack: string[];
46
- doLiteral: boolean;
47
- inLiteral: boolean;
48
- literalParts: string[];
49
- literalPartStack: string[][];
50
- lockLiteral: boolean;
51
- addLinks: SortedArray<string>;
52
- doneLinks: SortedArray<string>;
53
- headTitle?: string;
54
- fileName?: string;
55
- filePath?: string;
56
- included: boolean;
57
- inlineElements: SortedArray<string>;
58
- literalAttributes: SortedArray<string>;
59
- literalElements: SortedArray<string>;
60
- unclosingTags: SortedArray<string>;
61
- onAttribute?: (name: string, value: string) => void;
62
- onTagOpen?: (name: string) => void;
63
- onTagOpened?: (name: string) => void;
64
- onTagClose?: (name: string) => void;
65
- parsers: VariableParser[];
66
- prefixes: string;
67
- constructor(data?: any | undefined, containerData?: any | undefined);
68
- applyLiterals(text: string, parts?: string[]): string;
69
- closeTag(shouldInLiteral: boolean, targetIndex: number): boolean;
70
- combineLiterals(text: string, parts?: string[]): string;
71
- debugEvents(): void;
72
- embedHtmlResponse(htmlResponse: HtmlResponse): void;
73
- getCleanContext(): {
74
- addLinks: SortedArray<string>;
75
- doneLinks: SortedArray<string>;
76
- included: boolean;
77
- index: number;
78
- inLiteral: boolean;
79
- length: number;
80
- literalPartStack: never[];
81
- literalParts: never[];
82
- source: string;
83
- start: number;
84
- target: string;
85
- targetStack: never[];
86
- };
87
- getPosition(): {
88
- index: number;
89
- start: number;
90
- target: string;
91
- };
92
- getContext(): {
93
- addLinks: SortedArray<string>;
94
- doneLinks: SortedArray<string>;
95
- included: boolean;
96
- index: number;
97
- inLiteral: boolean;
98
- length: number;
99
- literalParts: string[];
100
- literalPartStack: string[][];
101
- source: string;
102
- start: number;
103
- target: string;
104
- targetStack: string[];
105
- };
106
- include(path: string, data: any): Promise<string>;
107
- includePath(filePath: string): string;
108
- isContextClean(): boolean;
109
- literalTarget(index: number, isTitle?: boolean): void;
110
- normalizeLink(link: string): string;
111
- parseBuffer(buffer: string): Promise<string>;
112
- parseExpression(data: any, open: Open | '<', close: Close, finalClose?: Final): Promise<boolean | undefined>;
113
- parseFile(fileName: string, containerFileName?: string | false): Promise<string>;
114
- parsePath(expression: string, data: any): Promise<any>;
115
- parseVariable(variable: string, data: any): Promise<any>;
116
- parseVars(): Promise<string>;
117
- setSource(source: string, index?: number, start?: number, target?: string): void;
118
- skipBlock(): void;
119
- sourceToTarget(): void;
120
- startsExpression(char: string, open?: Open, close?: Close): boolean;
121
- trimEndLine(string: string): string;
122
- }
123
- export {};
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