@reidelsaltres/pureper 0.1.76 → 0.1.77

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.
@@ -1,6 +1,6 @@
1
1
  export default class PHTMLParser {
2
2
  private static extractBalancedBlock;
3
- private parseForLoops;
3
+ private static extractBalancedParens;
4
4
  private static rules;
5
5
  variables: Record<string, unknown>;
6
6
  addVariable(name: string, value: unknown): this;
@@ -1 +1 @@
1
- {"version":3,"file":"PHTMLParser.d.ts","sourceRoot":"","sources":["../../src/foundation/PHTMLParser.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,OAAO,OAAO,WAAW;IAG5B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAcnC,OAAO,CAAC,aAAa;IA4CrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAKlB;IACK,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;IAExC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAQ/C,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAWxE,OAAO,CAAC,cAAc;IAMf,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;CAWrE"}
1
+ {"version":3,"file":"PHTMLParser.d.ts","sourceRoot":"","sources":["../../src/foundation/PHTMLParser.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,OAAO,OAAO,WAAW;IAG5B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAcnC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAiBpC,OAAO,CAAC,MAAM,CAAC,KAAK,CA6FlB;IACK,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;IAExC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAQ/C,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAWxE,OAAO,CAAC,cAAc;IAOf,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;CAyCrE"}
@@ -1,5 +1,7 @@
1
1
  class PHTMLParserRule {
2
2
  pattern;
3
+ // replacer can return either a string replacement OR an object with { text, end }
4
+ // where `end` is a position in the source string just after the consumed range.
3
5
  replacer;
4
6
  constructor(pattern, replacer) {
5
7
  this.pattern = pattern;
@@ -25,46 +27,116 @@ export default class PHTMLParser {
25
27
  return null;
26
28
  return { block: content.slice(start + 1, i - 1), end: i };
27
29
  }
28
- // Parses @for constructs with proper nesting support using brace counting.
29
- parseForLoops(content, scope) {
30
- const forPattern = /@for\s*\(([A-Za-z0-9_$]+)\s+in\s+([A-Za-z0-9_$.]+)\s*\)\s*\{/g;
31
- let result = '';
32
- let lastIndex = 0;
33
- let match;
34
- while ((match = forPattern.exec(content)) !== null) {
35
- // Append text before this @for
36
- result += content.slice(lastIndex, match.index);
30
+ // Extracts a balanced parenthesis block starting at `start` (which must be '(')
31
+ static extractBalancedParens(content, start) {
32
+ if (content[start] !== '(')
33
+ return null;
34
+ let depth = 1;
35
+ let i = start + 1;
36
+ while (i < content.length && depth > 0) {
37
+ if (content[i] === '(')
38
+ depth++;
39
+ else if (content[i] === ')')
40
+ depth--;
41
+ i++;
42
+ }
43
+ if (depth !== 0)
44
+ return null;
45
+ return { block: content.slice(start + 1, i - 1), end: i };
46
+ }
47
+ // Note: @for handling moved to a rule in `rules` so it can participate in the
48
+ // ordered rules pipeline. The actual exec loop for the for-rule is implemented
49
+ // inside parse() to allow balanced-brace extraction.
50
+ static rules = [
51
+ // Rule for nested @for blocks. This rule's replacer computes the
52
+ // replacement for one @for occurrence when given a RegExpExecArray
53
+ // (it expects match.index and match.input to be present so the parser
54
+ // can extract the full balanced block that follows the match).
55
+ new PHTMLParserRule(/@for\s*\(([A-Za-z0-9_$]+)\s+in\s+([A-Za-z0-9_$.]+)\s*\)\s*\{/g, (parser, m, scope) => {
56
+ // `m` will be a RegExpExecArray-like object when used by the
57
+ // exec-based loop inside `parse()` so we can treat it accordingly.
58
+ const match = m;
37
59
  const iterVar = match[1];
38
60
  const iterableExpr = match[2];
39
- const blockStart = match.index + match[0].length - 1; // position of '{'
40
- const extracted = PHTMLParser.extractBalancedBlock(content, blockStart);
41
- if (!extracted) {
42
- // Unbalanced braces, just append the match as-is and continue
43
- result += match[0];
44
- lastIndex = match.index + match[0].length;
45
- continue;
46
- }
61
+ const offset = Number(match[match.length - 2]); // offset position
62
+ const input = String(match[match.length - 1]); // source string
63
+ const blockStart = offset + match[0].length - 1; // position of '{'
64
+ const extracted = PHTMLParser.extractBalancedBlock(input, blockStart);
65
+ if (!extracted)
66
+ return match[0];
47
67
  const inner = extracted.block;
48
- lastIndex = extracted.end;
49
- forPattern.lastIndex = lastIndex; // Update regex position
50
- const resolved = this.resolveExpression(iterableExpr, scope);
68
+ const resolved = parser.resolveExpression(iterableExpr, scope);
51
69
  const arr = Array.isArray(resolved) ? resolved : [];
52
70
  const resultParts = [];
53
71
  for (const item of arr) {
54
72
  const fullScope = Object.assign({}, scope, { [iterVar]: item });
55
- // Recursively parse inner block (handles nested @for and other rules)
56
- const t = this.parse(inner, fullScope);
57
- resultParts.push(t);
73
+ resultParts.push(parser.parse(inner, fullScope));
58
74
  }
59
- result += resultParts.join('\n');
60
- }
61
- // Append remaining content after last match
62
- result += content.slice(lastIndex);
63
- return result;
64
- }
65
- static rules = [
66
- new PHTMLParserRule(/@\(\(([\s\S]+?)\)\)/g, (parser, m, scope) => parser.stringifyValue(parser.resolveExpression(m[1], scope))),
67
- new PHTMLParserRule(/@\(([\s\S]+?)\)/g, (parser, m, scope) => parser.stringifyValue(parser.resolveExpression(m[1], scope))),
75
+ // return both the replacement text and the position after the full balanced block
76
+ return { text: resultParts.join('\n'), end: extracted.end };
77
+ }),
78
+ // Double-paren expression: @(( code )) — use exec+balanced-start extraction
79
+ new PHTMLParserRule(/@\(\(/g, (parser, m, scope) => {
80
+ const match = m;
81
+ const offset = Number(match[match.length - 2]);
82
+ const input = String(match[match.length - 1]);
83
+ const blockStart = offset + match[0].length - 1; // points at the inner '('
84
+ // extract balanced parentheses
85
+ const extracted = PHTMLParser.extractBalancedParens(input, blockStart);
86
+ if (!extracted)
87
+ return match[0];
88
+ const code = extracted.block;
89
+ // Evaluate code using parser.variables + local scope
90
+ const ctx = Object.assign({}, parser.variables, scope || {});
91
+ let result;
92
+ try {
93
+ // try to evaluate as expression first
94
+ const fn = new Function('with(this){ return (' + code + '); }');
95
+ result = fn.call(ctx);
96
+ }
97
+ catch (e) {
98
+ try {
99
+ const fn2 = new Function('with(this){ ' + code + ' }');
100
+ result = fn2.call(ctx);
101
+ }
102
+ catch (e2) {
103
+ // On error return empty string
104
+ return '';
105
+ }
106
+ }
107
+ // final end should include the outer ')' if present (for @(( ... )) constructs)
108
+ let finalEnd = extracted.end;
109
+ if (input[extracted.end] === ')')
110
+ finalEnd = extracted.end + 1;
111
+ return { text: parser.stringifyValue(result), end: finalEnd };
112
+ }),
113
+ // Single-paren expression: @( code ) — similar extraction, processed after double-paren rule
114
+ new PHTMLParserRule(/@\(/g, (parser, m, scope) => {
115
+ const match = m;
116
+ const offset = Number(match[match.length - 2]);
117
+ const input = String(match[match.length - 1]);
118
+ const blockStart = offset + match[0].length - 1; // points at '('
119
+ const extracted = PHTMLParser.extractBalancedParens(input, blockStart);
120
+ if (!extracted)
121
+ return match[0];
122
+ const code = extracted.block;
123
+ const ctx = Object.assign({}, parser.variables, scope || {});
124
+ let result;
125
+ try {
126
+ const fn = new Function('with(this){ return (' + code + '); }');
127
+ result = fn.call(ctx);
128
+ }
129
+ catch (e) {
130
+ try {
131
+ const fn2 = new Function('with(this){ ' + code + ' }');
132
+ result = fn2.call(ctx);
133
+ }
134
+ catch (e2) {
135
+ return '';
136
+ }
137
+ }
138
+ return { text: parser.stringifyValue(result), end: extracted.end };
139
+ }),
68
140
  ];
69
141
  variables = {};
70
142
  addVariable(name, value) {
@@ -90,14 +162,40 @@ export default class PHTMLParser {
90
162
  return '';
91
163
  if (typeof val === 'string')
92
164
  return val;
93
- return JSON.stringify(val, Object.keys(val.constructor.prototype));
165
+ const str = String(val);
166
+ return str;
94
167
  }
95
168
  parse(content, scope) {
96
- // First, parse @for loops with proper nesting support
97
- let working = this.parseForLoops(content, scope);
98
- // Then apply other rules
169
+ let working = content;
99
170
  for (const rule of PHTMLParser.rules) {
100
- working = working.replace(rule.pattern, (...args) => rule.replacer(this, args, scope));
171
+ const pattern = rule.pattern;
172
+ let result = '';
173
+ let lastIndex = 0;
174
+ let match;
175
+ // reset scanning position
176
+ pattern.lastIndex = 0;
177
+ while ((match = pattern.exec(working)) !== null) {
178
+ // append text before this match
179
+ result += working.slice(lastIndex, match.index);
180
+ // build args array similar to replace(): [...match, offset, input]
181
+ const args = [...match, match.index, working];
182
+ const out = rule.replacer(this, args, scope);
183
+ if (typeof out === 'string') {
184
+ // simple replacement: advance by matched token length
185
+ result += out;
186
+ lastIndex = match.index + match[0].length;
187
+ }
188
+ else {
189
+ // object replacement provides final end index — consume until that position
190
+ result += out.text;
191
+ lastIndex = out.end;
192
+ }
193
+ // make sure regex scanning continues from the correct place
194
+ pattern.lastIndex = lastIndex;
195
+ }
196
+ // append tail and update working
197
+ result += working.slice(lastIndex);
198
+ working = result;
101
199
  }
102
200
  return working;
103
201
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PHTMLParser.js","sourceRoot":"","sources":["../../src/foundation/PHTMLParser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PHTMLParser.js","sourceRoot":"","sources":["../../src/foundation/PHTMLParser.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reidelsaltres/pureper",
3
- "version": "0.1.76",
3
+ "version": "0.1.77",
4
4
  "description": "Minimal library extracted from the Pureper SPA foundation — utilities and base classes for components/pages.",
5
5
  "type": "module",
6
6
  "main": "out/src/index.js",
@@ -1,9 +1,11 @@
1
1
  class PHTMLParserRule {
2
2
  public pattern: RegExp;
3
3
 
4
- public replacer: (parser: PHTMLParser, match: IArguments, scope?: Record<string, any>) => string;
4
+ // replacer can return either a string replacement OR an object with { text, end }
5
+ // where `end` is a position in the source string just after the consumed range.
6
+ public replacer: (parser: PHTMLParser, match: IArguments, scope?: Record<string, any>) => string | { text: string; end: number };
5
7
 
6
- constructor(pattern: RegExp, replacer: (parser: PHTMLParser, match: IArguments, scope?: Record<string, any>) => string) {
8
+ constructor(pattern: RegExp, replacer: (parser: PHTMLParser, match: IArguments, scope?: Record<string, any>) => string | { text: string; end: number }) {
7
9
  this.pattern = pattern;
8
10
  this.replacer = replacer;
9
11
  }
@@ -25,56 +27,117 @@ export default class PHTMLParser {
25
27
  return { block: content.slice(start + 1, i - 1), end: i };
26
28
  }
27
29
 
28
- // Parses @for constructs with proper nesting support using brace counting.
29
- private parseForLoops(content: string, scope?: Record<string, any>): string {
30
- const forPattern = /@for\s*\(([A-Za-z0-9_$]+)\s+in\s+([A-Za-z0-9_$.]+)\s*\)\s*\{/g;
31
- let result = '';
32
- let lastIndex = 0;
33
- let match: RegExpExecArray | null;
34
-
35
- while ((match = forPattern.exec(content)) !== null) {
36
- // Append text before this @for
37
- result += content.slice(lastIndex, match.index);
38
-
39
- const iterVar = match[1];
40
- const iterableExpr = match[2];
41
- const blockStart = match.index + match[0].length - 1; // position of '{'
42
-
43
- const extracted = PHTMLParser.extractBalancedBlock(content, blockStart);
44
- if (!extracted) {
45
- // Unbalanced braces, just append the match as-is and continue
46
- result += match[0];
47
- lastIndex = match.index + match[0].length;
48
- continue;
49
- }
50
-
51
- const inner = extracted.block;
52
- lastIndex = extracted.end;
53
- forPattern.lastIndex = lastIndex; // Update regex position
30
+ // Extracts a balanced parenthesis block starting at `start` (which must be '(')
31
+ private static extractBalancedParens(content: string, start: number): { block: string; end: number } | null {
32
+ if (content[start] !== '(') return null;
33
+ let depth = 1;
34
+ let i = start + 1;
35
+ while (i < content.length && depth > 0) {
36
+ if (content[i] === '(') depth++;
37
+ else if (content[i] === ')') depth--;
38
+ i++;
39
+ }
40
+ if (depth !== 0) return null;
41
+ return { block: content.slice(start + 1, i - 1), end: i };
42
+ }
54
43
 
55
- const resolved = this.resolveExpression(iterableExpr, scope);
56
- const arr = Array.isArray(resolved) ? resolved : [];
44
+ // Note: @for handling moved to a rule in `rules` so it can participate in the
45
+ // ordered rules pipeline. The actual exec loop for the for-rule is implemented
46
+ // inside parse() to allow balanced-brace extraction.
57
47
 
58
- const resultParts: string[] = [];
59
- for (const item of arr) {
60
- const fullScope = Object.assign({}, scope, { [iterVar]: item });
61
- // Recursively parse inner block (handles nested @for and other rules)
62
- const t = this.parse(inner, fullScope);
63
- resultParts.push(t);
48
+ private static rules: PHTMLParserRule[] = [
49
+ // Rule for nested @for blocks. This rule's replacer computes the
50
+ // replacement for one @for occurrence when given a RegExpExecArray
51
+ // (it expects match.index and match.input to be present so the parser
52
+ // can extract the full balanced block that follows the match).
53
+ new PHTMLParserRule(/@for\s*\(([A-Za-z0-9_$]+)\s+in\s+([A-Za-z0-9_$.]+)\s*\)\s*\{/g,
54
+ (parser, m, scope) => {
55
+ // `m` will be a RegExpExecArray-like object when used by the
56
+ // exec-based loop inside `parse()` so we can treat it accordingly.
57
+ const match = m as any as RegExpExecArray;
58
+ const iterVar = match[1];
59
+ const iterableExpr = match[2];
60
+ const offset = Number(match[match.length - 2]); // offset position
61
+ const input = String(match[match.length - 1]); // source string
62
+
63
+ const blockStart = offset + match[0].length - 1; // position of '{'
64
+ const extracted = PHTMLParser.extractBalancedBlock(input, blockStart);
65
+ if (!extracted) return match[0];
66
+
67
+ const inner = extracted.block;
68
+ const resolved = parser.resolveExpression(iterableExpr, scope);
69
+ const arr = Array.isArray(resolved) ? resolved : [];
70
+
71
+ const resultParts: string[] = [];
72
+ for (const item of arr) {
73
+ const fullScope = Object.assign({}, scope, { [iterVar]: item });
74
+ resultParts.push(parser.parse(inner, fullScope));
75
+ }
76
+
77
+ // return both the replacement text and the position after the full balanced block
78
+ return { text: resultParts.join('\n'), end: extracted.end };
79
+ }),
80
+ // Double-paren expression: @(( code )) — use exec+balanced-start extraction
81
+ new PHTMLParserRule(/@\(\(/g, (parser, m, scope) => {
82
+ const match = m as any as RegExpExecArray;
83
+ const offset = Number(match[match.length - 2]);
84
+ const input = String(match[match.length - 1]);
85
+ const blockStart = offset + match[0].length - 1; // points at the inner '('
86
+
87
+ // extract balanced parentheses
88
+ const extracted = PHTMLParser.extractBalancedParens(input, blockStart);
89
+ if (!extracted) return match[0];
90
+
91
+ const code = extracted.block;
92
+ // Evaluate code using parser.variables + local scope
93
+ const ctx = Object.assign({}, parser.variables, scope || {});
94
+ let result: any;
95
+ try {
96
+ // try to evaluate as expression first
97
+ const fn = new Function('with(this){ return (' + code + '); }');
98
+ result = fn.call(ctx);
99
+ } catch (e) {
100
+ try {
101
+ const fn2 = new Function('with(this){ ' + code + ' }');
102
+ result = fn2.call(ctx);
103
+ } catch (e2) {
104
+ // On error return empty string
105
+ return '';
106
+ }
64
107
  }
65
- result += resultParts.join('\n');
66
- }
67
108
 
68
- // Append remaining content after last match
69
- result += content.slice(lastIndex);
70
- return result;
71
- }
109
+ // final end should include the outer ')' if present (for @(( ... )) constructs)
110
+ let finalEnd = extracted.end;
111
+ if (input[extracted.end] === ')') finalEnd = extracted.end + 1;
112
+ return { text: parser.stringifyValue(result), end: finalEnd };
113
+ }),
114
+ // Single-paren expression: @( code ) — similar extraction, processed after double-paren rule
115
+ new PHTMLParserRule(/@\(/g, (parser, m, scope) => {
116
+ const match = m as any as RegExpExecArray;
117
+ const offset = Number(match[match.length - 2]);
118
+ const input = String(match[match.length - 1]);
119
+ const blockStart = offset + match[0].length - 1; // points at '('
120
+
121
+ const extracted = PHTMLParser.extractBalancedParens(input, blockStart);
122
+ if (!extracted) return match[0];
123
+
124
+ const code = extracted.block;
125
+ const ctx = Object.assign({}, parser.variables, scope || {});
126
+ let result: any;
127
+ try {
128
+ const fn = new Function('with(this){ return (' + code + '); }');
129
+ result = fn.call(ctx);
130
+ } catch (e) {
131
+ try {
132
+ const fn2 = new Function('with(this){ ' + code + ' }');
133
+ result = fn2.call(ctx);
134
+ } catch (e2) {
135
+ return '';
136
+ }
137
+ }
72
138
 
73
- private static rules: PHTMLParserRule[] = [
74
- new PHTMLParserRule(/@\(\(([\s\S]+?)\)\)/g, (parser, m, scope) =>
75
- parser.stringifyValue(parser.resolveExpression(m[1], scope))),
76
- new PHTMLParserRule(/@\(([\s\S]+?)\)/g, (parser, m, scope) =>
77
- parser.stringifyValue(parser.resolveExpression(m[1], scope))),
139
+ return { text: parser.stringifyValue(result), end: extracted.end };
140
+ }),
78
141
  ];
79
142
  public variables: Record<string, unknown> = {};
80
143
 
@@ -100,18 +163,49 @@ export default class PHTMLParser {
100
163
  private stringifyValue(val: any): string {
101
164
  if (val == null) return '';
102
165
  if (typeof val === 'string') return val;
103
- return JSON.stringify(val, Object.keys(val.constructor.prototype));
166
+ const str = String(val);
167
+ return str;
104
168
  }
105
169
 
106
170
  public parse(content: string, scope?: Record<string, any>): string {
107
- // First, parse @for loops with proper nesting support
108
- let working = this.parseForLoops(content, scope);
109
-
110
- // Then apply other rules
171
+ let working = content;
172
+
111
173
  for (const rule of PHTMLParser.rules) {
112
- working = working.replace(rule.pattern,
113
- (...args) => rule.replacer(this, args as any, scope));
174
+ const pattern = rule.pattern;
175
+ let result = '';
176
+ let lastIndex = 0;
177
+ let match: RegExpExecArray | null;
178
+
179
+ // reset scanning position
180
+ pattern.lastIndex = 0;
181
+ while ((match = pattern.exec(working)) !== null) {
182
+ // append text before this match
183
+ result += working.slice(lastIndex, match.index);
184
+
185
+ // build args array similar to replace(): [...match, offset, input]
186
+ const args = [...match, match.index, working] as any as IArguments;
187
+ const out = rule.replacer(this, args, scope);
188
+
189
+ if (typeof out === 'string') {
190
+ // simple replacement: advance by matched token length
191
+ result += out;
192
+ lastIndex = match.index + match[0].length;
193
+ }
194
+ else {
195
+ // object replacement provides final end index — consume until that position
196
+ result += out.text;
197
+ lastIndex = out.end;
198
+ }
199
+
200
+ // make sure regex scanning continues from the correct place
201
+ pattern.lastIndex = lastIndex;
202
+ }
203
+
204
+ // append tail and update working
205
+ result += working.slice(lastIndex);
206
+ working = result;
114
207
  }
208
+
115
209
  return working;
116
210
  }
117
211
  }