wikiparser-node 0.3.0 → 0.4.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/.eslintrc.json +472 -34
- package/README.md +1 -1
- package/config/default.json +58 -30
- package/config/llwiki.json +22 -90
- package/config/moegirl.json +51 -13
- package/config/zhwiki.json +1269 -0
- package/index.js +114 -104
- package/lib/element.js +448 -440
- package/lib/node.js +335 -115
- package/lib/ranges.js +27 -18
- package/lib/text.js +146 -0
- package/lib/title.js +13 -5
- package/mixin/attributeParent.js +70 -24
- package/mixin/fixedToken.js +14 -6
- package/mixin/hidden.js +6 -4
- package/mixin/sol.js +27 -10
- package/package.json +9 -3
- package/parser/brackets.js +22 -17
- package/parser/commentAndExt.js +18 -16
- package/parser/converter.js +14 -13
- package/parser/externalLinks.js +12 -11
- package/parser/hrAndDoubleUnderscore.js +23 -14
- package/parser/html.js +10 -9
- package/parser/links.js +15 -14
- package/parser/list.js +12 -11
- package/parser/magicLinks.js +12 -11
- package/parser/quotes.js +6 -5
- package/parser/selector.js +175 -0
- package/parser/table.js +25 -18
- package/printed/example.json +120 -0
- package/src/arg.js +56 -32
- package/src/atom/hidden.js +5 -2
- package/src/atom/index.js +17 -9
- package/src/attribute.js +182 -100
- package/src/converter.js +68 -41
- package/src/converterFlags.js +67 -45
- package/src/converterRule.js +117 -65
- package/src/extLink.js +66 -18
- package/src/gallery.js +42 -15
- package/src/heading.js +34 -15
- package/src/html.js +97 -35
- package/src/imageParameter.js +83 -54
- package/src/index.js +299 -178
- package/src/link/category.js +20 -52
- package/src/link/file.js +59 -28
- package/src/link/galleryImage.js +21 -7
- package/src/link/index.js +146 -60
- package/src/magicLink.js +34 -12
- package/src/nowiki/comment.js +22 -10
- package/src/nowiki/dd.js +37 -22
- package/src/nowiki/doubleUnderscore.js +16 -7
- package/src/nowiki/hr.js +11 -7
- package/src/nowiki/index.js +16 -9
- package/src/nowiki/list.js +2 -2
- package/src/nowiki/noinclude.js +8 -4
- package/src/nowiki/quote.js +11 -7
- package/src/onlyinclude.js +19 -7
- package/src/parameter.js +65 -38
- package/src/syntax.js +26 -20
- package/src/table/index.js +260 -165
- package/src/table/td.js +98 -52
- package/src/table/tr.js +102 -58
- package/src/tagPair/ext.js +27 -19
- package/src/tagPair/include.js +16 -11
- package/src/tagPair/index.js +64 -29
- package/src/transclude.js +170 -93
- package/test/api.js +83 -0
- package/test/real.js +133 -0
- package/test/test.js +28 -0
- package/test/util.js +80 -0
- package/tool/index.js +41 -31
- package/typings/api.d.ts +13 -0
- package/typings/array.d.ts +28 -0
- package/typings/event.d.ts +24 -0
- package/typings/index.d.ts +46 -4
- package/typings/node.d.ts +15 -9
- package/typings/parser.d.ts +7 -0
- package/typings/tool.d.ts +3 -2
- package/util/debug.js +21 -18
- package/util/string.js +40 -27
- package/typings/element.d.ts +0 -28
package/lib/node.js
CHANGED
|
@@ -1,56 +1,153 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {typeError, externalUse} = require('../util/debug'),
|
|
4
|
-
{text
|
|
4
|
+
{text} = require('../util/string'),
|
|
5
5
|
assert = require('assert/strict'),
|
|
6
|
-
|
|
6
|
+
EventEmitter = require('events'),
|
|
7
|
+
Parser = require('..');
|
|
7
8
|
|
|
9
|
+
/** 类似Node */
|
|
8
10
|
class AstNode {
|
|
9
|
-
/** @type {
|
|
11
|
+
/** @type {string} */ type;
|
|
12
|
+
/** @type {this[]} */ childNodes = [];
|
|
10
13
|
/** @type {this} */ #parentNode;
|
|
11
14
|
/** @type {string[]} */ #optional = [];
|
|
15
|
+
#events = new EventEmitter();
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* 检查在某个位置增删子节点是否合法
|
|
19
|
+
* @param {number} i 增删位置
|
|
20
|
+
* @param {number} addition 将会插入的子节点个数
|
|
21
|
+
* @throws `RangeError` 指定位置不存在子节点
|
|
22
|
+
*/
|
|
23
|
+
#verifyChild = (i, addition = 0) => {
|
|
24
|
+
if (typeof i !== 'number') {
|
|
25
|
+
this.typeError('verifyChild', 'Number');
|
|
26
|
+
}
|
|
27
|
+
const {childNodes: {length}} = this;
|
|
28
|
+
if (i < -length || i >= length + addition || !Number.isInteger(i)) {
|
|
29
|
+
throw new RangeError(`不存在第 ${i} 个子节点!`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** 首位子节点 */
|
|
13
34
|
get firstChild() {
|
|
14
35
|
return this.childNodes[0];
|
|
15
36
|
}
|
|
37
|
+
|
|
38
|
+
/** 末位子节点 */
|
|
16
39
|
get lastChild() {
|
|
17
40
|
return this.childNodes.at(-1);
|
|
18
41
|
}
|
|
42
|
+
|
|
43
|
+
/** 父节点 */
|
|
19
44
|
get parentNode() {
|
|
20
45
|
return this.#parentNode;
|
|
21
46
|
}
|
|
22
|
-
|
|
47
|
+
|
|
48
|
+
/** 是否具有根节点 */
|
|
49
|
+
get isConnected() {
|
|
50
|
+
return this.getRootNode().type === 'root';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 不是自身的根节点 */
|
|
54
|
+
get ownerDocument() {
|
|
55
|
+
const root = this.getRootNode();
|
|
56
|
+
return root.type === 'root' && root !== this ? root : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 后一个兄弟节点
|
|
61
|
+
* @complexity `n`
|
|
62
|
+
*/
|
|
23
63
|
get nextSibling() {
|
|
24
64
|
const childNodes = this.#parentNode?.childNodes;
|
|
25
65
|
return childNodes && childNodes[childNodes.indexOf(this) + 1];
|
|
26
66
|
}
|
|
27
|
-
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 前一个兄弟节点
|
|
70
|
+
* @complexity `n`
|
|
71
|
+
*/
|
|
28
72
|
get previousSibling() {
|
|
29
73
|
const childNodes = this.#parentNode?.childNodes;
|
|
30
74
|
return childNodes && childNodes[childNodes.indexOf(this) - 1];
|
|
31
75
|
}
|
|
32
76
|
|
|
77
|
+
/**
|
|
78
|
+
* 后一个非文本兄弟节点
|
|
79
|
+
* @complexity `n`
|
|
80
|
+
* @returns {this}
|
|
81
|
+
*/
|
|
82
|
+
get nextElementSibling() {
|
|
83
|
+
const childNodes = this.#parentNode?.childNodes,
|
|
84
|
+
i = childNodes?.indexOf(this);
|
|
85
|
+
return childNodes?.slice(i + 1)?.find(({type}) => type !== 'text');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 前一个非文本兄弟节点
|
|
90
|
+
* @complexity `n`
|
|
91
|
+
* @returns {this}
|
|
92
|
+
*/
|
|
93
|
+
get previousElementSibling() {
|
|
94
|
+
const childNodes = this.#parentNode?.childNodes,
|
|
95
|
+
i = childNodes?.indexOf(this);
|
|
96
|
+
return childNodes?.slice(0, i)?.findLast(({type}) => type !== 'text');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 后方是否还有其他节点(不含后代)
|
|
101
|
+
* @returns {boolean}
|
|
102
|
+
* @complexity `n`
|
|
103
|
+
*/
|
|
104
|
+
get eof() {
|
|
105
|
+
const {type, parentNode} = this;
|
|
106
|
+
if (type === 'root') {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
let {nextSibling} = this;
|
|
110
|
+
while (nextSibling?.type === 'text' && String(nextSibling).trim() === '') {
|
|
111
|
+
({nextSibling} = nextSibling);
|
|
112
|
+
}
|
|
113
|
+
return nextSibling === undefined && parentNode?.eof;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 标记仅用于代码调试的方法
|
|
118
|
+
* @param {string} method 方法名称
|
|
119
|
+
* @throws `Error`
|
|
120
|
+
*/
|
|
33
121
|
debugOnly(method = 'debugOnly') {
|
|
34
122
|
throw new Error(`${this.constructor.name}.${method} 方法仅用于代码调试!`);
|
|
35
123
|
}
|
|
36
124
|
|
|
37
125
|
/**
|
|
38
|
-
*
|
|
39
|
-
* @param
|
|
126
|
+
* 抛出`TypeError`
|
|
127
|
+
* @param {string} method 方法名称
|
|
128
|
+
* @param {...string} types 可接受的参数类型
|
|
40
129
|
*/
|
|
41
130
|
typeError(method, ...types) {
|
|
42
131
|
return typeError(this.constructor, method, ...types);
|
|
43
132
|
}
|
|
44
133
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
134
|
+
/**
|
|
135
|
+
* 冻结部分属性
|
|
136
|
+
* @param {string|string[]} keys 属性键
|
|
137
|
+
* @param {boolean} permanent 是否永久
|
|
138
|
+
*/
|
|
139
|
+
seal(keys, permanent) {
|
|
47
140
|
if (!Parser.running && !Parser.debugging) {
|
|
48
141
|
this.debugOnly('seal');
|
|
49
142
|
}
|
|
50
143
|
keys = Array.isArray(keys) ? keys : [keys];
|
|
51
|
-
|
|
144
|
+
if (!permanent) {
|
|
145
|
+
this.#optional.push(...keys);
|
|
146
|
+
}
|
|
52
147
|
for (const key of keys) {
|
|
53
|
-
Object.defineProperty(this, key, {
|
|
148
|
+
Object.defineProperty(this, key, {
|
|
149
|
+
writable: false, enumerable: Boolean(this[key]), configurable: !permanent,
|
|
150
|
+
});
|
|
54
151
|
}
|
|
55
152
|
return this;
|
|
56
153
|
}
|
|
@@ -60,7 +157,11 @@ class AstNode {
|
|
|
60
157
|
Object.freeze(this.childNodes);
|
|
61
158
|
}
|
|
62
159
|
|
|
63
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* 是否是全同节点
|
|
162
|
+
* @param {this} node 待比较的节点
|
|
163
|
+
* @throws `assert.AssertionError`
|
|
164
|
+
*/
|
|
64
165
|
isEqualNode(node) {
|
|
65
166
|
try {
|
|
66
167
|
assert.deepStrictEqual(this, node);
|
|
@@ -73,40 +174,49 @@ class AstNode {
|
|
|
73
174
|
return true;
|
|
74
175
|
}
|
|
75
176
|
|
|
76
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* 是否具有某属性
|
|
179
|
+
* @param {PropertyKey} key 属性键
|
|
180
|
+
*/
|
|
77
181
|
hasAttribute(key) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
182
|
+
const type = typeof key;
|
|
183
|
+
return type === 'string' || type === 'number' || type === 'symbol'
|
|
184
|
+
? key in this
|
|
185
|
+
: this.typeError('hasAttribute', 'String', 'Number', 'Symbol');
|
|
82
186
|
}
|
|
83
187
|
|
|
84
188
|
/**
|
|
85
|
-
*
|
|
189
|
+
* 获取属性值。除非用于私有属性,否则总是返回字符串。
|
|
86
190
|
* @template {string} T
|
|
87
|
-
* @param {T} key
|
|
191
|
+
* @param {T} key 属性键
|
|
88
192
|
* @returns {TokenAttribute<T>}
|
|
89
193
|
*/
|
|
90
194
|
getAttribute(key) {
|
|
91
195
|
if (key === 'optional') {
|
|
92
196
|
return [...this.#optional];
|
|
197
|
+
} else if (key === 'verifyChild') {
|
|
198
|
+
return this.#verifyChild;
|
|
93
199
|
}
|
|
94
200
|
return this.hasAttribute(key) ? String(this[key]) : undefined;
|
|
95
201
|
}
|
|
96
202
|
|
|
203
|
+
/** 获取所有属性键 */
|
|
97
204
|
getAttributeNames() {
|
|
98
205
|
const names = Object.getOwnPropertyNames(this);
|
|
99
206
|
return names.filter(name => typeof this[name] !== 'function');
|
|
100
207
|
}
|
|
101
208
|
|
|
209
|
+
/** 是否具有任意属性 */
|
|
102
210
|
hasAttributes() {
|
|
103
211
|
return this.getAttributeNames().length > 0;
|
|
104
212
|
}
|
|
105
213
|
|
|
106
214
|
/**
|
|
215
|
+
* 设置属性
|
|
107
216
|
* @template {string} T
|
|
108
|
-
* @param {T} key
|
|
109
|
-
* @param {TokenAttribute<T>} value
|
|
217
|
+
* @param {T} key 属性键
|
|
218
|
+
* @param {TokenAttribute<T>} value 属性值
|
|
219
|
+
* @throws `RangeError` 禁止手动指定的属性
|
|
110
220
|
*/
|
|
111
221
|
setAttribute(key, value) {
|
|
112
222
|
if (key === 'parentNode') {
|
|
@@ -133,7 +243,11 @@ class AstNode {
|
|
|
133
243
|
return this;
|
|
134
244
|
}
|
|
135
245
|
|
|
136
|
-
/**
|
|
246
|
+
/**
|
|
247
|
+
* 移除某属性
|
|
248
|
+
* @param {PropertyKey} key 属性键
|
|
249
|
+
* @throws `RangeError` 不可删除的属性
|
|
250
|
+
*/
|
|
137
251
|
removeAttribute(key) {
|
|
138
252
|
if (this.hasAttribute(key)) {
|
|
139
253
|
const descriptor = Object.getOwnPropertyDescriptor(this, key);
|
|
@@ -145,8 +259,10 @@ class AstNode {
|
|
|
145
259
|
}
|
|
146
260
|
|
|
147
261
|
/**
|
|
148
|
-
*
|
|
149
|
-
* @param {
|
|
262
|
+
* 开关某属性
|
|
263
|
+
* @param {PropertyKey} key 属性键
|
|
264
|
+
* @param {boolean|undefined} force 强制开启或关闭
|
|
265
|
+
* @throws `RangeError` 不为Boolean类型的属性值
|
|
150
266
|
*/
|
|
151
267
|
toggleAttribute(key, force) {
|
|
152
268
|
if (force !== undefined && typeof force !== 'boolean') {
|
|
@@ -157,13 +273,9 @@ class AstNode {
|
|
|
157
273
|
this.setAttribute(key, force === true || force === undefined && !this[key]);
|
|
158
274
|
}
|
|
159
275
|
|
|
160
|
-
/** @complexity `n` */
|
|
161
|
-
toString(separator = '') {
|
|
162
|
-
return this.childNodes.map(String).join(separator);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
276
|
/**
|
|
166
277
|
* 可见部分
|
|
278
|
+
* @param {string} separator 子节点间的连接符
|
|
167
279
|
* @returns {string}
|
|
168
280
|
* @complexity `n`
|
|
169
281
|
*/
|
|
@@ -171,51 +283,128 @@ class AstNode {
|
|
|
171
283
|
return text(this.childNodes, separator);
|
|
172
284
|
}
|
|
173
285
|
|
|
286
|
+
/** 是否具有子节点 */
|
|
174
287
|
hasChildNodes() {
|
|
175
288
|
return this.childNodes.length > 0;
|
|
176
289
|
}
|
|
177
290
|
|
|
178
291
|
/**
|
|
179
|
-
*
|
|
180
|
-
* @param {this} node
|
|
292
|
+
* 是自身或后代节点
|
|
293
|
+
* @param {this} node 待检测节点
|
|
181
294
|
* @returns {boolean}
|
|
182
295
|
* @complexity `n`
|
|
183
296
|
*/
|
|
184
297
|
contains(node) {
|
|
185
|
-
|
|
186
|
-
this.
|
|
298
|
+
return node instanceof AstNode
|
|
299
|
+
? node === this || this.childNodes.some(child => child.contains(node))
|
|
300
|
+
: this.typeError('contains', 'AstNode');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 添加事件监听
|
|
305
|
+
* @param {string|string[]} types 事件类型
|
|
306
|
+
* @param {AstListener} listener 监听函数
|
|
307
|
+
* @param {{once: boolean}} options 选项
|
|
308
|
+
*/
|
|
309
|
+
addEventListener(types, listener, options) {
|
|
310
|
+
if (Array.isArray(types)) {
|
|
311
|
+
for (const type of types) {
|
|
312
|
+
this.addEventListener(type, listener, options);
|
|
313
|
+
}
|
|
314
|
+
} else if (typeof types !== 'string' || typeof listener !== 'function') {
|
|
315
|
+
this.typeError('addEventListener', 'String', 'Function');
|
|
316
|
+
} else {
|
|
317
|
+
this.#events[options?.once ? 'once' : 'on'](types, listener);
|
|
187
318
|
}
|
|
188
|
-
return node === this || this.childNodes.some(child => child instanceof AstNode && child.contains(node));
|
|
189
319
|
}
|
|
190
320
|
|
|
191
|
-
/**
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
321
|
+
/**
|
|
322
|
+
* 移除事件监听
|
|
323
|
+
* @param {string|string[]} types 事件类型
|
|
324
|
+
* @param {AstListener} listener 监听函数
|
|
325
|
+
*/
|
|
326
|
+
removeEventListener(types, listener) {
|
|
327
|
+
if (Array.isArray(types)) {
|
|
328
|
+
for (const type of types) {
|
|
329
|
+
this.removeEventListener(type, listener);
|
|
330
|
+
}
|
|
331
|
+
} else if (typeof types !== 'string' || typeof listener !== 'function') {
|
|
332
|
+
this.typeError('removeEventListener', 'String', 'Function');
|
|
333
|
+
} else {
|
|
334
|
+
this.#events.off(types, listener);
|
|
197
335
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 移除事件的所有监听
|
|
340
|
+
* @param {string|string[]} types 事件类型
|
|
341
|
+
*/
|
|
342
|
+
removeAllEventListeners(types) {
|
|
343
|
+
if (Array.isArray(types)) {
|
|
344
|
+
for (const type of types) {
|
|
345
|
+
this.removeAllEventListeners(type);
|
|
346
|
+
}
|
|
347
|
+
} else if (types !== undefined && typeof types !== 'string') {
|
|
348
|
+
this.typeError('removeAllEventListeners', 'String');
|
|
349
|
+
} else {
|
|
350
|
+
this.#events.removeAllListeners(types);
|
|
201
351
|
}
|
|
202
352
|
}
|
|
203
353
|
|
|
204
|
-
/**
|
|
354
|
+
/**
|
|
355
|
+
* 列举事件监听
|
|
356
|
+
* @param {string} type 事件类型
|
|
357
|
+
* @returns {AstListener[]}
|
|
358
|
+
*/
|
|
359
|
+
listEventListeners(type) {
|
|
360
|
+
return typeof type === 'string' ? this.#events.listeners(type) : this.typeError('listEventListeners', 'String');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 触发事件
|
|
365
|
+
* @param {AstEvent} e 事件对象
|
|
366
|
+
* @param {any} data 事件数据
|
|
367
|
+
*/
|
|
368
|
+
dispatchEvent(e, data) {
|
|
369
|
+
if (!(e instanceof Event)) {
|
|
370
|
+
this.typeError('dispatchEvent', 'Event');
|
|
371
|
+
} else if (!e.target) { // 初始化
|
|
372
|
+
Object.defineProperty(e, 'target', {value: this, enumerable: true});
|
|
373
|
+
|
|
374
|
+
/** 终止冒泡 */
|
|
375
|
+
e.stopPropagation = function() {
|
|
376
|
+
Object.defineProperty(this, 'bubbles', {value: false});
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
Object.defineProperties(e, { // 每次bubble更新
|
|
380
|
+
prevTarget: {value: e.currentTarget, enumerable: true, configurable: true},
|
|
381
|
+
currentTarget: {value: this, enumerable: true, configurable: true},
|
|
382
|
+
});
|
|
383
|
+
this.#events.emit(e.type, e, data);
|
|
384
|
+
if (e.bubbles && this.parentNode) {
|
|
385
|
+
this.parentNode.dispatchEvent(e, data);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 移除子节点
|
|
391
|
+
* @param {number} i 移除位置
|
|
392
|
+
*/
|
|
205
393
|
removeAt(i) {
|
|
206
|
-
this.verifyChild(i);
|
|
394
|
+
this.getAttribute('verifyChild')(i);
|
|
207
395
|
const childNodes = [...this.childNodes],
|
|
208
|
-
[node] = childNodes.splice(i, 1)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
this.setAttribute('childNodes', childNodes);
|
|
396
|
+
[node] = childNodes.splice(i, 1),
|
|
397
|
+
e = new Event('remove', {bubbles: true});
|
|
398
|
+
node.setAttribute('parentNode');
|
|
399
|
+
this.setAttribute('childNodes', childNodes).dispatchEvent(e, {position: i, removed: node});
|
|
213
400
|
return node;
|
|
214
401
|
}
|
|
215
402
|
|
|
216
403
|
/**
|
|
217
|
-
*
|
|
404
|
+
* 获取子节点的位置
|
|
405
|
+
* @param {this} node 子节点
|
|
218
406
|
* @complexity `n`
|
|
407
|
+
* @throws `RangeError` 找不到子节点
|
|
219
408
|
*/
|
|
220
409
|
#getChildIndex(node) {
|
|
221
410
|
const {childNodes} = this,
|
|
@@ -223,15 +412,14 @@ class AstNode {
|
|
|
223
412
|
if (i === -1) {
|
|
224
413
|
Parser.error('找不到子节点!', node);
|
|
225
414
|
throw new RangeError('找不到子节点!');
|
|
226
|
-
} else if (typeof node === 'string' && childNodes.lastIndexOf(node) > i) {
|
|
227
|
-
throw new RangeError(`重复的纯文本节点 ${noWrap(node)}!`);
|
|
228
415
|
}
|
|
229
416
|
return i;
|
|
230
417
|
}
|
|
231
418
|
|
|
232
419
|
/**
|
|
233
|
-
*
|
|
234
|
-
* @
|
|
420
|
+
* 移除子节点
|
|
421
|
+
* @template {this} T
|
|
422
|
+
* @param {T} node 子节点
|
|
235
423
|
* @complexity `n`
|
|
236
424
|
*/
|
|
237
425
|
removeChild(node) {
|
|
@@ -240,36 +428,40 @@ class AstNode {
|
|
|
240
428
|
}
|
|
241
429
|
|
|
242
430
|
/**
|
|
243
|
-
*
|
|
244
|
-
* @
|
|
431
|
+
* 插入子节点
|
|
432
|
+
* @template {this} T
|
|
433
|
+
* @param {T} node 待插入的子节点
|
|
434
|
+
* @param {number} i 插入位置
|
|
245
435
|
* @complexity `n`
|
|
436
|
+
* @throws `RangeError` 不能插入祖先节点
|
|
246
437
|
*/
|
|
247
438
|
insertAt(node, i = this.childNodes.length) {
|
|
248
|
-
if (
|
|
249
|
-
this.typeError('insertAt', 'String', '
|
|
250
|
-
} else if (node
|
|
439
|
+
if (!(node instanceof AstNode)) {
|
|
440
|
+
this.typeError('insertAt', 'String', 'AstNode');
|
|
441
|
+
} else if (node.contains(this)) {
|
|
251
442
|
Parser.error('不能插入祖先节点!', node);
|
|
252
443
|
throw new RangeError('不能插入祖先节点!');
|
|
253
444
|
}
|
|
254
|
-
this.verifyChild(i, 1);
|
|
255
|
-
const childNodes = [...this.childNodes]
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
445
|
+
this.getAttribute('verifyChild')(i, 1);
|
|
446
|
+
const childNodes = [...this.childNodes],
|
|
447
|
+
e = new Event('insert', {bubbles: true}),
|
|
448
|
+
j = Parser.running ? -1 : childNodes.indexOf(node);
|
|
449
|
+
if (j === -1) {
|
|
450
|
+
node.parentNode?.removeChild(node);
|
|
451
|
+
node.setAttribute('parentNode', this);
|
|
452
|
+
} else {
|
|
453
|
+
childNodes.splice(j, 1);
|
|
264
454
|
}
|
|
265
455
|
childNodes.splice(i, 0, node);
|
|
266
|
-
this.setAttribute('childNodes', childNodes)
|
|
456
|
+
this.setAttribute('childNodes', childNodes)
|
|
457
|
+
.dispatchEvent(e, {position: i < 0 ? i + this.childNodes.length - 1 : i, inserted: node});
|
|
267
458
|
return node;
|
|
268
459
|
}
|
|
269
460
|
|
|
270
461
|
/**
|
|
271
|
-
*
|
|
272
|
-
* @
|
|
462
|
+
* 在末尾插入子节点
|
|
463
|
+
* @template {this} T
|
|
464
|
+
* @param {T} node 插入节点
|
|
273
465
|
* @complexity `n`
|
|
274
466
|
*/
|
|
275
467
|
appendChild(node) {
|
|
@@ -277,22 +469,21 @@ class AstNode {
|
|
|
277
469
|
}
|
|
278
470
|
|
|
279
471
|
/**
|
|
280
|
-
*
|
|
281
|
-
* @
|
|
282
|
-
* @param {
|
|
472
|
+
* 在指定位置前插入子节点
|
|
473
|
+
* @template {this} T
|
|
474
|
+
* @param {T} child 插入节点
|
|
475
|
+
* @param {this} reference 指定位置处的子节点
|
|
283
476
|
* @complexity `n`
|
|
284
477
|
*/
|
|
285
478
|
insertBefore(child, reference) {
|
|
286
|
-
|
|
287
|
-
return this.appendChild(child);
|
|
288
|
-
}
|
|
289
|
-
return this.insertAt(child, this.#getChildIndex(reference));
|
|
479
|
+
return reference === undefined ? this.appendChild(child) : this.insertAt(child, this.#getChildIndex(reference));
|
|
290
480
|
}
|
|
291
481
|
|
|
292
482
|
/**
|
|
293
|
-
*
|
|
294
|
-
* @
|
|
295
|
-
* @param {
|
|
483
|
+
* 替换子节点
|
|
484
|
+
* @template {this} T
|
|
485
|
+
* @param {this} newChild 新子节点
|
|
486
|
+
* @param {T} oldChild 原子节点
|
|
296
487
|
* @complexity `n`
|
|
297
488
|
*/
|
|
298
489
|
replaceChild(newChild, oldChild) {
|
|
@@ -302,56 +493,85 @@ class AstNode {
|
|
|
302
493
|
return oldChild;
|
|
303
494
|
}
|
|
304
495
|
|
|
305
|
-
/**
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
496
|
+
/**
|
|
497
|
+
* 在当前节点前后插入兄弟节点
|
|
498
|
+
* @param {this[]} nodes 插入节点
|
|
499
|
+
* @param {number} offset 插入的相对位置
|
|
500
|
+
* @complexity `n`
|
|
501
|
+
* @throws `Error` 不存在父节点
|
|
502
|
+
*/
|
|
503
|
+
#insertAdjacent(nodes, offset) {
|
|
504
|
+
const {parentNode} = this;
|
|
505
|
+
if (!parentNode) {
|
|
506
|
+
throw new Error('不存在父节点!');
|
|
309
507
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
throw new RangeError(`第 ${i} 个子节点是 ${oldText.constructor.name}!`);
|
|
508
|
+
const i = parentNode.childNodes.indexOf(this) + offset;
|
|
509
|
+
for (let j = 0; j < nodes.length; j++) {
|
|
510
|
+
parentNode.insertAt(nodes[j], i + j);
|
|
314
511
|
}
|
|
315
|
-
const childNodes = [...this.childNodes];
|
|
316
|
-
childNodes.splice(i, 1, str);
|
|
317
|
-
this.setAttribute('childNodes', childNodes);
|
|
318
|
-
return oldText;
|
|
319
512
|
}
|
|
320
513
|
|
|
321
514
|
/**
|
|
322
|
-
*
|
|
323
|
-
* @param {
|
|
515
|
+
* 在后方批量插入兄弟节点
|
|
516
|
+
* @param {...this} nodes 插入节点
|
|
517
|
+
* @complexity `n`
|
|
324
518
|
*/
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
519
|
+
after(...nodes) {
|
|
520
|
+
this.#insertAdjacent(nodes, 1);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* 在前方批量插入兄弟节点
|
|
525
|
+
* @param {...this} nodes 插入节点
|
|
526
|
+
* @complexity `n`
|
|
527
|
+
*/
|
|
528
|
+
before(...nodes) {
|
|
529
|
+
this.#insertAdjacent(nodes, 0);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* 移除当前节点
|
|
534
|
+
* @complexity `n`
|
|
535
|
+
* @throws `Error` 不存在父节点
|
|
536
|
+
*/
|
|
537
|
+
remove() {
|
|
538
|
+
const {parentNode} = this;
|
|
539
|
+
if (!parentNode) {
|
|
540
|
+
throw new Error('不存在父节点!');
|
|
333
541
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
542
|
+
parentNode.removeChild(this);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* 将当前节点批量替换为新的节点
|
|
547
|
+
* @param {...this} nodes 插入节点
|
|
548
|
+
* @complexity `n`
|
|
549
|
+
*/
|
|
550
|
+
replaceWith(...nodes) {
|
|
551
|
+
this.after(...nodes);
|
|
552
|
+
this.remove();
|
|
338
553
|
}
|
|
339
554
|
|
|
340
|
-
/**
|
|
555
|
+
/**
|
|
556
|
+
* 合并相邻的文本子节点
|
|
557
|
+
* @complexity `n`
|
|
558
|
+
*/
|
|
341
559
|
normalize() {
|
|
342
|
-
const
|
|
560
|
+
const AstText = require('./text');
|
|
561
|
+
const /** @type {AstText[]} */ childNodes = [...this.childNodes];
|
|
343
562
|
for (let i = childNodes.length - 1; i >= 0; i--) {
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
563
|
+
const {type, data} = childNodes[i];
|
|
564
|
+
if (data === '') {
|
|
346
565
|
childNodes.splice(i, 1);
|
|
347
|
-
} else if (
|
|
348
|
-
childNodes[i - 1]
|
|
566
|
+
} else if (type === 'text' && childNodes[i - 1]?.type === 'text') {
|
|
567
|
+
childNodes[i - 1].setAttribute('data', childNodes[i - 1].data + data);
|
|
349
568
|
childNodes.splice(i, 1);
|
|
350
569
|
}
|
|
351
570
|
}
|
|
352
571
|
this.setAttribute('childNodes', childNodes);
|
|
353
572
|
}
|
|
354
573
|
|
|
574
|
+
/** 获取根节点 */
|
|
355
575
|
getRootNode() {
|
|
356
576
|
let {parentNode} = this;
|
|
357
577
|
while (parentNode?.parentNode) {
|