nsp-server-pages 0.1.2 → 0.2.1

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 (41) hide show
  1. package/README.md +2 -1
  2. package/cjs/index.js +6 -1
  3. package/cjs/package.json +1 -0
  4. package/cjs/src/app.js +17 -17
  5. package/cjs/src/catch.js +4 -3
  6. package/cjs/src/loaders.js +1 -1
  7. package/cjs/src/mount.js +6 -6
  8. package/cjs/src/parser/attr.js +92 -0
  9. package/{src/parse-el.js → cjs/src/parser/el.js} +15 -28
  10. package/cjs/src/parser/jsp.js +88 -0
  11. package/cjs/src/parser/scriptlet.js +47 -0
  12. package/cjs/src/parser/tag.js +134 -0
  13. package/{src/parse-text.js → cjs/src/parser/text.js} +13 -25
  14. package/cjs/src/stack-store.js +31 -0
  15. package/cjs/src/taglib.js +7 -7
  16. package/esm/index.js +2 -0
  17. package/esm/package.json +4 -0
  18. package/{src → esm/src}/app.js +16 -16
  19. package/{src → esm/src}/catch.js +4 -3
  20. package/{src → esm/src}/mount.js +6 -6
  21. package/esm/src/parser/attr.js +88 -0
  22. package/{cjs/src/parse-el.js → esm/src/parser/el.js} +11 -32
  23. package/esm/src/parser/jsp.js +83 -0
  24. package/esm/src/parser/scriptlet.js +43 -0
  25. package/esm/src/parser/tag.js +130 -0
  26. package/{cjs/src/parse-text.js → esm/src/parser/text.js} +9 -29
  27. package/esm/src/stack-store.js +27 -0
  28. package/{src → esm/src}/taglib.js +7 -7
  29. package/package.json +14 -13
  30. package/types/hooks.d.ts +103 -0
  31. package/{index.d.ts → types/index.d.ts} +62 -35
  32. package/cjs/src/parse-attr.js +0 -91
  33. package/cjs/src/parse-jsp.js +0 -206
  34. package/cjs/src/parse-scriptlet.js +0 -71
  35. package/index.js +0 -1
  36. package/src/parse-attr.js +0 -87
  37. package/src/parse-jsp.js +0 -201
  38. package/src/parse-scriptlet.js +0 -67
  39. /package/{src → esm/src}/bundle.js +0 -0
  40. /package/{src → esm/src}/concat.js +0 -0
  41. /package/{src → esm/src}/loaders.js +0 -0
@@ -0,0 +1,130 @@
1
+ import { Attr } from "./attr.js";
2
+ import { Text } from "./text.js";
3
+ const emptyText = {
4
+ '""': true,
5
+ "''": true,
6
+ "``": true,
7
+ "null": true,
8
+ "undefined": true,
9
+ "": true,
10
+ };
11
+ const isTranspiler = (v) => ("function" === typeof v?.toJS);
12
+ /**
13
+ * Root node or an taglib node
14
+ */
15
+ export class Tag {
16
+ constructor(app, src) {
17
+ this.app = app;
18
+ this.src = src;
19
+ this.children = [];
20
+ this.tagName = src?.match(/^<\/?([^\s=/>]+)/)?.[1];
21
+ }
22
+ append(node) {
23
+ this.children.push(node);
24
+ }
25
+ isOpen() {
26
+ return !/\/\s*>$/.test(this.src);
27
+ }
28
+ isClose() {
29
+ return /^<\//.test(this.src);
30
+ }
31
+ getBodyJS(option) {
32
+ const { app } = this;
33
+ const { indent, trimSpaces, vName } = app.options;
34
+ const SP = option?.SP ?? (("string" === typeof indent) ? indent : (+indent ? " ".repeat(+indent) : ""));
35
+ const LF = option?.LF ?? "\n";
36
+ const nextLF = LF + SP;
37
+ const nextOption = { SP, LF: nextLF };
38
+ const { children } = this;
39
+ const args = children.map(item => {
40
+ if (isTranspiler(item)) {
41
+ return item.toJS(nextOption);
42
+ }
43
+ else if (!/\S/.test(item)) {
44
+ // item with only whitespace
45
+ return (trimSpaces !== false) ? '""' : JSON.stringify(item);
46
+ }
47
+ else {
48
+ if (trimSpaces !== false) {
49
+ item = item.replace(/^\s*[\r\n]/s, "\n");
50
+ item = item.replace(/\s*[\r\n]\s*$/s, "\n");
51
+ item = item.replace(/^[ \t]+/s, " ");
52
+ item = item.replace(/[ \t]+$/s, " ");
53
+ }
54
+ let js = new Text(app, item).toJS(nextOption);
55
+ if (/\(.+?\)|\$\{.+?}/s.test(js)) {
56
+ js = `${vName} => ${js}`; // array function
57
+ }
58
+ return js;
59
+ }
60
+ }).filter(v => !emptyText[v]);
61
+ // empty body
62
+ if (!children.length) {
63
+ return "";
64
+ }
65
+ // keep a single empty string at least if all arguments are trimmed
66
+ if (!args.length) {
67
+ args.push('""');
68
+ }
69
+ const last = args.length - 1;
70
+ args.forEach((v, idx) => {
71
+ const isComment = /^\/\/[^\n]*$/s.test(v);
72
+ if (idx !== last && !isComment) {
73
+ args[idx] += ",";
74
+ }
75
+ else if (idx === last && isComment) {
76
+ args[idx] += LF;
77
+ }
78
+ });
79
+ const bodyL = /^`\n/s.test(args.at(0)) ? "" : nextLF;
80
+ const bodyR = /(\n`|[)\s])$/s.test(args.at(-1)) ? "" : LF;
81
+ return bodyL + args.join(nextLF) + bodyR;
82
+ }
83
+ /**
84
+ * Transpile JSP document to JavaScript source code
85
+ */
86
+ toJS(option) {
87
+ const { app, tagName } = this;
88
+ const { indent, nspName } = app.options;
89
+ // root element
90
+ if (!tagName) {
91
+ const bodyJS = this.getBodyJS(option);
92
+ return `${nspName}.bundle(${bodyJS})`; // root element
93
+ }
94
+ if (this.isClose())
95
+ return; // invalid
96
+ const commentJS = this.getCommentJS(option);
97
+ const attr = new Attr(app, this.src);
98
+ const body = this.getBodyJS(option);
99
+ const SP = option?.SP ?? (("string" === typeof indent) ? indent : (+indent ? " ".repeat(+indent) : ""));
100
+ const LF = option?.LF ?? "\n";
101
+ const nextLF = LF + SP;
102
+ const nextOption = { SP, LF: (body ? nextLF : LF) };
103
+ const type = `parse.tag.${tagName}`;
104
+ const def = { app, name: tagName, attr, body, LF: LF, nextLF };
105
+ const tagJS = app.process(type, def) ?? this.getTagJS(def, nextOption);
106
+ return commentJS ? commentJS + tagJS : tagJS;
107
+ }
108
+ getCommentJS(option) {
109
+ const { app, src } = this;
110
+ if (!app.options.comment)
111
+ return;
112
+ const currentLF = option?.LF ?? "\n";
113
+ return `// ${src?.replace(/\s*[\r\n]\s*/g, " ") ?? ""}${currentLF}`;
114
+ }
115
+ getTagJS(def, option) {
116
+ const { app, tagName } = this;
117
+ const { nspName, vName } = app.options;
118
+ const attrRaw = def.attr.toJS(option);
119
+ // transpile attributes to array function if they include variables
120
+ const hasVars = /\(.+?\)|\$\{.+?}/s.test(attrRaw);
121
+ const attrArg = hasVars ? `, ${vName} => (${attrRaw})` : `, ${attrRaw}`;
122
+ const nameJS = JSON.stringify(tagName);
123
+ const hasAttr = /:/.test(attrRaw);
124
+ let bodyArg = def.body;
125
+ if (bodyArg)
126
+ bodyArg = ((/^\n/.test(bodyArg)) ? "," : ", ") + bodyArg;
127
+ const restJS = bodyArg ? (attrArg + bodyArg) : (hasAttr ? attrArg : "");
128
+ return `${nspName}.tag(${nameJS}${restJS})`;
129
+ }
130
+ }
@@ -1,8 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseText = void 0;
4
- const parse_el_js_1 = require("./parse-el.js");
5
- const parse_scriptlet_js_1 = require("./parse-scriptlet.js");
1
+ import { EL } from "./el.js";
2
+ import { Scriptlet } from "./scriptlet.js";
6
3
  /**
7
4
  * escape special characters in Template Literal
8
5
  */
@@ -19,35 +16,18 @@ const bodyRegExp = new RegExp(bodyRE, "s");
19
16
  /**
20
17
  * Parser for: text content
21
18
  */
22
- const parseText = (app, src) => new TextParser(app, src);
23
- exports.parseText = parseText;
24
- class TextParser {
19
+ export class Text {
25
20
  constructor(app, src) {
26
21
  this.app = app;
27
- this.src = src;
28
- //
22
+ this.src = app.process("before.parse.text", src) ?? src;
29
23
  }
30
24
  /**
31
25
  * Transpile ${expression} and <% scriptlet %> to JavaScript source code
32
26
  */
33
27
  toJS(option) {
34
- return textToJS(this.app, this.src, option);
35
- }
36
- /**
37
- * Compile ${expression} and <% scriptlet %> to JavaScript function instance
38
- */
39
- toFn() {
40
- const { app } = this;
41
- const { nspName, vName } = app.options;
42
- const js = this.toJS();
43
- try {
44
- const fn = Function(nspName, vName, `return ${js}`);
45
- return (context) => fn(app, context);
46
- }
47
- catch (e) {
48
- app.log("TextParser: " + js?.substring(0, 1000));
49
- throw e;
50
- }
28
+ const { app, src } = this;
29
+ const js = app.process("parse.text", src) ?? textToJS(app, src, option);
30
+ return app.process("after.parse.text", js) ?? js;
51
31
  }
52
32
  }
53
33
  /**
@@ -66,7 +46,7 @@ const textToJS = (app, src, option) => {
66
46
  const isAsync = /^#/s.test(value);
67
47
  value = value.replace(/^[$#]\{\s*/s, "");
68
48
  value = value.replace(/\s*}$/s, "");
69
- const item = (0, parse_el_js_1.parseEL)(app, value);
49
+ const item = new EL(app, value);
70
50
  if (isAsync) {
71
51
  items.push({ toJS: (option) => `await ${item.toJS(option)}` });
72
52
  }
@@ -76,7 +56,7 @@ const textToJS = (app, src, option) => {
76
56
  }
77
57
  else if (i3 === 2) {
78
58
  // <% scriptlet %>
79
- const item = (0, parse_scriptlet_js_1.parseScriptlet)(app, value);
59
+ const item = new Scriptlet(app, value);
80
60
  items.push(item);
81
61
  }
82
62
  else {
@@ -0,0 +1,27 @@
1
+ export class StackStore {
2
+ constructor(value) {
3
+ this.stack = [];
4
+ if (arguments.length) {
5
+ this.set(value);
6
+ }
7
+ }
8
+ open(value) {
9
+ this.stack.unshift(value);
10
+ }
11
+ close() {
12
+ return this.stack.shift();
13
+ }
14
+ get() {
15
+ return this.stack[0];
16
+ }
17
+ set(value) {
18
+ this.stack[0] = value;
19
+ }
20
+ find(test) {
21
+ for (const data of this.stack) {
22
+ if (test(data)) {
23
+ return data;
24
+ }
25
+ }
26
+ }
27
+ }
@@ -1,6 +1,6 @@
1
1
  import { toXML } from "to-xml";
2
- export const addTagLib = (app, tagLibDef) => {
3
- const { fnMap, tagMap } = app;
2
+ export function addTagLib(tagLibDef) {
3
+ const { fnMap, tagMap } = this;
4
4
  const { ns, fn, tag } = tagLibDef;
5
5
  if (fn) {
6
6
  for (const name in fn) {
@@ -12,14 +12,14 @@ export const addTagLib = (app, tagLibDef) => {
12
12
  tagMap.set(`${ns}:${name}`, tag[name]);
13
13
  }
14
14
  }
15
- };
16
- export const prepareTag = (app, name, attr, body) => {
17
- const { tagMap } = app;
15
+ }
16
+ export function prepareTag(name, attr, body) {
17
+ const { tagMap } = this;
18
18
  const tagFn = tagMap.get(name) || defaultTagFn;
19
19
  const attrFn = !attr ? () => ({}) : (typeof attr !== "function") ? () => attr : attr;
20
- const tagDef = { name, app, attr: attrFn, body };
20
+ const tagDef = { name, app: this, attr: attrFn, body };
21
21
  return tagFn(tagDef);
22
- };
22
+ }
23
23
  const defaultTagFn = (tagDef) => {
24
24
  const { name } = tagDef;
25
25
  // tagDef.app.log(`Unknown tag: ${name}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nsp-server-pages",
3
3
  "description": "NSP JavaScript Server Pages for Node.js",
4
- "version": "0.1.2",
4
+ "version": "0.2.1",
5
5
  "author": "@kawanet",
6
6
  "bugs": {
7
7
  "url": "https://github.com/kawanet/nsp-server-pages/issues"
@@ -13,25 +13,26 @@
13
13
  "devDependencies": {
14
14
  "@rollup/plugin-node-resolve": "^15.2.1",
15
15
  "@types/mocha": "^10.0.1",
16
- "@types/node": "^20.5.4",
16
+ "@types/node": "^20.5.7",
17
17
  "mocha": "^10.2.0",
18
- "typescript": "^5.1.6"
18
+ "typescript": "^5.2.2"
19
19
  },
20
20
  "exports": {
21
21
  "import": {
22
- "types": "./index.d.ts",
23
- "default": "./index.js"
22
+ "types": "./types/index.d.ts",
23
+ "default": "./esm/index.js"
24
24
  },
25
25
  "require": "./cjs/index.js"
26
26
  },
27
27
  "files": [
28
- "cjs/index.js",
28
+ "cjs/*.js",
29
29
  "cjs/package.json",
30
- "cjs/src/*.js",
31
- "index.d.ts",
32
- "index.js",
33
- "package.json",
34
- "src/*.js"
30
+ "cjs/src/**/*.js",
31
+ "esm/*.js",
32
+ "esm/package.json",
33
+ "esm/src/**/*.js",
34
+ "types/*.d.ts",
35
+ "package.json"
35
36
  ],
36
37
  "homepage": "https://github.com/kawanet/nsp-server-pages#readme",
37
38
  "keywords": [
@@ -41,7 +42,7 @@
41
42
  "taglib"
42
43
  ],
43
44
  "license": "Apache-2.0",
44
- "main": "index.js",
45
+ "main": "./esm/index.js",
45
46
  "repository": {
46
47
  "type": "git",
47
48
  "url": "git+https://github.com/kawanet/nsp-server-pages.git"
@@ -54,5 +55,5 @@
54
55
  },
55
56
  "sideEffects": false,
56
57
  "type": "module",
57
- "types": "./index.d.ts"
58
+ "types": "./types/index.d.ts"
58
59
  }
@@ -0,0 +1,103 @@
1
+ import type {NSP} from "./index";
2
+
3
+ type RuntimeErrorHookType = "error";
4
+
5
+ type RuntimeScriptletHookTypes =
6
+ "directive"
7
+ | "declaration"
8
+ | "expression"
9
+ | "scriptlet";
10
+
11
+ type BeforeParseHookTypes =
12
+ "before.parse.attr"
13
+ | "before.parse.comment"
14
+ | "before.parse.declaration"
15
+ | "before.parse.directive"
16
+ | "before.parse.el"
17
+ | "before.parse.expression"
18
+ | "before.parse.jsp"
19
+ | "before.parse.text";
20
+
21
+ type ParseHookTypes =
22
+ "parse.attr"
23
+ | "parse.comment"
24
+ | "parse.declaration"
25
+ | "parse.directive"
26
+ | "parse.el"
27
+ | "parse.expression"
28
+ | "parse.jsp"
29
+ | "parse.text";
30
+
31
+ type AfterParseHookTypes =
32
+ "after.parse.attr"
33
+ | "after.parse.comment"
34
+ | "after.parse.declaration"
35
+ | "after.parse.directive"
36
+ | "after.parse.el"
37
+ | "after.parse.expression"
38
+ | "after.parse.jsp"
39
+ | "after.parse.text";
40
+
41
+ type ParseTagHookTypes = `parse.tag.${string}`;
42
+
43
+ type KnownHookTypes =
44
+ RuntimeErrorHookType
45
+ | RuntimeScriptletHookTypes
46
+ | BeforeParseHookTypes
47
+ | ParseHookTypes
48
+ | AfterParseHookTypes
49
+ | ParseTagHookTypes;
50
+
51
+ export interface Hooks {
52
+ /**
53
+ * ==== RUNTIME HOOKS ====
54
+ *
55
+ * hook called when an Error thrown.
56
+ * return a string to output the error message and cancel the exception.
57
+ * return undefined to stop the page with the exception.
58
+ */
59
+ hook(type: RuntimeErrorHookType, fn: (e: Error, context: any) => string | void): void;
60
+
61
+ /**
62
+ * hooks called with JSP directive, declaration, scriptlet on runtime.
63
+ */
64
+ hook(type: RuntimeScriptletHookTypes, fn: (src: string, context: any) => string | void): void;
65
+
66
+ /**
67
+ * ==== TRANSPILER HOOKS ====
68
+ *
69
+ * hooks called with input JSP document before transpiling started.
70
+ * return a string to modify the input.
71
+ * return undefined not to modify the input.
72
+ */
73
+ hook(type: BeforeParseHookTypes, fn: (src: string) => string | void): void;
74
+
75
+ /**
76
+ * hooks called with JSP document to replace our default transpiler.
77
+ * return a string if you have own transpiler for the type.
78
+ * return undefined for our default transpiler to work.
79
+ */
80
+ hook(type: ParseHookTypes, fn: (src: string) => string | void): void;
81
+
82
+ /**
83
+ * hooks called with output JavaScript code after transpiling done.
84
+ * return a string to modify the output.
85
+ * return undefined not to modify the output.
86
+ */
87
+ hook(type: AfterParseHookTypes, fn: (src: string) => string | void): void;
88
+
89
+ /**
90
+ * hooks called with TagParserDef to transpile tag implementation inline.
91
+ *
92
+ * @example
93
+ * nsp.hook("parse.tag.c:set", tag => {
94
+ * return `v => { v[${tag.attr.get("var")}] = ${tag.attr.get("value")} }`;
95
+ * });
96
+ */
97
+ hook<A>(type: ParseTagHookTypes, fn: (tag: NSP.TagParserDef<A>) => string | void): void;
98
+
99
+ /**
100
+ * ==== OTHER HOOKS ====
101
+ */
102
+ hook(type: Exclude<string, KnownHookTypes>, fn: (...args: any[]) => any): void;
103
+ }
@@ -4,9 +4,11 @@
4
4
  * @see https://github.com/kawanet/nsp-server-pages
5
5
  */
6
6
 
7
+ import type {Hooks} from "./hooks.js";
8
+
7
9
  export const createNSP: (options?: NSP.Options) => NSP.App;
8
10
 
9
- declare namespace NSP {
11
+ export declare namespace NSP {
10
12
  type NodeFn<T> = (context: T) => string | Promise<string>;
11
13
 
12
14
  type Node<T> = string | NodeFn<T>;
@@ -52,10 +54,10 @@ declare namespace NSP {
52
54
  storeKey?: string;
53
55
 
54
56
  /**
55
- * indent size for JavaScript source generated
56
- * @default 0
57
+ * indent spaces for JavaScript source generated
58
+ * @default 0 (no indent)
57
59
  */
58
- indent?: number;
60
+ indent?: number | string;
59
61
 
60
62
  /**
61
63
  * add comments at toJS() result
@@ -74,23 +76,10 @@ declare namespace NSP {
74
76
  * @default false
75
77
  */
76
78
  nullish?: boolean;
77
-
78
- /**
79
- * expression filter before transpile starts
80
- */
81
- prefilter?: (src: string) => string;
82
-
83
- /**
84
- * expression filter after transpile done
85
- */
86
- postfilter?: (src: string) => string;
87
79
  }
88
80
 
89
- interface App {
90
- fnMap: Map<string, (...args: any[]) => any>;
91
- loaders: LoaderFn[];
81
+ interface App extends Hooks {
92
82
  options: Options;
93
- tagMap: Map<string, TagFn<any>>;
94
83
 
95
84
  /**
96
85
  * register a tag library
@@ -142,27 +131,20 @@ declare namespace NSP {
142
131
  mount(path: RegExp | string, fn: LoaderFn): void;
143
132
 
144
133
  /**
145
- * register a hook function
134
+ * register a hook function. see hooks.d.ts for detail
146
135
  */
147
- hook(type: "error", fn: (e: Error, context?: any) => string | void): void;
148
-
149
- hook(type: "directive", fn: (src: string, context?: any) => string | void): void;
150
-
151
- hook(type: "declaration", fn: (src: string, context?: any) => string | void): void;
152
-
153
- hook(type: "scriptlet", fn: (src: string, context?: any) => string | void): void;
154
136
 
155
- hook(type: string, fn: (...args: any[]) => any): void;
137
+ // hook(type: string, fn: (...args: any[]) => any): void;
156
138
 
157
139
  /**
158
140
  * parse a JSP document
159
141
  */
160
- parse(src: string): Parser;
142
+ parse(src: string): JspParser;
161
143
 
162
144
  /**
163
145
  * get a private data store in context
164
146
  */
165
- store<S>(context: any, key: string, initFn?: () => S): S;
147
+ store<P>(context: any, key: string): StackStore<P>;
166
148
 
167
149
  /**
168
150
  * generates a NodeFn for the tag
@@ -187,22 +169,67 @@ declare namespace NSP {
187
169
  tag?: { [name: string]: TagFn<any> };
188
170
  }
189
171
 
172
+ interface StackStore<P> {
173
+ /**
174
+ * set value to the store
175
+ */
176
+ set(value: P): void;
177
+
178
+ /**
179
+ * get value from the store
180
+ */
181
+ get(): P;
182
+
183
+ /**
184
+ * open a new layer
185
+ */
186
+ open(value?: P): void;
187
+
188
+ /**
189
+ * close the current layer
190
+ */
191
+ close(): P;
192
+
193
+ /**
194
+ * find a value from layers with the test function
195
+ */
196
+ find(test: (data: P) => boolean): P;
197
+ }
198
+
190
199
  interface ToJSOption {
191
- indent?: number;
200
+ LF?: string;
201
+ SP?: string;
192
202
  }
193
203
 
194
- /**
195
- * Parser for JSP document
196
- */
197
- interface Parser {
204
+ interface Transpiler {
198
205
  /**
199
- * transpile the JSP document to JavaScript source code
206
+ * transpile to JavaScript source code
200
207
  */
201
208
  toJS(option?: ToJSOption): string;
209
+ }
202
210
 
211
+ /**
212
+ * Parser for JSP document
213
+ */
214
+ interface JspParser extends Transpiler {
203
215
  /**
204
216
  * compile the JSP document as a NodeFn
205
217
  */
206
218
  toFn<T>(): NodeFn<T>;
207
219
  }
220
+
221
+ interface TagParserDef<A> {
222
+ app: App;
223
+ name: string;
224
+ attr: AttrParser<A>;
225
+ body: string;
226
+ LF: string;
227
+ nextLF: string;
228
+ }
229
+
230
+ interface AttrParser<A> extends Transpiler {
231
+ keys(): (keyof A)[];
232
+
233
+ get(key: keyof A): string; // JavaScript
234
+ }
208
235
  }
@@ -1,91 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseAttr = void 0;
4
- const parse_text_js_1 = require("./parse-text.js");
5
- /**
6
- * Parser for HTML tag attributes <tagName attr="value"/>
7
- */
8
- const parseAttr = (app, src) => new AttrParser(app, src);
9
- exports.parseAttr = parseAttr;
10
- class AttrParser {
11
- constructor(app, src) {
12
- this.app = app;
13
- this.src = src;
14
- //
15
- }
16
- /**
17
- * Transpile HTML tag attributes to JavaScript source code
18
- */
19
- toJS(option) {
20
- return attrToJS(this.app, this.src, option);
21
- }
22
- /**
23
- * Compile HTML tag attributes to JavaScript function instance
24
- */
25
- toFn() {
26
- const { app } = this;
27
- const { nspName, vName } = app.options;
28
- const js = this.toJS();
29
- try {
30
- const fn = Function(nspName, vName, `return ${js}`);
31
- return (context) => fn(app, context);
32
- }
33
- catch (e) {
34
- app.log("AttrParser: " + js.substring(0, 1000));
35
- throw e;
36
- }
37
- }
38
- }
39
- /**
40
- * Transpile HTML tag attributes to JavaScript source code
41
- */
42
- const attrToJS = (app, tag, option) => {
43
- tag = tag?.replace(/^\s*<\S+\s*/s, "");
44
- tag = tag?.replace(/\s*\/?>\s*$/s, "");
45
- const indent = +app.options.indent || 0;
46
- const currentIndent = +option?.indent || 0;
47
- const nextIndent = currentIndent + indent;
48
- const currentLF = currentIndent ? "\n" + " ".repeat(currentIndent) : "\n";
49
- const nextLF = nextIndent ? "\n" + " ".repeat(nextIndent) : "\n";
50
- const keys = [];
51
- const index = {};
52
- tag?.replace(/([^\s='"]+)(\s*=(?:\s*"([^"]*)"|\s*'([^']*)'|([^\s='"]*)))?/g, (_, key, eq, v1, v2, v3) => {
53
- if (!index[key])
54
- keys.push(key);
55
- index[key] = (eq ? unescapeXML(v1 || v2 || v3 || "") : true);
56
- return "";
57
- });
58
- const items = keys.map(key => {
59
- let value = index[key];
60
- if (!/^[A-Za-z_]\w+$/.test(key)) {
61
- key = JSON.stringify(key);
62
- }
63
- if ("string" === typeof value) {
64
- value = (0, parse_text_js_1.parseText)(app, value).toJS({ indent: nextIndent });
65
- }
66
- return `${key}: ${value}`;
67
- });
68
- // no arguments
69
- if (!keys.length)
70
- return 'null';
71
- const js = items.join(`,${nextLF}`);
72
- const trailingComma = (keys.length > 1) ? "," : "";
73
- return `{${nextLF}${js}${trailingComma}${currentLF}}`;
74
- };
75
- const UNESCAPE = {
76
- "&amp;": "&",
77
- "&lt;": "<",
78
- "&gt;": ">",
79
- "&apos;": "'",
80
- "&quot;": '"'
81
- };
82
- const unescapeXML = (str) => {
83
- return str?.replace(/(&(?:lt|gt|amp|apos|quot|#(?:\d{1,6}|x[0-9a-fA-F]{1,5}));)/g, (str) => {
84
- if (str[1] === "#") {
85
- const code = (str[2] === "x") ? parseInt(str.substring(3), 16) : parseInt(str.substr(2), 10);
86
- if (code > -1)
87
- return String.fromCharCode(code);
88
- }
89
- return UNESCAPE[str] || str;
90
- });
91
- };