wikiparser-node 0.0.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 (65) hide show
  1. package/.eslintrc.json +229 -0
  2. package/LICENSE +674 -0
  3. package/README.md +1896 -0
  4. package/config/default.json +766 -0
  5. package/config/llwiki.json +686 -0
  6. package/config/moegirl.json +721 -0
  7. package/index.js +159 -0
  8. package/jsconfig.json +7 -0
  9. package/lib/element.js +690 -0
  10. package/lib/node.js +357 -0
  11. package/lib/ranges.js +122 -0
  12. package/lib/title.js +57 -0
  13. package/mixin/attributeParent.js +67 -0
  14. package/mixin/fixedToken.js +32 -0
  15. package/mixin/hidden.js +22 -0
  16. package/package.json +30 -0
  17. package/parser/brackets.js +107 -0
  18. package/parser/commentAndExt.js +61 -0
  19. package/parser/externalLinks.js +30 -0
  20. package/parser/hrAndDoubleUnderscore.js +26 -0
  21. package/parser/html.js +41 -0
  22. package/parser/links.js +92 -0
  23. package/parser/magicLinks.js +40 -0
  24. package/parser/quotes.js +63 -0
  25. package/parser/table.js +97 -0
  26. package/src/arg.js +150 -0
  27. package/src/atom/hidden.js +10 -0
  28. package/src/atom/index.js +33 -0
  29. package/src/attribute.js +342 -0
  30. package/src/extLink.js +116 -0
  31. package/src/heading.js +91 -0
  32. package/src/html.js +144 -0
  33. package/src/imageParameter.js +172 -0
  34. package/src/index.js +602 -0
  35. package/src/link/category.js +88 -0
  36. package/src/link/file.js +201 -0
  37. package/src/link/index.js +214 -0
  38. package/src/listToken.js +47 -0
  39. package/src/magicLink.js +66 -0
  40. package/src/nowiki/comment.js +45 -0
  41. package/src/nowiki/doubleUnderscore.js +42 -0
  42. package/src/nowiki/hr.js +41 -0
  43. package/src/nowiki/index.js +37 -0
  44. package/src/nowiki/noinclude.js +24 -0
  45. package/src/nowiki/quote.js +37 -0
  46. package/src/onlyinclude.js +42 -0
  47. package/src/parameter.js +165 -0
  48. package/src/syntax.js +80 -0
  49. package/src/table/index.js +867 -0
  50. package/src/table/td.js +259 -0
  51. package/src/table/tr.js +244 -0
  52. package/src/tagPair/ext.js +85 -0
  53. package/src/tagPair/include.js +45 -0
  54. package/src/tagPair/index.js +91 -0
  55. package/src/transclude.js +627 -0
  56. package/tool/index.js +898 -0
  57. package/typings/element.d.ts +28 -0
  58. package/typings/index.d.ts +49 -0
  59. package/typings/node.d.ts +23 -0
  60. package/typings/parser.d.ts +9 -0
  61. package/typings/table.d.ts +14 -0
  62. package/typings/token.d.ts +21 -0
  63. package/typings/tool.d.ts +10 -0
  64. package/util/debug.js +70 -0
  65. package/util/string.js +60 -0
package/lib/element.js ADDED
@@ -0,0 +1,690 @@
1
+ 'use strict';
2
+
3
+ const {typeError, externalUse} = require('../util/debug'),
4
+ {toCase} = require('../util/string'),
5
+ {nth} = require('./ranges'),
6
+ EventEmitter = require('events'),
7
+ AstNode = require('./node'),
8
+ /** @type {Parser} */ Parser = require('..');
9
+
10
+ class AstElement extends AstNode {
11
+ /** @type {string} */ type;
12
+ /** @type {string} */ name;
13
+ #events = new EventEmitter();
14
+
15
+ /** @complexity `n` */
16
+ get children() {
17
+ const /** @type {this[]} */ children = this.childNodes.filter(ele => ele instanceof AstElement);
18
+ return children;
19
+ }
20
+ /** @complexity `n` */
21
+ get childElementCount() {
22
+ return this.children.length;
23
+ }
24
+ /** @returns {this} */
25
+ get firstElementChild() {
26
+ return this.childNodes.find(ele => ele instanceof AstElement);
27
+ }
28
+ /** @complexity `n` */
29
+ get lastElementChild() {
30
+ return this.children.at(-1);
31
+ }
32
+ get parentElement() {
33
+ return this.parentNode;
34
+ }
35
+ get isConnected() {
36
+ return this.getRootNode().type === 'root';
37
+ }
38
+ /** @complexity `n` */
39
+ get nextElementSibling() {
40
+ const children = this.parentElement?.children;
41
+ return children?.[children?.indexOf(this) + 1];
42
+ }
43
+ /** @complexity `n` */
44
+ get previousElementSibling() {
45
+ const children = this.parentElement?.children;
46
+ return children?.[children?.indexOf(this) - 1];
47
+ }
48
+ /** @complexity `n` */
49
+ get hidden() {
50
+ return this.text() === '';
51
+ }
52
+ /** @complexity `n` */
53
+ get nextVisibleSibling() {
54
+ let {nextSibling} = this;
55
+ while (nextSibling === '' || nextSibling instanceof AstElement && nextSibling.hidden) {
56
+ ({nextSibling} = nextSibling);
57
+ }
58
+ return nextSibling;
59
+ }
60
+ /** @complexity `n` */
61
+ get previousVisibleSibling() {
62
+ let {previousSibling} = this;
63
+ while (previousSibling === '' || previousSibling instanceof AstElement && previousSibling.hidden) {
64
+ ({previousSibling} = previousSibling);
65
+ }
66
+ return previousSibling;
67
+ }
68
+
69
+ constructor() {
70
+ super();
71
+ this.seal('name');
72
+ }
73
+
74
+ /** @complexity `n` */
75
+ destroy() {
76
+ if (this.parentNode) {
77
+ throw new Error('不能销毁子节点!');
78
+ }
79
+ for (const element of this.children) {
80
+ element.setAttribute('parentNode');
81
+ }
82
+ Object.setPrototypeOf(this, null);
83
+ }
84
+
85
+ /**
86
+ * @param {string|string[]} types
87
+ * @param {AstListener} listener
88
+ * @param {{once: boolean}} options
89
+ */
90
+ addEventListener(types, listener, options) {
91
+ if (Array.isArray(types)) {
92
+ for (const type of types) {
93
+ this.addEventListener(type, listener, options);
94
+ }
95
+ } else if (typeof types !== 'string' || typeof listener !== 'function') {
96
+ typeError(this, 'addEventListener', 'String', 'Function');
97
+ } else {
98
+ this.#events[options?.once ? 'once' : 'on'](types, listener);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * @param {string|string[]} types
104
+ * @param {AstListener} listener
105
+ */
106
+ removeEventListener(types, listener) {
107
+ if (Array.isArray(types)) {
108
+ for (const type of types) {
109
+ this.removeEventListener(type, listener);
110
+ }
111
+ } else if (typeof types !== 'string' || typeof listener !== 'function') {
112
+ typeError(this, 'removeEventListener', 'String', 'Function');
113
+ } else {
114
+ this.#events.off(types, listener);
115
+ }
116
+ }
117
+
118
+ /** @param {string|string[]} types */
119
+ removeAllEventListeners(types) {
120
+ if (Array.isArray(types)) {
121
+ for (const type of types) {
122
+ this.removeAllEventListeners(type);
123
+ }
124
+ } else if (types !== undefined && typeof types !== 'string') {
125
+ typeError(this, 'removeAllEventListeners', 'String');
126
+ } else {
127
+ this.#events.removeAllListeners(types);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * @param {string} type
133
+ * @returns {AstListener[]}
134
+ */
135
+ listEventListeners(type) {
136
+ if (typeof type !== 'string') {
137
+ typeError(this, 'listEventListeners', 'String');
138
+ }
139
+ return this.#events.listeners(type);
140
+ }
141
+
142
+ /**
143
+ * @param {AstEvent} e
144
+ * @param {any} data
145
+ */
146
+ dispatchEvent(e, data) {
147
+ if (!(e instanceof Event)) {
148
+ typeError(this, 'dispatchEvent', 'Event');
149
+ } else if (!e.target) { // 初始化
150
+ Object.defineProperty(e, 'target', {value: this, enumerable: true});
151
+ e.stopPropagation = function() {
152
+ Object.defineProperty(this, 'bubbles', {value: false});
153
+ };
154
+ }
155
+ Object.defineProperties(e, { // 每次bubble更新
156
+ prevTarget: {value: e.currentTarget, enumerable: true, configurable: true},
157
+ currentTarget: {value: this, enumerable: true, configurable: true},
158
+ });
159
+ this.#events.emit(e.type, e, data);
160
+ if (e.bubbles && this.parentElement) {
161
+ this.parentElement.dispatchEvent(e, data);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @template {string} T
167
+ * @param {T} key
168
+ * @param {TokenAttribute<T>} value
169
+ */
170
+ setAttribute(key, value) {
171
+ if (key === 'name' && externalUse('setAttribute')) {
172
+ throw new RangeError(`禁止手动指定 ${key} 属性!`);
173
+ }
174
+ return super.setAttribute(key, value);
175
+ }
176
+
177
+ /** @param {number} i */
178
+ removeAt(i) {
179
+ const element = super.removeAt(i),
180
+ e = new Event('remove', {bubbles: true});
181
+ this.dispatchEvent(e, {position: i, removed: element});
182
+ return element;
183
+ }
184
+
185
+ /**
186
+ * @template {string|this} T
187
+ * @param {T} element
188
+ * @complexity `n`
189
+ */
190
+ insertAt(element, i = this.childNodes.length) {
191
+ super.insertAt(element, i);
192
+ const e = new Event('insert', {bubbles: true});
193
+ this.dispatchEvent(e, {position: i < 0 ? i + this.childNodes.length - 1 : i, inserted: element});
194
+ return element;
195
+ }
196
+
197
+ /** @param {string} str */
198
+ setText(str, i = 0) {
199
+ const oldText = super.setText(str, i),
200
+ e = new Event('text', {bubbles: true});
201
+ this.dispatchEvent(e, {position: i, oldText, newText: str});
202
+ return oldText;
203
+ }
204
+
205
+ /**
206
+ * @param {...string|this} elements
207
+ * @complexity `n`
208
+ */
209
+ append(...elements) {
210
+ for (const element of elements) {
211
+ this.appendChild(element);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * @param {...string|this} elements
217
+ * @complexity `n`
218
+ */
219
+ prepend(...elements) {
220
+ for (const [i, element] of elements.entries()) {
221
+ this.insertAt(element, i);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * @param {...string|this} elements
227
+ * @complexity `n`
228
+ */
229
+ replaceChildren(...elements) {
230
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
231
+ this.removeAt(i);
232
+ }
233
+ this.append(...elements);
234
+ }
235
+
236
+ /**
237
+ * @param {(string|this)[]} elements
238
+ * @param {number} offset
239
+ * @complexity `n`
240
+ */
241
+ #insertAdjacent(elements, offset) {
242
+ const {parentNode} = this;
243
+ if (!parentNode) {
244
+ throw new Error('不存在父节点!');
245
+ }
246
+ const i = parentNode.childNodes.indexOf(this) + offset;
247
+ for (const [j, element] of elements.entries()) {
248
+ parentNode.insertAt(element, i + j);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * @param {...string|this} elements
254
+ * @complexity `n`
255
+ */
256
+ after(...elements) {
257
+ this.#insertAdjacent(elements, 1);
258
+ }
259
+
260
+ /**
261
+ * @param {...string|this} elements
262
+ * @complexity `n`
263
+ */
264
+ before(...elements) {
265
+ this.#insertAdjacent(elements, 0);
266
+ }
267
+
268
+ /** @complexity `n` */
269
+ remove() {
270
+ const {parentNode} = this;
271
+ if (!parentNode) {
272
+ throw new Error('不存在父节点!');
273
+ }
274
+ parentNode.removeChild(this);
275
+ }
276
+
277
+ /**
278
+ * @param {...string|this} elements
279
+ * @complexity `n`
280
+ */
281
+ replaceWith(...elements) {
282
+ this.after(...elements);
283
+ this.remove();
284
+ }
285
+
286
+ /**
287
+ * @param {string} key
288
+ * @param {string|undefined} equal - `equal`存在时`val`和`i`也一定存在
289
+ * @param {string|undefined} val
290
+ * @param {string|undefined} i
291
+ */
292
+ matchesAttr(key, equal, val, i) {
293
+ if (externalUse('matchesAttr')) {
294
+ throw new Error(`禁止外部调用 ${this.constructor.name}.matchesAttr 方法!`);
295
+ } else if (!equal) {
296
+ return this.hasAttribute(key);
297
+ } else if (!this.hasAttribute(key)) {
298
+ return equal === '!=';
299
+ }
300
+ val = toCase(val, i);
301
+ if (equal === '~=') {
302
+ let /** @type {Iterable<string>} */ thisVals = this[key];
303
+ if (typeof thisVals === 'string') {
304
+ thisVals = thisVals.split(/\s/);
305
+ }
306
+ return Boolean(thisVals?.[Symbol.iterator]) && [...thisVals].some(v => toCase(v, i) === val);
307
+ }
308
+ const thisVal = toCase(this.getAttribute(key), i);
309
+ switch (equal) {
310
+ case '|=':
311
+ return thisVal === val || thisVal.startsWith(`${val}-`);
312
+ case '^=':
313
+ return thisVal.startsWith(val);
314
+ case '$=':
315
+ return thisVal.endsWith(val);
316
+ case '*=':
317
+ return thisVal.includes(val);
318
+ case '!=':
319
+ return thisVal !== val;
320
+ default: // `=`
321
+ return thisVal === val;
322
+ }
323
+ }
324
+
325
+ /** @type {Record<pseudo, boolean>} */ static #pseudo = {
326
+ root: false,
327
+ is: true,
328
+ not: true,
329
+ 'nth-child': true,
330
+ 'nth-of-type': true,
331
+ 'nth-last-child': true,
332
+ 'nth-last-of-type': true,
333
+ 'first-child': false,
334
+ 'first-of-type': false,
335
+ 'last-child': false,
336
+ 'last-of-type': false,
337
+ 'only-child': false,
338
+ 'only-of-type': false,
339
+ empty: false,
340
+ contains: true,
341
+ has: true,
342
+ header: false,
343
+ parent: false,
344
+ hidden: false,
345
+ visible: false,
346
+ };
347
+ /** @type {pseudo[]} */ static #pseudoKeys = Object.keys(AstElement.#pseudo);
348
+ static #pseudoRegex = new RegExp(
349
+ `:(${this.#pseudoKeys.join('|')})(?:\\(\\s*("[^"]*"|'[^']*'|[^()]*?)\\s*\\))?(?=:|\\s*(?:,|$))`,
350
+ 'g',
351
+ );
352
+ static #simplePseudoRegex = new RegExp(`:(?:${this.#pseudoKeys.join('|')})(?:\\(.*?\\))?(?=:|\\s*(?:,|$))`, 'g');
353
+
354
+ /**
355
+ * @returns {boolean}
356
+ * @complexity `n`
357
+ */
358
+ matches(selector = '', simple = false) {
359
+ if (typeof selector !== 'string') {
360
+ typeError(this, 'matches', 'String');
361
+ } else if (!selector.trim()) {
362
+ return true;
363
+ }
364
+ simple &&= Parser.running;
365
+ const /** @type {Record<string, string>} */ escapedQuotes = {'"': '&quot;', "'": '&apos;'},
366
+ escapedSelector = selector.replace(/\\["']/g, m => escapedQuotes[m[1]]);
367
+ if (simple || !AstElement.#pseudoRegex.test(escapedSelector)) {
368
+ if (!simple && selector.includes(',')) {
369
+ return Parser.run(() => selector.split(',').some(str => this.matches(str, true)));
370
+ }
371
+ const mt = escapedSelector.match(AstElement.#simplePseudoRegex);
372
+ if (mt) {
373
+ Parser.error(
374
+ '检测到不规范的伪选择器!嵌套伪选择器时请使用引号包裹内层,多层嵌套时请使用"\\"转义引号。',
375
+ mt.map(s => s.replace(
376
+ /&(quot|apos);/g,
377
+ /** @param {string} p1 */ (_, p1) => `\\${p1 === 'quot' ? '"' : "'"}`,
378
+ )),
379
+ );
380
+ }
381
+ const /** @type {Record<string, string>} */ entities = {comma: ',', ratio: ':'},
382
+ /** @type {string[][]} */ attributes = [],
383
+ plainSelector = selector.replace(
384
+ /&(comma|ratio);/g, /** @param {string} name */ (_, name) => entities[name],
385
+ ).replace(
386
+ /\[\s*(\w+)\s*(?:([~|^$*!]?=)\s*("[^"]*"|'[^']*'|[^[\]]*?)\s*(\si)?\s*)?]/g,
387
+ /** @type {function(...string): ''} */ (_, key, equal, val, i) => {
388
+ if (equal) {
389
+ const quotes = val.match(/^(["']).*\1$/)?.[1];
390
+ attributes.push([
391
+ key,
392
+ equal,
393
+ quotes ? val.slice(1, -1).replaceAll(escapedQuotes[quotes], quotes) : val,
394
+ i,
395
+ ]);
396
+ } else {
397
+ attributes.push([key]);
398
+ }
399
+ return '';
400
+ },
401
+ ),
402
+ [type, ...parts] = plainSelector.trim().split('#'),
403
+ name = parts.join('#');
404
+ return (!type || this.type === type) && (!name || this.name === name)
405
+ && attributes.every(args => this.matchesAttr(...args));
406
+ }
407
+ /*
408
+ * 先将`\\'`转义成`&apos;`,将`\\"`转义成`&quot;`,即escapedSelector
409
+ * 在去掉一重`:pseudo()`时,如果使用了`'`,则将内部的`&apos;`解码成`'`;如果使用了`"`,则将内部的`&quot;`解码成`"`
410
+ */
411
+ const /** @type {pseudoCall} */ calls = Object.fromEntries(AstElement.#pseudoKeys.map(f => [f, []])),
412
+ selectors = escapedSelector.replace(
413
+ AstElement.#pseudoRegex,
414
+ /** @type {function(...string): string} */ (m, f, arg) => {
415
+ if (!arg) {
416
+ calls[f].push('');
417
+ return m;
418
+ }
419
+ const quotes = arg.match(/^(["']).*\1$/)?.[1];
420
+ calls[f].push(quotes ? arg.slice(1, -1).replaceAll(escapedQuotes[quotes], quotes) : arg);
421
+ return `:${f}(${calls[f].length - 1})`;
422
+ },
423
+ ).split(','),
424
+ {parentElement, hidden} = this,
425
+ childNodes = parentElement?.childNodes,
426
+ childrenOfType = parentElement?.children?.filter(child => child.type === this.type),
427
+ index = (childNodes?.indexOf(this) ?? 0) + 1,
428
+ indexOfType = (childrenOfType?.indexOf(this) ?? 0) + 1,
429
+ lastIndex = (childNodes?.length ?? 1) - index + 1,
430
+ lastIndexOfType = (childrenOfType?.length ?? 1) - indexOfType + 1,
431
+ content = this.toString(),
432
+ plainPseudo = AstElement.#pseudoKeys.filter(f => !AstElement.#pseudo[f] && calls[f].length);
433
+ if (plainPseudo.length) {
434
+ Parser.warn('检测到伪选择器,请确认是否需要将":"转义成"&ratio;"。', plainPseudo);
435
+ }
436
+ return selectors.some(str => {
437
+ const /** @type {pseudoCall} */ curCalls = Object.fromEntries(AstElement.#pseudoKeys.map(f => [f, []]));
438
+ str = str.replace(AstElement.#pseudoRegex, /** @type {function(...string): ''} */ (_, f, i) => {
439
+ curCalls[f].push(i ? calls[f][i] : '');
440
+ return '';
441
+ });
442
+ return Parser.run(() => this.matches(str, true))
443
+ && (curCalls.root.length === 0 || !parentElement)
444
+ && curCalls.is.every(s => this.matches(s))
445
+ && !curCalls.not.some(s => this.matches(s))
446
+ && curCalls['nth-child'].every(s => nth(s, index))
447
+ && curCalls['nth-of-type'].every(s => nth(s, indexOfType))
448
+ && curCalls['nth-last-child'].every(s => nth(s, lastIndex))
449
+ && curCalls['nth-last-of-type'].every(s => nth(s, lastIndexOfType))
450
+ && (curCalls['first-child'].length === 0 || nth('1', index))
451
+ && (curCalls['first-of-type'].length === 0 || nth('1', indexOfType))
452
+ && (curCalls['last-child'].length === 0 || nth('1', lastIndex))
453
+ && (curCalls['last-of-type'].length === 0 || nth('1', lastIndexOfType))
454
+ && (curCalls['only-child'].length === 0 || !parentElement || childNodes.length === 1)
455
+ && (curCalls['only-of-type'].length === 0 || !parentElement || childrenOfType.length === 1)
456
+ && (curCalls.empty.length === 0 || this.childElementCount === 0)
457
+ && curCalls.contains.every(s => content.includes(s))
458
+ && curCalls.has.every(s => Boolean(this.querySelector(s)))
459
+ && (curCalls.header.length === 0 || this.type === 'heading')
460
+ && (curCalls.parent.length === 0 || this.childElementCount > 0)
461
+ && (curCalls.hidden.length === 0 || hidden)
462
+ && (curCalls.visible.length === 0 || !hidden);
463
+ });
464
+ }
465
+
466
+ getAncestors() {
467
+ const /** @type {this[]} */ ancestors = [];
468
+ let {parentElement} = this;
469
+ while (parentElement) {
470
+ ancestors.push(parentElement);
471
+ ({parentElement} = parentElement);
472
+ }
473
+ return ancestors;
474
+ }
475
+
476
+ /**
477
+ * @param {this} other
478
+ * @complexity `n`
479
+ */
480
+ comparePosition(other) {
481
+ if (!(other instanceof AstElement)) {
482
+ typeError(this, 'comparePosition', 'AstElement');
483
+ } else if (this === other) {
484
+ return 0;
485
+ } else if (this.contains(other)) {
486
+ return -1;
487
+ } else if (other.contains(this)) {
488
+ return 1;
489
+ } else if (this.getRootNode() !== other.getRootNode()) {
490
+ throw new Error('不在同一个语法树!');
491
+ }
492
+ const aAncestors = [...this.getAncestors().reverse(), this],
493
+ bAncestors = [...other.getAncestors().reverse(), other],
494
+ depth = aAncestors.findIndex((ancestor, i) => bAncestors[i] !== ancestor),
495
+ commonAncestor = aAncestors[depth - 1],
496
+ {childNodes} = commonAncestor;
497
+ return childNodes.indexOf(aAncestors[depth]) - childNodes.indexOf(bAncestors[depth]);
498
+ }
499
+
500
+ closest(selector = '') {
501
+ let {parentElement} = this;
502
+ while (parentElement) {
503
+ if (parentElement.matches(selector)) {
504
+ return parentElement;
505
+ }
506
+ ({parentElement} = parentElement);
507
+ }
508
+ }
509
+
510
+ /**
511
+ * @returns {this|undefined}
512
+ * @complexity `n`
513
+ */
514
+ querySelector(selector = '') {
515
+ for (const child of this.children) {
516
+ if (child.matches(selector)) {
517
+ return child;
518
+ }
519
+ const descendant = child.querySelector(selector);
520
+ if (descendant) {
521
+ return descendant;
522
+ }
523
+ }
524
+ }
525
+
526
+ /** @complexity `n` */
527
+ querySelectorAll(selector = '') {
528
+ const /** @type {this[]} */ descendants = [];
529
+ for (const child of this.children) {
530
+ if (child.matches(selector)) {
531
+ descendants.push(child);
532
+ }
533
+ descendants.push(...child.querySelectorAll(selector));
534
+ }
535
+ return descendants;
536
+ }
537
+
538
+ /**
539
+ * @param {number} index
540
+ * @complexity `n`
541
+ */
542
+ posFromIndex(index) {
543
+ if (typeof index !== 'number') {
544
+ typeError(this, 'posFromIndex', 'Number');
545
+ }
546
+ const text = this.toString();
547
+ if (index < -text.length || index >= text.length || !Number.isInteger(index)) {
548
+ return;
549
+ }
550
+ const lines = text.slice(0, index).split('\n');
551
+ return {top: lines.length - 1, left: lines.at(-1).length};
552
+ }
553
+
554
+ /**
555
+ * @param {number} top
556
+ * @param {number} left
557
+ * @complexity `n`
558
+ */
559
+ indexFromPos(top, left) {
560
+ if (typeof top !== 'number' || typeof left !== 'number') {
561
+ typeError(this, 'indexFromPos', 'Number');
562
+ } else if (top < 0 || left < 0 || !Number.isInteger(top) || !Number.isInteger(left)) {
563
+ return;
564
+ }
565
+ const lines = this.toString().split('\n');
566
+ if (lines.length < top + 1 || lines[top].length < left) {
567
+ return;
568
+ }
569
+ return lines.slice(0, top).reduce((acc, curLine) => acc + curLine.length + 1, 0) + left;
570
+ }
571
+
572
+ /** @complexity `n` */
573
+ #getDimension() {
574
+ const lines = this.toString().split('\n');
575
+ return {height: lines.length, width: lines.at(-1).length};
576
+ }
577
+
578
+ /**
579
+ * 获取当前节点的相对位置,或其第`j`个子节点的相对位置
580
+ * @param {number|undefined} j
581
+ * @complexity `n`
582
+ */
583
+ getRelativeIndex(j) {
584
+ if (j !== undefined && typeof j !== 'number') {
585
+ typeError(this, 'getRelativeIndex', 'Number');
586
+ }
587
+ let /** @type {(string|this)[]} */ childNodes;
588
+ /**
589
+ * 使用前需要先给`childNodes`赋值
590
+ * @param {number} end
591
+ * @param {this} parent
592
+ * @returns {number}
593
+ */
594
+ const getIndex = (end, parent) => childNodes.slice(0, end).reduce(
595
+ (acc, cur, i) => acc + String(cur).length + parent.getGaps(i),
596
+ 0,
597
+ ) + parent.getPadding();
598
+ if (j === undefined) {
599
+ const {parentElement} = this;
600
+ if (!parentElement) {
601
+ return 0;
602
+ }
603
+ ({childNodes} = parentElement);
604
+ return getIndex(childNodes.indexOf(this), parentElement);
605
+ }
606
+ this.verifyChild(j, 1);
607
+ ({childNodes} = this);
608
+ return getIndex(j, this);
609
+ }
610
+
611
+ /**
612
+ * 获取当前节点的绝对位置
613
+ * @returns {number}
614
+ * @complexity `n`
615
+ */
616
+ getAbsoluteIndex() {
617
+ const {parentElement} = this;
618
+ return parentElement ? parentElement.getAbsoluteIndex() + this.getRelativeIndex() : 0;
619
+ }
620
+
621
+ /**
622
+ * 获取当前节点的相对位置,或其第`j`个子节点的相对位置
623
+ * @param {number|undefined} j
624
+ * @complexity `n`
625
+ */
626
+ #getPosition(j) {
627
+ if (j === undefined) {
628
+ const {parentElement} = this;
629
+ if (!parentElement) {
630
+ return {top: 0, left: 0};
631
+ }
632
+ return parentElement.posFromIndex(this.getRelativeIndex());
633
+ }
634
+ return this.posFromIndex(this.getRelativeIndex(j));
635
+ }
636
+
637
+ /** @complexity `n` */
638
+ getBoundingClientRect() {
639
+ const root = this.getRootNode();
640
+ return {...this.#getDimension(), ...root.posFromIndex(this.getAbsoluteIndex())};
641
+ }
642
+
643
+ /** @complexity `n` */
644
+ get offsetHeight() {
645
+ return this.#getDimension().height;
646
+ }
647
+ /** @complexity `n` */
648
+ get offsetWidth() {
649
+ return this.#getDimension().width;
650
+ }
651
+ /** @complexity `n` */
652
+ get offsetTop() {
653
+ return this.#getPosition().top;
654
+ }
655
+ /** @complexity `n` */
656
+ get offsetLeft() {
657
+ return this.#getPosition().left;
658
+ }
659
+
660
+ getPadding() {
661
+ return 0;
662
+ }
663
+
664
+ getGaps() {
665
+ return 0;
666
+ }
667
+
668
+ /** @complexity `n` */
669
+ get style() {
670
+ return {...this.#getPosition(), ...this.#getDimension(), padding: this.getPadding()};
671
+ }
672
+
673
+ /**
674
+ * 不作为特殊语法的文字
675
+ * @returns {[number, string][]}
676
+ * @complexity `n`
677
+ */
678
+ plain() {
679
+ const index = this.getAbsoluteIndex();
680
+ return this.childNodes.flatMap((node, i) => {
681
+ if (node instanceof AstElement) {
682
+ return node.plain();
683
+ }
684
+ return node ? [[index + this.getRelativeIndex(i), node]] : [];
685
+ });
686
+ }
687
+ }
688
+
689
+ Parser.classes.AstElement = __filename;
690
+ module.exports = AstElement;