wikiparser-node 0.7.0-m → 0.7.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 (80) hide show
  1. package/README.md +39 -0
  2. package/config/llwiki.json +35 -0
  3. package/config/moegirl.json +44 -0
  4. package/config/zhwiki.json +466 -0
  5. package/index.js +259 -11
  6. package/lib/element.js +481 -7
  7. package/lib/node.js +552 -6
  8. package/lib/ranges.js +130 -0
  9. package/lib/text.js +112 -21
  10. package/lib/title.js +27 -0
  11. package/mixin/attributeParent.js +117 -0
  12. package/mixin/fixedToken.js +40 -0
  13. package/mixin/hidden.js +3 -0
  14. package/mixin/singleLine.js +31 -0
  15. package/mixin/sol.js +65 -0
  16. package/package.json +5 -4
  17. package/parser/brackets.js +1 -0
  18. package/parser/commentAndExt.js +4 -3
  19. package/parser/converter.js +1 -0
  20. package/parser/externalLinks.js +1 -0
  21. package/parser/hrAndDoubleUnderscore.js +1 -0
  22. package/parser/html.js +1 -0
  23. package/parser/links.js +5 -4
  24. package/parser/list.js +1 -0
  25. package/parser/magicLinks.js +5 -4
  26. package/parser/quotes.js +2 -1
  27. package/parser/selector.js +177 -0
  28. package/parser/table.js +1 -0
  29. package/src/arg.js +116 -2
  30. package/src/atom/hidden.js +2 -0
  31. package/src/atom/index.js +17 -0
  32. package/src/attribute.js +171 -4
  33. package/src/attributes.js +306 -4
  34. package/src/charinsert.js +57 -1
  35. package/src/converter.js +108 -2
  36. package/src/converterFlags.js +187 -0
  37. package/src/converterRule.js +184 -1
  38. package/src/extLink.js +120 -1
  39. package/src/gallery.js +55 -5
  40. package/src/hasNowiki/index.js +12 -0
  41. package/src/hasNowiki/pre.js +12 -0
  42. package/src/heading.js +55 -4
  43. package/src/html.js +118 -3
  44. package/src/imageParameter.js +176 -5
  45. package/src/imagemap.js +60 -1
  46. package/src/imagemapLink.js +13 -1
  47. package/src/index.js +527 -3
  48. package/src/link/category.js +37 -1
  49. package/src/link/file.js +159 -2
  50. package/src/link/galleryImage.js +59 -1
  51. package/src/link/index.js +269 -1
  52. package/src/magicLink.js +90 -9
  53. package/src/nested/choose.js +1 -0
  54. package/src/nested/combobox.js +1 -0
  55. package/src/nested/index.js +30 -3
  56. package/src/nested/references.js +1 -0
  57. package/src/nowiki/comment.js +25 -1
  58. package/src/nowiki/dd.js +47 -1
  59. package/src/nowiki/doubleUnderscore.js +31 -1
  60. package/src/nowiki/hr.js +20 -1
  61. package/src/nowiki/index.js +23 -1
  62. package/src/nowiki/list.js +5 -2
  63. package/src/nowiki/noinclude.js +14 -0
  64. package/src/nowiki/quote.js +16 -2
  65. package/src/onlyinclude.js +26 -1
  66. package/src/paramTag/index.js +24 -1
  67. package/src/paramTag/inputbox.js +44 -0
  68. package/src/parameter.js +148 -6
  69. package/src/syntax.js +68 -0
  70. package/src/table/index.js +940 -2
  71. package/src/table/td.js +225 -5
  72. package/src/table/tr.js +247 -2
  73. package/src/tagPair/ext.js +24 -4
  74. package/src/tagPair/include.js +24 -0
  75. package/src/tagPair/index.js +52 -2
  76. package/src/transclude.js +512 -11
  77. package/tool/index.js +1202 -0
  78. package/util/debug.js +73 -0
  79. package/util/string.js +48 -1
  80. package/config/minimum.json +0 -142
package/lib/node.js CHANGED
@@ -1,12 +1,34 @@
1
1
  'use strict';
2
2
 
3
- const {text} = require('../util/string');
3
+ const {text} = require('../util/string'),
4
+ {typeError} = require('../util/debug'),
5
+ assert = require('assert/strict'),
6
+ EventEmitter = require('events'),
7
+ Parser = require('..');
4
8
 
5
9
  /** 类似Node */
6
10
  class AstNode {
7
11
  /** @type {string} */ type;
8
12
  /** @type {this[]} */ childNodes = [];
9
13
  /** @type {this} */ #parentNode;
14
+ /** @type {Set<string>} */ #optional = new Set();
15
+ #events = new EventEmitter();
16
+
17
+ /**
18
+ * 检查在某个位置增删子节点是否合法
19
+ * @param {number} i 增删位置
20
+ * @param {number} addition 将会插入的子节点个数
21
+ * @throws `RangeError` 指定位置不存在子节点
22
+ */
23
+ #verifyChild = (i, addition = 0) => {
24
+ if (!Number.isInteger(i)) {
25
+ this.typeError('verifyChild', 'Number');
26
+ }
27
+ const {childNodes: {length}} = this;
28
+ if (i < -length || i >= length + addition) {
29
+ throw new RangeError(`不存在第 ${i} 个子节点!`);
30
+ }
31
+ };
10
32
 
11
33
  /** 首位子节点 */
12
34
  get firstChild() {
@@ -41,6 +63,159 @@ class AstNode {
41
63
  return childNodes && childNodes[childNodes.indexOf(this) - 1];
42
64
  }
43
65
 
66
+ /**
67
+ * 后一个非文本兄弟节点
68
+ * @complexity `n`
69
+ * @returns {this}
70
+ */
71
+ get nextElementSibling() {
72
+ const childNodes = this.#parentNode?.childNodes,
73
+ i = childNodes?.indexOf(this);
74
+ return childNodes?.slice(i + 1)?.find(({type}) => type !== 'text');
75
+ }
76
+
77
+ /**
78
+ * 前一个非文本兄弟节点
79
+ * @complexity `n`
80
+ * @returns {this}
81
+ */
82
+ get previousElementSibling() {
83
+ const childNodes = this.#parentNode?.childNodes,
84
+ i = childNodes?.indexOf(this);
85
+ return childNodes?.slice(0, i)?.findLast(({type}) => type !== 'text');
86
+ }
87
+
88
+ /** 是否具有根节点 */
89
+ get isConnected() {
90
+ return this.getRootNode().type === 'root';
91
+ }
92
+
93
+ /** 不是自身的根节点 */
94
+ get ownerDocument() {
95
+ const root = this.getRootNode();
96
+ return root.type === 'root' && root !== this ? root : undefined;
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
+ constructor() {
117
+ Object.defineProperty(this, 'childNodes', {writable: false});
118
+ Object.freeze(this.childNodes);
119
+ }
120
+
121
+ /**
122
+ * 标记仅用于代码调试的方法
123
+ * @param {string} method
124
+ * @throws `Error`
125
+ */
126
+ debugOnly(method = 'debugOnly') {
127
+ throw new Error(`${this.constructor.name}.${method} 方法仅用于代码调试!`);
128
+ }
129
+
130
+ /**
131
+ * 抛出`TypeError`
132
+ * @param {string} method
133
+ * @param {...string} types 可接受的参数类型
134
+ */
135
+ typeError(method, ...types) {
136
+ return typeError(this.constructor, method, ...types);
137
+ }
138
+
139
+ /**
140
+ * 冻结部分属性
141
+ * @param {string|string[]} keys 属性键
142
+ * @param {boolean} permanent 是否永久
143
+ */
144
+ seal(keys, permanent) {
145
+ if (!Parser.running && !Parser.debugging) {
146
+ this.debugOnly('seal');
147
+ }
148
+ keys = Array.isArray(keys) ? keys : [keys];
149
+ if (!permanent) {
150
+ for (const key of keys) {
151
+ this.#optional.add(key);
152
+ }
153
+ }
154
+ for (const key of keys) {
155
+ Object.defineProperty(this, key, {
156
+ writable: false, enumerable: !permanent && Boolean(this[key]), configurable: !permanent,
157
+ });
158
+ }
159
+ }
160
+
161
+ /**
162
+ * 是否是全同节点
163
+ * @param {this} node 待比较的节点
164
+ * @throws `assert.AssertionError`
165
+ */
166
+ isEqualNode(node) {
167
+ try {
168
+ assert.deepStrictEqual(this, node);
169
+ } catch (e) {
170
+ if (e instanceof assert.AssertionError) {
171
+ return false;
172
+ }
173
+ throw e;
174
+ }
175
+ return true;
176
+ }
177
+
178
+ /** 获取所有属性键 */
179
+ getAttributeNames() {
180
+ const names = Object.getOwnPropertyNames(this);
181
+ return names.filter(name => typeof this[name] !== 'function');
182
+ }
183
+
184
+ /** 是否具有任意属性 */
185
+ hasAttributes() {
186
+ return this.getAttributeNames().length > 0;
187
+ }
188
+
189
+ /**
190
+ * 移除某属性
191
+ * @param {PropertyKey} key 属性键
192
+ * @throws `RangeError` 不可删除的属性
193
+ */
194
+ removeAttribute(key) {
195
+ if (this.hasAttribute(key)) {
196
+ const descriptor = Object.getOwnPropertyDescriptor(this, key);
197
+ if (!descriptor || !descriptor.writable) {
198
+ throw new RangeError(`属性 ${key} 不可删除!`);
199
+ }
200
+ delete this[key];
201
+ }
202
+ }
203
+
204
+ /**
205
+ * 开关某属性
206
+ * @param {PropertyKey} key 属性键
207
+ * @param {boolean|undefined} force 强制开启或关闭
208
+ * @throws `RangeError` 不为Boolean类型的属性值
209
+ */
210
+ toggleAttribute(key, force) {
211
+ if (force !== undefined && typeof force !== 'boolean') {
212
+ this.typeError('toggleAttribute', 'Boolean');
213
+ } else if (this.hasAttribute(key) && typeof this[key] !== 'boolean') {
214
+ throw new RangeError(`${key} 属性的值不为 Boolean!`);
215
+ }
216
+ this.setAttribute(key, force === true || force === undefined && !this[key]);
217
+ }
218
+
44
219
  /**
45
220
  * 是否具有某属性
46
221
  * @param {PropertyKey} key 属性键
@@ -49,7 +224,7 @@ class AstNode {
49
224
  const type = typeof key;
50
225
  return type === 'string' || type === 'number' || type === 'symbol'
51
226
  ? key in this
52
- : false;
227
+ : this.typeError('hasAttribute', 'String', 'Number', 'Symbol');
53
228
  }
54
229
 
55
230
  /**
@@ -59,6 +234,11 @@ class AstNode {
59
234
  * @returns {TokenAttribute<T>}
60
235
  */
61
236
  getAttribute(key) {
237
+ if (key === 'optional') {
238
+ return new Set(this.#optional);
239
+ } else if (key === 'verifyChild') {
240
+ return this.#verifyChild;
241
+ }
62
242
  return this.hasAttribute(key) ? String(this[key]) : undefined;
63
243
  }
64
244
 
@@ -71,6 +251,17 @@ class AstNode {
71
251
  setAttribute(key, value) {
72
252
  if (key === 'parentNode') {
73
253
  this.#parentNode = value;
254
+ } else if (Object.hasOwn(this, key)) {
255
+ const descriptor = Object.getOwnPropertyDescriptor(this, key);
256
+ if (this.#optional.has(key)) {
257
+ descriptor.enumerable = Boolean(value);
258
+ }
259
+ const oldValue = this[key],
260
+ frozen = oldValue !== null && typeof oldValue === 'object' && Object.isFrozen(oldValue);
261
+ Object.defineProperty(this, key, {...descriptor, value});
262
+ if (frozen && value !== null && typeof value === 'object') {
263
+ Object.freeze(value);
264
+ }
74
265
  } else {
75
266
  this[key] = value;
76
267
  }
@@ -92,8 +283,13 @@ class AstNode {
92
283
  * @param {number} i 移除位置
93
284
  */
94
285
  removeAt(i) {
95
- const {childNodes} = this,
286
+ this.getAttribute('verifyChild')(i);
287
+ const childNodes = [...this.childNodes],
288
+ e = new Event('remove', {bubbles: true}),
96
289
  [node] = childNodes.splice(i, 1);
290
+ node.setAttribute('parentNode');
291
+ this.setAttribute('childNodes', childNodes);
292
+ this.dispatchEvent(e, {position: i, removed: node});
97
293
  return node;
98
294
  }
99
295
 
@@ -103,24 +299,299 @@ class AstNode {
103
299
  * @param {T} node 待插入的子节点
104
300
  * @param {number} i 插入位置
105
301
  * @complexity `n`
302
+ * @throws `RangeError` 不能插入祖先节点
106
303
  */
107
304
  insertAt(node, i = this.childNodes.length) {
108
- const {childNodes} = this,
109
- j = -1;
305
+ if (!(node instanceof AstNode)) {
306
+ this.typeError('insertAt', 'String', 'AstNode');
307
+ } else if (node.contains(this)) {
308
+ Parser.error('不能插入祖先节点!', node);
309
+ throw new RangeError('不能插入祖先节点!');
310
+ }
311
+ this.getAttribute('verifyChild')(i, 1);
312
+ const childNodes = [...this.childNodes],
313
+ e = new Event('insert', {bubbles: true}),
314
+ j = Parser.running ? -1 : childNodes.indexOf(node);
110
315
  if (j === -1) {
316
+ node.parentNode?.removeChild(node);
111
317
  node.setAttribute('parentNode', this);
318
+ } else {
319
+ childNodes.splice(j, 1);
112
320
  }
113
321
  childNodes.splice(i, 0, node);
322
+ this.setAttribute('childNodes', childNodes);
323
+ this.dispatchEvent(e, {position: i < 0 ? i + this.childNodes.length - 1 : i, inserted: node});
324
+ return node;
325
+ }
326
+
327
+ /**
328
+ * 获取子节点的位置
329
+ * @param {this} node 子节点
330
+ * @complexity `n`
331
+ * @throws `RangeError` 找不到子节点
332
+ */
333
+ #getChildIndex(node) {
334
+ const {childNodes} = this,
335
+ i = childNodes.indexOf(node);
336
+ if (i === -1) {
337
+ Parser.error('找不到子节点!', node);
338
+ throw new RangeError('找不到子节点!');
339
+ }
340
+ return i;
341
+ }
342
+
343
+ /**
344
+ * 移除子节点
345
+ * @template {this} T
346
+ * @param {T} node 子节点
347
+ * @complexity `n`
348
+ */
349
+ removeChild(node) {
350
+ this.removeAt(this.#getChildIndex(node));
114
351
  return node;
115
352
  }
116
353
 
354
+ /**
355
+ * 在末尾插入子节点
356
+ * @template {this} T
357
+ * @param {T} node 插入节点
358
+ * @complexity `n`
359
+ */
360
+ appendChild(node) {
361
+ return this.insertAt(node);
362
+ }
363
+
364
+ /**
365
+ * 在指定位置前插入子节点
366
+ * @template {this} T
367
+ * @param {T} child 插入节点
368
+ * @param {this} reference 指定位置处的子节点
369
+ * @complexity `n`
370
+ */
371
+ insertBefore(child, reference) {
372
+ return reference === undefined ? this.insertAt(child) : this.insertAt(child, this.#getChildIndex(reference));
373
+ }
374
+
375
+ /**
376
+ * 替换子节点
377
+ * @template {this} T
378
+ * @param {this} newChild 新子节点
379
+ * @param {T} oldChild 原子节点
380
+ * @complexity `n`
381
+ */
382
+ replaceChild(newChild, oldChild) {
383
+ const i = this.#getChildIndex(oldChild);
384
+ this.removeAt(i);
385
+ this.insertAt(newChild, i);
386
+ return oldChild;
387
+ }
388
+
389
+ /**
390
+ * 在当前节点前后插入兄弟节点
391
+ * @param {this[]} nodes 插入节点
392
+ * @param {number} offset 插入的相对位置
393
+ * @complexity `n`
394
+ * @throws `Error` 不存在父节点
395
+ */
396
+ #insertAdjacent(nodes, offset) {
397
+ const {parentNode} = this;
398
+ if (!parentNode) {
399
+ throw new Error('不存在父节点!');
400
+ }
401
+ const i = parentNode.childNodes.indexOf(this) + offset;
402
+ for (let j = 0; j < nodes.length; j++) {
403
+ parentNode.insertAt(nodes[j], i + j);
404
+ }
405
+ }
406
+
407
+ /**
408
+ * 在后方批量插入兄弟节点
409
+ * @param {...this} nodes 插入节点
410
+ * @complexity `n`
411
+ */
412
+ after(...nodes) {
413
+ this.#insertAdjacent(nodes, 1);
414
+ }
415
+
416
+ /**
417
+ * 在前方批量插入兄弟节点
418
+ * @param {...this} nodes 插入节点
419
+ * @complexity `n`
420
+ */
421
+ before(...nodes) {
422
+ this.#insertAdjacent(nodes, 0);
423
+ }
424
+
425
+ /**
426
+ * 移除当前节点
427
+ * @complexity `n`
428
+ * @throws `Error` 不存在父节点
429
+ */
430
+ remove() {
431
+ const {parentNode} = this;
432
+ if (!parentNode) {
433
+ throw new Error('不存在父节点!');
434
+ }
435
+ parentNode.removeChild(this);
436
+ }
437
+
438
+ /**
439
+ * 将当前节点批量替换为新的节点
440
+ * @param {...this} nodes 插入节点
441
+ * @complexity `n`
442
+ */
443
+ replaceWith(...nodes) {
444
+ this.after(...nodes);
445
+ this.remove();
446
+ }
447
+
448
+ /** 是否具有子节点 */
449
+ hasChildNodes() {
450
+ return this.childNodes.length > 0;
451
+ }
452
+
453
+ /**
454
+ * 是自身或后代节点
455
+ * @param {this} node 待检测节点
456
+ * @returns {boolean}
457
+ * @complexity `n`
458
+ */
459
+ contains(node) {
460
+ return node instanceof AstNode
461
+ ? node === this || this.childNodes.some(child => child.contains(node))
462
+ : this.typeError('contains', 'AstNode');
463
+ }
464
+
465
+ /**
466
+ * 添加事件监听
467
+ * @param {string|string[]} types 事件类型
468
+ * @param {AstListener} listener 监听函数
469
+ * @param {{once: boolean}} options 选项
470
+ */
471
+ addEventListener(types, listener, options) {
472
+ if (Array.isArray(types)) {
473
+ for (const type of types) {
474
+ this.addEventListener(type, listener, options);
475
+ }
476
+ } else if (typeof types === 'string' && typeof listener === 'function') {
477
+ this.#events[options?.once ? 'once' : 'on'](types, listener);
478
+ } else {
479
+ this.typeError('addEventListener', 'String', 'Function');
480
+ }
481
+ }
482
+
483
+ /**
484
+ * 移除事件监听
485
+ * @param {string|string[]} types 事件类型
486
+ * @param {AstListener} listener 监听函数
487
+ */
488
+ removeEventListener(types, listener) {
489
+ if (Array.isArray(types)) {
490
+ for (const type of types) {
491
+ this.removeEventListener(type, listener);
492
+ }
493
+ } else if (typeof types === 'string' && typeof listener === 'function') {
494
+ this.#events.off(types, listener);
495
+ } else {
496
+ this.typeError('removeEventListener', 'String', 'Function');
497
+ }
498
+ }
499
+
500
+ /**
501
+ * 移除事件的所有监听
502
+ * @param {string|string[]} types 事件类型
503
+ */
504
+ removeAllEventListeners(types) {
505
+ if (Array.isArray(types)) {
506
+ for (const type of types) {
507
+ this.removeAllEventListeners(type);
508
+ }
509
+ } else if (types === undefined || typeof types === 'string') {
510
+ this.#events.removeAllListeners(types);
511
+ } else {
512
+ this.typeError('removeAllEventListeners', 'String');
513
+ }
514
+ }
515
+
516
+ /**
517
+ * 列举事件监听
518
+ * @param {string} type 事件类型
519
+ * @returns {AstListener[]}
520
+ */
521
+ listEventListeners(type) {
522
+ return typeof type === 'string' ? this.#events.listeners(type) : this.typeError('listEventListeners', 'String');
523
+ }
524
+
525
+ /**
526
+ * 触发事件
527
+ * @param {AstEvent} e 事件对象
528
+ * @param {*} data 事件数据
529
+ */
530
+ dispatchEvent(e, data) {
531
+ if (!(e instanceof Event)) {
532
+ this.typeError('dispatchEvent', 'Event');
533
+ } else if (!e.target) { // 初始化
534
+ Object.defineProperty(e, 'target', {value: this, enumerable: true});
535
+
536
+ /** 终止冒泡 */
537
+ e.stopPropagation = function() {
538
+ Object.defineProperty(this, 'bubbles', {value: false});
539
+ };
540
+ }
541
+ Object.defineProperties(e, { // 每次bubble更新
542
+ prevTarget: {value: e.currentTarget, enumerable: true, configurable: true},
543
+ currentTarget: {value: this, enumerable: true, configurable: true},
544
+ });
545
+ this.#events.emit(e.type, e, data);
546
+ if (e.bubbles && this.parentNode) {
547
+ this.parentNode.dispatchEvent(e, data);
548
+ }
549
+ }
550
+
551
+ /** 获取所有祖先节点 */
552
+ getAncestors() {
553
+ const /** @type {this[]} */ ancestors = [];
554
+ let {parentNode} = this;
555
+ while (parentNode) {
556
+ ancestors.push(parentNode);
557
+ ({parentNode} = parentNode);
558
+ }
559
+ return ancestors;
560
+ }
561
+
562
+ /**
563
+ * 比较和另一个节点的相对位置
564
+ * @param {this} other 待比较的节点
565
+ * @complexity `n`
566
+ * @throws `Error` 不在同一个语法树
567
+ */
568
+ compareDocumentPosition(other) {
569
+ if (!(other instanceof AstNode)) {
570
+ this.typeError('compareDocumentPosition', 'AstNode');
571
+ } else if (this === other) {
572
+ return 0;
573
+ } else if (this.contains(other)) {
574
+ return -1;
575
+ } else if (other.contains(this)) {
576
+ return 1;
577
+ } else if (this.getRootNode() !== other.getRootNode()) {
578
+ throw new Error('不在同一个语法树!');
579
+ }
580
+ const aAncestors = [...this.getAncestors().reverse(), this],
581
+ bAncestors = [...other.getAncestors().reverse(), other],
582
+ depth = aAncestors.findIndex((ancestor, i) => bAncestors[i] !== ancestor),
583
+ commonAncestor = aAncestors[depth - 1],
584
+ {childNodes} = commonAncestor;
585
+ return childNodes.indexOf(aAncestors[depth]) - childNodes.indexOf(bAncestors[depth]);
586
+ }
587
+
117
588
  /**
118
589
  * 合并相邻的文本子节点
119
590
  * @complexity `n`
120
591
  */
121
592
  normalize() {
122
593
  const AstText = require('./text');
123
- const /** @type {{childNodes: AstText[]}} */ {childNodes} = this;
594
+ const /** @type {AstText[]} */ childNodes = [...this.childNodes];
124
595
  for (let i = childNodes.length - 1; i >= 0; i--) {
125
596
  const {type, data} = childNodes[i];
126
597
  if (this.getGaps(i - 1)) {
@@ -132,6 +603,7 @@ class AstNode {
132
603
  childNodes.splice(i, 1);
133
604
  }
134
605
  }
606
+ this.setAttribute('childNodes', childNodes);
135
607
  }
136
608
 
137
609
  /** 获取根节点 */
@@ -149,6 +621,9 @@ class AstNode {
149
621
  * @complexity `n`
150
622
  */
151
623
  posFromIndex(index) {
624
+ if (!Number.isInteger(index)) {
625
+ this.typeError('posFromIndex', 'Number');
626
+ }
152
627
  const str = String(this);
153
628
  if (index >= -str.length && index <= str.length) {
154
629
  const lines = str.slice(0, index).split('\n');
@@ -157,6 +632,22 @@ class AstNode {
157
632
  return undefined;
158
633
  }
159
634
 
635
+ /**
636
+ * 将行列号转换为字符位置
637
+ * @param {number} top 行号
638
+ * @param {number} left 列号
639
+ * @complexity `n`
640
+ */
641
+ indexFromPos(top, left) {
642
+ if (!Number.isInteger(top) || !Number.isInteger(left)) {
643
+ this.typeError('indexFromPos', 'Number');
644
+ }
645
+ const lines = String(this).split('\n');
646
+ return top >= 0 && left >= 0 && lines.length >= top + 1 && lines[top].length >= left
647
+ ? lines.slice(0, top).reduce((acc, curLine) => acc + curLine.length + 1, 0) + left
648
+ : undefined;
649
+ }
650
+
160
651
  /**
161
652
  * 获取行数和最后一行的列数
162
653
  * @complexity `n`
@@ -202,10 +693,40 @@ class AstNode {
202
693
  }
203
694
  return 0;
204
695
  }
696
+ this.getAttribute('verifyChild')(j, 1);
205
697
  ({childNodes} = this);
206
698
  return getIndex(j, this);
207
699
  }
208
700
 
701
+ /**
702
+ * 获取当前节点的绝对位置
703
+ * @returns {number}
704
+ * @complexity `n`
705
+ */
706
+ getAbsoluteIndex() {
707
+ const {parentNode} = this;
708
+ return parentNode ? parentNode.getAbsoluteIndex() + this.getRelativeIndex() : 0;
709
+ }
710
+
711
+ /**
712
+ * 获取当前节点的相对位置,或其第`j`个子节点的相对位置
713
+ * @param {number|undefined} j 子节点序号
714
+ * @complexity `n`
715
+ */
716
+ #getPosition(j) {
717
+ return j === undefined
718
+ ? this.parentNode?.posFromIndex(this.getRelativeIndex()) ?? {top: 0, left: 0}
719
+ : this.posFromIndex(this.getRelativeIndex(j));
720
+ }
721
+
722
+ /**
723
+ * 获取当前节点的行列位置和大小
724
+ * @complexity `n`
725
+ */
726
+ getBoundingClientRect() {
727
+ return {...this.#getDimension(), ...this.getRootNode().posFromIndex(this.getAbsoluteIndex())};
728
+ }
729
+
209
730
  /**
210
731
  * 行数
211
732
  * @complexity `n`
@@ -221,6 +742,31 @@ class AstNode {
221
742
  get offsetWidth() {
222
743
  return this.#getDimension().width;
223
744
  }
745
+
746
+ /**
747
+ * 相对于父容器的行号
748
+ * @complexity `n`
749
+ */
750
+ get offsetTop() {
751
+ return this.#getPosition().top;
752
+ }
753
+
754
+ /**
755
+ * 相对于父容器的列号
756
+ * @complexity `n`
757
+ */
758
+ get offsetLeft() {
759
+ return this.#getPosition().left;
760
+ }
761
+
762
+ /**
763
+ * 位置、大小和padding
764
+ * @complexity `n`
765
+ */
766
+ get style() {
767
+ return {...this.#getPosition(), ...this.#getDimension(), padding: this.getPadding()};
768
+ }
224
769
  }
225
770
 
771
+ Parser.classes.AstNode = __filename;
226
772
  module.exports = AstNode;