wikiparser-node 0.8.1-m → 0.9.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/README.md +39 -0
  2. package/config/moegirl.json +1 -0
  3. package/i18n/zh-hans.json +44 -0
  4. package/i18n/zh-hant.json +44 -0
  5. package/index.js +264 -10
  6. package/lib/element.js +507 -33
  7. package/lib/node.js +550 -6
  8. package/lib/ranges.js +130 -0
  9. package/lib/text.js +111 -41
  10. package/lib/title.js +28 -5
  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 +54 -0
  16. package/package.json +9 -8
  17. package/parser/brackets.js +9 -2
  18. package/parser/commentAndExt.js +3 -5
  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 +6 -5
  24. package/parser/list.js +1 -0
  25. package/parser/magicLinks.js +5 -4
  26. package/parser/quotes.js +1 -0
  27. package/parser/selector.js +177 -0
  28. package/parser/table.js +1 -0
  29. package/src/arg.js +123 -5
  30. package/src/atom/hidden.js +2 -0
  31. package/src/atom/index.js +17 -0
  32. package/src/attribute.js +191 -8
  33. package/src/attributes.js +311 -8
  34. package/src/charinsert.js +97 -0
  35. package/src/converter.js +108 -2
  36. package/src/converterFlags.js +190 -3
  37. package/src/converterRule.js +185 -4
  38. package/src/extLink.js +122 -2
  39. package/src/gallery.js +59 -11
  40. package/src/hasNowiki/index.js +12 -0
  41. package/src/hasNowiki/pre.js +12 -0
  42. package/src/heading.js +57 -6
  43. package/src/html.js +133 -12
  44. package/src/imageParameter.js +232 -38
  45. package/src/imagemap.js +65 -6
  46. package/src/imagemapLink.js +14 -2
  47. package/src/index.js +537 -8
  48. package/src/link/category.js +32 -1
  49. package/src/link/file.js +173 -11
  50. package/src/link/galleryImage.js +63 -5
  51. package/src/link/index.js +268 -9
  52. package/src/magicLink.js +92 -11
  53. package/src/nested/choose.js +1 -0
  54. package/src/nested/combobox.js +1 -0
  55. package/src/nested/index.js +31 -7
  56. package/src/nested/references.js +1 -0
  57. package/src/nowiki/comment.js +27 -3
  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 +25 -3
  62. package/src/nowiki/list.js +5 -2
  63. package/src/nowiki/noinclude.js +14 -0
  64. package/src/nowiki/quote.js +17 -3
  65. package/src/onlyinclude.js +26 -1
  66. package/src/paramTag/index.js +27 -4
  67. package/src/paramTag/inputbox.js +4 -1
  68. package/src/parameter.js +150 -8
  69. package/src/syntax.js +68 -0
  70. package/src/table/index.js +941 -4
  71. package/src/table/td.js +229 -10
  72. package/src/table/tr.js +249 -4
  73. package/src/tagPair/ext.js +36 -9
  74. package/src/tagPair/include.js +24 -0
  75. package/src/tagPair/index.js +51 -2
  76. package/src/transclude.js +547 -29
  77. package/tool/index.js +1202 -0
  78. package/util/debug.js +73 -0
  79. package/util/lint.js +8 -7
  80. package/util/string.js +67 -1
  81. package/config/minimum.json +0 -142
package/lib/element.js CHANGED
@@ -2,27 +2,456 @@
2
2
 
3
3
  const fs = require('fs'),
4
4
  path = require('path'),
5
+ {toCase, noWrap, print} = require('../util/string'),
6
+ {nth} = require('./ranges'),
7
+ parseSelector = require('../parser/selector'),
8
+ Parser = require('..'),
5
9
  AstNode = require('./node'),
6
10
  AstText = require('./text');
7
11
 
12
+ const lintIgnoredExt = new Set([
13
+ 'nowiki',
14
+ 'pre',
15
+ 'charinsert',
16
+ 'score',
17
+ 'syntaxhighlight',
18
+ 'source',
19
+ 'math',
20
+ 'chem',
21
+ 'ce',
22
+ 'graph',
23
+ 'mapframe',
24
+ 'maplink',
25
+ 'quiz',
26
+ 'templatedata',
27
+ 'timeline',
28
+ ]);
29
+
30
+ /**
31
+ * 检测:lang()伪选择器
32
+ * @param {AstElement & {attributes: Records<string, string|true>}} node 节点
33
+ * @param {RegExp} regex 语言正则
34
+ */
35
+ const matchesLang = ({attributes}, regex) => {
36
+ const /** @type {string} */ lang = attributes?.lang;
37
+ return typeof lang === 'string' && regex.test(lang);
38
+ };
39
+
8
40
  /** 类似HTMLElement */
9
41
  class AstElement extends AstNode {
10
42
  /** @type {string} */ name;
11
43
 
44
+ /**
45
+ * 检查是否符合某条属性规则
46
+ * @param {string} key 属性键
47
+ * @param {string|undefined} equal 属性规则运算符,`equal`存在时`val`和`i`也一定存在
48
+ * @param {string|undefined} val 属性值
49
+ * @param {string|undefined} i 是否对大小写不敏感
50
+ * @throws `RangeError` 复杂属性
51
+ */
52
+ #matchesAttr = (key, equal, val, i) => {
53
+ if (!equal) {
54
+ return this.hasAttribute(key);
55
+ } else if (!this.hasAttribute(key)) {
56
+ return equal === '!=';
57
+ }
58
+ val = toCase(val, i);
59
+ let thisVal = this.getAttribute(key);
60
+ if (thisVal instanceof RegExp) {
61
+ thisVal = thisVal.source;
62
+ }
63
+ if (equal === '~=') {
64
+ const thisVals = typeof thisVal === 'string' ? thisVal.split(/\s/u) : thisVal;
65
+ return Boolean(thisVals?.[Symbol.iterator]) && [...thisVals].some(v => toCase(v, i) === val);
66
+ } else if (typeof thisVal !== 'string') {
67
+ throw new RangeError(`复杂属性 ${key} 不能用于选择器!`);
68
+ }
69
+ thisVal = toCase(thisVal, i);
70
+ switch (equal) {
71
+ case '|=':
72
+ return thisVal === val || thisVal.startsWith(`${val}-`);
73
+ case '^=':
74
+ return thisVal.startsWith(val);
75
+ case '$=':
76
+ return thisVal.endsWith(val);
77
+ case '*=':
78
+ return thisVal.includes(val);
79
+ case '!=':
80
+ return thisVal !== val;
81
+ default: // `=`
82
+ return thisVal === val;
83
+ }
84
+ };
85
+
12
86
  /** 子节点总数 */
13
87
  get length() {
14
88
  return this.childNodes.length;
15
89
  }
16
90
 
91
+ /**
92
+ * 全部非文本子节点
93
+ * @complexity `n`
94
+ */
95
+ get children() {
96
+ const /** @type {this[]} */ children = this.childNodes.filter(({type}) => type !== 'text');
97
+ return children;
98
+ }
99
+
100
+ /**
101
+ * 首位非文本子节点
102
+ * @returns {this}
103
+ */
104
+ get firstElementChild() {
105
+ return this.childNodes.find(({type}) => type !== 'text');
106
+ }
107
+
108
+ /**
109
+ * 末位非文本子节点
110
+ * @complexity `n`
111
+ */
112
+ get lastElementChild() {
113
+ return this.children.at(-1);
114
+ }
115
+
116
+ /**
117
+ * 非文本子节点总数
118
+ * @complexity `n`
119
+ */
120
+ get childElementCount() {
121
+ return this.children.length;
122
+ }
123
+
124
+ /** 父节点 */
125
+ get parentElement() {
126
+ return this.parentNode;
127
+ }
128
+
129
+ /**
130
+ * AstElement.prototype.text()的getter写法
131
+ * @complexity `n`
132
+ */
133
+ get outerText() {
134
+ return this.text();
135
+ }
136
+
137
+ /**
138
+ * 不可见
139
+ */
140
+ get hidden() {
141
+ return this.text() === '';
142
+ }
143
+
144
+ /**
145
+ * 后一个可见的兄弟节点
146
+ * @complexity `n`
147
+ */
148
+ get nextVisibleSibling() {
149
+ let {nextSibling} = this;
150
+ while (nextSibling?.text() === '') {
151
+ ({nextSibling} = nextSibling);
152
+ }
153
+ return nextSibling;
154
+ }
155
+
156
+ /**
157
+ * 前一个可见的兄弟节点
158
+ * @complexity `n`
159
+ */
160
+ get previousVisibleSibling() {
161
+ let {previousSibling} = this;
162
+ while (previousSibling?.text() === '') {
163
+ ({previousSibling} = previousSibling);
164
+ }
165
+ return previousSibling;
166
+ }
167
+
168
+ /** 内部高度 */
169
+ get clientHeight() {
170
+ const {innerText} = this;
171
+ return typeof innerText === 'string' ? innerText.split('\n').length : undefined;
172
+ }
173
+
174
+ /** 内部宽度 */
175
+ get clientWidth() {
176
+ const {innerText} = this;
177
+ return typeof innerText === 'string' ? innerText.split('\n').at(-1).length : undefined;
178
+ }
179
+
180
+ constructor() {
181
+ super();
182
+ this.seal('name');
183
+ }
184
+
185
+ /**
186
+ * 销毁
187
+ * @complexity `n`
188
+ * @param {boolean} deep 是否从根节点开始销毁
189
+ * @throws `Error` 不能销毁子节点
190
+ */
191
+ destroy(deep) {
192
+ if (this.parentNode && !deep) {
193
+ throw new Error('不能销毁子节点!');
194
+ }
195
+ this.parentNode?.destroy(deep);
196
+ for (const child of this.childNodes) {
197
+ child.setAttribute('parentNode');
198
+ }
199
+ Object.setPrototypeOf(this, null);
200
+ }
201
+
202
+ /**
203
+ * @override
204
+ * @template {string} T
205
+ * @param {T} key 属性键
206
+ * @returns {TokenAttribute<T>}
207
+ */
208
+ getAttribute(key) {
209
+ return key === 'matchesAttr' ? this.#matchesAttr : super.getAttribute(key);
210
+ }
211
+
212
+ /** 是否受保护。保护条件来自Token,这里仅提前用于:required和:optional伪选择器。 */
213
+ #isProtected() {
214
+ const /** @type {{parentNode: AstElement & {constructor: {fixed: boolean}}}} */ {parentNode} = this;
215
+ if (!parentNode) {
216
+ return undefined;
217
+ }
218
+ const {childNodes, constructor: {fixed}} = parentNode,
219
+ protectedIndices = parentNode.getAttribute('protectedChildren')?.applyTo(childNodes);
220
+ return fixed || protectedIndices?.includes(childNodes.indexOf(this));
221
+ }
222
+
223
+ /**
224
+ * 检查是否符合解析后的选择器,不含节点关系
225
+ * @this {AstElement & {link: string|Title, constructor: {fixed: boolean}}}
226
+ * @param {SelectorArray} step 解析后的选择器
227
+ * @throws `SyntaxError` 未定义的伪选择器
228
+ */
229
+ #matches(step) {
230
+ const Title = require('./title');
231
+ const {parentNode, type, name, childNodes, link, constructor: {fixed, name: tokenName}} = this,
232
+ children = parentNode?.children,
233
+ childrenOfType = children?.filter(({type: t}) => t === type),
234
+ siblingsCount = children?.length ?? 1,
235
+ siblingsCountOfType = childrenOfType?.length ?? 1,
236
+ index = (children?.indexOf(this) ?? 0) + 1,
237
+ indexOfType = (childrenOfType?.indexOf(this) ?? 0) + 1,
238
+ lastIndex = siblingsCount - index + 1,
239
+ lastIndexOfType = siblingsCountOfType - indexOfType + 1;
240
+ return step.every(selector => {
241
+ if (typeof selector === 'string') {
242
+ switch (selector) { // 情形1:简单伪选择器、type和name
243
+ case '*':
244
+ return true;
245
+ case ':root':
246
+ return !parentNode;
247
+ case ':first-child':
248
+ return index === 1;
249
+ case ':first-of-type':
250
+ return indexOfType === 1;
251
+ case ':last-child':
252
+ return lastIndex === 1;
253
+ case ':last-of-type':
254
+ return lastIndexOfType === 1;
255
+ case ':only-child':
256
+ return siblingsCount === 1;
257
+ case ':only-of-type':
258
+ return siblingsCountOfType === 1;
259
+ case ':empty':
260
+ return !childNodes.some(child => child instanceof AstElement || String(child));
261
+ case ':parent':
262
+ return childNodes.some(child => child instanceof AstElement || String(child));
263
+ case ':header':
264
+ return type === 'heading';
265
+ case ':hidden':
266
+ return this.text() === '';
267
+ case ':visible':
268
+ return this.text() !== '';
269
+ case ':only-whitespace':
270
+ return this.text().trim() === '';
271
+ case ':any-link':
272
+ return type === 'link' || type === 'free-ext-link' || type === 'ext-link'
273
+ || (type === 'file' || type === 'gallery-image' && link);
274
+ case ':local-link':
275
+ return (type === 'link' || type === 'file' || type === 'gallery-image')
276
+ && link instanceof Title && link.title === '';
277
+ case ':read-only':
278
+ return fixed;
279
+ case ':read-write':
280
+ return !fixed;
281
+ case ':invalid':
282
+ return type === 'table-inter' || tokenName === 'HiddenToken';
283
+ case ':required':
284
+ return this.#isProtected() === true;
285
+ case ':optional':
286
+ return this.#isProtected() === false;
287
+ default: {
288
+ const [t, n] = selector.split('#');
289
+ return (!t || t === type || Boolean(Parser.typeAliases[type]?.includes(t)))
290
+ && (!n || n === name);
291
+ }
292
+ }
293
+ } else if (selector.length === 4) { // 情形2:属性选择器
294
+ return this.getAttribute('matchesAttr')(...selector);
295
+ }
296
+ const [s, pseudo] = selector; // 情形3:复杂伪选择器
297
+ switch (pseudo) {
298
+ case 'is':
299
+ return this.matches(s);
300
+ case 'not':
301
+ return !this.matches(s);
302
+ case 'nth-child':
303
+ return nth(s, index);
304
+ case 'nth-of-type':
305
+ return nth(s, indexOfType);
306
+ case 'nth-last-child':
307
+ return nth(s, lastIndex);
308
+ case 'nth-last-of-type':
309
+ return nth(s, lastIndexOfType);
310
+ case 'contains':
311
+ return this.text().includes(s);
312
+ case 'has':
313
+ return Boolean(this.querySelector(s));
314
+ case 'lang': {
315
+ const regex = new RegExp(`^${s}(?:-|$)`, 'u');
316
+ return matchesLang(this, regex)
317
+ || this.getAncestors().some(ancestor => matchesLang(ancestor, regex));
318
+ }
319
+ default:
320
+ throw new SyntaxError(`未定义的伪选择器!${pseudo}`);
321
+ }
322
+ });
323
+ }
324
+
325
+ /**
326
+ * 检查是否符合选择器
327
+ * @param {string|SelectorArray[]} selector
328
+ * @returns {boolean}
329
+ * @complexity `n`
330
+ */
331
+ matches(selector) {
332
+ if (selector === undefined) {
333
+ return true;
334
+ } else if (typeof selector === 'string') {
335
+ const stack = parseSelector(selector),
336
+ /** @type {Set<string>} */
337
+ pseudos = new Set(stack.flat(2).filter(step => typeof step === 'string' && step[0] === ':'));
338
+ if (pseudos.size > 0) {
339
+ Parser.warn('检测到伪选择器,请确认是否需要将":"转义成"\\:"。', pseudos);
340
+ }
341
+ return Parser.run(() => stack.some(condition => this.matches(condition)));
342
+ } else if (!Parser.running) {
343
+ this.typeError('matches', 'String');
344
+ }
345
+ selector = [...selector];
346
+ const step = selector.pop();
347
+ if (this.#matches(step)) {
348
+ const {parentNode, previousElementSibling} = this;
349
+ switch (selector.at(-1)?.relation) {
350
+ case undefined:
351
+ return true;
352
+ case '>':
353
+ return parentNode?.matches(selector);
354
+ case '+':
355
+ return previousElementSibling?.matches(selector);
356
+ case '~': {
357
+ if (!parentNode) {
358
+ return false;
359
+ }
360
+ const {children} = parentNode,
361
+ i = children.indexOf(this);
362
+ return children.slice(0, i).some(child => child.matches(selector));
363
+ }
364
+ default: // ' '
365
+ return this.getAncestors().some(ancestor => ancestor.matches(selector));
366
+ }
367
+ }
368
+ return false;
369
+ }
370
+
371
+ /**
372
+ * 符合选择器的第一个后代节点
373
+ * @param {string} selector
374
+ * @returns {this|undefined}
375
+ * @complexity `n`
376
+ */
377
+ querySelector(selector) {
378
+ for (const child of this.children) {
379
+ if (child.matches(selector)) {
380
+ return child;
381
+ }
382
+ const descendant = child.querySelector(selector);
383
+ if (descendant) {
384
+ return descendant;
385
+ }
386
+ }
387
+ return undefined;
388
+ }
389
+
390
+ /**
391
+ * 符合选择器的所有后代节点
392
+ * @param {string} selector
393
+ * @complexity `n`
394
+ */
395
+ querySelectorAll(selector) {
396
+ const /** @type {this[]} */ descendants = [];
397
+ for (const child of this.children) {
398
+ if (child.matches(selector)) {
399
+ descendants.push(child);
400
+ }
401
+ descendants.push(...child.querySelectorAll(selector));
402
+ }
403
+ return descendants;
404
+ }
405
+
406
+ /**
407
+ * 类选择器
408
+ * @param {string} className 类名之一
409
+ */
410
+ getElementsByClassName(className) {
411
+ return typeof className === 'string'
412
+ ? this.querySelectorAll(`[className~="${className.replace(/(?<!\\)"/gu, '\\"')}"]`)
413
+ : this.typeError('getElementsByClassName', 'String');
414
+ }
415
+
416
+ /**
417
+ * 标签名选择器
418
+ * @param {string} name 标签名
419
+ */
420
+ getElementsByTagName(name) {
421
+ if (typeof name === 'string') {
422
+ name = name.replace(/(?<!\\)"/gu, '\\"');
423
+ return this.querySelectorAll(`ext[name="${name}"], html[name="${name}"]`);
424
+ }
425
+ return this.typeError('getElementsByTagName', 'String');
426
+ }
427
+
428
+ /**
429
+ * 获取某一行的wikitext
430
+ * @param {number} n 行号
431
+ */
432
+ getLine(n) {
433
+ return String(this).split('\n', n + 1).at(-1);
434
+ }
435
+
436
+ /**
437
+ * 在开头批量插入子节点
438
+ * @param {...this} elements 插入节点
439
+ * @complexity `n`
440
+ */
441
+ prepend(...elements) {
442
+ for (let i = 0; i < elements.length; i++) {
443
+ this.insertAt(elements[i], i);
444
+ }
445
+ }
446
+
17
447
  /**
18
448
  * 最近的祖先节点
19
449
  * @param {string} selector
20
450
  */
21
451
  closest(selector) {
22
- const types = new Set(selector.split(',').map(type => type.trim()));
23
452
  let {parentNode} = this;
24
453
  while (parentNode) {
25
- if (types.has(parentNode.type)) {
454
+ if (parentNode.matches(selector)) {
26
455
  return parentNode;
27
456
  }
28
457
  ({parentNode} = parentNode);
@@ -47,7 +476,9 @@ class AstElement extends AstNode {
47
476
  * @complexity `n`
48
477
  */
49
478
  replaceChildren(...elements) {
50
- this.childNodes.length = 0;
479
+ for (let i = this.length - 1; i >= 0; i--) {
480
+ this.removeAt(i);
481
+ }
51
482
  this.append(...elements);
52
483
  }
53
484
 
@@ -55,52 +486,39 @@ class AstElement extends AstNode {
55
486
  * 修改文本子节点
56
487
  * @param {string} str 新文本
57
488
  * @param {number} i 子节点位置
489
+ * @throws `RangeError` 对应位置的子节点不是文本节点
58
490
  */
59
491
  setText(str, i = 0) {
60
- const /** @type {AstText} */ oldText = this.childNodes[i],
61
- {type, data} = oldText;
492
+ this.getAttribute('verifyChild')(i);
493
+ const /** @type {AstText} */ oldText = this.childNodes.at(i),
494
+ {type, data, constructor: {name}} = oldText;
62
495
  if (type === 'text') {
63
496
  oldText.replaceData(str);
64
497
  return data;
65
498
  }
66
- return undefined;
499
+ throw new RangeError(`第 ${i} 个子节点是 ${name}!`);
67
500
  }
68
501
 
69
502
  /**
70
503
  * 还原为wikitext
504
+ * @param {string} selector
71
505
  * @param {string} separator 子节点间的连接符
72
506
  * @returns {string}
73
507
  */
74
508
  toString(selector, separator = '') {
75
- return this.childNodes.map(child => child.toString()).join(separator);
76
- }
77
-
78
- static lintIgnoredExt = new Set([
79
- 'nowiki',
80
- 'pre',
81
- 'charinsert',
82
- 'score',
83
- 'syntaxhighlight',
84
- 'source',
85
- 'math',
86
- 'chem',
87
- 'ce',
88
- 'graph',
89
- 'mapframe',
90
- 'maplink',
91
- 'quiz',
92
- 'templatedata',
93
- 'timeline',
94
- ]);
509
+ return selector && this.matches(selector)
510
+ ? ''
511
+ : this.childNodes.map(child => child.toString(selector)).join(separator);
512
+ }
95
513
 
96
514
  /**
97
515
  * Linter
98
516
  * @param {number} start 起始位置
99
517
  */
100
- lint(start = 0) {
518
+ lint(start = this.getAbsoluteIndex()) {
101
519
  const SyntaxToken = require('../src/syntax');
102
520
  if (this instanceof SyntaxToken || this.constructor.hidden
103
- || this.type === 'ext-inner' && AstElement.lintIgnoredExt.has(this.name)
521
+ || this.type === 'ext-inner' && lintIgnoredExt.has(this.name)
104
522
  ) {
105
523
  return [];
106
524
  }
@@ -113,17 +531,27 @@ class AstElement extends AstNode {
113
531
  return errors;
114
532
  }
115
533
 
534
+ /**
535
+ * 以HTML格式打印
536
+ * @param {printOpt} opt 选项
537
+ * @returns {string}
538
+ */
539
+ print(opt = {}) {
540
+ return String(this)
541
+ ? `<span class="wpb-${opt.class || this.type}">${print(this.childNodes, opt)}</span>`
542
+ : '';
543
+ }
544
+
116
545
  /**
117
546
  * 保存为JSON
118
547
  * @param {string} file 文件名
119
548
  * @returns {Record<string, *>}
120
549
  */
121
550
  json(file) {
122
- const {childNodes, ...prop} = this,
123
- json = {
124
- ...prop,
125
- childNodes: childNodes.map(child => child.type === 'text' ? String(child) : child.json()),
126
- };
551
+ const json = {
552
+ ...this,
553
+ childNodes: this.childNodes.map(child => child.type === 'text' ? String(child) : child.json()),
554
+ };
127
555
  if (typeof file === 'string') {
128
556
  fs.writeFileSync(
129
557
  path.join(__dirname.slice(0, -4), 'printed', `${file}${file.endsWith('.json') ? '' : '.json'}`),
@@ -132,6 +560,52 @@ class AstElement extends AstNode {
132
560
  }
133
561
  return json;
134
562
  }
563
+
564
+ /**
565
+ * 输出AST
566
+ * @param {number} depth 当前深度
567
+ * @returns {void}
568
+ */
569
+ echo(depth = 0) {
570
+ if (!Number.isInteger(depth) || depth < 0) {
571
+ this.typeError('print', 'Number');
572
+ }
573
+ const indent = ' '.repeat(depth),
574
+ str = String(this),
575
+ {childNodes, type, length} = this;
576
+ if (childNodes.every(child => child.type === 'text' || !String(child))) {
577
+ console.log(`${indent}\x1B[32m<%s>\x1B[0m${noWrap(str)}\x1B[32m</%s>\x1B[0m`, type, type);
578
+ return undefined;
579
+ }
580
+ Parser.info(`${indent}<${type}>`);
581
+ let i = this.getPadding();
582
+ if (i) {
583
+ console.log(`${indent} ${noWrap(str.slice(0, i))}`);
584
+ }
585
+ for (let j = 0; j < length; j++) {
586
+ const child = childNodes[j],
587
+ childStr = String(child),
588
+ gap = j === length - 1 ? 0 : this.getGaps(j);
589
+ if (!childStr) {
590
+ // pass
591
+ } else if (child.type === 'text') {
592
+ console.log(`${indent} ${noWrap(String(child))}`);
593
+ } else {
594
+ child.echo(depth + 1);
595
+ }
596
+ i += childStr.length;
597
+ if (gap) {
598
+ console.log(`${indent} ${noWrap(str.slice(i, i + gap))}`);
599
+ i += gap;
600
+ }
601
+ }
602
+ if (i < str.length) {
603
+ console.log(`${indent} ${noWrap(str.slice(i))}`);
604
+ }
605
+ Parser.info(`${indent}</${type}>`);
606
+ return undefined;
607
+ }
135
608
  }
136
609
 
610
+ Parser.classes.AstElement = __filename;
137
611
  module.exports = AstElement;