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.
Files changed (81) hide show
  1. package/.eslintrc.json +472 -34
  2. package/README.md +1 -1
  3. package/config/default.json +58 -30
  4. package/config/llwiki.json +22 -90
  5. package/config/moegirl.json +51 -13
  6. package/config/zhwiki.json +1269 -0
  7. package/index.js +114 -104
  8. package/lib/element.js +448 -440
  9. package/lib/node.js +335 -115
  10. package/lib/ranges.js +27 -18
  11. package/lib/text.js +146 -0
  12. package/lib/title.js +13 -5
  13. package/mixin/attributeParent.js +70 -24
  14. package/mixin/fixedToken.js +14 -6
  15. package/mixin/hidden.js +6 -4
  16. package/mixin/sol.js +27 -10
  17. package/package.json +9 -3
  18. package/parser/brackets.js +22 -17
  19. package/parser/commentAndExt.js +18 -16
  20. package/parser/converter.js +14 -13
  21. package/parser/externalLinks.js +12 -11
  22. package/parser/hrAndDoubleUnderscore.js +23 -14
  23. package/parser/html.js +10 -9
  24. package/parser/links.js +15 -14
  25. package/parser/list.js +12 -11
  26. package/parser/magicLinks.js +12 -11
  27. package/parser/quotes.js +6 -5
  28. package/parser/selector.js +175 -0
  29. package/parser/table.js +25 -18
  30. package/printed/example.json +120 -0
  31. package/src/arg.js +56 -32
  32. package/src/atom/hidden.js +5 -2
  33. package/src/atom/index.js +17 -9
  34. package/src/attribute.js +182 -100
  35. package/src/converter.js +68 -41
  36. package/src/converterFlags.js +67 -45
  37. package/src/converterRule.js +117 -65
  38. package/src/extLink.js +66 -18
  39. package/src/gallery.js +42 -15
  40. package/src/heading.js +34 -15
  41. package/src/html.js +97 -35
  42. package/src/imageParameter.js +83 -54
  43. package/src/index.js +299 -178
  44. package/src/link/category.js +20 -52
  45. package/src/link/file.js +59 -28
  46. package/src/link/galleryImage.js +21 -7
  47. package/src/link/index.js +146 -60
  48. package/src/magicLink.js +34 -12
  49. package/src/nowiki/comment.js +22 -10
  50. package/src/nowiki/dd.js +37 -22
  51. package/src/nowiki/doubleUnderscore.js +16 -7
  52. package/src/nowiki/hr.js +11 -7
  53. package/src/nowiki/index.js +16 -9
  54. package/src/nowiki/list.js +2 -2
  55. package/src/nowiki/noinclude.js +8 -4
  56. package/src/nowiki/quote.js +11 -7
  57. package/src/onlyinclude.js +19 -7
  58. package/src/parameter.js +65 -38
  59. package/src/syntax.js +26 -20
  60. package/src/table/index.js +260 -165
  61. package/src/table/td.js +98 -52
  62. package/src/table/tr.js +102 -58
  63. package/src/tagPair/ext.js +27 -19
  64. package/src/tagPair/include.js +16 -11
  65. package/src/tagPair/index.js +64 -29
  66. package/src/transclude.js +170 -93
  67. package/test/api.js +83 -0
  68. package/test/real.js +133 -0
  69. package/test/test.js +28 -0
  70. package/test/util.js +80 -0
  71. package/tool/index.js +41 -31
  72. package/typings/api.d.ts +13 -0
  73. package/typings/array.d.ts +28 -0
  74. package/typings/event.d.ts +24 -0
  75. package/typings/index.d.ts +46 -4
  76. package/typings/node.d.ts +15 -9
  77. package/typings/parser.d.ts +7 -0
  78. package/typings/tool.d.ts +3 -2
  79. package/util/debug.js +21 -18
  80. package/util/string.js +40 -27
  81. 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, noWrap} = require('../util/string'),
4
+ {text} = require('../util/string'),
5
5
  assert = require('assert/strict'),
6
- /** @type {Parser} */ Parser = require('..');
6
+ EventEmitter = require('events'),
7
+ Parser = require('..');
7
8
 
9
+ /** 类似Node */
8
10
  class AstNode {
9
- /** @type {(string|this)[]} */ childNodes = [];
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
- /** @complexity `n` */
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
- /** @complexity `n` */
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
- * @param {string} method
39
- * @param {...string} types
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
- /** @param {string|string[]} keys */
46
- seal(keys) {
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
- this.#optional.push(...keys);
144
+ if (!permanent) {
145
+ this.#optional.push(...keys);
146
+ }
52
147
  for (const key of keys) {
53
- Object.defineProperty(this, key, {writable: false, enumerable: Boolean(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
- /** @param {this} node */
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
- /** @param {PropertyKey} key */
177
+ /**
178
+ * 是否具有某属性
179
+ * @param {PropertyKey} key 属性键
180
+ */
77
181
  hasAttribute(key) {
78
- if (!['string', 'number', 'symbol'].includes(typeof key)) {
79
- this.typeError('hasAttribute', 'String', 'Number', 'Symbol');
80
- }
81
- return key in this;
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
- /** @param {PropertyKey} key */
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
- * @param {PropertyKey} name
149
- * @param {boolean|undefined} force
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
- if (!(node instanceof AstNode)) {
186
- this.typeError('contains', 'Token');
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
- /** @param {number} i */
192
- verifyChild(i, addition = 0) {
193
- if (!Parser.debugging && externalUse('verifyChild')) {
194
- this.debugOnly('verifyChild');
195
- } else if (typeof i !== 'number') {
196
- this.typeError('verifyChild', 'Number');
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
- const {length} = this.childNodes;
199
- if (i < -length || i >= length + addition || !Number.isInteger(i)) {
200
- throw new RangeError(`不存在第 ${i} 个子节点!`);
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
- /** @param {number} i */
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
- if (node instanceof AstNode) {
210
- node.setAttribute('parentNode');
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
- * @param {string|this} node
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
- * @template {string|this} T
234
- * @param {T} node
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
- * @template {string|this} T
244
- * @param {T} node
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 (typeof node !== 'string' && !(node instanceof AstNode)) {
249
- this.typeError('insertAt', 'String', 'Token');
250
- } else if (node instanceof AstNode && node.contains(this)) {
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
- if (node instanceof AstNode) {
257
- const j = Parser.running ? -1 : childNodes.indexOf(node);
258
- if (j !== -1) {
259
- childNodes.splice(j, 1);
260
- } else {
261
- node.parentNode?.removeChild(node);
262
- node.setAttribute('parentNode', this);
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
- * @template {string|this} T
272
- * @param {T} node
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
- * @template {string|this} T
281
- * @param {T} child
282
- * @param {string|this} reference
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
- if (reference === undefined) {
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
- * @template {string|this} T
294
- * @param {string|this} newChild
295
- * @param {T} oldChild
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
- /** @param {string} str */
306
- setText(str, i = 0) {
307
- if (typeof str !== 'string') {
308
- this.typeError('setText', 'String');
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
- this.verifyChild(i);
311
- const oldText = this.childNodes.at(i);
312
- if (typeof oldText !== 'string') {
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
- * @param {number} i
323
- * @param {number} offset
515
+ * 在后方批量插入兄弟节点
516
+ * @param {...this} nodes 插入节点
517
+ * @complexity `n`
324
518
  */
325
- splitText(i, offset) {
326
- if (typeof offset !== 'number') {
327
- this.typeError('splitText', 'Number');
328
- }
329
- this.verifyChild(i);
330
- const oldText = this.childNodes.at(i);
331
- if (typeof oldText !== 'string') {
332
- throw new RangeError(`第 ${i} 个子节点是 ${oldText.constructor.name}!`);
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
- const newText = oldText.slice(offset);
335
- this.insertAt(newText, i + 1);
336
- this.setText(oldText.slice(0, offset), i);
337
- return newText;
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
- /** @complexity `n` */
555
+ /**
556
+ * 合并相邻的文本子节点
557
+ * @complexity `n`
558
+ */
341
559
  normalize() {
342
- const childNodes = [...this.childNodes];
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 str = childNodes[i];
345
- if (str === '') {
563
+ const {type, data} = childNodes[i];
564
+ if (data === '') {
346
565
  childNodes.splice(i, 1);
347
- } else if (typeof str === 'string' && typeof childNodes[i - 1] === 'string') {
348
- childNodes[i - 1] += str;
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) {