nsp-server-pages 0.1.1 → 0.2.0
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/cjs/index.js +6 -1
- package/cjs/package.json +1 -0
- package/cjs/src/app.js +20 -20
- package/cjs/src/catch.js +10 -9
- 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 +130 -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 +19 -19
- package/{src → esm/src}/catch.js +10 -9
- 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 +126 -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 -40
- 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
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Text = void 0;
|
|
4
|
+
const el_js_1 = require("./el.js");
|
|
5
|
+
const scriptlet_js_1 = require("./scriptlet.js");
|
|
3
6
|
/**
|
|
4
7
|
* escape special characters in Template Literal
|
|
5
8
|
*/
|
|
@@ -16,36 +19,21 @@ const bodyRegExp = new RegExp(bodyRE, "s");
|
|
|
16
19
|
/**
|
|
17
20
|
* Parser for: text content
|
|
18
21
|
*/
|
|
19
|
-
|
|
20
|
-
class TextParser {
|
|
22
|
+
class Text {
|
|
21
23
|
constructor(app, src) {
|
|
22
24
|
this.app = app;
|
|
23
|
-
this.src = src;
|
|
24
|
-
//
|
|
25
|
+
this.src = app.process("before.parse.text", src) ?? src;
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Transpile ${expression} and <% scriptlet %> to JavaScript source code
|
|
28
29
|
*/
|
|
29
30
|
toJS(option) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* Compile ${expression} and <% scriptlet %> to JavaScript function instance
|
|
34
|
-
*/
|
|
35
|
-
toFn() {
|
|
36
|
-
const { app } = this;
|
|
37
|
-
const { nspName, vName } = app.options;
|
|
38
|
-
const js = this.toJS();
|
|
39
|
-
try {
|
|
40
|
-
const fn = Function(nspName, vName, `return ${js}`);
|
|
41
|
-
return (context) => fn(app, context);
|
|
42
|
-
}
|
|
43
|
-
catch (e) {
|
|
44
|
-
app.log("TextParser: " + js?.substring(0, 1000));
|
|
45
|
-
throw e;
|
|
46
|
-
}
|
|
31
|
+
const { app, src } = this;
|
|
32
|
+
const js = app.process("parse.text", src) ?? textToJS(app, src, option);
|
|
33
|
+
return app.process("after.parse.text", js) ?? js;
|
|
47
34
|
}
|
|
48
35
|
}
|
|
36
|
+
exports.Text = Text;
|
|
49
37
|
/**
|
|
50
38
|
* Transpile ${expression}, #{async expression} and <% scriptlet %> to JavaScript source code
|
|
51
39
|
*/
|
|
@@ -62,7 +50,7 @@ const textToJS = (app, src, option) => {
|
|
|
62
50
|
const isAsync = /^#/s.test(value);
|
|
63
51
|
value = value.replace(/^[$#]\{\s*/s, "");
|
|
64
52
|
value = value.replace(/\s*}$/s, "");
|
|
65
|
-
const item =
|
|
53
|
+
const item = new el_js_1.EL(app, value);
|
|
66
54
|
if (isAsync) {
|
|
67
55
|
items.push({ toJS: (option) => `await ${item.toJS(option)}` });
|
|
68
56
|
}
|
|
@@ -72,7 +60,7 @@ const textToJS = (app, src, option) => {
|
|
|
72
60
|
}
|
|
73
61
|
else if (i3 === 2) {
|
|
74
62
|
// <% scriptlet %>
|
|
75
|
-
const item =
|
|
63
|
+
const item = new scriptlet_js_1.Scriptlet(app, value);
|
|
76
64
|
items.push(item);
|
|
77
65
|
}
|
|
78
66
|
else {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StackStore = void 0;
|
|
4
|
+
class StackStore {
|
|
5
|
+
constructor(value) {
|
|
6
|
+
this.stack = [];
|
|
7
|
+
if (arguments.length) {
|
|
8
|
+
this.set(value);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
open(value) {
|
|
12
|
+
this.stack.unshift(value);
|
|
13
|
+
}
|
|
14
|
+
close() {
|
|
15
|
+
return this.stack.shift();
|
|
16
|
+
}
|
|
17
|
+
get() {
|
|
18
|
+
return this.stack[0];
|
|
19
|
+
}
|
|
20
|
+
set(value) {
|
|
21
|
+
this.stack[0] = value;
|
|
22
|
+
}
|
|
23
|
+
find(test) {
|
|
24
|
+
for (const data of this.stack) {
|
|
25
|
+
if (test(data)) {
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.StackStore = StackStore;
|
package/cjs/src/taglib.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.prepareTag = exports.addTagLib = void 0;
|
|
4
4
|
const to_xml_1 = require("to-xml");
|
|
5
|
-
|
|
6
|
-
const { fnMap, tagMap } =
|
|
5
|
+
function addTagLib(tagLibDef) {
|
|
6
|
+
const { fnMap, tagMap } = this;
|
|
7
7
|
const { ns, fn, tag } = tagLibDef;
|
|
8
8
|
if (fn) {
|
|
9
9
|
for (const name in fn) {
|
|
@@ -15,15 +15,15 @@ const addTagLib = (app, tagLibDef) => {
|
|
|
15
15
|
tagMap.set(`${ns}:${name}`, tag[name]);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
}
|
|
18
|
+
}
|
|
19
19
|
exports.addTagLib = addTagLib;
|
|
20
|
-
|
|
21
|
-
const { tagMap } =
|
|
20
|
+
function prepareTag(name, attr, body) {
|
|
21
|
+
const { tagMap } = this;
|
|
22
22
|
const tagFn = tagMap.get(name) || defaultTagFn;
|
|
23
23
|
const attrFn = !attr ? () => ({}) : (typeof attr !== "function") ? () => attr : attr;
|
|
24
|
-
const tagDef = { name, app, attr: attrFn, body };
|
|
24
|
+
const tagDef = { name, app: this, attr: attrFn, body };
|
|
25
25
|
return tagFn(tagDef);
|
|
26
|
-
}
|
|
26
|
+
}
|
|
27
27
|
exports.prepareTag = prepareTag;
|
|
28
28
|
const defaultTagFn = (tagDef) => {
|
|
29
29
|
const { name } = tagDef;
|
package/esm/index.js
ADDED
package/esm/package.json
ADDED
package/{src → esm/src}/app.js
RENAMED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { load, mount } from "./mount.js";
|
|
2
2
|
import { FileLoader, JsLoader, JspLoader } from "./loaders.js";
|
|
3
|
-
import {
|
|
3
|
+
import { JSP } from "./parser/jsp.js";
|
|
4
4
|
import { catchFn } from "./catch.js";
|
|
5
5
|
import { bundle } from "./bundle.js";
|
|
6
6
|
import { addTagLib, prepareTag } from "./taglib.js";
|
|
7
7
|
import { concat } from "./concat.js";
|
|
8
|
-
|
|
9
|
-
class App {
|
|
8
|
+
import { StackStore } from "./stack-store.js";
|
|
9
|
+
export class App {
|
|
10
10
|
constructor(options) {
|
|
11
11
|
this.loaders = [];
|
|
12
12
|
this.tagMap = new Map();
|
|
13
13
|
this.fnMap = new Map();
|
|
14
|
-
this.
|
|
14
|
+
this.hooks = new Map();
|
|
15
15
|
this.options = options = Object.create(options || null);
|
|
16
16
|
if (!options.vName)
|
|
17
17
|
options.vName = "v";
|
|
@@ -21,12 +21,12 @@ class App {
|
|
|
21
21
|
options.storeKey = "#nsp";
|
|
22
22
|
}
|
|
23
23
|
hook(type, fn) {
|
|
24
|
-
this.
|
|
24
|
+
this.hooks.set(type, fn);
|
|
25
25
|
}
|
|
26
|
-
process(type,
|
|
27
|
-
const fn = this.
|
|
26
|
+
process(type, ...args) {
|
|
27
|
+
const fn = this.hooks.get(type);
|
|
28
28
|
if (fn)
|
|
29
|
-
return fn(
|
|
29
|
+
return fn.apply(this, args);
|
|
30
30
|
}
|
|
31
31
|
log(message) {
|
|
32
32
|
const logger = this.options.logger || console;
|
|
@@ -42,11 +42,11 @@ class App {
|
|
|
42
42
|
return fn;
|
|
43
43
|
}
|
|
44
44
|
addTagLib(tagLibDef) {
|
|
45
|
-
addTagLib(this, tagLibDef);
|
|
45
|
+
addTagLib.call(this, tagLibDef);
|
|
46
46
|
}
|
|
47
47
|
tag(name, attr, ..._) {
|
|
48
48
|
const bodyFn = bundle(arguments, 2);
|
|
49
|
-
const tagFn = prepareTag(this, name, attr, bodyFn);
|
|
49
|
+
const tagFn = prepareTag.call(this, name, attr, bodyFn);
|
|
50
50
|
return catchFn(this, tagFn);
|
|
51
51
|
}
|
|
52
52
|
bundle(..._) {
|
|
@@ -54,35 +54,35 @@ class App {
|
|
|
54
54
|
return catchFn(this, fn);
|
|
55
55
|
}
|
|
56
56
|
parse(src) {
|
|
57
|
-
return
|
|
57
|
+
return new JSP(this, src);
|
|
58
58
|
}
|
|
59
59
|
mount(path, fn) {
|
|
60
|
-
return mount(this, path, fn);
|
|
60
|
+
return mount.call(this, path, fn);
|
|
61
61
|
}
|
|
62
62
|
load(path) {
|
|
63
|
-
return load(this, path);
|
|
63
|
+
return load.call(this, path);
|
|
64
64
|
}
|
|
65
65
|
loadJS(file) {
|
|
66
|
-
const loader =
|
|
66
|
+
const loader = (this.jsLoader ??= new JsLoader(this));
|
|
67
67
|
return loader.load(file);
|
|
68
68
|
}
|
|
69
69
|
loadJSP(file) {
|
|
70
|
-
const loader =
|
|
70
|
+
const loader = (this.jspLoader ??= new JspLoader(this));
|
|
71
71
|
return loader.load(file);
|
|
72
72
|
}
|
|
73
73
|
loadFile(file) {
|
|
74
|
-
const loader =
|
|
74
|
+
const loader = (this.fileLoader ??= new FileLoader(this));
|
|
75
75
|
return loader.load(file);
|
|
76
76
|
}
|
|
77
|
-
store(context, key
|
|
77
|
+
store(context, key) {
|
|
78
78
|
if ("object" !== typeof context && context == null) {
|
|
79
79
|
throw new Error("Context must be an object");
|
|
80
80
|
}
|
|
81
81
|
const { storeKey } = this.options;
|
|
82
|
-
const map =
|
|
82
|
+
const map = (context[storeKey] ??= new Map());
|
|
83
83
|
let value = map.get(key);
|
|
84
84
|
if (value == null) {
|
|
85
|
-
value =
|
|
85
|
+
value = new StackStore();
|
|
86
86
|
map.set(key, value);
|
|
87
87
|
}
|
|
88
88
|
return value;
|
package/{src → esm/src}/catch.js
RENAMED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { toXML } from "to-xml";
|
|
2
1
|
const isPromise = (v) => v && (typeof v.catch === "function");
|
|
3
|
-
const escapeError = (e) => toXML({ "#": (e?.message || String(e)) });
|
|
4
2
|
export const catchFn = (app, fn) => {
|
|
5
3
|
return context => {
|
|
6
4
|
try {
|
|
@@ -18,16 +16,19 @@ export const catchFn = (app, fn) => {
|
|
|
18
16
|
function errorHandler(e) {
|
|
19
17
|
// just throw the error if it's already handled
|
|
20
18
|
if (context != null) {
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
19
|
+
const store = app.store(context, "error");
|
|
20
|
+
if (store.find(err => (err === e))) {
|
|
23
21
|
throw e;
|
|
24
|
-
|
|
22
|
+
}
|
|
23
|
+
store.open(e);
|
|
25
24
|
}
|
|
26
|
-
// call the error
|
|
25
|
+
// call the error hook
|
|
27
26
|
const result = app.process("error", e, context);
|
|
28
|
-
if
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
// if the hook returns nothing, throw the error
|
|
28
|
+
if (result == null)
|
|
29
|
+
throw e;
|
|
30
|
+
// if the hook returns a string, show it
|
|
31
|
+
return result;
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
34
|
};
|
package/{src → esm/src}/mount.js
RENAMED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
export
|
|
1
|
+
export function mount(match, fn) {
|
|
2
2
|
const test = ("string" !== typeof match) ? match : {
|
|
3
3
|
test: ((path) => path.startsWith(match))
|
|
4
4
|
};
|
|
5
|
-
|
|
5
|
+
this.loaders.push(!test ? fn : path => {
|
|
6
6
|
if (test.test(path))
|
|
7
7
|
return fn(path);
|
|
8
8
|
});
|
|
9
|
-
}
|
|
10
|
-
export
|
|
11
|
-
const { loaders } =
|
|
9
|
+
}
|
|
10
|
+
export async function load(path) {
|
|
11
|
+
const { loaders } = this;
|
|
12
12
|
const search = path.replace(/^[^?]*\??/, "");
|
|
13
13
|
path = path.replace(/\?.*$/, "");
|
|
14
14
|
path = path.replace(/^\/*/, "/");
|
|
@@ -26,4 +26,4 @@ export const load = async (app, path) => {
|
|
|
26
26
|
}
|
|
27
27
|
return fn(context);
|
|
28
28
|
};
|
|
29
|
-
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Text } from "./text.js";
|
|
2
|
+
const isSafeKey = (key) => /^[A-Za-z_]\w+$/.test(key);
|
|
3
|
+
/**
|
|
4
|
+
* Parser for HTML tag attributes <tagName attr="value"/>
|
|
5
|
+
*/
|
|
6
|
+
export class Attr {
|
|
7
|
+
constructor(app, src) {
|
|
8
|
+
this.app = app;
|
|
9
|
+
this.src = app.process("before.parse.attr", src) ?? src;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Transpile HTML tag attributes to JavaScript source code
|
|
13
|
+
*/
|
|
14
|
+
toJS(option) {
|
|
15
|
+
const { app, src } = this;
|
|
16
|
+
const js = app.process("parse.attr", src) ?? this._toJS(option);
|
|
17
|
+
return app.process("after.parse.attr", js) ?? js;
|
|
18
|
+
}
|
|
19
|
+
keys() {
|
|
20
|
+
return Object.keys(this.getIndex());
|
|
21
|
+
}
|
|
22
|
+
get(key) {
|
|
23
|
+
return this.getIndex()[key];
|
|
24
|
+
}
|
|
25
|
+
getIndex() {
|
|
26
|
+
let { index, src } = this;
|
|
27
|
+
if (index)
|
|
28
|
+
return index;
|
|
29
|
+
index = this.index = {};
|
|
30
|
+
if (!src)
|
|
31
|
+
return index;
|
|
32
|
+
src = src.replace(/^\s*<\S+\s*/s, "");
|
|
33
|
+
src = src.replace(/\s*\/?>\s*$/s, "");
|
|
34
|
+
src.replace(/([^\s='"]+)(\s*=(?:\s*"([^"]*)"|\s*'([^']*)'|([^\s='"]*)))?/g, (_, key, eq, v1, v2, v3) => {
|
|
35
|
+
if (eq) {
|
|
36
|
+
const value = unescapeXML(v1 || v2 || v3 || "");
|
|
37
|
+
index[key] = new Text(this.app, value).toJS({});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const value = true;
|
|
41
|
+
index[key] = String(value);
|
|
42
|
+
}
|
|
43
|
+
return "";
|
|
44
|
+
});
|
|
45
|
+
return index;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Transpile HTML tag attributes to JavaScript source code
|
|
49
|
+
*/
|
|
50
|
+
_toJS(option) {
|
|
51
|
+
const { app } = this;
|
|
52
|
+
const { indent } = app.options;
|
|
53
|
+
const spaces = +indent ? " ".repeat(+indent) : (indent ?? "");
|
|
54
|
+
const currentLF = option?.LF ?? "\n";
|
|
55
|
+
const nextLF = currentLF + spaces;
|
|
56
|
+
const keys = this.keys();
|
|
57
|
+
const items = keys.map(key => {
|
|
58
|
+
if (!isSafeKey(key)) {
|
|
59
|
+
key = JSON.stringify(key);
|
|
60
|
+
}
|
|
61
|
+
const value = this.get(key);
|
|
62
|
+
return `${key}: ${value}`;
|
|
63
|
+
});
|
|
64
|
+
// no arguments
|
|
65
|
+
if (!keys.length)
|
|
66
|
+
return 'null';
|
|
67
|
+
const js = items.join(`,${nextLF}`);
|
|
68
|
+
const trailingComma = (keys.length > 1) ? "," : "";
|
|
69
|
+
return `{${nextLF}${js}${trailingComma}${currentLF}}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const UNESCAPE = {
|
|
73
|
+
"&": "&",
|
|
74
|
+
"<": "<",
|
|
75
|
+
">": ">",
|
|
76
|
+
"'": "'",
|
|
77
|
+
""": '"'
|
|
78
|
+
};
|
|
79
|
+
const unescapeXML = (str) => {
|
|
80
|
+
return str?.replace(/(&(?:lt|gt|amp|apos|quot|#(?:\d{1,6}|x[0-9a-fA-F]{1,5}));)/g, (str) => {
|
|
81
|
+
if (str[1] === "#") {
|
|
82
|
+
const code = (str[2] === "x") ? parseInt(str.substring(3), 16) : parseInt(str.substr(2), 10);
|
|
83
|
+
if (code > -1)
|
|
84
|
+
return String.fromCharCode(code);
|
|
85
|
+
}
|
|
86
|
+
return UNESCAPE[str] || str;
|
|
87
|
+
});
|
|
88
|
+
};
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseEL = void 0;
|
|
4
1
|
const trim = (str) => str.replace(/^\s+/s, "").replace(/\s+$/s, "");
|
|
5
2
|
const wordMap = {
|
|
6
3
|
and: "&&",
|
|
@@ -33,39 +30,23 @@ const itemRegExp = new RegExp(`(${itemRE})`, "s");
|
|
|
33
30
|
/**
|
|
34
31
|
* Simplified transformer for expression language
|
|
35
32
|
*/
|
|
36
|
-
|
|
37
|
-
exports.parseEL = parseEL;
|
|
38
|
-
class ElParser {
|
|
33
|
+
export class EL {
|
|
39
34
|
constructor(app, src) {
|
|
40
35
|
this.app = app;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Compile ${EL} to JavaScript function instance
|
|
46
|
-
*/
|
|
47
|
-
toFn() {
|
|
48
|
-
const { app } = this;
|
|
49
|
-
const { nspName, vName } = app.options;
|
|
50
|
-
const js = this.toJS();
|
|
51
|
-
try {
|
|
52
|
-
const fn = Function(nspName, vName, `return ${js}`);
|
|
53
|
-
return (context) => fn(app, context);
|
|
54
|
-
}
|
|
55
|
-
catch (e) {
|
|
56
|
-
app.log("ElParser: " + js?.substring(0, 1000));
|
|
57
|
-
throw e;
|
|
58
|
-
}
|
|
36
|
+
src = trim(src);
|
|
37
|
+
this.src = app.process("before.parse.el", src) ?? src;
|
|
59
38
|
}
|
|
60
39
|
/**
|
|
61
40
|
* Transpile ${EL} to JavaScript source code
|
|
62
41
|
*/
|
|
63
|
-
toJS(
|
|
64
|
-
const { app } = this;
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
42
|
+
toJS(option) {
|
|
43
|
+
const { app, src } = this;
|
|
44
|
+
const js = app.process("parse.el", src) ?? this._toJS(option);
|
|
45
|
+
return app.process("after.parse.el", js) ?? js;
|
|
46
|
+
}
|
|
47
|
+
_toJS(_) {
|
|
48
|
+
const { app, src } = this;
|
|
49
|
+
const { nullish } = app.options;
|
|
69
50
|
if (src == null)
|
|
70
51
|
return 'null';
|
|
71
52
|
const array = src.split(itemRegExp);
|
|
@@ -102,8 +83,6 @@ class ElParser {
|
|
|
102
83
|
}
|
|
103
84
|
js = `${js} ?? ""`;
|
|
104
85
|
}
|
|
105
|
-
if (postfilter)
|
|
106
|
-
js = postfilter(js);
|
|
107
86
|
return js;
|
|
108
87
|
}
|
|
109
88
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Scriptlet } from "./scriptlet.js";
|
|
2
|
+
import { StackStore } from "../stack-store.js";
|
|
3
|
+
import { Tag } from "./tag.js";
|
|
4
|
+
/**
|
|
5
|
+
* Parser for JSP document
|
|
6
|
+
*/
|
|
7
|
+
export class JSP {
|
|
8
|
+
constructor(app, src) {
|
|
9
|
+
this.app = app;
|
|
10
|
+
this.src = app.process("before.parse.jsp", src) ?? src;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Transpile JSP document to JavaScript source code
|
|
14
|
+
*/
|
|
15
|
+
toJS(option) {
|
|
16
|
+
const { app, src } = this;
|
|
17
|
+
const js = app.process("parse.jsp", src) ?? jspToJS(app, src, option);
|
|
18
|
+
return app.process("after.parse.jsp", js) ?? js;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compile JSP document to JavaScript function
|
|
22
|
+
*/
|
|
23
|
+
toFn() {
|
|
24
|
+
const { app } = this;
|
|
25
|
+
const { nspName } = app.options;
|
|
26
|
+
const js = this.toJS();
|
|
27
|
+
try {
|
|
28
|
+
const fn = Function(nspName, `return ${js}`);
|
|
29
|
+
return fn(app);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
app.log("JspParser: " + js?.substring(0, 1000));
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const nameRE = `[A-Za-z][A-Za-z0-9]*`;
|
|
38
|
+
const stringRE = `"(?:\\\\[.]|[^\\\\"])*"|'(?:\\\\[.]|[^\\\\'])*'`;
|
|
39
|
+
const insideRE = `[^"']|${stringRE}`;
|
|
40
|
+
const tagRegExp = new RegExp(`(</?${nameRE}:(?:${insideRE})*?>)|(<%(?:${insideRE})*?%>)`, "s");
|
|
41
|
+
export const jspToJS = (app, src, option) => {
|
|
42
|
+
const root = new Tag(app);
|
|
43
|
+
const tree = new StackStore(root);
|
|
44
|
+
const array = src.split(tagRegExp);
|
|
45
|
+
for (let i = 0; i < array.length; i++) {
|
|
46
|
+
const i3 = i % 3;
|
|
47
|
+
let str = array[i];
|
|
48
|
+
if (i3 === 1 && str) {
|
|
49
|
+
// taglib
|
|
50
|
+
const tag = new Tag(app, str);
|
|
51
|
+
// close-tag
|
|
52
|
+
if (tag.isClose()) {
|
|
53
|
+
const closed = tree.close();
|
|
54
|
+
if (!closed) {
|
|
55
|
+
throw new Error(`invalid closing tag: </${tag.tagName}>`);
|
|
56
|
+
}
|
|
57
|
+
if (closed.tagName !== tag.tagName) {
|
|
58
|
+
throw new Error(`invalid closing tag: <${closed.tagName}></${tag.tagName}>`);
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
tree.get().append(tag);
|
|
63
|
+
// open-tag
|
|
64
|
+
if (tag.isOpen()) {
|
|
65
|
+
tree.open(tag);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (i3 === 2 && str) {
|
|
69
|
+
// <% scriptlet %>
|
|
70
|
+
const item = new Scriptlet(app, str);
|
|
71
|
+
tree.get().append(item);
|
|
72
|
+
}
|
|
73
|
+
else if (i3 === 0) {
|
|
74
|
+
// text node
|
|
75
|
+
tree.get().append(str);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const closed = tree.close();
|
|
79
|
+
if (closed !== root) {
|
|
80
|
+
throw new Error(`invalid closing tag: </${closed?.tagName}>`);
|
|
81
|
+
}
|
|
82
|
+
return root.toJS(option);
|
|
83
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const typeMap = {
|
|
2
|
+
"<%-": "comment",
|
|
3
|
+
"<%@": "directive",
|
|
4
|
+
"<%!": "declaration",
|
|
5
|
+
"<%=": "expression",
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Parser for Directive, Declaration, Scriptlet
|
|
9
|
+
* <%-- comment --%>
|
|
10
|
+
* <%@ directive %>
|
|
11
|
+
* <%! declaration(s) %>
|
|
12
|
+
* <% scriptlet %>
|
|
13
|
+
* <%= expression %>
|
|
14
|
+
*/
|
|
15
|
+
export class Scriptlet {
|
|
16
|
+
constructor(app, src) {
|
|
17
|
+
this.app = app;
|
|
18
|
+
const type = this.type = typeMap[src.substring(0, 3)] || "scriptlet";
|
|
19
|
+
this.src = app.process(`before.parse.${type}`, src) ?? src;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Transpile <% scriptlet %> to JavaScript source code
|
|
23
|
+
*/
|
|
24
|
+
toJS(option) {
|
|
25
|
+
const { app, src, type } = this;
|
|
26
|
+
const js = app.process(`parse.${type}`, src) ?? this._toJS(option);
|
|
27
|
+
return app.process(`after.parse.${type}`, js) ?? js;
|
|
28
|
+
}
|
|
29
|
+
_toJS(option) {
|
|
30
|
+
const { app, type } = this;
|
|
31
|
+
const { nspName, vName } = app.options;
|
|
32
|
+
const currentLF = option?.LF ?? "\n";
|
|
33
|
+
let { src } = this;
|
|
34
|
+
if (type === "comment") {
|
|
35
|
+
src = src.replace(/[ \t]*[\r\n]+/sg, `${currentLF}// `);
|
|
36
|
+
return `// ${src}`;
|
|
37
|
+
}
|
|
38
|
+
app.log(`${type} found: ${src?.substring(0, 1000)}`);
|
|
39
|
+
src = /`|\$\{/.test(src) ? JSON.stringify(src) : "`" + src + "`";
|
|
40
|
+
src = `${vName} => ${nspName}.process("${type}", ${src}, ${vName})`;
|
|
41
|
+
return src;
|
|
42
|
+
}
|
|
43
|
+
}
|