@reidelsaltres/pureper 0.1.163 → 0.1.168
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/out/foundation/Triplet.d.ts.map +1 -1
- package/out/foundation/Triplet.js +5 -4
- package/out/foundation/Triplet.js.map +1 -1
- package/out/foundation/component_api/Component.d.ts +2 -2
- package/out/foundation/component_api/Component.d.ts.map +1 -1
- package/out/foundation/component_api/Component.js.map +1 -1
- package/out/foundation/component_api/UniHtml.d.ts +10 -4
- package/out/foundation/component_api/UniHtml.d.ts.map +1 -1
- package/out/foundation/component_api/UniHtml.js +15 -7
- package/out/foundation/component_api/UniHtml.js.map +1 -1
- package/out/foundation/engine/TemplateEngine.d.ts +4 -93
- package/out/foundation/engine/TemplateEngine.d.ts.map +1 -1
- package/out/foundation/engine/TemplateEngine.js +44 -221
- package/out/foundation/engine/TemplateEngine.js.map +1 -1
- package/out/foundation/engine/TemplateEngine.old.d.ts +96 -0
- package/out/foundation/engine/TemplateEngine.old.d.ts.map +1 -0
- package/out/foundation/engine/TemplateEngine.old.js +235 -0
- package/out/foundation/engine/TemplateEngine.old.js.map +1 -0
- package/out/foundation/worker/Router.js +1 -1
- package/out/foundation/worker/Router.js.map +1 -1
- package/package.json +1 -1
- package/src/foundation/Triplet.ts +6 -6
- package/src/foundation/component_api/Component.ts +1 -2
- package/src/foundation/component_api/UniHtml.ts +22 -12
- package/src/foundation/engine/TemplateEngine.old.ts +318 -0
- package/src/foundation/engine/TemplateEngine.ts +49 -299
- package/src/foundation/worker/Router.ts +1 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import Scope from './Scope.js';
|
|
2
|
+
import Expression from './Expression.js';
|
|
3
|
+
import EscapeHandler from './EscapeHandler.js';
|
|
4
|
+
import TemplateInstance, { TemplateSection, FragmentBinding } from './TemplateInstance.js';
|
|
5
|
+
import Rule, { RuleMatch, RuleResult, SyntaxRule, AttributeRule } from './Rule.js';
|
|
6
|
+
import Observable from '../api/Observer.js';
|
|
7
|
+
|
|
8
|
+
// Import rules
|
|
9
|
+
import ExpressionRule from './rules/syntax/ExpressionRule.js';
|
|
10
|
+
import IfRule from './rules/syntax/IfRule.js';
|
|
11
|
+
import ForRule from './rules/syntax/ForRule.js';
|
|
12
|
+
import RefRule from './rules/attribute/RefRule.js';
|
|
13
|
+
import EventRule from './rules/attribute/EventRule.js';
|
|
14
|
+
import InjectionRule from './rules/attribute/InjectionRule.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* TemplateEngineOptions - настройки TemplateEngine
|
|
18
|
+
*/
|
|
19
|
+
export interface TemplateEngineOptions {
|
|
20
|
+
/** Оставлять синтаксис атрибутивных Rule в финальном HTML */
|
|
21
|
+
showAttributeRule?: boolean;
|
|
22
|
+
/** Включить предупреждения отладки */
|
|
23
|
+
debugWarnings?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* ProcessResult - результат обработки шаблона
|
|
28
|
+
*/
|
|
29
|
+
export interface ProcessResult {
|
|
30
|
+
output: string;
|
|
31
|
+
observables: Observable<any>[];
|
|
32
|
+
sections: TemplateSection[];
|
|
33
|
+
/** ID созданного фрагмента (если был создан) */
|
|
34
|
+
fragmentId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* TemplateEngine - главный класс шаблонизатора.
|
|
39
|
+
* Обрабатывает HTML-шаблон с Rule и создаёт TemplateInstance.
|
|
40
|
+
*/
|
|
41
|
+
export default class TemplateEngine {
|
|
42
|
+
private readonly scope: Scope;
|
|
43
|
+
private readonly options: TemplateEngineOptions;
|
|
44
|
+
private readonly syntaxRules: SyntaxRule[] = [];
|
|
45
|
+
private readonly attributeRules: AttributeRule[] = [];
|
|
46
|
+
|
|
47
|
+
constructor(scope: Scope | object, options?: TemplateEngineOptions) {
|
|
48
|
+
this.scope = scope instanceof Scope ? scope : Scope.from(scope);
|
|
49
|
+
this.options = {
|
|
50
|
+
showAttributeRule: false,
|
|
51
|
+
debugWarnings: true,
|
|
52
|
+
...options
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Register default rules (sorted by priority)
|
|
56
|
+
this.registerDefaultRules();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Зарегистрировать стандартные правила
|
|
61
|
+
*/
|
|
62
|
+
private registerDefaultRules(): void {
|
|
63
|
+
// Syntax rules
|
|
64
|
+
this.syntaxRules.push(new ForRule());
|
|
65
|
+
this.syntaxRules.push(new IfRule());
|
|
66
|
+
this.syntaxRules.push(new ExpressionRule());
|
|
67
|
+
|
|
68
|
+
// Attribute rules
|
|
69
|
+
this.attributeRules.push(new RefRule());
|
|
70
|
+
this.attributeRules.push(new EventRule());
|
|
71
|
+
this.attributeRules.push(new InjectionRule());
|
|
72
|
+
|
|
73
|
+
// Sort by priority
|
|
74
|
+
this.syntaxRules.sort((a, b) => a.priority - b.priority);
|
|
75
|
+
this.attributeRules.sort((a, b) => a.priority - b.priority);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Добавить пользовательское правило
|
|
80
|
+
*/
|
|
81
|
+
public addRule(rule: Rule): void {
|
|
82
|
+
if (rule.type === 'syntax') {
|
|
83
|
+
this.syntaxRules.push(rule as SyntaxRule);
|
|
84
|
+
this.syntaxRules.sort((a, b) => a.priority - b.priority);
|
|
85
|
+
} else {
|
|
86
|
+
this.attributeRules.push(rule as AttributeRule);
|
|
87
|
+
this.attributeRules.sort((a, b) => a.priority - b.priority);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Получить Scope
|
|
93
|
+
*/
|
|
94
|
+
public getScope(): Scope {
|
|
95
|
+
return this.scope;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Обработать шаблон и вернуть TemplateInstance
|
|
100
|
+
*/
|
|
101
|
+
public parse(template: string): TemplateInstance {
|
|
102
|
+
const templateInstance = new TemplateInstance(this.scope);
|
|
103
|
+
const result = this.processTemplateWithFragments(template, this.scope, templateInstance, null);
|
|
104
|
+
|
|
105
|
+
// Устанавливаем корневой фрагмент
|
|
106
|
+
if (result.fragmentId) {
|
|
107
|
+
templateInstance.setRootFragment(result.fragmentId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add sections and track observables
|
|
111
|
+
for (const section of result.sections) {
|
|
112
|
+
templateInstance.addSection(section);
|
|
113
|
+
|
|
114
|
+
// Track observables
|
|
115
|
+
for (const observable of section.result.observables || []) {
|
|
116
|
+
templateInstance.trackObservable(observable, section, (s) => {
|
|
117
|
+
return this.processTemplate(s.sourceTemplate, this.scope);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return templateInstance;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Обработать шаблон с созданием фрагментов
|
|
127
|
+
*/
|
|
128
|
+
private processTemplateWithFragments(
|
|
129
|
+
template: string,
|
|
130
|
+
scope: Scope,
|
|
131
|
+
instance: TemplateInstance,
|
|
132
|
+
parentFragmentId: string | null
|
|
133
|
+
): ProcessResult {
|
|
134
|
+
const result = this.processTemplate(template, scope);
|
|
135
|
+
|
|
136
|
+
// Создаём фрагмент для этого результата
|
|
137
|
+
const fragmentId = instance.createFragment(
|
|
138
|
+
result.output,
|
|
139
|
+
template,
|
|
140
|
+
result.sections,
|
|
141
|
+
parentFragmentId
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
...result,
|
|
146
|
+
fragmentId
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Обработать шаблон (внутренний метод, используется Rule для рекурсии)
|
|
152
|
+
*/
|
|
153
|
+
public processTemplate(template: string, scope: Scope): ProcessResult {
|
|
154
|
+
const allObservables: Observable<any>[] = [];
|
|
155
|
+
const allSections: TemplateSection[] = [];
|
|
156
|
+
|
|
157
|
+
// Step 1: Handle @@ escape sequences
|
|
158
|
+
let processed = EscapeHandler.process(template, (escaped) => {
|
|
159
|
+
let result = escaped;
|
|
160
|
+
|
|
161
|
+
// Step 2: Process syntax rules (in priority order)
|
|
162
|
+
for (const rule of this.syntaxRules) {
|
|
163
|
+
result = this.processRule(rule, result, scope, allObservables, allSections);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Step 3: Process attribute rules
|
|
167
|
+
for (const rule of this.attributeRules) {
|
|
168
|
+
// Skip injection - processed last
|
|
169
|
+
if (rule.name === 'injection') continue;
|
|
170
|
+
result = this.processRule(rule, result, scope, allObservables, allSections);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return result;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Step 4: Process injection rules last
|
|
177
|
+
const injectionRule = this.attributeRules.find(r => r.name === 'injection');
|
|
178
|
+
if (injectionRule) {
|
|
179
|
+
processed = this.processRule(injectionRule, processed, scope, allObservables, allSections);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Step 5: Handle showAttributeRule option
|
|
183
|
+
if (!this.options.showAttributeRule) {
|
|
184
|
+
processed = this.removeAttributeSyntax(processed);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
output: processed,
|
|
189
|
+
observables: allObservables,
|
|
190
|
+
sections: allSections
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Обработать одно правило
|
|
196
|
+
*/
|
|
197
|
+
private processRule(
|
|
198
|
+
rule: Rule,
|
|
199
|
+
template: string,
|
|
200
|
+
scope: Scope,
|
|
201
|
+
observables: Observable<any>[],
|
|
202
|
+
sections: TemplateSection[]
|
|
203
|
+
): string {
|
|
204
|
+
const matches = rule.find(template);
|
|
205
|
+
|
|
206
|
+
// Sort matches by position (reverse to process from end)
|
|
207
|
+
matches.sort((a, b) => b.start - a.start);
|
|
208
|
+
|
|
209
|
+
let result = template;
|
|
210
|
+
|
|
211
|
+
for (const match of matches) {
|
|
212
|
+
try {
|
|
213
|
+
const ruleResult = rule.execute(match, scope, this) as RuleResult;
|
|
214
|
+
|
|
215
|
+
// Track observables
|
|
216
|
+
if (ruleResult.observables) {
|
|
217
|
+
observables.push(...ruleResult.observables);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Create section
|
|
221
|
+
const section: TemplateSection = {
|
|
222
|
+
rule,
|
|
223
|
+
match,
|
|
224
|
+
result: ruleResult,
|
|
225
|
+
sourceTemplate: match.fullMatch,
|
|
226
|
+
children: [],
|
|
227
|
+
subscriptions: []
|
|
228
|
+
};
|
|
229
|
+
sections.push(section);
|
|
230
|
+
|
|
231
|
+
// Replace in template
|
|
232
|
+
result = result.slice(0, match.start) + ruleResult.output + result.slice(match.end);
|
|
233
|
+
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error(`[TemplateEngine] Error processing rule "${rule.name}":`, error);
|
|
236
|
+
// Continue processing other rules
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Удалить синтаксис атрибутивных Rule из финального HTML
|
|
245
|
+
*/
|
|
246
|
+
private removeAttributeSyntax(template: string): string {
|
|
247
|
+
// Remove @[ref], @on[...], @injection[...] patterns that weren't processed
|
|
248
|
+
return template
|
|
249
|
+
.replace(/@\[ref\]\s*=\s*["'][^"']*["']/gi, '')
|
|
250
|
+
.replace(/@on\[[a-zA-Z]+\]\s*=\s*["'][^"']*["']/gi, '')
|
|
251
|
+
.replace(/@injection\[(head|tail)\]\s*=\s*["'][^"']*["']/gi, '');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Добавить новый шаблон в существующий TemplateInstance.
|
|
256
|
+
* Обрабатывает шаблон и добавляет результат как новый фрагмент.
|
|
257
|
+
* Если instance привязан к контейнерам, DOM обновится автоматически.
|
|
258
|
+
*
|
|
259
|
+
* @param instance - существующий TemplateInstance
|
|
260
|
+
* @param template - новый шаблон для добавления
|
|
261
|
+
* @param customScope - опциональный scope для нового шаблона
|
|
262
|
+
* @returns ID созданного фрагмента
|
|
263
|
+
*/
|
|
264
|
+
public appendTemplate(instance: TemplateInstance, template: string, customScope?: Scope | object): string {
|
|
265
|
+
const scope = customScope
|
|
266
|
+
? (customScope instanceof Scope ? customScope : Scope.from(customScope))
|
|
267
|
+
: instance.getScope();
|
|
268
|
+
|
|
269
|
+
// Обрабатываем шаблон
|
|
270
|
+
const result = this.processTemplate(template, scope);
|
|
271
|
+
|
|
272
|
+
// Создаём фрагмент
|
|
273
|
+
const fragmentId = instance.createFragment(
|
|
274
|
+
result.output,
|
|
275
|
+
template,
|
|
276
|
+
result.sections,
|
|
277
|
+
null // без родителя - это отдельный фрагмент
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// Добавляем секции и отслеживаем Observable
|
|
281
|
+
for (const section of result.sections) {
|
|
282
|
+
instance.addSection(section);
|
|
283
|
+
|
|
284
|
+
for (const observable of section.result.observables || []) {
|
|
285
|
+
instance.trackObservable(observable, section, (s) => {
|
|
286
|
+
return this.processTemplate(s.sourceTemplate, scope);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Вставляем в привязанные контейнеры
|
|
292
|
+
instance.insertAppendedFragment(fragmentId);
|
|
293
|
+
|
|
294
|
+
return fragmentId;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Статический метод для быстрой обработки
|
|
299
|
+
*/
|
|
300
|
+
public static process(template: string, scope: object, options?: TemplateEngineOptions): string {
|
|
301
|
+
const engine = new TemplateEngine(scope, options);
|
|
302
|
+
const TemplateInstance = engine.parse(template);
|
|
303
|
+
return TemplateInstance.getTemplate();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Создать TemplateInstance из шаблона
|
|
308
|
+
*/
|
|
309
|
+
public static create(template: string, scope: object, options?: TemplateEngineOptions): TemplateInstance {
|
|
310
|
+
const engine = new TemplateEngine(scope, options);
|
|
311
|
+
return engine.parse(template);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Re-export useful types
|
|
316
|
+
export { Scope, Expression, TemplateInstance, Rule, SyntaxRule, AttributeRule };
|
|
317
|
+
export { ExpressionRule, IfRule, ForRule, RefRule, EventRule, InjectionRule };
|
|
318
|
+
export * from './exceptions/TemplateExceptions.js';
|
|
@@ -1,318 +1,68 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import EscapeHandler from './EscapeHandler.js';
|
|
4
|
-
import TemplateInstance, { TemplateSection, FragmentBinding } from './TemplateInstance.js';
|
|
5
|
-
import Rule, { RuleMatch, RuleResult, SyntaxRule, AttributeRule } from './Rule.js';
|
|
6
|
-
import Observable from '../api/Observer.js';
|
|
1
|
+
import Expression from "./Expression";
|
|
2
|
+
import Scope from "./Scope";
|
|
7
3
|
|
|
8
|
-
// Import rules
|
|
9
|
-
import ExpressionRule from './rules/syntax/ExpressionRule.js';
|
|
10
|
-
import IfRule from './rules/syntax/IfRule.js';
|
|
11
|
-
import ForRule from './rules/syntax/ForRule.js';
|
|
12
|
-
import RefRule from './rules/attribute/RefRule.js';
|
|
13
|
-
import EventRule from './rules/attribute/EventRule.js';
|
|
14
|
-
import InjectionRule from './rules/attribute/InjectionRule.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* TemplateEngineOptions - настройки TemplateEngine
|
|
18
|
-
*/
|
|
19
|
-
export interface TemplateEngineOptions {
|
|
20
|
-
/** Оставлять синтаксис атрибутивных Rule в финальном HTML */
|
|
21
|
-
showAttributeRule?: boolean;
|
|
22
|
-
/** Включить предупреждения отладки */
|
|
23
|
-
debugWarnings?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* ProcessResult - результат обработки шаблона
|
|
28
|
-
*/
|
|
29
|
-
export interface ProcessResult {
|
|
30
|
-
output: string;
|
|
31
|
-
observables: Observable<any>[];
|
|
32
|
-
sections: TemplateSection[];
|
|
33
|
-
/** ID созданного фрагмента (если был создан) */
|
|
34
|
-
fragmentId?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* TemplateEngine - главный класс шаблонизатора.
|
|
39
|
-
* Обрабатывает HTML-шаблон с Rule и создаёт TemplateInstance.
|
|
40
|
-
*/
|
|
41
4
|
export default class TemplateEngine {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// Register default rules (sorted by priority)
|
|
56
|
-
this.registerDefaultRules();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Зарегистрировать стандартные правила
|
|
61
|
-
*/
|
|
62
|
-
private registerDefaultRules(): void {
|
|
63
|
-
// Syntax rules
|
|
64
|
-
this.syntaxRules.push(new ForRule());
|
|
65
|
-
this.syntaxRules.push(new IfRule());
|
|
66
|
-
this.syntaxRules.push(new ExpressionRule());
|
|
67
|
-
|
|
68
|
-
// Attribute rules
|
|
69
|
-
this.attributeRules.push(new RefRule());
|
|
70
|
-
this.attributeRules.push(new EventRule());
|
|
71
|
-
this.attributeRules.push(new InjectionRule());
|
|
72
|
-
|
|
73
|
-
// Sort by priority
|
|
74
|
-
this.syntaxRules.sort((a, b) => a.priority - b.priority);
|
|
75
|
-
this.attributeRules.sort((a, b) => a.priority - b.priority);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Добавить пользовательское правило
|
|
80
|
-
*/
|
|
81
|
-
public addRule(rule: Rule): void {
|
|
82
|
-
if (rule.type === 'syntax') {
|
|
83
|
-
this.syntaxRules.push(rule as SyntaxRule);
|
|
84
|
-
this.syntaxRules.sort((a, b) => a.priority - b.priority);
|
|
5
|
+
public static process(root: Node, onlyRoot: boolean = false): Node[] {
|
|
6
|
+
let elements: Node[] = [];
|
|
7
|
+
const walker = document.createTreeWalker(
|
|
8
|
+
root,
|
|
9
|
+
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
|
|
10
|
+
);
|
|
11
|
+
if (onlyRoot) {
|
|
12
|
+
walker.nextNode();
|
|
13
|
+
while (walker.nextSibling()) {
|
|
14
|
+
const node = walker.currentNode;
|
|
15
|
+
elements.push(node);
|
|
16
|
+
}
|
|
85
17
|
} else {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Получить Scope
|
|
93
|
-
*/
|
|
94
|
-
public getScope(): Scope {
|
|
95
|
-
return this.scope;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Обработать шаблон и вернуть TemplateInstance
|
|
100
|
-
*/
|
|
101
|
-
public parse(template: string): TemplateInstance {
|
|
102
|
-
const templateInstance = new TemplateInstance(this.scope);
|
|
103
|
-
const result = this.processTemplateWithFragments(template, this.scope, templateInstance, null);
|
|
104
|
-
|
|
105
|
-
// Устанавливаем корневой фрагмент
|
|
106
|
-
if (result.fragmentId) {
|
|
107
|
-
templateInstance.setRootFragment(result.fragmentId);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Add sections and track observables
|
|
111
|
-
for (const section of result.sections) {
|
|
112
|
-
templateInstance.addSection(section);
|
|
113
|
-
|
|
114
|
-
// Track observables
|
|
115
|
-
for (const observable of section.result.observables || []) {
|
|
116
|
-
templateInstance.trackObservable(observable, section, (s) => {
|
|
117
|
-
return this.processTemplate(s.sourceTemplate, this.scope);
|
|
118
|
-
});
|
|
18
|
+
while (walker.nextNode()) {
|
|
19
|
+
const node = walker.currentNode;
|
|
20
|
+
elements.push(node);
|
|
119
21
|
}
|
|
120
22
|
}
|
|
121
|
-
|
|
122
|
-
return templateInstance;
|
|
23
|
+
return elements;
|
|
123
24
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
private processTemplateWithFragments(
|
|
129
|
-
template: string,
|
|
130
|
-
scope: Scope,
|
|
131
|
-
instance: TemplateInstance,
|
|
132
|
-
parentFragmentId: string | null
|
|
133
|
-
): ProcessResult {
|
|
134
|
-
const result = this.processTemplate(template, scope);
|
|
135
|
-
|
|
136
|
-
// Создаём фрагмент для этого результата
|
|
137
|
-
const fragmentId = instance.createFragment(
|
|
138
|
-
result.output,
|
|
139
|
-
template,
|
|
140
|
-
result.sections,
|
|
141
|
-
parentFragmentId
|
|
25
|
+
public static processFors(root: Node, scope: Scope): Scope {
|
|
26
|
+
const walker = document.createTreeWalker(
|
|
27
|
+
root,
|
|
28
|
+
NodeFilter.SHOW_TEXT,
|
|
142
29
|
);
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
...result,
|
|
146
|
-
fragmentId
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
30
|
|
|
150
|
-
/**
|
|
151
|
-
* Обработать шаблон (внутренний метод, используется Rule для рекурсии)
|
|
152
|
-
*/
|
|
153
|
-
public processTemplate(template: string, scope: Scope): ProcessResult {
|
|
154
|
-
const allObservables: Observable<any>[] = [];
|
|
155
|
-
const allSections: TemplateSection[] = [];
|
|
156
31
|
|
|
157
|
-
|
|
158
|
-
let
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Step 3: Process attribute rules
|
|
167
|
-
for (const rule of this.attributeRules) {
|
|
168
|
-
// Skip injection - processed last
|
|
169
|
-
if (rule.name === 'injection') continue;
|
|
170
|
-
result = this.processRule(rule, result, scope, allObservables, allSections);
|
|
32
|
+
let leftCircleBracketCount = 0;
|
|
33
|
+
let rightCircleBracketCount = 0;
|
|
34
|
+
while (walker.nextNode()) {
|
|
35
|
+
const node = walker.currentNode;
|
|
36
|
+
if (node.textContent?.includes("@for(")) {
|
|
37
|
+
leftCircleBracketCount = 1;
|
|
38
|
+
rightCircleBracketCount = 0;
|
|
171
39
|
}
|
|
40
|
+
if (node.textContent?.includes("(")) leftCircleBracketCount++;
|
|
41
|
+
if (node.textContent?.includes(")")) rightCircleBracketCount++;
|
|
42
|
+
if (leftCircleBracketCount > 0 && leftCircleBracketCount === rightCircleBracketCount) {
|
|
172
43
|
|
|
173
|
-
return result;
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Step 4: Process injection rules last
|
|
177
|
-
const injectionRule = this.attributeRules.find(r => r.name === 'injection');
|
|
178
|
-
if (injectionRule) {
|
|
179
|
-
processed = this.processRule(injectionRule, processed, scope, allObservables, allSections);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Step 5: Handle showAttributeRule option
|
|
183
|
-
if (!this.options.showAttributeRule) {
|
|
184
|
-
processed = this.removeAttributeSyntax(processed);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
output: processed,
|
|
189
|
-
observables: allObservables,
|
|
190
|
-
sections: allSections
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Обработать одно правило
|
|
196
|
-
*/
|
|
197
|
-
private processRule(
|
|
198
|
-
rule: Rule,
|
|
199
|
-
template: string,
|
|
200
|
-
scope: Scope,
|
|
201
|
-
observables: Observable<any>[],
|
|
202
|
-
sections: TemplateSection[]
|
|
203
|
-
): string {
|
|
204
|
-
const matches = rule.find(template);
|
|
205
|
-
|
|
206
|
-
// Sort matches by position (reverse to process from end)
|
|
207
|
-
matches.sort((a, b) => b.start - a.start);
|
|
208
|
-
|
|
209
|
-
let result = template;
|
|
210
|
-
|
|
211
|
-
for (const match of matches) {
|
|
212
|
-
try {
|
|
213
|
-
const ruleResult = rule.execute(match, scope, this) as RuleResult;
|
|
214
|
-
|
|
215
|
-
// Track observables
|
|
216
|
-
if (ruleResult.observables) {
|
|
217
|
-
observables.push(...ruleResult.observables);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Create section
|
|
221
|
-
const section: TemplateSection = {
|
|
222
|
-
rule,
|
|
223
|
-
match,
|
|
224
|
-
result: ruleResult,
|
|
225
|
-
sourceTemplate: match.fullMatch,
|
|
226
|
-
children: [],
|
|
227
|
-
subscriptions: []
|
|
228
|
-
};
|
|
229
|
-
sections.push(section);
|
|
230
|
-
|
|
231
|
-
// Replace in template
|
|
232
|
-
result = result.slice(0, match.start) + ruleResult.output + result.slice(match.end);
|
|
233
|
-
|
|
234
|
-
} catch (error) {
|
|
235
|
-
console.error(`[TemplateEngine] Error processing rule "${rule.name}":`, error);
|
|
236
|
-
// Continue processing other rules
|
|
237
44
|
}
|
|
238
45
|
}
|
|
239
|
-
|
|
240
|
-
return result;
|
|
46
|
+
return scope;
|
|
241
47
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
private removeAttributeSyntax(template: string): string {
|
|
247
|
-
// Remove @[ref], @on[...], @injection[...] patterns that weren't processed
|
|
248
|
-
return template
|
|
249
|
-
.replace(/@\[ref\]\s*=\s*["'][^"']*["']/gi, '')
|
|
250
|
-
.replace(/@on\[[a-zA-Z]+\]\s*=\s*["'][^"']*["']/gi, '')
|
|
251
|
-
.replace(/@injection\[(head|tail)\]\s*=\s*["'][^"']*["']/gi, '');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Добавить новый шаблон в существующий TemplateInstance.
|
|
256
|
-
* Обрабатывает шаблон и добавляет результат как новый фрагмент.
|
|
257
|
-
* Если instance привязан к контейнерам, DOM обновится автоматически.
|
|
258
|
-
*
|
|
259
|
-
* @param instance - существующий TemplateInstance
|
|
260
|
-
* @param template - новый шаблон для добавления
|
|
261
|
-
* @param customScope - опциональный scope для нового шаблона
|
|
262
|
-
* @returns ID созданного фрагмента
|
|
263
|
-
*/
|
|
264
|
-
public appendTemplate(instance: TemplateInstance, template: string, customScope?: Scope | object): string {
|
|
265
|
-
const scope = customScope
|
|
266
|
-
? (customScope instanceof Scope ? customScope : Scope.from(customScope))
|
|
267
|
-
: instance.getScope();
|
|
268
|
-
|
|
269
|
-
// Обрабатываем шаблон
|
|
270
|
-
const result = this.processTemplate(template, scope);
|
|
271
|
-
|
|
272
|
-
// Создаём фрагмент
|
|
273
|
-
const fragmentId = instance.createFragment(
|
|
274
|
-
result.output,
|
|
275
|
-
template,
|
|
276
|
-
result.sections,
|
|
277
|
-
null // без родителя - это отдельный фрагмент
|
|
48
|
+
public static processAttributes(root: Node, scope: Scope): Scope {
|
|
49
|
+
const walker = document.createTreeWalker(
|
|
50
|
+
root,
|
|
51
|
+
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
|
|
278
52
|
);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
53
|
+
|
|
54
|
+
while (walker.nextNode()) {
|
|
55
|
+
const node = walker.currentNode;
|
|
56
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
57
|
+
const element = node as Element;
|
|
58
|
+
if (element.hasAttribute("ref")) scope.set(element.getAttribute("ref")!, element);
|
|
59
|
+
[...element.attributes].filter(a => /^on/.test(a.name)).forEach(attr => {
|
|
60
|
+
const eventName = attr.name.substring(2);
|
|
61
|
+
const expr = new Expression(attr.value);
|
|
62
|
+
element.addEventListener(eventName, (e) => {});
|
|
287
63
|
});
|
|
288
64
|
}
|
|
289
65
|
}
|
|
290
|
-
|
|
291
|
-
// Вставляем в привязанные контейнеры
|
|
292
|
-
instance.insertAppendedFragment(fragmentId);
|
|
293
|
-
|
|
294
|
-
return fragmentId;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Статический метод для быстрой обработки
|
|
299
|
-
*/
|
|
300
|
-
public static process(template: string, scope: object, options?: TemplateEngineOptions): string {
|
|
301
|
-
const engine = new TemplateEngine(scope, options);
|
|
302
|
-
const TemplateInstance = engine.parse(template);
|
|
303
|
-
return TemplateInstance.getTemplate();
|
|
66
|
+
return scope;
|
|
304
67
|
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Создать TemplateInstance из шаблона
|
|
308
|
-
*/
|
|
309
|
-
public static create(template: string, scope: object, options?: TemplateEngineOptions): TemplateInstance {
|
|
310
|
-
const engine = new TemplateEngine(scope, options);
|
|
311
|
-
return engine.parse(template);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Re-export useful types
|
|
316
|
-
export { Scope, Expression, TemplateInstance, Rule, SyntaxRule, AttributeRule };
|
|
317
|
-
export { ExpressionRule, IfRule, ForRule, RefRule, EventRule, InjectionRule };
|
|
318
|
-
export * from './exceptions/TemplateExceptions.js';
|
|
68
|
+
}
|