@reidelsaltres/pureper 0.1.72 → 0.1.74

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,4 +1,6 @@
1
1
  export default class PHTMLParser {
2
+ private static extractBalancedBlock;
3
+ private parseForLoops;
2
4
  private static rules;
3
5
  variables: Record<string, unknown>;
4
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;IAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAyClB;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;CAQrE"}
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"}
@@ -7,39 +7,62 @@ class PHTMLParserRule {
7
7
  }
8
8
  }
9
9
  export default class PHTMLParser {
10
- static rules = [
11
- // Rule: for-loops like @for (item in items) { ... }
12
- new PHTMLParserRule(/@for\s*\(([A-Za-z0-9_$]+)\s+in\s+([A-Za-z0-9_$.]+)\s*\)\s*\{([\s\S]*?)\}/g, (parser, m, scope) => {
13
- // m[1] = iteration variable name, m[2] = iterable expression, m[3] = inner block
14
- const iterVar = m[1];
15
- const iterableExpr = m[2];
16
- const inner = m[3];
17
- const resolved = parser.resolveExpression(iterableExpr, scope);
10
+ // Extracts a balanced block starting from position `start` in `content`.
11
+ // Returns { block, end } where `block` is the content inside braces and `end` is the position after closing brace.
12
+ static extractBalancedBlock(content, start) {
13
+ if (content[start] !== '{')
14
+ return null;
15
+ let depth = 1;
16
+ let i = start + 1;
17
+ while (i < content.length && depth > 0) {
18
+ if (content[i] === '{')
19
+ depth++;
20
+ else if (content[i] === '}')
21
+ depth--;
22
+ i++;
23
+ }
24
+ if (depth !== 0)
25
+ return null;
26
+ return { block: content.slice(start + 1, i - 1), end: i };
27
+ }
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);
37
+ const iterVar = match[1];
38
+ 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
+ }
47
+ const inner = extracted.block;
48
+ lastIndex = extracted.end;
49
+ forPattern.lastIndex = lastIndex; // Update regex position
50
+ const resolved = this.resolveExpression(iterableExpr, scope);
18
51
  const arr = Array.isArray(resolved) ? resolved : [];
19
52
  const resultParts = [];
20
- let i = 0;
21
53
  for (const item of arr) {
22
54
  const fullScope = Object.assign({}, scope, { [iterVar]: item });
23
- // parse inner block for this item
24
- let t = parser.parse(inner, fullScope);
25
- // normalize whitespace: remove leading/trailing whitespace lines and
26
- if (i === 0) {
27
- t = t.trim();
28
- t = " " + t;
29
- }
30
- t = t.split(/\n/)
31
- //.map(line => line.trim())
32
- .filter(line => line.length > 0)
33
- .toString();
34
- //.join('\n');
55
+ // Recursively parse inner block (handles nested @for and other rules)
56
+ const t = this.parse(inner, fullScope);
35
57
  resultParts.push(t);
36
- i++;
37
58
  }
38
- // join each item's rendered chunk with a single newline so output is compact
39
- return resultParts.join('\n');
40
- // Parse inner block for each element in the iterable using a local scope
41
- //return arr.map((el) => parser.parse(inner, Object.assign({}, scope, { [iterVar]: el }))).join('');
42
- }),
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 = [
43
66
  new PHTMLParserRule(/@\(\(([\s\S]+?)\)\)/g, (parser, m, scope) => parser.stringifyValue(parser.resolveExpression(m[1], scope))),
44
67
  new PHTMLParserRule(/@\(([\s\S]+?)\)/g, (parser, m, scope) => parser.stringifyValue(parser.resolveExpression(m[1], scope))),
45
68
  ];
@@ -67,10 +90,12 @@ export default class PHTMLParser {
67
90
  return '';
68
91
  if (typeof val === 'string')
69
92
  return val;
70
- return String(val);
93
+ return JSON.stringify(val);
71
94
  }
72
95
  parse(content, scope) {
73
- let working = content;
96
+ // First, parse @for loops with proper nesting support
97
+ let working = this.parseForLoops(content, scope);
98
+ // Then apply other rules
74
99
  for (const rule of PHTMLParser.rules) {
75
100
  working = working.replace(rule.pattern, (...args) => rule.replacer(this, args, scope));
76
101
  }
@@ -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.72",
3
+ "version": "0.1.74",
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",
@@ -10,43 +10,67 @@ class PHTMLParserRule {
10
10
  }
11
11
 
12
12
  export default class PHTMLParser {
13
+ // Extracts a balanced block starting from position `start` in `content`.
14
+ // Returns { block, end } where `block` is the content inside braces and `end` is the position after closing brace.
15
+ private static extractBalancedBlock(content: string, start: number): { block: string; end: number } | null {
16
+ if (content[start] !== '{') return null;
17
+ let depth = 1;
18
+ let i = start + 1;
19
+ while (i < content.length && depth > 0) {
20
+ if (content[i] === '{') depth++;
21
+ else if (content[i] === '}') depth--;
22
+ i++;
23
+ }
24
+ if (depth !== 0) return null;
25
+ return { block: content.slice(start + 1, i - 1), end: i };
26
+ }
27
+
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
54
+
55
+ const resolved = this.resolveExpression(iterableExpr, scope);
56
+ const arr = Array.isArray(resolved) ? resolved : [];
57
+
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);
64
+ }
65
+ result += resultParts.join('\n');
66
+ }
67
+
68
+ // Append remaining content after last match
69
+ result += content.slice(lastIndex);
70
+ return result;
71
+ }
72
+
13
73
  private static rules: PHTMLParserRule[] = [
14
- // Rule: for-loops like @for (item in items) { ... }
15
- new PHTMLParserRule(/@for\s*\(([A-Za-z0-9_$]+)\s+in\s+([A-Za-z0-9_$.]+)\s*\)\s*\{([\s\S]*?)\}/g,
16
- (parser, m, scope) => {
17
- // m[1] = iteration variable name, m[2] = iterable expression, m[3] = inner block
18
- const iterVar = m[1];
19
- const iterableExpr = m[2];
20
- const inner = m[3];
21
-
22
- const resolved = parser.resolveExpression(iterableExpr, scope);
23
- const arr = Array.isArray(resolved) ? resolved : [];
24
-
25
- const resultParts: string[] = [];
26
- let i = 0;
27
- for (const item of arr) {
28
- const fullScope = Object.assign({}, scope, { [iterVar]: item });
29
- // parse inner block for this item
30
- let t = parser.parse(inner, fullScope);
31
- // normalize whitespace: remove leading/trailing whitespace lines and
32
- if (i === 0) {
33
- t = t.trim();
34
- t = " " + t;
35
- }
36
- t = t.split(/\n/)
37
- //.map(line => line.trim())
38
- .filter(line => line.length > 0)
39
- .toString();
40
- //.join('\n');
41
- resultParts.push(t);
42
- i++;
43
- }
44
- // join each item's rendered chunk with a single newline so output is compact
45
- return resultParts.join('\n');
46
-
47
- // Parse inner block for each element in the iterable using a local scope
48
- //return arr.map((el) => parser.parse(inner, Object.assign({}, scope, { [iterVar]: el }))).join('');
49
- }),
50
74
  new PHTMLParserRule(/@\(\(([\s\S]+?)\)\)/g, (parser, m, scope) =>
51
75
  parser.stringifyValue(parser.resolveExpression(m[1], scope))),
52
76
  new PHTMLParserRule(/@\(([\s\S]+?)\)/g, (parser, m, scope) =>
@@ -76,11 +100,14 @@ export default class PHTMLParser {
76
100
  private stringifyValue(val: any): string {
77
101
  if (val == null) return '';
78
102
  if (typeof val === 'string') return val;
79
- return String(val);
103
+ return JSON.stringify(val);
80
104
  }
81
105
 
82
106
  public parse(content: string, scope?: Record<string, any>): string {
83
- let working = content;
107
+ // First, parse @for loops with proper nesting support
108
+ let working = this.parseForLoops(content, scope);
109
+
110
+ // Then apply other rules
84
111
  for (const rule of PHTMLParser.rules) {
85
112
  working = working.replace(rule.pattern,
86
113
  (...args) => rule.replacer(this, args as any, scope));