wikiparser-node 0.4.0 → 0.6.1

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 (87) hide show
  1. package/config/default.json +129 -66
  2. package/config/zhwiki.json +4 -4
  3. package/index.js +97 -65
  4. package/lib/element.js +159 -302
  5. package/lib/node.js +384 -198
  6. package/lib/ranges.js +3 -4
  7. package/lib/text.js +65 -36
  8. package/lib/title.js +9 -8
  9. package/mixin/fixedToken.js +4 -4
  10. package/mixin/hidden.js +2 -0
  11. package/mixin/sol.js +16 -7
  12. package/package.json +14 -3
  13. package/parser/brackets.js +8 -2
  14. package/parser/commentAndExt.js +1 -1
  15. package/parser/converter.js +1 -1
  16. package/parser/externalLinks.js +2 -2
  17. package/parser/hrAndDoubleUnderscore.js +8 -7
  18. package/parser/links.js +8 -9
  19. package/parser/magicLinks.js +1 -1
  20. package/parser/selector.js +5 -5
  21. package/parser/table.js +18 -16
  22. package/src/arg.js +71 -42
  23. package/src/atom/index.js +7 -5
  24. package/src/attribute.js +102 -64
  25. package/src/charinsert.js +91 -0
  26. package/src/converter.js +34 -15
  27. package/src/converterFlags.js +87 -40
  28. package/src/converterRule.js +59 -53
  29. package/src/extLink.js +45 -37
  30. package/src/gallery.js +71 -16
  31. package/src/hasNowiki/index.js +42 -0
  32. package/src/hasNowiki/pre.js +40 -0
  33. package/src/heading.js +41 -18
  34. package/src/html.js +76 -48
  35. package/src/imageParameter.js +73 -51
  36. package/src/imagemap.js +205 -0
  37. package/src/imagemapLink.js +43 -0
  38. package/src/index.js +243 -138
  39. package/src/link/category.js +10 -14
  40. package/src/link/file.js +112 -56
  41. package/src/link/galleryImage.js +74 -10
  42. package/src/link/index.js +86 -61
  43. package/src/magicLink.js +48 -21
  44. package/src/nested/choose.js +24 -0
  45. package/src/nested/combobox.js +23 -0
  46. package/src/nested/index.js +88 -0
  47. package/src/nested/references.js +23 -0
  48. package/src/nowiki/comment.js +18 -4
  49. package/src/nowiki/dd.js +2 -2
  50. package/src/nowiki/doubleUnderscore.js +16 -11
  51. package/src/nowiki/index.js +12 -0
  52. package/src/nowiki/quote.js +28 -1
  53. package/src/onlyinclude.js +15 -8
  54. package/src/paramTag/index.js +83 -0
  55. package/src/paramTag/inputbox.js +42 -0
  56. package/src/parameter.js +73 -46
  57. package/src/syntax.js +9 -1
  58. package/src/table/index.js +58 -44
  59. package/src/table/td.js +63 -63
  60. package/src/table/tr.js +52 -35
  61. package/src/tagPair/ext.js +60 -43
  62. package/src/tagPair/include.js +11 -1
  63. package/src/tagPair/index.js +29 -20
  64. package/src/transclude.js +214 -166
  65. package/tool/index.js +720 -439
  66. package/util/base.js +17 -0
  67. package/util/debug.js +1 -1
  68. package/{test/util.js → util/diff.js} +15 -19
  69. package/util/lint.js +40 -0
  70. package/util/string.js +37 -20
  71. package/.eslintrc.json +0 -714
  72. package/errors/README +0 -1
  73. package/jsconfig.json +0 -7
  74. package/printed/README +0 -1
  75. package/printed/example.json +0 -120
  76. package/test/api.js +0 -83
  77. package/test/real.js +0 -133
  78. package/test/test.js +0 -28
  79. package/typings/api.d.ts +0 -13
  80. package/typings/array.d.ts +0 -28
  81. package/typings/event.d.ts +0 -24
  82. package/typings/index.d.ts +0 -94
  83. package/typings/node.d.ts +0 -29
  84. package/typings/parser.d.ts +0 -16
  85. package/typings/table.d.ts +0 -14
  86. package/typings/token.d.ts +0 -22
  87. package/typings/tool.d.ts +0 -11
package/lib/element.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs'),
4
- {externalUse} = require('../util/debug'),
5
- {toCase, noWrap} = require('../util/string'),
4
+ path = require('path'),
5
+ {toCase, noWrap, print} = require('../util/string'),
6
6
  {nth} = require('./ranges'),
7
7
  parseSelector = require('../parser/selector'),
8
8
  Parser = require('..'),
@@ -29,6 +29,7 @@ class AstElement extends AstNode {
29
29
  * @param {string|undefined} equal 属性规则运算符,`equal`存在时`val`和`i`也一定存在
30
30
  * @param {string|undefined} val 属性值
31
31
  * @param {string|undefined} i 是否对大小写不敏感
32
+ * @throws `RangeError` 复杂属性
32
33
  */
33
34
  #matchesAttr = (key, equal, val, i) => {
34
35
  if (!equal) {
@@ -37,14 +38,17 @@ class AstElement extends AstNode {
37
38
  return equal === '!=';
38
39
  }
39
40
  val = toCase(val, i);
41
+ let thisVal = this.getAttribute(key);
42
+ if (thisVal instanceof RegExp) {
43
+ thisVal = thisVal.source;
44
+ }
40
45
  if (equal === '~=') {
41
- let /** @type {Iterable<string>} */ thisVals = this[key];
42
- if (typeof thisVals === 'string') {
43
- thisVals = thisVals.split(/\s/u);
44
- }
46
+ const thisVals = typeof thisVal === 'string' ? thisVal.split(/\s/u) : thisVal;
45
47
  return Boolean(thisVals?.[Symbol.iterator]) && [...thisVals].some(v => toCase(v, i) === val);
48
+ } else if (typeof thisVal !== 'string') {
49
+ throw new RangeError(`复杂属性 ${key} 不能用于选择器!`);
46
50
  }
47
- const thisVal = toCase(this.getAttribute(key), i);
51
+ thisVal = toCase(thisVal, i);
48
52
  switch (equal) {
49
53
  case '|=':
50
54
  return thisVal === val || thisVal.startsWith(`${val}-`);
@@ -61,6 +65,11 @@ class AstElement extends AstNode {
61
65
  }
62
66
  };
63
67
 
68
+ /** 子节点总数 */
69
+ get length() {
70
+ return this.childNodes.length;
71
+ }
72
+
64
73
  /**
65
74
  * 全部非文本子节点
66
75
  * @complexity `n`
@@ -70,14 +79,6 @@ class AstElement extends AstNode {
70
79
  return children;
71
80
  }
72
81
 
73
- /**
74
- * 非文本子节点总数
75
- * @complexity `n`
76
- */
77
- get childElementCount() {
78
- return this.children.length;
79
- }
80
-
81
82
  /**
82
83
  * 首位非文本子节点
83
84
  * @returns {this}
@@ -88,10 +89,18 @@ class AstElement extends AstNode {
88
89
 
89
90
  /**
90
91
  * 末位非文本子节点
91
- * @returns {this}
92
+ * @complexity `n`
92
93
  */
93
94
  get lastElementChild() {
94
- return this.childNodes.findLast(({type}) => type !== 'text');
95
+ return this.children.at(-1);
96
+ }
97
+
98
+ /**
99
+ * 非文本子节点总数
100
+ * @complexity `n`
101
+ */
102
+ get childElementCount() {
103
+ return this.children.length;
95
104
  }
96
105
 
97
106
  /** 父节点 */
@@ -138,6 +147,18 @@ class AstElement extends AstNode {
138
147
  return previousSibling;
139
148
  }
140
149
 
150
+ /** 内部高度 */
151
+ get clientHeight() {
152
+ const {innerText} = this;
153
+ return typeof innerText === 'string' ? innerText.split('\n').length : undefined;
154
+ }
155
+
156
+ /** 内部宽度 */
157
+ get clientWidth() {
158
+ const {innerText} = this;
159
+ return typeof innerText === 'string' ? innerText.split('\n').at(-1).length : undefined;
160
+ }
161
+
141
162
  constructor() {
142
163
  super();
143
164
  this.seal('name');
@@ -170,71 +191,6 @@ class AstElement extends AstNode {
170
191
  return key === 'matchesAttr' ? this.#matchesAttr : super.getAttribute(key);
171
192
  }
172
193
 
173
- /**
174
- * @override
175
- * @template {string} T
176
- * @param {T} key 属性键
177
- * @param {TokenAttribute<T>} value 属性值
178
- * @throws `RangeError` 禁止手动指定的属性
179
- */
180
- setAttribute(key, value) {
181
- if (key === 'name' && externalUse('setAttribute')) {
182
- throw new RangeError(`禁止手动指定 ${key} 属性!`);
183
- }
184
- return super.setAttribute(key, value);
185
- }
186
-
187
- /**
188
- * 在末尾批量插入子节点
189
- * @param {...this} elements 插入节点
190
- * @complexity `n`
191
- */
192
- append(...elements) {
193
- for (const element of elements) {
194
- this.appendChild(element);
195
- }
196
- }
197
-
198
- /**
199
- * 在开头批量插入子节点
200
- * @param {...this} elements 插入节点
201
- * @complexity `n`
202
- */
203
- prepend(...elements) {
204
- for (let i = 0; i < elements.length; i++) {
205
- this.insertAt(elements[i], i);
206
- }
207
- }
208
-
209
- /**
210
- * 批量替换子节点
211
- * @param {...this} elements 新的子节点
212
- * @complexity `n`
213
- */
214
- replaceChildren(...elements) {
215
- for (let i = this.childNodes.length - 1; i >= 0; i--) {
216
- this.removeAt(i);
217
- }
218
- this.append(...elements);
219
- }
220
-
221
- /**
222
- * 修改文本子节点
223
- * @param {string} str 新文本
224
- * @param {number} i 子节点位置
225
- * @throws `RangeError` 对应位置的子节点不是文本节点
226
- */
227
- setText(str, i = 0) {
228
- this.getAttribute('verifyChild')(i);
229
- const /** @type {AstText} */ oldText = this.childNodes.at(i),
230
- {type, data, constructor: {name}} = oldText;
231
- if (type !== 'text') {
232
- throw new RangeError(`第 ${i} 个子节点是 ${name}!`);
233
- }
234
- oldText.replaceData(str);
235
- return data;
236
- }
237
-
238
194
  /** 是否受保护。保护条件来自Token,这里仅提前用于:required和:optional伪选择器。 */
239
195
  #isProtected() {
240
196
  const /** @type {{parentNode: AstElement & {constructor: {fixed: boolean}}}} */ {parentNode} = this;
@@ -298,7 +254,7 @@ class AstElement extends AstNode {
298
254
  || (type === 'file' || type === 'gallery-image' && link);
299
255
  case ':local-link':
300
256
  return (type === 'link' || type === 'file' || type === 'gallery-image')
301
- && link?.startsWith('#');
257
+ && link?.[0] === '#';
302
258
  case ':read-only':
303
259
  return fixed;
304
260
  case ':read-write':
@@ -353,17 +309,17 @@ class AstElement extends AstNode {
353
309
  * @returns {boolean}
354
310
  * @complexity `n`
355
311
  */
356
- matches(selector = '') {
357
- if (typeof selector === 'string') {
358
- return Parser.run(() => {
359
- const stack = parseSelector(selector),
360
- /** @type {Set<string>} */
361
- pseudos = new Set(stack.flat(2).filter(step => typeof step === 'string' && step.startsWith(':')));
362
- if (pseudos.size > 0) {
363
- Parser.warn('检测到伪选择器,请确认是否需要将":"转义成"\\:"。', pseudos);
364
- }
365
- return stack.some(condition => this.matches(condition));
366
- });
312
+ matches(selector) {
313
+ if (selector === undefined) {
314
+ return true;
315
+ } else if (typeof selector === 'string') {
316
+ const stack = parseSelector(selector),
317
+ /** @type {Set<string>} */
318
+ pseudos = new Set(stack.flat(2).filter(step => typeof step === 'string' && step[0] === ':'));
319
+ if (pseudos.size > 0) {
320
+ Parser.warn('检测到伪选择器,请确认是否需要将":"转义成"\\:"。', pseudos);
321
+ }
322
+ return Parser.run(() => stack.some(condition => this.matches(condition)));
367
323
  } else if (!Parser.running) {
368
324
  this.typeError('matches', 'String');
369
325
  }
@@ -393,59 +349,11 @@ class AstElement extends AstNode {
393
349
  return false;
394
350
  }
395
351
 
396
- /**
397
- * 还原为wikitext
398
- * @param {string} selector
399
- * @param {string} separator 子节点间的连接符
400
- */
401
- toString(selector, separator = '') {
402
- return selector && this.matches(selector)
403
- ? ''
404
- : this.childNodes.map(child => child.toString(selector)).join(separator);
405
- }
406
-
407
- /** 获取所有祖先节点 */
408
- getAncestors() {
409
- const /** @type {this[]} */ ancestors = [];
410
- let {parentNode} = this;
411
- while (parentNode) {
412
- ancestors.push(parentNode);
413
- ({parentNode} = parentNode);
414
- }
415
- return ancestors;
416
- }
417
-
418
- /**
419
- * 比较和另一个节点的相对位置
420
- * @param {this} other 待比较的节点
421
- * @complexity `n`
422
- * @throws `Error` 不在同一个语法树
423
- */
424
- compareDocumentPosition(other) {
425
- if (!(other instanceof AstElement)) {
426
- this.typeError('compareDocumentPosition', 'AstElement');
427
- } else if (this === other) {
428
- return 0;
429
- } else if (this.contains(other)) {
430
- return -1;
431
- } else if (other.contains(this)) {
432
- return 1;
433
- } else if (this.getRootNode() !== other.getRootNode()) {
434
- throw new Error('不在同一个语法树!');
435
- }
436
- const aAncestors = [...this.getAncestors().reverse(), this],
437
- bAncestors = [...other.getAncestors().reverse(), other],
438
- depth = aAncestors.findIndex((ancestor, i) => bAncestors[i] !== ancestor),
439
- commonAncestor = aAncestors[depth - 1],
440
- {childNodes} = commonAncestor;
441
- return childNodes.indexOf(aAncestors[depth]) - childNodes.indexOf(bAncestors[depth]);
442
- }
443
-
444
352
  /**
445
353
  * 最近的祖先节点
446
354
  * @param {string} selector
447
355
  */
448
- closest(selector = '') {
356
+ closest(selector) {
449
357
  let {parentNode} = this;
450
358
  while (parentNode) {
451
359
  if (parentNode.matches(selector)) {
@@ -462,7 +370,7 @@ class AstElement extends AstNode {
462
370
  * @returns {this|undefined}
463
371
  * @complexity `n`
464
372
  */
465
- querySelector(selector = '') {
373
+ querySelector(selector) {
466
374
  for (const child of this.children) {
467
375
  if (child.matches(selector)) {
468
376
  return child;
@@ -480,7 +388,7 @@ class AstElement extends AstNode {
480
388
  * @param {string} selector
481
389
  * @complexity `n`
482
390
  */
483
- querySelectorAll(selector = '') {
391
+ querySelectorAll(selector) {
484
392
  const /** @type {this[]} */ descendants = [];
485
393
  for (const child of this.children) {
486
394
  if (child.matches(selector)) {
@@ -522,203 +430,151 @@ class AstElement extends AstNode {
522
430
  }
523
431
 
524
432
  /**
525
- * 将字符位置转换为行列号
526
- * @param {number} index 字符位置
433
+ * 在开头批量插入子节点
434
+ * @param {...this} elements 插入节点
527
435
  * @complexity `n`
528
436
  */
529
- posFromIndex(index) {
530
- if (typeof index !== 'number') {
531
- this.typeError('posFromIndex', 'Number');
532
- }
533
- const text = String(this);
534
- if (index >= -text.length && index < text.length && Number.isInteger(index)) {
535
- const lines = text.slice(0, index).split('\n');
536
- return {top: lines.length - 1, left: lines.at(-1).length};
437
+ prepend(...elements) {
438
+ for (let i = 0; i < elements.length; i++) {
439
+ this.insertAt(elements[i], i);
537
440
  }
538
- return undefined;
539
441
  }
540
442
 
541
443
  /**
542
- * 将行列号转换为字符位置
543
- * @param {number} top 行号
544
- * @param {number} left 列号
444
+ * 在末尾批量插入子节点
445
+ * @param {...this} elements 插入节点
545
446
  * @complexity `n`
546
447
  */
547
- indexFromPos(top, left) {
548
- if (typeof top !== 'number' || typeof left !== 'number') {
549
- this.typeError('indexFromPos', 'Number');
448
+ append(...elements) {
449
+ for (const element of elements) {
450
+ this.insertAt(element);
550
451
  }
551
- const lines = String(this).split('\n');
552
- return top >= 0 && left >= 0 && Number.isInteger(top) && Number.isInteger(left)
553
- && lines.length >= top + 1 && lines[top].length >= left
554
- ? lines.slice(0, top).reduce((acc, curLine) => acc + curLine.length + 1, 0) + left
555
- : undefined;
556
- }
557
-
558
- /**
559
- * 获取行数和最后一行的列数
560
- * @complexity `n`
561
- */
562
- #getDimension() {
563
- const lines = String(this).split('\n');
564
- return {height: lines.length, width: lines.at(-1).length};
565
452
  }
566
453
 
567
454
  /**
568
- * 获取当前节点的相对字符位置,或其第`j`个子节点的相对字符位置
569
- * @param {number|undefined} j 子节点序号
455
+ * 批量替换子节点
456
+ * @param {...this} elements 新的子节点
570
457
  * @complexity `n`
571
458
  */
572
- getRelativeIndex(j) {
573
- if (j !== undefined && typeof j !== 'number') {
574
- this.typeError('getRelativeIndex', 'Number');
575
- }
576
- let /** @type {this[]} */ childNodes;
577
-
578
- /**
579
- * 获取子节点相对于父节点的字符位置,使用前需要先给`childNodes`赋值
580
- * @param {number} end 子节点序号
581
- * @param {this} parent 父节点
582
- * @returns {number}
583
- */
584
- const getIndex = (end, parent) => childNodes.slice(0, end).reduce(
585
- (acc, cur, i) => acc + String(cur).length + parent.getGaps(i),
586
- 0,
587
- ) + parent.getPadding();
588
- if (j === undefined) {
589
- const {parentNode} = this;
590
- if (!parentNode) {
591
- return 0;
592
- }
593
- ({childNodes} = parentNode);
594
- return getIndex(childNodes.indexOf(this), parentNode);
459
+ replaceChildren(...elements) {
460
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
461
+ this.removeAt(i);
595
462
  }
596
- this.getAttribute('verifyChild')(j, 1);
597
- ({childNodes} = this);
598
- return getIndex(j, this);
599
- }
600
-
601
- /**
602
- * 获取当前节点的绝对位置
603
- * @returns {number}
604
- * @complexity `n`
605
- */
606
- getAbsoluteIndex() {
607
- const {parentNode} = this;
608
- return parentNode ? parentNode.getAbsoluteIndex() + this.getRelativeIndex() : 0;
609
- }
610
-
611
- /**
612
- * 获取当前节点的相对位置,或其第`j`个子节点的相对位置
613
- * @param {number|undefined} j 子节点序号
614
- * @complexity `n`
615
- */
616
- #getPosition(j) {
617
- return j === undefined
618
- ? this.parentNode?.posFromIndex(this.getRelativeIndex()) ?? {top: 0, left: 0}
619
- : this.posFromIndex(this.getRelativeIndex(j));
620
- }
621
-
622
- /**
623
- * 获取当前节点的行列位置和大小
624
- * @complexity `n`
625
- */
626
- getBoundingClientRect() {
627
- const root = this.getRootNode();
628
- return {...this.#getDimension(), ...root.posFromIndex(this.getAbsoluteIndex())};
629
- }
630
-
631
- /** 第一个子节点前的间距 */
632
- getPadding() {
633
- return 0;
634
- }
635
-
636
- /** 子节点间距 */
637
- getGaps() {
638
- return 0;
463
+ this.append(...elements);
639
464
  }
640
465
 
641
466
  /**
642
- * 行数
643
- * @complexity `n`
467
+ * 修改文本子节点
468
+ * @param {string} str 新文本
469
+ * @param {number} i 子节点位置
470
+ * @throws `RangeError` 对应位置的子节点不是文本节点
644
471
  */
645
- get offsetHeight() {
646
- return this.#getDimension().height;
472
+ setText(str, i = 0) {
473
+ this.getAttribute('verifyChild')(i);
474
+ const /** @type {AstText} */ oldText = this.childNodes.at(i),
475
+ {type, data, constructor: {name}} = oldText;
476
+ if (type === 'text') {
477
+ oldText.replaceData(str);
478
+ return data;
479
+ }
480
+ throw new RangeError(`第 ${i} 个子节点是 ${name}!`);
647
481
  }
648
482
 
649
483
  /**
650
- * 最后一行的列数
651
- * @complexity `n`
484
+ * 还原为wikitext
485
+ * @param {string} selector
486
+ * @param {string} separator 子节点间的连接符
487
+ * @returns {string}
652
488
  */
653
- get offsetWidth() {
654
- return this.#getDimension().width;
489
+ toString(selector, separator = '') {
490
+ return selector && this.matches(selector)
491
+ ? ''
492
+ : this.childNodes.map(child => child.toString(selector)).join(separator);
655
493
  }
656
494
 
657
- /**
658
- * 行号
659
- * @complexity `n`
660
- */
661
- get offsetTop() {
662
- return this.#getPosition().top;
495
+ static lintIgnoredExt = new Set([
496
+ 'nowiki',
497
+ 'pre',
498
+ 'charinsert',
499
+ 'score',
500
+ 'syntaxhighlight',
501
+ 'source',
502
+ 'math',
503
+ 'chem',
504
+ 'ce',
505
+ 'graph',
506
+ 'mapframe',
507
+ 'maplink',
508
+ 'quiz',
509
+ 'templatedata',
510
+ 'timeline',
511
+ ]);
512
+
513
+ /**
514
+ * Linter
515
+ * @param {number} start 起始位置
516
+ */
517
+ lint(start = 0) {
518
+ const SyntaxToken = require('../src/syntax');
519
+ if (this instanceof SyntaxToken || this.constructor.hidden
520
+ || this.type === 'ext-inner' && AstElement.lintIgnoredExt.has(this.name)
521
+ ) {
522
+ return [];
523
+ }
524
+ const /** @type {LintError[]} */ errors = [];
525
+ for (let i = 0, cur = start + this.getPadding(); i < this.childNodes.length; i++) {
526
+ const child = this.childNodes[i];
527
+ errors.push(...child.lint(cur));
528
+ cur += String(child).length + this.getGaps(i);
529
+ }
530
+ return errors;
663
531
  }
664
532
 
665
533
  /**
666
- * 列号
667
- * @complexity `n`
534
+ * 以HTML格式打印
535
+ * @param {printOpt} opt 选项
536
+ * @returns {string}
668
537
  */
669
- get offsetLeft() {
670
- return this.#getPosition().left;
538
+ print(opt = {}) {
539
+ return String(this)
540
+ ? `<span class="wpb-${opt.class ?? this.type}">${print(this.childNodes, opt)}</span>`
541
+ : '';
671
542
  }
672
543
 
673
544
  /**
674
- * 位置、大小和padding
675
- * @complexity `n`
545
+ * 保存为JSON
546
+ * @param {string} file 文件名
547
+ * @returns {Record<string, *>}
676
548
  */
677
- get style() {
678
- return {...this.#getPosition(), ...this.#getDimension(), padding: this.getPadding()};
679
- }
680
-
681
- /** 内部高度 */
682
- get clientHeight() {
683
- const {innerText} = this;
684
- return typeof innerText === 'string' ? innerText.split('\n').length : undefined;
685
- }
686
-
687
- /** 内部宽度 */
688
- get clientWidth() {
689
- const {innerText} = this;
690
- return typeof innerText === 'string' ? innerText.split('\n').at(-1).length : undefined;
549
+ json(file) {
550
+ const {childNodes, ...prop} = this,
551
+ json = {
552
+ ...prop,
553
+ childNodes: childNodes.map(child => child.type === 'text' ? String(child) : child.json()),
554
+ };
555
+ if (typeof file === 'string') {
556
+ fs.writeFileSync(
557
+ path.join(__dirname.slice(0, -4), 'printed', `${file}${file.endsWith('.json') ? '' : '.json'}`),
558
+ JSON.stringify(json, null, 2),
559
+ );
560
+ }
561
+ return json;
691
562
  }
692
563
 
693
564
  /**
694
565
  * 输出AST
695
- * @template {'markup'|'json'} T
696
- * @param {T} format 输出格式
697
- * @param {T extends 'markup' ? number : string} depth 输出深度
698
- * @returns {T extends 'markup' ? void : Record<string, any>}
566
+ * @param {number} depth 当前深度
567
+ * @returns {void}
699
568
  */
700
- print(format = 'markup', depth = 0) {
701
- if (format === 'json') {
702
- const {childNodes, ...prop} = this,
703
- json = {
704
- ...prop,
705
- childNodes: childNodes.map(child => child.type === 'text' ? String(child) : child.print('json')),
706
- };
707
- if (typeof depth === 'string') {
708
- fs.writeFileSync(
709
- `${__dirname.slice(0, -3)}printed/${depth}${depth.endsWith('.json') ? '' : '.json'}`,
710
- JSON.stringify(json, null, 2),
711
- );
712
- }
713
- return json;
714
- } else if (typeof depth !== 'number') {
569
+ echo(depth = 0) {
570
+ if (!Number.isInteger(depth) || depth < 0) {
715
571
  this.typeError('print', 'Number');
716
572
  }
717
573
  const indent = ' '.repeat(depth),
718
574
  str = String(this),
719
- {childNodes, type, firstChild} = this,
575
+ {childNodes, type} = this,
720
576
  {length} = childNodes;
721
- if (!str || length === 0 || firstChild.type === 'text' && String(firstChild) === str) {
577
+ if (childNodes.every(child => child.type === 'text' || !String(child))) {
722
578
  console.log(`${indent}\x1B[32m<%s>\x1B[0m${noWrap(str)}\x1B[32m</%s>\x1B[0m`, type, type);
723
579
  return undefined;
724
580
  }
@@ -736,11 +592,12 @@ class AstElement extends AstNode {
736
592
  } else if (child.type === 'text') {
737
593
  console.log(`${indent} ${noWrap(String(child))}`);
738
594
  } else {
739
- child.print('markup', depth + 1);
595
+ child.echo(depth + 1);
740
596
  }
741
- i += childStr.length + gap;
597
+ i += childStr.length;
742
598
  if (gap) {
743
- console.log(`${indent} ${noWrap(str.slice(i - gap, i))}`);
599
+ console.log(`${indent} ${noWrap(str.slice(i, i + gap))}`);
600
+ i += gap;
744
601
  }
745
602
  }
746
603
  if (i < str.length) {