@reidelsaltres/pureper 0.1.175 → 0.1.176

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.
Files changed (37) hide show
  1. package/out/foundation/Triplet.d.ts.map +1 -1
  2. package/out/foundation/Triplet.js +3 -5
  3. package/out/foundation/Triplet.js.map +1 -1
  4. package/out/foundation/component_api/Component.d.ts +2 -2
  5. package/out/foundation/component_api/Component.d.ts.map +1 -1
  6. package/out/foundation/component_api/Component.js.map +1 -1
  7. package/out/foundation/component_api/UniHtml.d.ts +4 -4
  8. package/out/foundation/component_api/UniHtml.d.ts.map +1 -1
  9. package/out/foundation/component_api/UniHtml.js +4 -11
  10. package/out/foundation/component_api/UniHtml.js.map +1 -1
  11. package/out/foundation/engine/Expression.d.ts.map +1 -1
  12. package/out/foundation/engine/Expression.js.map +1 -1
  13. package/out/foundation/engine/TemplateEngine.d.ts +45 -3
  14. package/out/foundation/engine/TemplateEngine.d.ts.map +1 -1
  15. package/out/foundation/engine/TemplateEngine.js +385 -49
  16. package/out/foundation/engine/TemplateEngine.js.map +1 -1
  17. package/package.json +1 -1
  18. package/src/foundation/Triplet.ts +5 -6
  19. package/src/foundation/component_api/Component.ts +2 -1
  20. package/src/foundation/component_api/UniHtml.ts +14 -21
  21. package/src/foundation/engine/Expression.ts +1 -2
  22. package/src/foundation/engine/TemplateEngine.ts +414 -53
  23. package/src/foundation/engine/BalancedParser.ts +0 -353
  24. package/src/foundation/engine/EscapeHandler.ts +0 -54
  25. package/src/foundation/engine/Rule.ts +0 -138
  26. package/src/foundation/engine/TemplateEngine.old.ts +0 -318
  27. package/src/foundation/engine/TemplateInstance.md +0 -110
  28. package/src/foundation/engine/TemplateInstance.old.ts +0 -673
  29. package/src/foundation/engine/TemplateInstance.ts +0 -843
  30. package/src/foundation/engine/exceptions/TemplateExceptions.ts +0 -27
  31. package/src/foundation/engine/rules/attribute/EventRule.ts +0 -171
  32. package/src/foundation/engine/rules/attribute/InjectionRule.ts +0 -140
  33. package/src/foundation/engine/rules/attribute/RefRule.ts +0 -126
  34. package/src/foundation/engine/rules/syntax/ExpressionRule.ts +0 -102
  35. package/src/foundation/engine/rules/syntax/ForRule.ts +0 -267
  36. package/src/foundation/engine/rules/syntax/IfRule.ts +0 -261
  37. package/src/foundation/hmle/Context.ts +0 -90
@@ -1,353 +0,0 @@
1
- /**
2
- * BalancedParser - утилита для парсинга сбалансированных скобок (), {}.
3
- * Корректно обрабатывает строки и комментарии.
4
- */
5
- export default class BalancedParser {
6
-
7
- /**
8
- * Найти все сбалансированные выражения с заданным opener.
9
- * @param input - входная строка
10
- * @param opener - открывающая последовательность, например '@(' или '@for('
11
- * @returns массив объектов с content и позициями
12
- */
13
- public static parseBalanced(
14
- input: string,
15
- opener: string,
16
- closerChar: ')' | '}' = ')'
17
- ): Array<{ content: string; start: number; end: number }> {
18
- const results: Array<{ content: string; start: number; end: number }> = [];
19
- const openerChar = closerChar === ')' ? '(' : '{';
20
- let i = 0;
21
-
22
- while (i < input.length) {
23
- const idx = input.indexOf(opener, i);
24
- if (idx === -1) break;
25
-
26
- const contentStart = idx + opener.length;
27
- let pos = contentStart;
28
- let depth = 1;
29
-
30
- while (pos < input.length && depth > 0) {
31
- const ch = input[pos];
32
-
33
- // Skip string literals
34
- if (ch === '"' || ch === "'" || ch === '`') {
35
- pos = this.skipString(input, pos, ch);
36
- continue;
37
- }
38
-
39
- // Skip single-line comments
40
- if (ch === '/' && input[pos + 1] === '/') {
41
- pos = this.skipLineComment(input, pos);
42
- continue;
43
- }
44
-
45
- // Skip multi-line comments
46
- if (ch === '/' && input[pos + 1] === '*') {
47
- pos = this.skipBlockComment(input, pos);
48
- continue;
49
- }
50
-
51
- if (ch === openerChar) depth++;
52
- else if (ch === closerChar) depth--;
53
-
54
- pos++;
55
- }
56
-
57
- if (depth === 0) {
58
- results.push({
59
- content: input.slice(contentStart, pos - 1),
60
- start: idx,
61
- end: pos
62
- });
63
- i = pos;
64
- } else {
65
- // Unbalanced, skip this opener and continue
66
- i = idx + 1;
67
- }
68
- }
69
-
70
- return results;
71
- }
72
-
73
- /**
74
- * Парсить блочные Rule типа @for(...) { ... }
75
- * @returns объект с condition (содержимое скобок) и block (содержимое фигурных скобок)
76
- */
77
- public static parseBlockRule(
78
- input: string,
79
- opener: string // например '@for', '@if'
80
- ): Array<{ condition: string; block: string; start: number; end: number }> {
81
- const results: Array<{ condition: string; block: string; start: number; end: number }> = [];
82
- const openerLower = opener.toLowerCase();
83
- let i = 0;
84
-
85
- while (i < input.length) {
86
- // Case-insensitive search
87
- const lowerInput = input.toLowerCase();
88
- let idx = lowerInput.indexOf(openerLower, i);
89
- if (idx === -1) break;
90
-
91
- // Check for @@ escape
92
- if (idx > 0 && input[idx - 1] === '@') {
93
- i = idx + 1;
94
- continue;
95
- }
96
-
97
- // Find opening parenthesis
98
- let parenStart = idx + opener.length;
99
- while (parenStart < input.length && /\s/.test(input[parenStart])) {
100
- parenStart++;
101
- }
102
-
103
- if (input[parenStart] !== '(') {
104
- i = idx + 1;
105
- continue;
106
- }
107
-
108
- // Parse balanced parentheses for condition
109
- let pos = parenStart + 1;
110
- let depth = 1;
111
-
112
- while (pos < input.length && depth > 0) {
113
- const ch = input[pos];
114
-
115
- if (ch === '"' || ch === "'" || ch === '`') {
116
- pos = this.skipString(input, pos, ch);
117
- continue;
118
- }
119
- if (ch === '/' && input[pos + 1] === '/') {
120
- pos = this.skipLineComment(input, pos);
121
- continue;
122
- }
123
- if (ch === '/' && input[pos + 1] === '*') {
124
- pos = this.skipBlockComment(input, pos);
125
- continue;
126
- }
127
-
128
- if (ch === '(') depth++;
129
- else if (ch === ')') depth--;
130
- pos++;
131
- }
132
-
133
- if (depth !== 0) {
134
- i = idx + 1;
135
- continue;
136
- }
137
-
138
- const condition = input.slice(parenStart + 1, pos - 1);
139
- const conditionEnd = pos;
140
-
141
- // Find opening brace for block
142
- let braceStart = conditionEnd;
143
- while (braceStart < input.length && /\s/.test(input[braceStart])) {
144
- braceStart++;
145
- }
146
-
147
- if (input[braceStart] !== '{') {
148
- i = idx + 1;
149
- continue;
150
- }
151
-
152
- // Parse balanced braces for block
153
- pos = braceStart + 1;
154
- depth = 1;
155
-
156
- while (pos < input.length && depth > 0) {
157
- const ch = input[pos];
158
-
159
- if (ch === '"' || ch === "'" || ch === '`') {
160
- pos = this.skipString(input, pos, ch);
161
- continue;
162
- }
163
- if (ch === '/' && input[pos + 1] === '/') {
164
- pos = this.skipLineComment(input, pos);
165
- continue;
166
- }
167
- if (ch === '/' && input[pos + 1] === '*') {
168
- pos = this.skipBlockComment(input, pos);
169
- continue;
170
- }
171
-
172
- if (ch === '{') depth++;
173
- else if (ch === '}') depth--;
174
- pos++;
175
- }
176
-
177
- if (depth !== 0) {
178
- i = idx + 1;
179
- continue;
180
- }
181
-
182
- const block = input.slice(braceStart + 1, pos - 1);
183
-
184
- results.push({
185
- condition,
186
- block,
187
- start: idx,
188
- end: pos
189
- });
190
-
191
- i = pos;
192
- }
193
-
194
- return results;
195
- }
196
-
197
- /**
198
- * Парсить @if/@elseif/@else цепочки
199
- */
200
- public static parseIfChain(input: string): Array<{
201
- type: 'if' | 'elseif' | 'else';
202
- condition?: string;
203
- block: string;
204
- start: number;
205
- end: number;
206
- }> {
207
- const results: Array<{
208
- type: 'if' | 'elseif' | 'else';
209
- condition?: string;
210
- block: string;
211
- start: number;
212
- end: number;
213
- }> = [];
214
-
215
- // Find @if first
216
- const ifMatches = this.parseBlockRule(input, '@if');
217
-
218
- for (const ifMatch of ifMatches) {
219
- results.push({
220
- type: 'if',
221
- condition: ifMatch.condition,
222
- block: ifMatch.block,
223
- start: ifMatch.start,
224
- end: ifMatch.end
225
- });
226
-
227
- // Look for @elseif/@else after this @if
228
- let searchPos = ifMatch.end;
229
-
230
- while (searchPos < input.length) {
231
- // Skip whitespace
232
- while (searchPos < input.length && /\s/.test(input[searchPos])) {
233
- searchPos++;
234
- }
235
-
236
- const remaining = input.slice(searchPos).toLowerCase();
237
-
238
- if (remaining.startsWith('@elseif')) {
239
- const elseifMatches = this.parseBlockRule(input.slice(searchPos), '@elseif');
240
- if (elseifMatches.length > 0) {
241
- const m = elseifMatches[0];
242
- results.push({
243
- type: 'elseif',
244
- condition: m.condition,
245
- block: m.block,
246
- start: searchPos + m.start,
247
- end: searchPos + m.end
248
- });
249
- searchPos = searchPos + m.end;
250
- continue;
251
- }
252
- } else if (remaining.startsWith('@else')) {
253
- // @else without condition
254
- let pos = searchPos + 5; // length of '@else'
255
- while (pos < input.length && /\s/.test(input[pos])) {
256
- pos++;
257
- }
258
-
259
- if (input[pos] === '{') {
260
- let bracePos = pos + 1;
261
- let depth = 1;
262
-
263
- while (bracePos < input.length && depth > 0) {
264
- const ch = input[bracePos];
265
- if (ch === '"' || ch === "'" || ch === '`') {
266
- bracePos = this.skipString(input, bracePos, ch);
267
- continue;
268
- }
269
- if (ch === '{') depth++;
270
- else if (ch === '}') depth--;
271
- bracePos++;
272
- }
273
-
274
- if (depth === 0) {
275
- results.push({
276
- type: 'else',
277
- block: input.slice(pos + 1, bracePos - 1),
278
- start: searchPos,
279
- end: bracePos
280
- });
281
- searchPos = bracePos;
282
- continue;
283
- }
284
- }
285
- }
286
-
287
- break; // No more @elseif/@else found
288
- }
289
- }
290
-
291
- return results;
292
- }
293
-
294
- /**
295
- * Skip over a string literal (handles escape sequences)
296
- */
297
- private static skipString(input: string, pos: number, quote: string): number {
298
- pos++; // skip opening quote
299
- while (pos < input.length) {
300
- if (input[pos] === '\\') {
301
- pos += 2; // skip escape sequence
302
- continue;
303
- }
304
- if (input[pos] === quote) {
305
- return pos + 1;
306
- }
307
- // Handle template literal ${...}
308
- if (quote === '`' && input[pos] === '$' && input[pos + 1] === '{') {
309
- pos += 2;
310
- let depth = 1;
311
- while (pos < input.length && depth > 0) {
312
- if (input[pos] === '{') depth++;
313
- else if (input[pos] === '}') depth--;
314
- pos++;
315
- }
316
- continue;
317
- }
318
- pos++;
319
- }
320
- return pos;
321
- }
322
-
323
- /**
324
- * Skip single-line comment
325
- */
326
- private static skipLineComment(input: string, pos: number): number {
327
- while (pos < input.length && input[pos] !== '\n') {
328
- pos++;
329
- }
330
- return pos + 1;
331
- }
332
-
333
- /**
334
- * Skip block comment
335
- */
336
- private static skipBlockComment(input: string, pos: number): number {
337
- pos += 2; // skip /*
338
- while (pos < input.length - 1) {
339
- if (input[pos] === '*' && input[pos + 1] === '/') {
340
- return pos + 2;
341
- }
342
- pos++;
343
- }
344
- return pos;
345
- }
346
-
347
- /**
348
- * Извлечь простые @(expression) без блоков
349
- */
350
- public static parseExpressions(input: string): Array<{ content: string; start: number; end: number }> {
351
- return this.parseBalanced(input, '@(', ')');
352
- }
353
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * EscapeHandler - обработка escape-последовательностей.
3
- * @@ -> @
4
- * @@@@ -> @@
5
- */
6
- export default class EscapeHandler {
7
-
8
- /**
9
- * Заменить @@ на специальный placeholder перед парсингом
10
- */
11
- public static escapeDoubleAt(input: string): { result: string; placeholder: string } {
12
- // Use a unique placeholder that won't appear in normal code
13
- const placeholder = '\x00AT_ESCAPE\x00';
14
- const result = input.replace(/@@/g, placeholder);
15
- return { result, placeholder };
16
- }
17
-
18
- /**
19
- * Восстановить @ из placeholder после парсинга
20
- */
21
- public static restoreEscapes(input: string, placeholder: string): string {
22
- return input.replace(new RegExp(placeholder.replace(/\x00/g, '\\x00'), 'g'), '@');
23
- }
24
-
25
- /**
26
- * Полный цикл: escape -> process -> restore
27
- */
28
- public static process(
29
- input: string,
30
- processor: (escaped: string) => string
31
- ): string {
32
- const { result: escaped, placeholder } = this.escapeDoubleAt(input);
33
- const processed = processor(escaped);
34
- return this.restoreEscapes(processed, placeholder);
35
- }
36
-
37
- /**
38
- * Проверить, является ли позиция escaped (предшествует @@)
39
- */
40
- public static isEscaped(input: string, position: number): boolean {
41
- if (position === 0) return false;
42
-
43
- // Count consecutive @ before this position
44
- let count = 0;
45
- let i = position - 1;
46
- while (i >= 0 && input[i] === '@') {
47
- count++;
48
- i--;
49
- }
50
-
51
- // If odd number of @ before, this @ is escaped
52
- return count % 2 === 1;
53
- }
54
- }
@@ -1,138 +0,0 @@
1
- import Scope from './Scope.js';
2
- import Expression from './Expression.js';
3
-
4
- /**
5
- * RuleMatch - результат поиска Rule в шаблоне
6
- */
7
- export interface RuleMatch {
8
- /** Полное совпадение включая синтаксис Rule */
9
- fullMatch: string;
10
- /** Начальная позиция в исходной строке */
11
- start: number;
12
- /** Конечная позиция в исходной строке */
13
- end: number;
14
- /** Дополнительные данные специфичные для Rule */
15
- data?: Record<string, any>;
16
- }
17
-
18
- /**
19
- * RuleResult - результат выполнения Rule
20
- */
21
- export interface RuleResult {
22
- /** HTML-результат для замены */
23
- output: string;
24
- /** Использованные Observable для отслеживания */
25
- observables?: any[];
26
- /** Дочерние Rule (для вложенных структур) */
27
- children?: Rule[];
28
- /** Дополнительные данные (например, refName для RefRule) */
29
- data?: Record<string, any>;
30
- }
31
-
32
- /**
33
- * RuleType - тип Rule
34
- */
35
- export type RuleType = 'syntax' | 'attribute';
36
-
37
- /**
38
- * Rule - базовый абстрактный класс для всех правил шаблонизатора.
39
- */
40
- export default abstract class Rule {
41
- /** Уникальное имя правила */
42
- public abstract readonly name: string;
43
-
44
- /** Тип правила: syntax или attribute */
45
- public abstract readonly type: RuleType;
46
-
47
- /** Приоритет выполнения (меньше = раньше) */
48
- public readonly priority: number = 100;
49
-
50
- /**
51
- * Найти все вхождения этого Rule в шаблоне.
52
- * @param template - исходный шаблон
53
- * @returns массив найденных совпадений
54
- */
55
- public abstract find(template: string): RuleMatch[];
56
-
57
- /**
58
- * Выполнить Rule и вернуть результат.
59
- * @param match - найденное совпадение
60
- * @param scope - текущий Scope
61
- * @param engine - ссылка на TemplateEngine (для рекурсивной обработки)
62
- */
63
- public abstract execute(
64
- match: RuleMatch,
65
- scope: Scope,
66
- engine?: any // TemplateEngine, circular dependency workaround
67
- ): RuleResult | Promise<RuleResult>;
68
-
69
- /**
70
- * Проверить, поддерживает ли Rule Observable значения
71
- */
72
- public supportsObservable(): boolean {
73
- return true;
74
- }
75
- }
76
-
77
- /**
78
- * SyntaxRule - базовый класс для синтаксических правил.
79
- * Синтаксические правила могут быть в любом месте шаблона (кроме атрибутов).
80
- */
81
- export abstract class SyntaxRule extends Rule {
82
- public readonly type: RuleType = 'syntax';
83
- }
84
-
85
- /**
86
- * AttributeRule - базовый класс для атрибутивных правил.
87
- * Атрибутивные правила работают только внутри HTML-тегов.
88
- */
89
- export abstract class AttributeRule extends Rule {
90
- public readonly type: RuleType = 'attribute';
91
-
92
- /**
93
- * Получить элемент, к которому применяется атрибут.
94
- * @param template - шаблон
95
- * @param attributePosition - позиция атрибута
96
- * @returns информация об элементе
97
- */
98
- protected findParentElement(
99
- template: string,
100
- attributePosition: number
101
- ): { tagName: string; tagStart: number; tagEnd: number } | null {
102
- // Find opening < before attribute
103
- let tagStart = attributePosition;
104
- while (tagStart > 0 && template[tagStart] !== '<') {
105
- tagStart--;
106
- }
107
-
108
- if (template[tagStart] !== '<') {
109
- return null;
110
- }
111
-
112
- // Extract tag name
113
- let nameEnd = tagStart + 1;
114
- while (nameEnd < template.length && /[a-zA-Z0-9_-]/.test(template[nameEnd])) {
115
- nameEnd++;
116
- }
117
- const tagName = template.slice(tagStart + 1, nameEnd);
118
-
119
- // Find closing >
120
- let tagEnd = attributePosition;
121
- let inString = false;
122
- let stringChar = '';
123
- while (tagEnd < template.length) {
124
- const ch = template[tagEnd];
125
- if (!inString && (ch === '"' || ch === "'")) {
126
- inString = true;
127
- stringChar = ch;
128
- } else if (inString && ch === stringChar) {
129
- inString = false;
130
- } else if (!inString && ch === '>') {
131
- break;
132
- }
133
- tagEnd++;
134
- }
135
-
136
- return { tagName, tagStart, tagEnd: tagEnd + 1 };
137
- }
138
- }