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
package/README.md CHANGED
@@ -11,7 +11,7 @@ NSP JavaScript Server Pages for Node.js
11
11
  - `${ f:h("tag") }` - custom taglib static function call
12
12
  - `<ns:tag attr="${ expression }"/>` - custom taglib action tag
13
13
  - `<%-- comments --%>` - comments in JSP just ignored
14
- - See TypeScript declaration [index.d.ts](https://github.com/kawanet/nsp-server-pages/blob/main/index.d.ts) for API detail.
14
+ - See [TypeScript declaration files](https://github.com/kawanet/nsp-server-pages/tree/main/types/) for API detail.
15
15
 
16
16
  ## SYNOPSIS
17
17
 
@@ -156,6 +156,7 @@ app.use("/", async (req, res, next) => {
156
156
  - https://github.com/kawanet/nsp-server-pages
157
157
  - https://github.com/kawanet/nsp-jstl-taglib
158
158
  - https://github.com/kawanet/nsp-struts1-taglib
159
+ - https://github.com/kawanet/nsp-seasar2-taglib
159
160
  - https://github.com/apache/tomcat
160
161
 
161
162
  ## LICENSE
package/cjs/index.js CHANGED
@@ -1 +1,6 @@
1
- exports.createNSP = require("./src/app.js").createNSP;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNSP = void 0;
4
+ const app_js_1 = require("./src/app.js");
5
+ const createNSP = (options) => new app_js_1.App(options);
6
+ exports.createNSP = createNSP;
package/cjs/package.json CHANGED
@@ -1,3 +1,4 @@
1
1
  {
2
+ "main": "./index.js",
2
3
  "type": "commonjs"
3
4
  }
package/cjs/src/app.js CHANGED
@@ -1,21 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createNSP = void 0;
3
+ exports.App = void 0;
4
4
  const mount_js_1 = require("./mount.js");
5
5
  const loaders_js_1 = require("./loaders.js");
6
- const parse_jsp_js_1 = require("./parse-jsp.js");
6
+ const jsp_js_1 = require("./parser/jsp.js");
7
7
  const catch_js_1 = require("./catch.js");
8
8
  const bundle_js_1 = require("./bundle.js");
9
9
  const taglib_js_1 = require("./taglib.js");
10
10
  const concat_js_1 = require("./concat.js");
11
- const createNSP = (options) => new App(options);
12
- exports.createNSP = createNSP;
11
+ const stack_store_js_1 = require("./stack-store.js");
13
12
  class App {
14
13
  constructor(options) {
15
14
  this.loaders = [];
16
15
  this.tagMap = new Map();
17
16
  this.fnMap = new Map();
18
- this.hooks = new Map;
17
+ this.hooks = new Map();
19
18
  this.options = options = Object.create(options || null);
20
19
  if (!options.vName)
21
20
  options.vName = "v";
@@ -30,7 +29,7 @@ class App {
30
29
  process(type, ...args) {
31
30
  const fn = this.hooks.get(type);
32
31
  if (fn)
33
- return fn.apply(null, args);
32
+ return fn.apply(this, args);
34
33
  }
35
34
  log(message) {
36
35
  const logger = this.options.logger || console;
@@ -46,11 +45,11 @@ class App {
46
45
  return fn;
47
46
  }
48
47
  addTagLib(tagLibDef) {
49
- (0, taglib_js_1.addTagLib)(this, tagLibDef);
48
+ taglib_js_1.addTagLib.call(this, tagLibDef);
50
49
  }
51
50
  tag(name, attr, ..._) {
52
51
  const bodyFn = (0, bundle_js_1.bundle)(arguments, 2);
53
- const tagFn = (0, taglib_js_1.prepareTag)(this, name, attr, bodyFn);
52
+ const tagFn = taglib_js_1.prepareTag.call(this, name, attr, bodyFn);
54
53
  return (0, catch_js_1.catchFn)(this, tagFn);
55
54
  }
56
55
  bundle(..._) {
@@ -58,37 +57,38 @@ class App {
58
57
  return (0, catch_js_1.catchFn)(this, fn);
59
58
  }
60
59
  parse(src) {
61
- return (0, parse_jsp_js_1.parseJSP)(this, src);
60
+ return new jsp_js_1.JSP(this, src);
62
61
  }
63
62
  mount(path, fn) {
64
- return (0, mount_js_1.mount)(this, path, fn);
63
+ return mount_js_1.mount.call(this, path, fn);
65
64
  }
66
65
  load(path) {
67
- return (0, mount_js_1.load)(this, path);
66
+ return mount_js_1.load.call(this, path);
68
67
  }
69
68
  loadJS(file) {
70
- const loader = this.jsLoader || (this.jsLoader = new loaders_js_1.JsLoader(this));
69
+ const loader = (this.jsLoader ??= new loaders_js_1.JsLoader(this));
71
70
  return loader.load(file);
72
71
  }
73
72
  loadJSP(file) {
74
- const loader = this.jspLoader || (this.jspLoader = new loaders_js_1.JspLoader(this));
73
+ const loader = (this.jspLoader ??= new loaders_js_1.JspLoader(this));
75
74
  return loader.load(file);
76
75
  }
77
76
  loadFile(file) {
78
- const loader = this.fileLoader || (this.fileLoader = new loaders_js_1.FileLoader(this));
77
+ const loader = (this.fileLoader ??= new loaders_js_1.FileLoader(this));
79
78
  return loader.load(file);
80
79
  }
81
- store(context, key, initFn) {
80
+ store(context, key) {
82
81
  if ("object" !== typeof context && context == null) {
83
82
  throw new Error("Context must be an object");
84
83
  }
85
84
  const { storeKey } = this.options;
86
- const map = context[storeKey] || (context[storeKey] = new Map());
85
+ const map = (context[storeKey] ??= new Map());
87
86
  let value = map.get(key);
88
87
  if (value == null) {
89
- value = initFn();
88
+ value = new stack_store_js_1.StackStore();
90
89
  map.set(key, value);
91
90
  }
92
91
  return value;
93
92
  }
94
93
  }
94
+ exports.App = App;
package/cjs/src/catch.js CHANGED
@@ -19,10 +19,11 @@ const catchFn = (app, fn) => {
19
19
  function errorHandler(e) {
20
20
  // just throw the error if it's already handled
21
21
  if (context != null) {
22
- const data = app.store(context, "error", () => ({}));
23
- if (data.error === e)
22
+ const store = app.store(context, "error");
23
+ if (store.find(err => (err === e))) {
24
24
  throw e;
25
- data.error = e;
25
+ }
26
+ store.open(e);
26
27
  }
27
28
  // call the error hook
28
29
  const result = app.process("error", e, context);
@@ -65,7 +65,7 @@ class JsLoader extends BaseLoader {
65
65
  })(async (file) => {
66
66
  const app = this.appRef.deref();
67
67
  app.log(`loading: ${file}`);
68
- const module = await import(file);
68
+ const module = await Promise.resolve(`${file}`).then(s => require(s));
69
69
  const name = getName(file);
70
70
  const fn = module[name];
71
71
  if (typeof fn !== "function") {
package/cjs/src/mount.js CHANGED
@@ -1,18 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.load = exports.mount = void 0;
4
- const mount = (app, match, fn) => {
4
+ function mount(match, fn) {
5
5
  const test = ("string" !== typeof match) ? match : {
6
6
  test: ((path) => path.startsWith(match))
7
7
  };
8
- app.loaders.push(!test ? fn : path => {
8
+ this.loaders.push(!test ? fn : path => {
9
9
  if (test.test(path))
10
10
  return fn(path);
11
11
  });
12
- };
12
+ }
13
13
  exports.mount = mount;
14
- const load = async (app, path) => {
15
- const { loaders } = app;
14
+ async function load(path) {
15
+ const { loaders } = this;
16
16
  const search = path.replace(/^[^?]*\??/, "");
17
17
  path = path.replace(/\?.*$/, "");
18
18
  path = path.replace(/^\/*/, "/");
@@ -30,5 +30,5 @@ const load = async (app, path) => {
30
30
  }
31
31
  return fn(context);
32
32
  };
33
- };
33
+ }
34
34
  exports.load = load;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Attr = void 0;
4
+ const text_js_1 = require("./text.js");
5
+ const isSafeKey = (key) => /^[A-Za-z_]\w+$/.test(key);
6
+ /**
7
+ * Parser for HTML tag attributes <tagName attr="value"/>
8
+ */
9
+ class Attr {
10
+ constructor(app, src) {
11
+ this.app = app;
12
+ this.src = app.process("before.parse.attr", src) ?? src;
13
+ }
14
+ /**
15
+ * Transpile HTML tag attributes to JavaScript source code
16
+ */
17
+ toJS(option) {
18
+ const { app, src } = this;
19
+ const js = app.process("parse.attr", src) ?? this._toJS(option);
20
+ return app.process("after.parse.attr", js) ?? js;
21
+ }
22
+ keys() {
23
+ return Object.keys(this.getIndex());
24
+ }
25
+ get(key) {
26
+ return this.getIndex()[key];
27
+ }
28
+ getIndex() {
29
+ let { index, src } = this;
30
+ if (index)
31
+ return index;
32
+ index = this.index = {};
33
+ if (!src)
34
+ return index;
35
+ src = src.replace(/^\s*<\S+\s*/s, "");
36
+ src = src.replace(/\s*\/?>\s*$/s, "");
37
+ src.replace(/([^\s='"]+)(\s*=(?:\s*"([^"]*)"|\s*'([^']*)'|([^\s='"]*)))?/g, (_, key, eq, v1, v2, v3) => {
38
+ if (eq) {
39
+ const value = unescapeXML(v1 || v2 || v3 || "");
40
+ index[key] = new text_js_1.Text(this.app, value).toJS({});
41
+ }
42
+ else {
43
+ const value = true;
44
+ index[key] = String(value);
45
+ }
46
+ return "";
47
+ });
48
+ return index;
49
+ }
50
+ /**
51
+ * Transpile HTML tag attributes to JavaScript source code
52
+ */
53
+ _toJS(option) {
54
+ const { app } = this;
55
+ const { indent } = app.options;
56
+ const SP = option?.SP ?? (("string" === typeof indent) ? indent : (+indent ? " ".repeat(+indent) : ""));
57
+ const LF = option?.LF ?? "\n";
58
+ const nextLF = LF + SP;
59
+ const keys = this.keys();
60
+ const items = keys.map(key => {
61
+ if (!isSafeKey(key)) {
62
+ key = JSON.stringify(key);
63
+ }
64
+ const value = this.get(key);
65
+ return `${key}: ${value}`;
66
+ });
67
+ // no arguments
68
+ if (!keys.length)
69
+ return 'null';
70
+ const js = items.join(`,${nextLF}`);
71
+ const trailingComma = (keys.length > 1) ? "," : "";
72
+ return `{${nextLF}${js}${trailingComma}${LF}}`;
73
+ }
74
+ }
75
+ exports.Attr = Attr;
76
+ const UNESCAPE = {
77
+ "&amp;": "&",
78
+ "&lt;": "<",
79
+ "&gt;": ">",
80
+ "&apos;": "'",
81
+ "&quot;": '"'
82
+ };
83
+ const unescapeXML = (str) => {
84
+ return str?.replace(/(&(?:lt|gt|amp|apos|quot|#(?:\d{1,6}|x[0-9a-fA-F]{1,5}));)/g, (str) => {
85
+ if (str[1] === "#") {
86
+ const code = (str[2] === "x") ? parseInt(str.substring(3), 16) : parseInt(str.substr(2), 10);
87
+ if (code > -1)
88
+ return String.fromCharCode(code);
89
+ }
90
+ return UNESCAPE[str] || str;
91
+ });
92
+ };
@@ -1,3 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EL = void 0;
1
4
  const trim = (str) => str.replace(/^\s+/s, "").replace(/\s+$/s, "");
2
5
  const wordMap = {
3
6
  and: "&&",
@@ -30,38 +33,23 @@ const itemRegExp = new RegExp(`(${itemRE})`, "s");
30
33
  /**
31
34
  * Simplified transformer for expression language
32
35
  */
33
- export const parseEL = (app, src) => new ElParser(app, src);
34
- class ElParser {
36
+ class EL {
35
37
  constructor(app, src) {
36
38
  this.app = app;
37
- this.src = src;
38
- //
39
- }
40
- /**
41
- * Compile ${EL} to JavaScript function instance
42
- */
43
- toFn() {
44
- const { app } = this;
45
- const { nspName, vName } = app.options;
46
- const js = this.toJS();
47
- try {
48
- const fn = Function(nspName, vName, `return ${js}`);
49
- return (context) => fn(app, context);
50
- }
51
- catch (e) {
52
- app.log("ElParser: " + js?.substring(0, 1000));
53
- throw e;
54
- }
39
+ src = trim(src);
40
+ this.src = app.process("before.parse.el", src) ?? src;
55
41
  }
56
42
  /**
57
43
  * Transpile ${EL} to JavaScript source code
58
44
  */
59
- toJS(_) {
60
- const { app } = this;
61
- const { nullish, prefilter, postfilter } = app.options;
62
- let src = trim(this.src);
63
- if (prefilter)
64
- src = prefilter(src);
45
+ toJS(option) {
46
+ const { app, src } = this;
47
+ const js = app.process("parse.el", src) ?? this._toJS(option);
48
+ return app.process("after.parse.el", js) ?? js;
49
+ }
50
+ _toJS(_) {
51
+ const { app, src } = this;
52
+ const { nullish } = app.options;
65
53
  if (src == null)
66
54
  return 'null';
67
55
  const array = src.split(itemRegExp);
@@ -98,8 +86,7 @@ class ElParser {
98
86
  }
99
87
  js = `${js} ?? ""`;
100
88
  }
101
- if (postfilter)
102
- js = postfilter(js);
103
89
  return js;
104
90
  }
105
91
  }
92
+ exports.EL = EL;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jspToJS = exports.JSP = void 0;
4
+ const scriptlet_js_1 = require("./scriptlet.js");
5
+ const stack_store_js_1 = require("../stack-store.js");
6
+ const tag_js_1 = require("./tag.js");
7
+ /**
8
+ * Parser for JSP document
9
+ */
10
+ class JSP {
11
+ constructor(app, src) {
12
+ this.app = app;
13
+ this.src = app.process("before.parse.jsp", src) ?? src;
14
+ }
15
+ /**
16
+ * Transpile JSP document to JavaScript source code
17
+ */
18
+ toJS(option) {
19
+ const { app, src } = this;
20
+ const js = app.process("parse.jsp", src) ?? (0, exports.jspToJS)(app, src, option);
21
+ return app.process("after.parse.jsp", js) ?? js;
22
+ }
23
+ /**
24
+ * Compile JSP document to JavaScript function
25
+ */
26
+ toFn() {
27
+ const { app } = this;
28
+ const { nspName } = app.options;
29
+ const js = this.toJS();
30
+ try {
31
+ const fn = Function(nspName, `return ${js}`);
32
+ return fn(app);
33
+ }
34
+ catch (e) {
35
+ app.log("JspParser: " + js?.substring(0, 1000));
36
+ throw e;
37
+ }
38
+ }
39
+ }
40
+ exports.JSP = JSP;
41
+ const nameRE = `[A-Za-z][A-Za-z0-9]*`;
42
+ const stringRE = `"(?:\\\\[.]|[^\\\\"])*"|'(?:\\\\[.]|[^\\\\'])*'`;
43
+ const insideRE = `[^"']|${stringRE}`;
44
+ const tagRegExp = new RegExp(`(</?${nameRE}:(?:${insideRE})*?>)|(<%(?:${insideRE})*?%>)`, "s");
45
+ const jspToJS = (app, src, option) => {
46
+ const root = new tag_js_1.Tag(app);
47
+ const tree = new stack_store_js_1.StackStore(root);
48
+ const array = src.split(tagRegExp);
49
+ for (let i = 0; i < array.length; i++) {
50
+ const i3 = i % 3;
51
+ let str = array[i];
52
+ if (i3 === 1 && str) {
53
+ // taglib
54
+ const tag = new tag_js_1.Tag(app, str);
55
+ // close-tag
56
+ if (tag.isClose()) {
57
+ const closed = tree.close();
58
+ if (!closed) {
59
+ throw new Error(`invalid closing tag: </${tag.tagName}>`);
60
+ }
61
+ if (closed.tagName !== tag.tagName) {
62
+ throw new Error(`invalid closing tag: <${closed.tagName}></${tag.tagName}>`);
63
+ }
64
+ continue;
65
+ }
66
+ tree.get().append(tag);
67
+ // open-tag
68
+ if (tag.isOpen()) {
69
+ tree.open(tag);
70
+ }
71
+ }
72
+ else if (i3 === 2 && str) {
73
+ // <% scriptlet %>
74
+ const item = new scriptlet_js_1.Scriptlet(app, str);
75
+ tree.get().append(item);
76
+ }
77
+ else if (i3 === 0) {
78
+ // text node
79
+ tree.get().append(str);
80
+ }
81
+ }
82
+ const closed = tree.close();
83
+ if (closed !== root) {
84
+ throw new Error(`invalid closing tag: </${closed?.tagName}>`);
85
+ }
86
+ return root.toJS(option);
87
+ };
88
+ exports.jspToJS = jspToJS;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scriptlet = void 0;
4
+ const typeMap = {
5
+ "<%-": "comment",
6
+ "<%@": "directive",
7
+ "<%!": "declaration",
8
+ "<%=": "expression",
9
+ };
10
+ /**
11
+ * Parser for Directive, Declaration, Scriptlet
12
+ * <%-- comment --%>
13
+ * <%@ directive %>
14
+ * <%! declaration(s) %>
15
+ * <% scriptlet %>
16
+ * <%= expression %>
17
+ */
18
+ class Scriptlet {
19
+ constructor(app, src) {
20
+ this.app = app;
21
+ const type = this.type = typeMap[src.substring(0, 3)] || "scriptlet";
22
+ this.src = app.process(`before.parse.${type}`, src) ?? src;
23
+ }
24
+ /**
25
+ * Transpile <% scriptlet %> to JavaScript source code
26
+ */
27
+ toJS(option) {
28
+ const { app, src, type } = this;
29
+ const js = app.process(`parse.${type}`, src) ?? this._toJS(option);
30
+ return app.process(`after.parse.${type}`, js) ?? js;
31
+ }
32
+ _toJS(option) {
33
+ const { app, type } = this;
34
+ const { nspName, vName } = app.options;
35
+ const currentLF = option?.LF ?? "\n";
36
+ let { src } = this;
37
+ if (type === "comment") {
38
+ src = src.replace(/[ \t]*[\r\n]+/sg, `${currentLF}// `);
39
+ return `// ${src}`;
40
+ }
41
+ app.log(`${type} found: ${src?.substring(0, 1000)}`);
42
+ src = /`|\$\{/.test(src) ? JSON.stringify(src) : "`" + src + "`";
43
+ src = `${vName} => ${nspName}.process("${type}", ${src}, ${vName})`;
44
+ return src;
45
+ }
46
+ }
47
+ exports.Scriptlet = Scriptlet;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Tag = void 0;
4
+ const attr_js_1 = require("./attr.js");
5
+ const text_js_1 = require("./text.js");
6
+ const emptyText = {
7
+ '""': true,
8
+ "''": true,
9
+ "``": true,
10
+ "null": true,
11
+ "undefined": true,
12
+ "": true,
13
+ };
14
+ const isTranspiler = (v) => ("function" === typeof v?.toJS);
15
+ /**
16
+ * Root node or an taglib node
17
+ */
18
+ class Tag {
19
+ constructor(app, src) {
20
+ this.app = app;
21
+ this.src = src;
22
+ this.children = [];
23
+ this.tagName = src?.match(/^<\/?([^\s=/>]+)/)?.[1];
24
+ }
25
+ append(node) {
26
+ this.children.push(node);
27
+ }
28
+ isOpen() {
29
+ return !/\/\s*>$/.test(this.src);
30
+ }
31
+ isClose() {
32
+ return /^<\//.test(this.src);
33
+ }
34
+ getBodyJS(option) {
35
+ const { app } = this;
36
+ const { indent, trimSpaces, vName } = app.options;
37
+ const SP = option?.SP ?? (("string" === typeof indent) ? indent : (+indent ? " ".repeat(+indent) : ""));
38
+ const LF = option?.LF ?? "\n";
39
+ const nextLF = LF + SP;
40
+ const nextOption = { SP, LF: nextLF };
41
+ const { children } = this;
42
+ const args = children.map(item => {
43
+ if (isTranspiler(item)) {
44
+ return item.toJS(nextOption);
45
+ }
46
+ else if (!/\S/.test(item)) {
47
+ // item with only whitespace
48
+ return (trimSpaces !== false) ? '""' : JSON.stringify(item);
49
+ }
50
+ else {
51
+ if (trimSpaces !== false) {
52
+ item = item.replace(/^\s*[\r\n]/s, "\n");
53
+ item = item.replace(/\s*[\r\n]\s*$/s, "\n");
54
+ item = item.replace(/^[ \t]+/s, " ");
55
+ item = item.replace(/[ \t]+$/s, " ");
56
+ }
57
+ let js = new text_js_1.Text(app, item).toJS(nextOption);
58
+ if (/\(.+?\)|\$\{.+?}/s.test(js)) {
59
+ js = `${vName} => ${js}`; // array function
60
+ }
61
+ return js;
62
+ }
63
+ }).filter(v => !emptyText[v]);
64
+ // empty body
65
+ if (!children.length) {
66
+ return "";
67
+ }
68
+ // keep a single empty string at least if all arguments are trimmed
69
+ if (!args.length) {
70
+ args.push('""');
71
+ }
72
+ const last = args.length - 1;
73
+ args.forEach((v, idx) => {
74
+ const isComment = /^\/\/[^\n]*$/s.test(v);
75
+ if (idx !== last && !isComment) {
76
+ args[idx] += ",";
77
+ }
78
+ else if (idx === last && isComment) {
79
+ args[idx] += LF;
80
+ }
81
+ });
82
+ const bodyL = /^`\n/s.test(args.at(0)) ? "" : nextLF;
83
+ const bodyR = /(\n`|[)\s])$/s.test(args.at(-1)) ? "" : LF;
84
+ return bodyL + args.join(nextLF) + bodyR;
85
+ }
86
+ /**
87
+ * Transpile JSP document to JavaScript source code
88
+ */
89
+ toJS(option) {
90
+ const { app, tagName } = this;
91
+ const { indent, nspName } = app.options;
92
+ // root element
93
+ if (!tagName) {
94
+ const bodyJS = this.getBodyJS(option);
95
+ return `${nspName}.bundle(${bodyJS})`; // root element
96
+ }
97
+ if (this.isClose())
98
+ return; // invalid
99
+ const commentJS = this.getCommentJS(option);
100
+ const attr = new attr_js_1.Attr(app, this.src);
101
+ const body = this.getBodyJS(option);
102
+ const SP = option?.SP ?? (("string" === typeof indent) ? indent : (+indent ? " ".repeat(+indent) : ""));
103
+ const LF = option?.LF ?? "\n";
104
+ const nextLF = LF + SP;
105
+ const nextOption = { SP, LF: (body ? nextLF : LF) };
106
+ const type = `parse.tag.${tagName}`;
107
+ const def = { app, name: tagName, attr, body, LF: LF, nextLF };
108
+ const tagJS = app.process(type, def) ?? this.getTagJS(def, nextOption);
109
+ return commentJS ? commentJS + tagJS : tagJS;
110
+ }
111
+ getCommentJS(option) {
112
+ const { app, src } = this;
113
+ if (!app.options.comment)
114
+ return;
115
+ const currentLF = option?.LF ?? "\n";
116
+ return `// ${src?.replace(/\s*[\r\n]\s*/g, " ") ?? ""}${currentLF}`;
117
+ }
118
+ getTagJS(def, option) {
119
+ const { app, tagName } = this;
120
+ const { nspName, vName } = app.options;
121
+ const attrRaw = def.attr.toJS(option);
122
+ // transpile attributes to array function if they include variables
123
+ const hasVars = /\(.+?\)|\$\{.+?}/s.test(attrRaw);
124
+ const attrArg = hasVars ? `, ${vName} => (${attrRaw})` : `, ${attrRaw}`;
125
+ const nameJS = JSON.stringify(tagName);
126
+ const hasAttr = /:/.test(attrRaw);
127
+ let bodyArg = def.body;
128
+ if (bodyArg)
129
+ bodyArg = ((/^\n/.test(bodyArg)) ? "," : ", ") + bodyArg;
130
+ const restJS = bodyArg ? (attrArg + bodyArg) : (hasAttr ? attrArg : "");
131
+ return `${nspName}.tag(${nameJS}${restJS})`;
132
+ }
133
+ }
134
+ exports.Tag = Tag;