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/src/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  /*
4
4
  * PHP解析器的步骤:
5
5
  * -1. 替换签名和`{{subst:}}`,参见Parser::preSaveTransform;这在revision中不可能保留,可以跳过
6
- * 0. 移除特定字符`\0`和`\x7f`,参见Parser::parse
6
+ * 0. 移除特定字符`\0`和`\x7F`,参见Parser::parse
7
7
  * 1. 注释/扩展标签('<'相关),参见Preprocessor_Hash::buildDomTreeArrayFromText和Sanitizer::decodeTagAttributes
8
8
  * 2. 模板/模板变量/标题,注意rightmost法则,以及`-{`和`[[`可以破坏`{{`或`{{{`语法,
9
9
  * 参见Preprocessor_Hash::buildDomTreeArrayFromText
@@ -19,7 +19,7 @@
19
19
  */
20
20
 
21
21
  /*
22
- * \0\d+.\x7f标记Token:
22
+ * \0\d+.\x7F标记Token:
23
23
  * e: ExtToken
24
24
  * c: CommentToken、NoIncludeToken和IncludeToken
25
25
  * !: `{{!}}`专用
@@ -28,6 +28,7 @@
28
28
  * -: `{{!-}}`专用
29
29
  * +: `{{!!}}`专用
30
30
  * ~: `{{=}}`专用
31
+ * m: `{{fullurl:}}`、`{{canonicalurl:}}`或`{{filepath:}}`
31
32
  * t: ArgToken或TranscludeToken
32
33
  * h: HeadingToken
33
34
  * x: HtmlToken
@@ -58,20 +59,12 @@ class Token extends AstElement {
58
59
  type = 'root';
59
60
  #stage = 0; // 解析阶段,参见顶部注释。只对plain Token有意义。
60
61
  #config;
61
- // 这个数组起两个作用:1. 数组中的Token会在build时替换`/\0\d+.\x7f/`标记;2. 数组中的Token会依次执行parseOnce和build方法。
62
+ // 这个数组起两个作用:1. 数组中的Token会在build时替换`/\0\d+.\x7F/`标记;2. 数组中的Token会依次执行parseOnce和build方法。
62
63
  #accum;
63
64
  /** @type {Record<string, Ranges>} */ #acceptable;
64
65
  #protectedChildren = new Ranges();
65
66
  /** @type {boolean} */ #include;
66
67
 
67
- /**
68
- * 保护部分子节点不被移除
69
- * @param {...string|number|Range} args 子节点范围
70
- */
71
- #protectChildren = (...args) => {
72
- this.#protectedChildren.push(...new Ranges(args));
73
- };
74
-
75
68
  /**
76
69
  * 将维基语法替换为占位符
77
70
  * @param {number} n 解析阶段
@@ -144,9 +137,36 @@ class Token extends AstElement {
144
137
  throw new Error(`解析错误!未正确标记的 Token:${s}`);
145
138
  });
146
139
 
140
+ /**
141
+ * 将占位符替换为子Token
142
+ * @complexity `n`
143
+ */
144
+ #build = () => {
145
+ this.#stage = MAX_STAGE;
146
+ const {length, firstChild} = this,
147
+ str = String(firstChild);
148
+ if (length === 1 && firstChild.type === 'text' && str.includes('\0')) {
149
+ this.replaceChildren(...this.#buildFromStr(str));
150
+ this.normalize();
151
+ if (this.type === 'root') {
152
+ for (const token of this.#accum) {
153
+ token.getAttribute('build')();
154
+ }
155
+ }
156
+ }
157
+ };
158
+
159
+ /**
160
+ * 保护部分子节点不被移除
161
+ * @param {...string|number|Range} args 子节点范围
162
+ */
163
+ #protectChildren = (...args) => {
164
+ this.#protectedChildren.push(...new Ranges(args));
165
+ };
166
+
147
167
  /** 所有图片,包括图库 */
148
168
  get images() {
149
- return this.querySelectorAll('file, gallery-image');
169
+ return this.querySelectorAll('file, gallery-image, imagemap-image');
150
170
  }
151
171
 
152
172
  /** 所有内链、外链和自由外链 */
@@ -167,7 +187,7 @@ class Token extends AstElement {
167
187
  constructor(wikitext, config = Parser.getConfig(), halfParsed = false, accum = [], acceptable = null) {
168
188
  super();
169
189
  if (typeof wikitext === 'string') {
170
- this.appendChild(halfParsed ? wikitext : wikitext.replaceAll('\0', '').replaceAll('\x7F', ''));
190
+ this.insertAt(halfParsed ? wikitext : wikitext.replaceAll(/[\0\x7F]/gu, ''));
171
191
  }
172
192
  this.#config = config;
173
193
  this.#accum = accum;
@@ -175,34 +195,6 @@ class Token extends AstElement {
175
195
  accum.push(this);
176
196
  }
177
197
 
178
- /**
179
- * 深拷贝所有子节点
180
- * @complexity `n`
181
- * @returns {(AstText|Token)[]}
182
- */
183
- cloneChildNodes() {
184
- return this.childNodes.map(child => child.cloneNode());
185
- }
186
-
187
- /**
188
- * 深拷贝节点
189
- * @complexity `n`
190
- * @throws `Error` 未定义复制方法
191
- */
192
- cloneNode() {
193
- if (!this.isPlain()) {
194
- throw new Error(`未定义 ${this.constructor.name} 的复制方法!`);
195
- }
196
- const cloned = this.cloneChildNodes();
197
- return Parser.run(() => {
198
- const token = new Token(undefined, this.#config, false, [], this.#acceptable);
199
- token.type = this.type;
200
- token.append(...cloned);
201
- token.getAttribute('protectChildren')(...this.#protectedChildren);
202
- return token;
203
- });
204
- }
205
-
206
198
  /**
207
199
  * @override
208
200
  * @template {string} T
@@ -211,22 +203,24 @@ class Token extends AstElement {
211
203
  */
212
204
  getAttribute(key) {
213
205
  switch (key) {
214
- case 'stage':
215
- return this.#stage;
216
206
  case 'config':
217
207
  return structuredClone(this.#config);
218
208
  case 'accum':
219
209
  return this.#accum;
210
+ case 'parseOnce':
211
+ return this.#parseOnce;
212
+ case 'buildFromStr':
213
+ return this.#buildFromStr;
214
+ case 'build':
215
+ return this.#build;
216
+ case 'stage':
217
+ return this.#stage;
220
218
  case 'acceptable':
221
219
  return this.#acceptable ? {...this.#acceptable} : null;
222
220
  case 'protectChildren':
223
221
  return this.#protectChildren;
224
222
  case 'protectedChildren':
225
223
  return new Ranges(this.#protectedChildren);
226
- case 'parseOnce':
227
- return this.#parseOnce;
228
- case 'buildFromStr':
229
- return this.#buildFromStr;
230
224
  case 'include': {
231
225
  if (this.#include !== undefined) {
232
226
  return this.#include;
@@ -252,16 +246,8 @@ class Token extends AstElement {
252
246
  * @template {string} T
253
247
  * @param {T} key 属性键
254
248
  * @param {TokenAttribute<T>} value 属性值
255
- * @throws `RangeError` 禁止手动指定私有属性
256
249
  */
257
250
  setAttribute(key, value) {
258
- if (key === 'include' || !Parser.running && (key === 'config' || key === 'accum')) {
259
- throw new RangeError(`禁止手动指定私有的 #${key} 属性!`);
260
- } else if (!Parser.debugging && (key === 'stage' || key === 'acceptable' || key === 'protectedChildren')
261
- && externalUse('setAttribute')
262
- ) {
263
- throw new RangeError(`使用 ${this.constructor.name}.setAttribute 方法设置私有属性 #${key} 仅用于代码调试!`);
264
- }
265
251
  switch (key) {
266
252
  case 'stage':
267
253
  if (this.#stage === 0 && this.type === 'root') {
@@ -269,15 +255,6 @@ class Token extends AstElement {
269
255
  }
270
256
  this.#stage = value;
271
257
  return this;
272
- case 'config':
273
- this.#config = value;
274
- return this;
275
- case 'accum':
276
- this.#accum = value;
277
- return this;
278
- case 'protectedChildren':
279
- this.#protectedChildren = value;
280
- return this;
281
258
  case 'acceptable': {
282
259
  const /** @type {acceptable} */ acceptable = {};
283
260
  if (value) {
@@ -308,36 +285,6 @@ class Token extends AstElement {
308
285
  return this.constructor === Token;
309
286
  }
310
287
 
311
- /**
312
- * @override
313
- * @param {number} i 移除位置
314
- * @returns {Token}
315
- * @complexity `n`
316
- * @throws `Error` 不可移除的子节点
317
- */
318
- removeAt(i) {
319
- if (typeof i !== 'number') {
320
- this.typeError('removeAt', 'Number');
321
- }
322
- const iPos = i < 0 ? i + this.childNodes.length : i;
323
- if (!Parser.running) {
324
- const protectedIndices = this.#protectedChildren.applyTo(this.childNodes);
325
- if (protectedIndices.includes(iPos)) {
326
- throw new Error(`${this.constructor.name} 的第 ${i} 个子节点不可移除!`);
327
- } else if (this.#acceptable) {
328
- const acceptableIndices = Object.fromEntries(
329
- Object.entries(this.#acceptable)
330
- .map(([str, ranges]) => [str, ranges.applyTo(this.childNodes.length - 1)]),
331
- ),
332
- nodesAfter = i === -1 ? [] : this.childNodes.slice(i + 1);
333
- if (nodesAfter.some(({constructor: {name}}, j) => !acceptableIndices[name].includes(i + j))) {
334
- throw new Error(`移除 ${this.constructor.name} 的第 ${i} 个子节点会破坏规定的顺序!`);
335
- }
336
- }
337
- }
338
- return super.removeAt(i);
339
- }
340
-
341
288
  /**
342
289
  * @override
343
290
  * @template {string|Token} T
@@ -372,6 +319,45 @@ class Token extends AstElement {
372
319
  return token;
373
320
  }
374
321
 
322
+ /**
323
+ * 规范化页面标题
324
+ * @param {string} title 标题(含或不含命名空间前缀)
325
+ * @param {number} defaultNs 命名空间
326
+ */
327
+ normalizeTitle(title, defaultNs = 0, halfParsed = false) {
328
+ return Parser.normalizeTitle(title, defaultNs, this.#include, this.#config, halfParsed);
329
+ }
330
+
331
+ /**
332
+ * @override
333
+ * @param {number} i 移除位置
334
+ * @returns {Token}
335
+ * @complexity `n`
336
+ * @throws `Error` 不可移除的子节点
337
+ */
338
+ removeAt(i) {
339
+ if (!Number.isInteger(i)) {
340
+ this.typeError('removeAt', 'Number');
341
+ }
342
+ const iPos = i < 0 ? i + this.childNodes.length : i;
343
+ if (!Parser.running) {
344
+ const protectedIndices = this.#protectedChildren.applyTo(this.childNodes);
345
+ if (protectedIndices.includes(iPos)) {
346
+ throw new Error(`${this.constructor.name} 的第 ${i} 个子节点不可移除!`);
347
+ } else if (this.#acceptable) {
348
+ const acceptableIndices = Object.fromEntries(
349
+ Object.entries(this.#acceptable)
350
+ .map(([str, ranges]) => [str, ranges.applyTo(this.childNodes.length - 1)]),
351
+ ),
352
+ nodesAfter = i === -1 ? [] : this.childNodes.slice(i + 1);
353
+ if (nodesAfter.some(({constructor: {name}}, j) => !acceptableIndices[name].includes(i + j))) {
354
+ throw new Error(`移除 ${this.constructor.name} 的第 ${i} 个子节点会破坏规定的顺序!`);
355
+ }
356
+ }
357
+ }
358
+ return super.removeAt(i);
359
+ }
360
+
375
361
  /**
376
362
  * 替换为同类节点
377
363
  * @param {Token} token 待替换的节点
@@ -444,6 +430,120 @@ class Token extends AstElement {
444
430
  throw new RangeError(`非法的标签名!${tagName}`);
445
431
  }
446
432
 
433
+ /**
434
+ * 创建纯文本节点
435
+ * @param {string} data 文本内容
436
+ */
437
+ createTextNode(data = '') {
438
+ return typeof data === 'string' ? new AstText(data) : this.typeError('createComment', 'String');
439
+ }
440
+
441
+ /**
442
+ * 找到给定位置所在的节点
443
+ * @param {number} index 位置
444
+ */
445
+ caretPositionFromIndex(index) {
446
+ if (index === undefined) {
447
+ return undefined;
448
+ } else if (!Number.isInteger(index)) {
449
+ this.typeError('caretPositionFromIndex', 'Number');
450
+ }
451
+ const {length} = String(this);
452
+ if (index > length || index < -length) {
453
+ return undefined;
454
+ } else if (index < 0) {
455
+ index += length;
456
+ }
457
+ let child = this, // eslint-disable-line unicorn/no-this-assignment
458
+ acc = 0,
459
+ start = 0;
460
+ while (child.type !== 'text') {
461
+ const {childNodes} = child;
462
+ acc += child.getPadding();
463
+ for (let i = 0; acc <= index && i < childNodes.length; i++) {
464
+ const cur = childNodes[i],
465
+ {length: l} = String(cur);
466
+ acc += l;
467
+ if (acc >= index) {
468
+ child = cur;
469
+ acc -= l;
470
+ start = acc;
471
+ break;
472
+ }
473
+ acc += child.getGaps(i);
474
+ }
475
+ if (child.childNodes === childNodes) {
476
+ return {offsetNode: child, offset: index - start};
477
+ }
478
+ }
479
+ return {offsetNode: child, offset: index - start};
480
+ }
481
+
482
+ /**
483
+ * 找到给定位置所在的节点
484
+ * @param {number} x 列数
485
+ * @param {number} y 行数
486
+ */
487
+ caretPositionFromPoint(x, y) {
488
+ return this.caretPositionFromIndex(this.indexFromPos(y, x));
489
+ }
490
+
491
+ /**
492
+ * 找到给定位置所在的最外层节点
493
+ * @param {number} index 位置
494
+ * @throws `Error` 不是根节点
495
+ */
496
+ elementFromIndex(index) {
497
+ if (index === undefined) {
498
+ return undefined;
499
+ } else if (!Number.isInteger(index)) {
500
+ this.typeError('elementFromIndex', 'Number');
501
+ } else if (this.type !== 'root') {
502
+ throw new Error('elementFromIndex方法只可用于根节点!');
503
+ }
504
+ const {length} = String(this);
505
+ if (index > length || index < -length) {
506
+ return undefined;
507
+ } else if (index < 0) {
508
+ index += length;
509
+ }
510
+ const {childNodes} = this;
511
+ let acc = 0,
512
+ i = 0;
513
+ for (; acc < index && i < childNodes.length; i++) {
514
+ const {length: l} = String(childNodes[i]);
515
+ acc += l;
516
+ }
517
+ return childNodes[i && i - 1];
518
+ }
519
+
520
+ /**
521
+ * 找到给定位置所在的最外层节点
522
+ * @param {number} x 列数
523
+ * @param {number} y 行数
524
+ */
525
+ elementFromPoint(x, y) {
526
+ return this.elementFromIndex(this.indexFromPos(y, x));
527
+ }
528
+
529
+ /**
530
+ * 找到给定位置所在的所有节点
531
+ * @param {number} index 位置
532
+ */
533
+ elementsFromIndex(index) {
534
+ const offsetNode = this.caretPositionFromIndex(index)?.offsetNode;
535
+ return offsetNode && [...offsetNode.getAncestors().reverse(), offsetNode];
536
+ }
537
+
538
+ /**
539
+ * 找到给定位置所在的所有节点
540
+ * @param {number} x 列数
541
+ * @param {number} y 行数
542
+ */
543
+ elementsFromPoint(x, y) {
544
+ return this.elementsFromIndex(this.indexFromPos(y, x));
545
+ }
546
+
447
547
  /**
448
548
  * 判断标题是否是跨维基链接
449
549
  * @param {string} title 标题
@@ -453,12 +553,31 @@ class Token extends AstElement {
453
553
  }
454
554
 
455
555
  /**
456
- * 规范化页面标题
457
- * @param {string} title 标题(含或不含命名空间前缀)
458
- * @param {number} defaultNs 命名空间
556
+ * 深拷贝所有子节点
557
+ * @complexity `n`
558
+ * @returns {(AstText|Token)[]}
459
559
  */
460
- normalizeTitle(title, defaultNs = 0, halfParsed = false) {
461
- return Parser.normalizeTitle(title, defaultNs, this.#include, this.#config, halfParsed);
560
+ cloneChildNodes() {
561
+ return this.childNodes.map(child => child.cloneNode());
562
+ }
563
+
564
+ /**
565
+ * 深拷贝节点
566
+ * @complexity `n`
567
+ * @throws `Error` 未定义复制方法
568
+ */
569
+ cloneNode() {
570
+ if (this.constructor !== Token) {
571
+ throw new Error(`未定义 ${this.constructor.name} 的复制方法!`);
572
+ }
573
+ const cloned = this.cloneChildNodes();
574
+ return Parser.run(() => {
575
+ const token = new Token(undefined, this.#config, false, [], this.#acceptable);
576
+ token.type = this.type;
577
+ token.append(...cloned);
578
+ token.getAttribute('protectChildren')(...this.#protectedChildren);
579
+ return token;
580
+ });
462
581
  }
463
582
 
464
583
  /**
@@ -499,7 +618,7 @@ class Token extends AstElement {
499
618
  * @complexity `n`
500
619
  */
501
620
  section(n) {
502
- return typeof n === 'number' ? this.sections()?.[n] : this.typeError('section', 'Number');
621
+ return Number.isInteger(n) ? this.sections()?.[n] : this.typeError('section', 'Number');
503
622
  }
504
623
 
505
624
  /**
@@ -521,27 +640,28 @@ class Token extends AstElement {
521
640
  if (!parentNode) {
522
641
  return undefined;
523
642
  }
524
- const {children} = parentNode,
525
- index = children.indexOf(this);
643
+ const {childNodes} = parentNode,
644
+ index = childNodes.indexOf(this);
526
645
  let i;
527
646
  for (i = index - 1; i >= 0; i--) {
528
- if (children[i].matches(`html${tag && '#'}${tag ?? ''}[selfClosing=false][closing=false]`)) {
647
+ const {type, name, selfClosing, closing} = childNodes[i];
648
+ if (type === 'html' && (!tag || name === tag) && selfClosing === false && closing === false) {
529
649
  break;
530
650
  }
531
651
  }
532
652
  if (i === -1) {
533
653
  return parentNode.findEnclosingHtml(tag);
534
654
  }
535
- const opening = children[i],
536
- {name} = opening;
537
- for (i = index + 1; i < children.length; i++) {
538
- if (children[i].matches(`html#${name}[selfClosing=false][closing=true]`)) {
655
+ const opening = childNodes[i];
656
+ for (i = index + 1; i < childNodes.length; i++) {
657
+ const {type, name, selfClosing, closing} = childNodes[i];
658
+ if (type === 'html' && name === opening.name && selfClosing === false && closing === true) {
539
659
  break;
540
660
  }
541
661
  }
542
- return i === children.length
662
+ return i === childNodes.length
543
663
  ? parentNode.findEnclosingHtml(tag)
544
- : [opening, children[i]];
664
+ : [opening, childNodes[i]];
545
665
  }
546
666
 
547
667
  /**
@@ -589,29 +709,6 @@ class Token extends AstElement {
589
709
  this.normalize();
590
710
  }
591
711
 
592
- /**
593
- * 将占位符替换为子Token
594
- * @complexity `n`
595
- */
596
- build() {
597
- if (!Parser.debugging && externalUse('build')) {
598
- this.debugOnly('build');
599
- }
600
- this.#stage = MAX_STAGE;
601
- const {childNodes: {length}, firstChild} = this,
602
- str = String(firstChild);
603
- if (length === 1 && firstChild.type === 'text' && str.includes('\0')) {
604
- this.replaceChildren(...this.#buildFromStr(str));
605
- this.normalize();
606
- if (this.type === 'root') {
607
- for (const token of this.#accum) {
608
- token.build();
609
- }
610
- }
611
- }
612
- return this;
613
- }
614
-
615
712
  /** 生成部分Token的`name`属性 */
616
713
  afterBuild() {
617
714
  if (!Parser.debugging && externalUse('afterBuild')) {
@@ -630,16 +727,18 @@ class Token extends AstElement {
630
727
  * @param {boolean} include 是否嵌入
631
728
  */
632
729
  parse(n = MAX_STAGE, include = false) {
633
- if (typeof n !== 'number') {
730
+ if (!Number.isInteger(n)) {
634
731
  this.typeError('parse', 'Number');
635
- } else if (n < MAX_STAGE && !Parser.debugging && Parser.warning && externalUse('parse')) {
636
- Parser.warn('指定解析层级的方法仅供熟练用户使用!');
637
732
  }
638
733
  this.#include = Boolean(include);
639
734
  while (this.#stage < n) {
640
735
  this.#parseOnce(this.#stage, include);
641
736
  }
642
- return n ? this.build().afterBuild() : this;
737
+ if (n) {
738
+ this.#build();
739
+ this.afterBuild();
740
+ }
741
+ return this;
643
742
  }
644
743
 
645
744
  /**
@@ -654,7 +753,9 @@ class Token extends AstElement {
654
753
  /** 解析花括号 */
655
754
  #parseBrackets() {
656
755
  const parseBrackets = require('../parser/brackets');
657
- this.setText(parseBrackets(String(this), this.#config, this.#accum));
756
+ const str = this.type === 'root' ? String(this) : `\0${String(this)}`,
757
+ parsed = parseBrackets(str, this.#config, this.#accum);
758
+ this.setText(this.type === 'root' ? parsed : parsed.slice(1));
658
759
  }
659
760
 
660
761
  /** 解析HTML标签 */
@@ -718,9 +819,13 @@ class Token extends AstElement {
718
819
 
719
820
  /** 解析列表 */
720
821
  #parseList() {
822
+ if (this.type === 'image-parameter') {
823
+ return;
824
+ }
721
825
  const parseList = require('../parser/list');
722
826
  const lines = String(this).split('\n');
723
- for (let i = this.type === 'root' ? 0 : 1; i < lines.length; i++) {
827
+ let i = this.type === 'root' || this.type === 'ext-inner' && this.type === 'poem' ? 0 : 1;
828
+ for (; i < lines.length; i++) {
724
829
  lines[i] = parseList(lines[i], this.#config, this.#accum);
725
830
  }
726
831
  this.setText(lines.join('\n'));
@@ -2,8 +2,7 @@
2
2
 
3
3
  const Title = require('../../lib/title'),
4
4
  Parser = require('../..'),
5
- LinkToken = require('.'),
6
- Token = require('..');
5
+ LinkToken = require('.');
7
6
 
8
7
  /**
9
8
  * 分类
@@ -12,17 +11,13 @@ const Title = require('../../lib/title'),
12
11
  class CategoryToken extends LinkToken {
13
12
  type = 'category';
14
13
 
15
- setLangLink = undefined;
16
- setFragment = undefined;
17
- asSelfLink = undefined;
18
- pipeTrick = undefined;
19
-
20
14
  /** 分类排序关键字 */
21
15
  get sortkey() {
22
- return this.children[1]?.text()
23
- ?.replaceAll(/&#(\d+);/gu, /** @param {string} p */ (_, p) => String.fromCodePoint(Number(p)))
24
- ?.replaceAll(/&#x([\da-f]+);/giu, /** @param {string} p */ (_, p) => String.fromCodePoint(parseInt(p, 16)))
25
- ?.replaceAll('\n', '') ?? '';
16
+ return this.childNodes[1]?.text()?.replaceAll(
17
+ /&#(\d+|x[\da-f]+);|\n/gu,
18
+ /** @param {string} p */
19
+ (_, p) => p ? String.fromCodePoint(p[0] === 'x' ? parseInt(p.slice(1), 16) : Number(p)) : '',
20
+ );
26
21
  }
27
22
 
28
23
  set sortkey(text) {
@@ -34,10 +29,11 @@ class CategoryToken extends LinkToken {
34
29
  * @param {string|undefined} text 排序关键字
35
30
  * @param {Title} title 分类页面标题对象
36
31
  * @param {accum} accum
32
+ * @param {string} delimiter `|`
37
33
  */
38
- constructor(link, text, title, config = Parser.getConfig(), accum = []) {
39
- super(link, text, title, config, accum);
40
- this.seal(['setFragment', 'asSelfLink', 'pipeTrick'], true);
34
+ constructor(link, text, title, config = Parser.getConfig(), accum = [], delimiter = '|') {
35
+ super(link, text, title, config, accum, delimiter);
36
+ this.seal(['selfLink', 'interwiki', 'setLangLink', 'setFragment', 'asSelfLink', 'pipeTrick'], true);
41
37
  }
42
38
 
43
39
  /**