@codady/utils 0.0.36 → 0.0.38
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 +36 -1
- package/dist/utils.cjs.js +191 -18
- package/dist/utils.cjs.min.js +3 -3
- package/dist/utils.esm.js +191 -18
- package/dist/utils.esm.min.js +3 -3
- package/dist/utils.umd.js +191 -18
- package/dist/utils.umd.min.js +3 -3
- package/dist.zip +0 -0
- package/examples/escapeHTML.html +140 -0
- package/examples/renderTpl.html +272 -0
- package/modules.js +13 -1
- package/modules.ts +12 -1
- package/package.json +1 -1
- package/src/comma - /345/211/257/346/234/254.js" +2 -0
- package/src/decodeHtmlEntities.js +25 -0
- package/src/decodeHtmlEntities.ts +26 -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/renderTpl.js +37 -14
- package/src/renderTpl.ts +38 -18
- package/src/renderTpt.js +73 -0
- package/src/toSingleLine.js +9 -0
- package/src/toSingleLine.ts +9 -0
- package/src/trimEmptyLines.js +1 -0
- package/src/trimEmptyLines.ts +1 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>模板引擎渲染实验场 - 2026 规范重构版</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--primary: #3498db;
|
|
10
|
+
--dark: #2c3e50;
|
|
11
|
+
--bg: #f4f7f9;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: "PingFang SC", system-ui, sans-serif;
|
|
16
|
+
line-height: 1.6;
|
|
17
|
+
max-width: 1100px;
|
|
18
|
+
margin: 0 auto;
|
|
19
|
+
padding: 30px;
|
|
20
|
+
background: var(--bg);
|
|
21
|
+
color: var(--dark);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
h1 {
|
|
25
|
+
border-left: 5px solid var(--primary);
|
|
26
|
+
padding-left: 15px;
|
|
27
|
+
font-size: 1.8rem;
|
|
28
|
+
margin-bottom: 5px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.intro {
|
|
32
|
+
color: #7f8c8d;
|
|
33
|
+
margin-bottom: 30px;
|
|
34
|
+
font-size: 0.95rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* 案例卡片样式 */
|
|
38
|
+
.case-card {
|
|
39
|
+
background: white;
|
|
40
|
+
border-radius: 12px;
|
|
41
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
42
|
+
margin-bottom: 30px;
|
|
43
|
+
border: 1px solid #e1e8ed;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.case-header {
|
|
48
|
+
background: var(--dark);
|
|
49
|
+
color: white;
|
|
50
|
+
padding: 12px 20px;
|
|
51
|
+
font-weight: bold;
|
|
52
|
+
display: flex;
|
|
53
|
+
justify-content: space-between;
|
|
54
|
+
align-items: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.case-body {
|
|
58
|
+
display: grid;
|
|
59
|
+
grid-template-columns: 1fr 1fr;
|
|
60
|
+
gap: 25px;
|
|
61
|
+
padding: 20px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* 模块标题 */
|
|
65
|
+
.section-title {
|
|
66
|
+
font-size: 0.75rem;
|
|
67
|
+
color: #95a5a6;
|
|
68
|
+
text-transform: uppercase;
|
|
69
|
+
margin-bottom: 10px;
|
|
70
|
+
font-weight: 700;
|
|
71
|
+
letter-spacing: 1px;
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.section-title::before {
|
|
77
|
+
content: "";
|
|
78
|
+
display: inline-block;
|
|
79
|
+
width: 4px;
|
|
80
|
+
height: 12px;
|
|
81
|
+
background: var(--primary);
|
|
82
|
+
margin-right: 8px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pre {
|
|
86
|
+
background: #1e272e;
|
|
87
|
+
color: #d2dae2;
|
|
88
|
+
padding: 15px;
|
|
89
|
+
border-radius: 6px;
|
|
90
|
+
margin: 0;
|
|
91
|
+
font-size: 0.85rem;
|
|
92
|
+
overflow-x: auto;
|
|
93
|
+
border: 1px solid #000;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.output-rendered {
|
|
97
|
+
border: 1px solid #dcdde1;
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
padding: 15px;
|
|
100
|
+
background: #fff;
|
|
101
|
+
min-height: 60px;
|
|
102
|
+
white-space: pre-wrap;
|
|
103
|
+
border: 1px solid #dcdde1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.output-source {
|
|
107
|
+
background: #fff5e6;
|
|
108
|
+
border: 1px solid #ffeaa7;
|
|
109
|
+
padding: 15px;
|
|
110
|
+
border-radius: 6px;
|
|
111
|
+
font-family: monospace;
|
|
112
|
+
font-size: 0.85rem;
|
|
113
|
+
word-break: break-all;
|
|
114
|
+
color: #d35400;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.tag {
|
|
118
|
+
background: var(--primary);
|
|
119
|
+
font-size: 0.7rem;
|
|
120
|
+
padding: 3px 10px;
|
|
121
|
+
border-radius: 20px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.desc-text {
|
|
125
|
+
font-size: 0.85rem;
|
|
126
|
+
color: #57606f;
|
|
127
|
+
margin-top: 10px;
|
|
128
|
+
background: #f1f2f6;
|
|
129
|
+
padding: 8px 12px;
|
|
130
|
+
border-radius: 4px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@media (max-width: 800px) {
|
|
135
|
+
.case-body {
|
|
136
|
+
grid-template-columns: 1fr;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
140
|
+
</head>
|
|
141
|
+
|
|
142
|
+
<body>
|
|
143
|
+
|
|
144
|
+
<h1>模板引擎渲染实验场</h1>
|
|
145
|
+
<p class="intro">采用 <code><script type="text/template"></code> 方式解耦模板与逻辑,彻底解决编辑器语法报错问题。</p>
|
|
146
|
+
|
|
147
|
+
<script type="text/template" id="tpl-basic">
|
|
148
|
+
用户:{{ user.name }} (ID: {{ user.id }})
|
|
149
|
+
总分计算:{{ (math.score + math.bonus) * 1.2 }} 分
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<script type="text/template" id="tpl-xss">
|
|
153
|
+
最新留言:{{ comment }}
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<script type="text/template" id="tpl-logic">
|
|
157
|
+
{{ if(this.score >= 60) { /}}
|
|
158
|
+
<b style="color:green">✅ 状态:考核通过</b>
|
|
159
|
+
{{ } else { /}}
|
|
160
|
+
<b style="color:red">❌ 状态:需重修</b>
|
|
161
|
+
{{ } /}}
|
|
162
|
+
</script>
|
|
163
|
+
|
|
164
|
+
<script type="text/template" id="tpl-attr">
|
|
165
|
+
<input type="text" value="{{ val }}" title="{{ val }}">
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<script type="text/template" id="tpl-multiline">
|
|
169
|
+
第一行:{{ line1 }}
|
|
170
|
+
第二行:{{ line2 }}
|
|
171
|
+
--- 模板自带多行演示 ---
|
|
172
|
+
这是模板里的第一行内容
|
|
173
|
+
这是模板里的第二行内容
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
<div id="demo-root"></div>
|
|
177
|
+
|
|
178
|
+
<script src="../dist/utils.umd.js"></script>
|
|
179
|
+
|
|
180
|
+
<script>
|
|
181
|
+
/**
|
|
182
|
+
* 案例配置列表
|
|
183
|
+
* templateId: 对应上面定义在 HTML 里的 script ID
|
|
184
|
+
*/
|
|
185
|
+
const cases = [
|
|
186
|
+
{
|
|
187
|
+
title: "1. 基础文本与数值运算",
|
|
188
|
+
desc: "测试简单的变量替换、深层对象访问以及数学表达式运算。",
|
|
189
|
+
templateId: "tpl-basic",
|
|
190
|
+
data: {
|
|
191
|
+
user: { name: "张三", id: 8801 },
|
|
192
|
+
math: { score: 80, bonus: 10 }
|
|
193
|
+
},
|
|
194
|
+
opts: {}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
title: "2. 安全防御:防止 XSS 攻击",
|
|
198
|
+
desc: "展示 'basic' 强度如何转义危险脚本,确保输出结果安全。",
|
|
199
|
+
templateId: "tpl-xss",
|
|
200
|
+
data: { comment: "<script>alert('危险')<\/script>你好,这是黑客的问候" },
|
|
201
|
+
opts: { escape: 'basic' }
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
title: "3. 逻辑控制 (if/else)",
|
|
205
|
+
desc: "使用 / 后缀标识原生 JS 语句。此时 > 符号在 script 标签内不会引发编辑器报错。",
|
|
206
|
+
templateId: "tpl-logic",
|
|
207
|
+
data: { score: 85 },
|
|
208
|
+
opts: { strict: true }
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
title: "4. HTML 属性安全保护",
|
|
212
|
+
desc: "使用 'attribute' 强度处理引号,防止属性被非法闭合。",
|
|
213
|
+
templateId: "tpl-attr",
|
|
214
|
+
data: { val: '双引号 " 和单引号 \' 的混合测试' },
|
|
215
|
+
opts: { escape: 'attribute' }
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
title: "6. 多行文本保留测试",
|
|
219
|
+
desc: "验证 toSingleLine 是否会误删数据中的换行。期望结果:预览中应保持换行。",
|
|
220
|
+
templateId: "tpl-multiline",
|
|
221
|
+
data: {
|
|
222
|
+
line1: "我是动态数据的第一行",
|
|
223
|
+
line2: "我是动态数据的第二行"
|
|
224
|
+
},
|
|
225
|
+
opts: {}
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const root = document.getElementById('demo-root');
|
|
230
|
+
|
|
231
|
+
cases.forEach(c => {
|
|
232
|
+
// A. 从 script 标签获取原始模板内容
|
|
233
|
+
const templateElement = document.getElementById(c.templateId);
|
|
234
|
+
const rawTemplate = templateElement ? templateElement.innerHTML.trim() : "";
|
|
235
|
+
|
|
236
|
+
// B. 执行渲染
|
|
237
|
+
let result = "";
|
|
238
|
+
try {
|
|
239
|
+
result = utils.renderTpl(rawTemplate, c.data, c.opts);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
result = "渲染异常:" + e.message;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// C. 动态创建 UI
|
|
245
|
+
const card = document.createElement('div');
|
|
246
|
+
card.className = 'case-card';
|
|
247
|
+
card.innerHTML = `
|
|
248
|
+
<div class="case-header">
|
|
249
|
+
<span>${c.title}</span>
|
|
250
|
+
<span class="tag">模式: ${c.opts.escape || '不转义'}</span>
|
|
251
|
+
</div>
|
|
252
|
+
<div class="case-body">
|
|
253
|
+
<div>
|
|
254
|
+
<div class="section-title">输入模板与数据</div>
|
|
255
|
+
<pre>模板 (来自 #${c.templateId}):\n${rawTemplate}\n\n数据:\n${JSON.stringify(c.data, null, 2)}</pre>
|
|
256
|
+
<div class="desc-text">ℹ️ ${c.desc}</div>
|
|
257
|
+
</div>
|
|
258
|
+
<div>
|
|
259
|
+
<div class="section-title">渲染产物 (HTML 源码)</div>
|
|
260
|
+
<div class="output-source">${result.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</div>
|
|
261
|
+
|
|
262
|
+
<div class="section-title" style="margin-top:20px;">浏览器渲染预览</div>
|
|
263
|
+
<div class="output-rendered">${result}</div>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
`;
|
|
267
|
+
root.appendChild(card);
|
|
268
|
+
});
|
|
269
|
+
</script>
|
|
270
|
+
</body>
|
|
271
|
+
|
|
272
|
+
</html>
|
package/modules.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Last modified: 2026/01/
|
|
2
|
+
* Last modified: 2026/01/16 14:54:20
|
|
3
3
|
*/
|
|
4
4
|
'use strict';
|
|
5
5
|
import deepClone from './src/deepClone';
|
|
6
6
|
import deepCloneToJSON from './src/deepCloneToJSON';
|
|
7
7
|
import getDataType from './src/getDataType';
|
|
8
8
|
import wrapArrayMethods from './src/wrapArrayMethods';
|
|
9
|
+
import renderTpl from './src/renderTpl';
|
|
9
10
|
import requireTypes from './src/requireTypes';
|
|
10
11
|
import getUniqueId from './src/getUniqueId';
|
|
11
12
|
import arrayMutableMethods from './src/arrayMutableMethods';
|
|
@@ -35,6 +36,11 @@ import typeWriter from './src/typeWriter';
|
|
|
35
36
|
import parseLLMStream from './src/parseLLMStream';
|
|
36
37
|
import toKebabCase from './src/toKebabCase';
|
|
37
38
|
import trimEmptyLines from './src/trimEmptyLines';
|
|
39
|
+
import decodeHtmlEntities from './src/decodeHtmlEntities';
|
|
40
|
+
import escapeHTML from './src/escapeHTML';
|
|
41
|
+
import escapeRegexMaps from './src/escapeRegexMaps';
|
|
42
|
+
import escapeCharsMaps from './src/escapeCharsMaps';
|
|
43
|
+
import toSingleLine from './src/toSingleLine';
|
|
38
44
|
const utils = {
|
|
39
45
|
//executeStr,
|
|
40
46
|
getDataType,
|
|
@@ -72,5 +78,11 @@ const utils = {
|
|
|
72
78
|
parseLLMStream,
|
|
73
79
|
toKebabCase,
|
|
74
80
|
trimEmptyLines,
|
|
81
|
+
decodeHtmlEntities,
|
|
82
|
+
escapeCharsMaps,
|
|
83
|
+
escapeRegexMaps,
|
|
84
|
+
escapeHTML,
|
|
85
|
+
toSingleLine,
|
|
86
|
+
renderTpl,
|
|
75
87
|
};
|
|
76
88
|
export default utils;
|
package/modules.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Last modified: 2026/01/
|
|
2
|
+
* Last modified: 2026/01/16 14:54:20
|
|
3
3
|
*/
|
|
4
4
|
'use strict'
|
|
5
5
|
import deepClone from './src/deepClone';
|
|
@@ -43,6 +43,11 @@ import typeWriter from './src/typeWriter';
|
|
|
43
43
|
import parseLLMStream from './src/parseLLMStream';
|
|
44
44
|
import toKebabCase from './src/toKebabCase';
|
|
45
45
|
import trimEmptyLines from './src/trimEmptyLines';
|
|
46
|
+
import decodeHtmlEntities from './src/decodeHtmlEntities';
|
|
47
|
+
import escapeHTML from './src/escapeHTML';
|
|
48
|
+
import escapeRegexMaps from './src/escapeRegexMaps';
|
|
49
|
+
import escapeCharsMaps from './src/escapeCharsMaps';
|
|
50
|
+
import toSingleLine from './src/toSingleLine';
|
|
46
51
|
|
|
47
52
|
const utils = {
|
|
48
53
|
//executeStr,
|
|
@@ -81,6 +86,12 @@ const utils = {
|
|
|
81
86
|
parseLLMStream,
|
|
82
87
|
toKebabCase,
|
|
83
88
|
trimEmptyLines,
|
|
89
|
+
decodeHtmlEntities,
|
|
90
|
+
escapeCharsMaps,
|
|
91
|
+
escapeRegexMaps,
|
|
92
|
+
escapeHTML,
|
|
93
|
+
toSingleLine,
|
|
94
|
+
renderTpl,
|
|
84
95
|
|
|
85
96
|
};
|
|
86
97
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codady/utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.38",
|
|
4
4
|
"author": "AXUI Development Team",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "This is a set of general-purpose JavaScript utility functions developed by the AXUI team. All functions are pure and do not involve CSS or other third-party libraries. They are suitable for any web front-end environment.",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2026/01/15 18:55:49
|
|
3
|
+
* This function decodes HTML entities in a string back to their corresponding characters.
|
|
4
|
+
* It handles the following entities:
|
|
5
|
+
* - '&' -> '&'
|
|
6
|
+
* - '<' -> '<'
|
|
7
|
+
* - '>' -> '>'
|
|
8
|
+
* - '"' -> '"'
|
|
9
|
+
* - ''' -> "'"
|
|
10
|
+
*
|
|
11
|
+
* This is commonly used to convert HTML-encoded strings back into their original form.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} text - The HTML-encoded string that needs to be decoded.
|
|
14
|
+
* @returns {string} - A new string with HTML entities replaced by their corresponding characters.
|
|
15
|
+
*/
|
|
16
|
+
const decodeHtmlEntities = (text) => {
|
|
17
|
+
// Check if the input text is empty or undefined
|
|
18
|
+
if (!text)
|
|
19
|
+
return '';
|
|
20
|
+
// Use the browser's built-in method to decode HTML entities by setting the text as innerHTML of a temporary element
|
|
21
|
+
const textArea = document.createElement('textarea');
|
|
22
|
+
textArea.innerHTML = text; // The browser will automatically decode the HTML entities
|
|
23
|
+
return textArea.value; // Get the decoded string from the text area
|
|
24
|
+
};
|
|
25
|
+
export default decodeHtmlEntities;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2026/01/15 18:55:49
|
|
3
|
+
* This function decodes HTML entities in a string back to their corresponding characters.
|
|
4
|
+
* It handles the following entities:
|
|
5
|
+
* - '&' -> '&'
|
|
6
|
+
* - '<' -> '<'
|
|
7
|
+
* - '>' -> '>'
|
|
8
|
+
* - '"' -> '"'
|
|
9
|
+
* - ''' -> "'"
|
|
10
|
+
*
|
|
11
|
+
* This is commonly used to convert HTML-encoded strings back into their original form.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} text - The HTML-encoded string that needs to be decoded.
|
|
14
|
+
* @returns {string} - A new string with HTML entities replaced by their corresponding characters.
|
|
15
|
+
*/
|
|
16
|
+
const decodeHtmlEntities = (text: string): string => {
|
|
17
|
+
// Check if the input text is empty or undefined
|
|
18
|
+
if (!text) return '';
|
|
19
|
+
|
|
20
|
+
// Use the browser's built-in method to decode HTML entities by setting the text as innerHTML of a temporary element
|
|
21
|
+
const textArea = document.createElement('textarea');
|
|
22
|
+
textArea.innerHTML = text; // The browser will automatically decode the HTML entities
|
|
23
|
+
return textArea.value; // Get the decoded string from the text area
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default decodeHtmlEntities;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2026/01/16 14:49:39
|
|
3
|
+
* escapeCharsMaps
|
|
4
|
+
* A collection of character escape mappings for different contexts in HTML and URI encoding.
|
|
5
|
+
*
|
|
6
|
+
* This object provides a set of character replacement mappings for use in different contexts such as:
|
|
7
|
+
* - `basic`: For basic HTML encoding (useful for preventing basic HTML injection).
|
|
8
|
+
* - `attribute`: For encoding characters within HTML attributes (useful for attributes like `src`, `href`).
|
|
9
|
+
* - `content`: For encoding characters within HTML content (useful for preventing XSS).
|
|
10
|
+
* - `uri`: For encoding characters in URIs (useful in attributes like `href` or `src`).
|
|
11
|
+
* - `paranoid`: A very strict escape for all potentially dangerous characters (useful for paranoid security contexts).
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
const escapeCharsMaps = {
|
|
15
|
+
//code或pre标签中代码高亮是使用basic
|
|
16
|
+
basic: {
|
|
17
|
+
'&': '&',
|
|
18
|
+
'<': '<',
|
|
19
|
+
'>': '>',
|
|
20
|
+
},
|
|
21
|
+
//需要用在标签属性上attribute
|
|
22
|
+
attribute: {
|
|
23
|
+
'&': '&',
|
|
24
|
+
'<': '<',
|
|
25
|
+
'>': '>',
|
|
26
|
+
'"': '"',
|
|
27
|
+
"'": ''',
|
|
28
|
+
'`': '`',
|
|
29
|
+
},
|
|
30
|
+
//html中的正文内容使用content
|
|
31
|
+
content: {
|
|
32
|
+
'&': '&',
|
|
33
|
+
'<': '<',
|
|
34
|
+
'>': '>',
|
|
35
|
+
'"': '"',
|
|
36
|
+
"'": ''',
|
|
37
|
+
'/': '/',
|
|
38
|
+
},
|
|
39
|
+
//用于url链接则使用uri
|
|
40
|
+
uri: {
|
|
41
|
+
'&': '&',
|
|
42
|
+
'<': '<',
|
|
43
|
+
'>': '>',
|
|
44
|
+
'"': '"',
|
|
45
|
+
"'": ''',
|
|
46
|
+
'(': '(',
|
|
47
|
+
')': ')',
|
|
48
|
+
'[': '[',
|
|
49
|
+
']': ']',
|
|
50
|
+
},
|
|
51
|
+
//极致转意,避免任何注入或非法代码
|
|
52
|
+
paranoid: {
|
|
53
|
+
'&': '&',
|
|
54
|
+
'<': '<',
|
|
55
|
+
'>': '>',
|
|
56
|
+
'"': '"',
|
|
57
|
+
"'": ''',
|
|
58
|
+
'`': '`',
|
|
59
|
+
'/': '/',
|
|
60
|
+
'=': '=',
|
|
61
|
+
'!': '!',
|
|
62
|
+
'#': '#',
|
|
63
|
+
'(': '(',
|
|
64
|
+
')': ')',
|
|
65
|
+
'[': '[',
|
|
66
|
+
']': ']',
|
|
67
|
+
'{': '{',
|
|
68
|
+
'}': '}',
|
|
69
|
+
':': ':',
|
|
70
|
+
';': ';',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
export default escapeCharsMaps;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since Last modified: 2026/01/16 14:49:39
|
|
3
|
+
* escapeCharsMaps
|
|
4
|
+
* A collection of character escape mappings for different contexts in HTML and URI encoding.
|
|
5
|
+
*
|
|
6
|
+
* This object provides a set of character replacement mappings for use in different contexts such as:
|
|
7
|
+
* - `basic`: For basic HTML encoding (useful for preventing basic HTML injection).
|
|
8
|
+
* - `attribute`: For encoding characters within HTML attributes (useful for attributes like `src`, `href`).
|
|
9
|
+
* - `content`: For encoding characters within HTML content (useful for preventing XSS).
|
|
10
|
+
* - `uri`: For encoding characters in URIs (useful in attributes like `href` or `src`).
|
|
11
|
+
* - `paranoid`: A very strict escape for all potentially dangerous characters (useful for paranoid security contexts).
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const escapeCharsMaps : Record<string, Record<string, string>> = {
|
|
16
|
+
//code或pre标签中代码高亮是使用basic
|
|
17
|
+
basic: {
|
|
18
|
+
'&': '&',
|
|
19
|
+
'<': '<',
|
|
20
|
+
'>': '>',
|
|
21
|
+
},
|
|
22
|
+
//需要用在标签属性上attribute
|
|
23
|
+
attribute: {
|
|
24
|
+
'&': '&',
|
|
25
|
+
'<': '<',
|
|
26
|
+
'>': '>',
|
|
27
|
+
'"': '"',
|
|
28
|
+
"'": ''',
|
|
29
|
+
'`': '`',
|
|
30
|
+
},
|
|
31
|
+
//html中的正文内容使用content
|
|
32
|
+
content: {
|
|
33
|
+
'&': '&',
|
|
34
|
+
'<': '<',
|
|
35
|
+
'>': '>',
|
|
36
|
+
'"': '"',
|
|
37
|
+
"'": ''',
|
|
38
|
+
'/': '/',
|
|
39
|
+
},
|
|
40
|
+
//用于url链接则使用uri
|
|
41
|
+
uri: {
|
|
42
|
+
'&': '&',
|
|
43
|
+
'<': '<',
|
|
44
|
+
'>': '>',
|
|
45
|
+
'"': '"',
|
|
46
|
+
"'": ''',
|
|
47
|
+
'(': '(',
|
|
48
|
+
')': ')',
|
|
49
|
+
'[': '[',
|
|
50
|
+
']': ']',
|
|
51
|
+
},
|
|
52
|
+
//极致转意,避免任何注入或非法代码
|
|
53
|
+
paranoid: {
|
|
54
|
+
'&': '&',
|
|
55
|
+
'<': '<',
|
|
56
|
+
'>': '>',
|
|
57
|
+
'"': '"',
|
|
58
|
+
"'": ''',
|
|
59
|
+
'`': '`',
|
|
60
|
+
'/': '/',
|
|
61
|
+
'=': '=',
|
|
62
|
+
'!': '!',
|
|
63
|
+
'#': '#',
|
|
64
|
+
'(': '(',
|
|
65
|
+
')': ')',
|
|
66
|
+
'[': '[',
|
|
67
|
+
']': ']',
|
|
68
|
+
'{': '{',
|
|
69
|
+
'}': '}',
|
|
70
|
+
':': ':',
|
|
71
|
+
';': ';',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
export default escapeCharsMaps;
|
package/src/escapeHTML.js
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @since Last modified: 2026/01/16 14:47:26
|
|
2
3
|
* @function escapeHTML
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @returns
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* escapeHTML(
|
|
11
|
-
*
|
|
4
|
+
* Escapes special characters in a string to prevent Cross-Site Scripting (XSS) attacks.
|
|
5
|
+
* * @param str - The raw string to be escaped.
|
|
6
|
+
* @param context - The context where the escaped string will be placed.
|
|
7
|
+
* Defaults to 'basic'.
|
|
8
|
+
* @returns The escaped string safe for the specified HTML context.
|
|
9
|
+
* * @example
|
|
10
|
+
* // Basic usage (HTML Body)
|
|
11
|
+
* escapeHTML('<script>', 'basic'); // "<script>"
|
|
12
|
+
* * @example
|
|
13
|
+
* // Attribute usage (e.g., <input value="...">)
|
|
14
|
+
* escapeHTML('value" onmouseover="alert(1)', 'attribute');
|
|
12
15
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'/': '/'
|
|
24
|
-
};
|
|
25
|
-
// Replace each special character in the input string with its corresponding HTML entity
|
|
26
|
-
return str.replace(/[&<>"'`=\/]/g, (match) => {
|
|
27
|
-
return escapes[match];
|
|
28
|
-
});
|
|
16
|
+
import escapeCharsMaps from "./escapeCharsMaps";
|
|
17
|
+
import escapeRegexMaps from "./escapeRegexMaps";
|
|
18
|
+
const escapeHTML = (str, strength = 'attribute') => {
|
|
19
|
+
// Return empty string if input is null, undefined, or not a string
|
|
20
|
+
if (typeof str !== 'string')
|
|
21
|
+
return '';
|
|
22
|
+
const map = escapeCharsMaps[strength], regex = escapeRegexMaps[strength];
|
|
23
|
+
// Use String.prototype.replace with a global regex.
|
|
24
|
+
// The callback function retrieves the replacement from the map using the matched character as key.
|
|
25
|
+
return str.replace(regex, (match) => map[match]);
|
|
29
26
|
};
|
|
27
|
+
export default escapeHTML;
|
package/src/escapeHTML.ts
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @since Last modified: 2026/01/16 14:47:26
|
|
2
3
|
* @function escapeHTML
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @returns
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* escapeHTML(
|
|
11
|
-
*
|
|
4
|
+
* Escapes special characters in a string to prevent Cross-Site Scripting (XSS) attacks.
|
|
5
|
+
* * @param str - The raw string to be escaped.
|
|
6
|
+
* @param context - The context where the escaped string will be placed.
|
|
7
|
+
* Defaults to 'basic'.
|
|
8
|
+
* @returns The escaped string safe for the specified HTML context.
|
|
9
|
+
* * @example
|
|
10
|
+
* // Basic usage (HTML Body)
|
|
11
|
+
* escapeHTML('<script>', 'basic'); // "<script>"
|
|
12
|
+
* * @example
|
|
13
|
+
* // Attribute usage (e.g., <input value="...">)
|
|
14
|
+
* escapeHTML('value" onmouseover="alert(1)', 'attribute');
|
|
12
15
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const escapes: { [key: string]: string } = {
|
|
16
|
-
'&': '&',
|
|
17
|
-
'<': '<',
|
|
18
|
-
'>': '>',
|
|
19
|
-
'"': '"',
|
|
20
|
-
"'": ''',
|
|
21
|
-
'`': '`',
|
|
22
|
-
'=': '=',
|
|
23
|
-
'/': '/'
|
|
24
|
-
};
|
|
16
|
+
import escapeCharsMaps from "./escapeCharsMaps";
|
|
17
|
+
import escapeRegexMaps from "./escapeRegexMaps";
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
export type EscapeStrength = 'basic' | 'attribute' | 'content' | 'uri' | 'paranoid';
|
|
20
|
+
|
|
21
|
+
const escapeHTML = (
|
|
22
|
+
str: string,
|
|
23
|
+
strength: EscapeStrength = 'attribute'
|
|
24
|
+
): string => {
|
|
25
|
+
// Return empty string if input is null, undefined, or not a string
|
|
26
|
+
if (typeof str !== 'string') return '';
|
|
27
|
+
|
|
28
|
+
const map = escapeCharsMaps[strength],
|
|
29
|
+
regex = escapeRegexMaps[strength];
|
|
30
|
+
// Use String.prototype.replace with a global regex.
|
|
31
|
+
// The callback function retrieves the replacement from the map using the matched character as key.
|
|
32
|
+
return str.replace(regex, (match) => map[match]);
|
|
30
33
|
};
|
|
34
|
+
export default escapeHTML;
|