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.
- package/README.md +2 -1
- package/cjs/index.js +6 -1
- package/cjs/package.json +1 -0
- package/cjs/src/app.js +17 -17
- package/cjs/src/catch.js +4 -3
- package/cjs/src/loaders.js +1 -1
- package/cjs/src/mount.js +6 -6
- package/cjs/src/parser/attr.js +92 -0
- package/{src/parse-el.js → cjs/src/parser/el.js} +15 -28
- package/cjs/src/parser/jsp.js +88 -0
- package/cjs/src/parser/scriptlet.js +47 -0
- package/cjs/src/parser/tag.js +134 -0
- package/{src/parse-text.js → cjs/src/parser/text.js} +13 -25
- package/cjs/src/stack-store.js +31 -0
- package/cjs/src/taglib.js +7 -7
- package/esm/index.js +2 -0
- package/esm/package.json +4 -0
- package/{src → esm/src}/app.js +16 -16
- package/{src → esm/src}/catch.js +4 -3
- package/{src → esm/src}/mount.js +6 -6
- package/esm/src/parser/attr.js +88 -0
- package/{cjs/src/parse-el.js → esm/src/parser/el.js} +11 -32
- package/esm/src/parser/jsp.js +83 -0
- package/esm/src/parser/scriptlet.js +43 -0
- package/esm/src/parser/tag.js +130 -0
- package/{cjs/src/parse-text.js → esm/src/parser/text.js} +9 -29
- package/esm/src/stack-store.js +27 -0
- package/{src → esm/src}/taglib.js +7 -7
- package/package.json +14 -13
- package/types/hooks.d.ts +103 -0
- package/{index.d.ts → types/index.d.ts} +62 -35
- package/cjs/src/parse-attr.js +0 -91
- package/cjs/src/parse-jsp.js +0 -206
- package/cjs/src/parse-scriptlet.js +0 -71
- package/index.js +0 -1
- package/src/parse-attr.js +0 -87
- package/src/parse-jsp.js +0 -201
- package/src/parse-scriptlet.js +0 -67
- /package/{src → esm/src}/bundle.js +0 -0
- /package/{src → esm/src}/concat.js +0 -0
- /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
|
|
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
|
-
|
|
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
package/cjs/src/app.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
|
60
|
+
return new jsp_js_1.JSP(this, src);
|
|
62
61
|
}
|
|
63
62
|
mount(path, fn) {
|
|
64
|
-
return
|
|
63
|
+
return mount_js_1.mount.call(this, path, fn);
|
|
65
64
|
}
|
|
66
65
|
load(path) {
|
|
67
|
-
return
|
|
66
|
+
return mount_js_1.load.call(this, path);
|
|
68
67
|
}
|
|
69
68
|
loadJS(file) {
|
|
70
|
-
const loader =
|
|
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 =
|
|
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 =
|
|
77
|
+
const loader = (this.fileLoader ??= new loaders_js_1.FileLoader(this));
|
|
79
78
|
return loader.load(file);
|
|
80
79
|
}
|
|
81
|
-
store(context, key
|
|
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 =
|
|
85
|
+
const map = (context[storeKey] ??= new Map());
|
|
87
86
|
let value = map.get(key);
|
|
88
87
|
if (value == null) {
|
|
89
|
-
value =
|
|
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
|
|
23
|
-
if (
|
|
22
|
+
const store = app.store(context, "error");
|
|
23
|
+
if (store.find(err => (err === e))) {
|
|
24
24
|
throw e;
|
|
25
|
-
|
|
25
|
+
}
|
|
26
|
+
store.open(e);
|
|
26
27
|
}
|
|
27
28
|
// call the error hook
|
|
28
29
|
const result = app.process("error", e, context);
|
package/cjs/src/loaders.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
4
|
+
function mount(match, fn) {
|
|
5
5
|
const test = ("string" !== typeof match) ? match : {
|
|
6
6
|
test: ((path) => path.startsWith(match))
|
|
7
7
|
};
|
|
8
|
-
|
|
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
|
-
|
|
15
|
-
const { loaders } =
|
|
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
|
+
"&": "&",
|
|
78
|
+
"<": "<",
|
|
79
|
+
">": ">",
|
|
80
|
+
"'": "'",
|
|
81
|
+
""": '"'
|
|
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
|
-
|
|
34
|
-
class ElParser {
|
|
36
|
+
class EL {
|
|
35
37
|
constructor(app, src) {
|
|
36
38
|
this.app = app;
|
|
37
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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;
|