wikiparser-node 0.5.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 (63) hide show
  1. package/config/default.json +129 -66
  2. package/config/zhwiki.json +4 -4
  3. package/index.js +74 -65
  4. package/lib/element.js +125 -152
  5. package/lib/node.js +251 -223
  6. package/lib/ranges.js +2 -2
  7. package/lib/text.js +64 -64
  8. package/lib/title.js +8 -7
  9. package/mixin/hidden.js +2 -0
  10. package/mixin/sol.js +1 -2
  11. package/package.json +4 -3
  12. package/parser/brackets.js +8 -2
  13. package/parser/externalLinks.js +1 -1
  14. package/parser/hrAndDoubleUnderscore.js +4 -4
  15. package/parser/links.js +7 -7
  16. package/parser/table.js +12 -10
  17. package/src/arg.js +53 -48
  18. package/src/atom/index.js +7 -5
  19. package/src/attribute.js +91 -80
  20. package/src/charinsert.js +91 -0
  21. package/src/converter.js +22 -11
  22. package/src/converterFlags.js +72 -62
  23. package/src/converterRule.js +49 -49
  24. package/src/extLink.js +30 -28
  25. package/src/gallery.js +56 -32
  26. package/src/hasNowiki/index.js +42 -0
  27. package/src/hasNowiki/pre.js +40 -0
  28. package/src/heading.js +15 -11
  29. package/src/html.js +38 -38
  30. package/src/imageParameter.js +64 -48
  31. package/src/imagemap.js +205 -0
  32. package/src/imagemapLink.js +43 -0
  33. package/src/index.js +222 -124
  34. package/src/link/category.js +4 -8
  35. package/src/link/file.js +95 -59
  36. package/src/link/galleryImage.js +74 -10
  37. package/src/link/index.js +61 -39
  38. package/src/magicLink.js +21 -22
  39. package/src/nested/choose.js +24 -0
  40. package/src/nested/combobox.js +23 -0
  41. package/src/nested/index.js +88 -0
  42. package/src/nested/references.js +23 -0
  43. package/src/nowiki/comment.js +17 -17
  44. package/src/nowiki/dd.js +2 -2
  45. package/src/nowiki/doubleUnderscore.js +14 -14
  46. package/src/nowiki/index.js +12 -0
  47. package/src/onlyinclude.js +10 -8
  48. package/src/paramTag/index.js +83 -0
  49. package/src/paramTag/inputbox.js +42 -0
  50. package/src/parameter.js +32 -18
  51. package/src/syntax.js +9 -1
  52. package/src/table/index.js +33 -32
  53. package/src/table/td.js +51 -57
  54. package/src/table/tr.js +6 -6
  55. package/src/tagPair/ext.js +58 -40
  56. package/src/tagPair/include.js +1 -1
  57. package/src/tagPair/index.js +21 -20
  58. package/src/transclude.js +158 -143
  59. package/tool/index.js +720 -439
  60. package/util/base.js +17 -0
  61. package/util/debug.js +1 -1
  62. package/util/diff.js +1 -1
  63. package/util/string.js +20 -20
package/lib/element.js CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  const fs = require('fs'),
4
4
  path = require('path'),
5
- {externalUse} = require('../util/debug'),
6
5
  {toCase, noWrap, print} = require('../util/string'),
7
6
  {nth} = require('./ranges'),
8
7
  parseSelector = require('../parser/selector'),
@@ -30,6 +29,7 @@ class AstElement extends AstNode {
30
29
  * @param {string|undefined} equal 属性规则运算符,`equal`存在时`val`和`i`也一定存在
31
30
  * @param {string|undefined} val 属性值
32
31
  * @param {string|undefined} i 是否对大小写不敏感
32
+ * @throws `RangeError` 复杂属性
33
33
  */
34
34
  #matchesAttr = (key, equal, val, i) => {
35
35
  if (!equal) {
@@ -38,14 +38,17 @@ class AstElement extends AstNode {
38
38
  return equal === '!=';
39
39
  }
40
40
  val = toCase(val, i);
41
+ let thisVal = this.getAttribute(key);
42
+ if (thisVal instanceof RegExp) {
43
+ thisVal = thisVal.source;
44
+ }
41
45
  if (equal === '~=') {
42
- let /** @type {Iterable<string>} */ thisVals = this[key];
43
- if (typeof thisVals === 'string') {
44
- thisVals = thisVals.split(/\s/u);
45
- }
46
+ const thisVals = typeof thisVal === 'string' ? thisVal.split(/\s/u) : thisVal;
46
47
  return Boolean(thisVals?.[Symbol.iterator]) && [...thisVals].some(v => toCase(v, i) === val);
48
+ } else if (typeof thisVal !== 'string') {
49
+ throw new RangeError(`复杂属性 ${key} 不能用于选择器!`);
47
50
  }
48
- const thisVal = toCase(this.getAttribute(key), i);
51
+ thisVal = toCase(thisVal, i);
49
52
  switch (equal) {
50
53
  case '|=':
51
54
  return thisVal === val || thisVal.startsWith(`${val}-`);
@@ -62,6 +65,11 @@ class AstElement extends AstNode {
62
65
  }
63
66
  };
64
67
 
68
+ /** 子节点总数 */
69
+ get length() {
70
+ return this.childNodes.length;
71
+ }
72
+
65
73
  /**
66
74
  * 全部非文本子节点
67
75
  * @complexity `n`
@@ -71,14 +79,6 @@ class AstElement extends AstNode {
71
79
  return children;
72
80
  }
73
81
 
74
- /**
75
- * 非文本子节点总数
76
- * @complexity `n`
77
- */
78
- get childElementCount() {
79
- return this.children.length;
80
- }
81
-
82
82
  /**
83
83
  * 首位非文本子节点
84
84
  * @returns {this}
@@ -89,10 +89,18 @@ class AstElement extends AstNode {
89
89
 
90
90
  /**
91
91
  * 末位非文本子节点
92
- * @returns {this}
92
+ * @complexity `n`
93
93
  */
94
94
  get lastElementChild() {
95
- 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;
96
104
  }
97
105
 
98
106
  /** 父节点 */
@@ -183,71 +191,6 @@ class AstElement extends AstNode {
183
191
  return key === 'matchesAttr' ? this.#matchesAttr : super.getAttribute(key);
184
192
  }
185
193
 
186
- /**
187
- * @override
188
- * @template {string} T
189
- * @param {T} key 属性键
190
- * @param {TokenAttribute<T>} value 属性值
191
- * @throws `RangeError` 禁止手动指定的属性
192
- */
193
- setAttribute(key, value) {
194
- if (key === 'name' && externalUse('setAttribute')) {
195
- throw new RangeError(`禁止手动指定 ${key} 属性!`);
196
- }
197
- return super.setAttribute(key, value);
198
- }
199
-
200
- /**
201
- * 在末尾批量插入子节点
202
- * @param {...this} elements 插入节点
203
- * @complexity `n`
204
- */
205
- append(...elements) {
206
- for (const element of elements) {
207
- this.appendChild(element);
208
- }
209
- }
210
-
211
- /**
212
- * 在开头批量插入子节点
213
- * @param {...this} elements 插入节点
214
- * @complexity `n`
215
- */
216
- prepend(...elements) {
217
- for (let i = 0; i < elements.length; i++) {
218
- this.insertAt(elements[i], i);
219
- }
220
- }
221
-
222
- /**
223
- * 批量替换子节点
224
- * @param {...this} elements 新的子节点
225
- * @complexity `n`
226
- */
227
- replaceChildren(...elements) {
228
- for (let i = this.childNodes.length - 1; i >= 0; i--) {
229
- this.removeAt(i);
230
- }
231
- this.append(...elements);
232
- }
233
-
234
- /**
235
- * 修改文本子节点
236
- * @param {string} str 新文本
237
- * @param {number} i 子节点位置
238
- * @throws `RangeError` 对应位置的子节点不是文本节点
239
- */
240
- setText(str, i = 0) {
241
- this.getAttribute('verifyChild')(i);
242
- const /** @type {AstText} */ oldText = this.childNodes.at(i),
243
- {type, data, constructor: {name}} = oldText;
244
- if (type === 'text') {
245
- oldText.replaceData(str);
246
- return data;
247
- }
248
- throw new RangeError(`第 ${i} 个子节点是 ${name}!`);
249
- }
250
-
251
194
  /** 是否受保护。保护条件来自Token,这里仅提前用于:required和:optional伪选择器。 */
252
195
  #isProtected() {
253
196
  const /** @type {{parentNode: AstElement & {constructor: {fixed: boolean}}}} */ {parentNode} = this;
@@ -366,17 +309,17 @@ class AstElement extends AstNode {
366
309
  * @returns {boolean}
367
310
  * @complexity `n`
368
311
  */
369
- matches(selector = '') {
370
- if (typeof selector === 'string') {
371
- return Parser.run(() => {
372
- const stack = parseSelector(selector),
373
- /** @type {Set<string>} */
374
- pseudos = new Set(stack.flat(2).filter(step => typeof step === 'string' && step[0] === ':'));
375
- if (pseudos.size > 0) {
376
- Parser.warn('检测到伪选择器,请确认是否需要将":"转义成"\\:"。', pseudos);
377
- }
378
- return stack.some(condition => this.matches(condition));
379
- });
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)));
380
323
  } else if (!Parser.running) {
381
324
  this.typeError('matches', 'String');
382
325
  }
@@ -406,59 +349,11 @@ class AstElement extends AstNode {
406
349
  return false;
407
350
  }
408
351
 
409
- /**
410
- * 还原为wikitext
411
- * @param {string} selector
412
- * @param {string} separator 子节点间的连接符
413
- */
414
- toString(selector, separator = '') {
415
- return selector && this.matches(selector)
416
- ? ''
417
- : this.childNodes.map(child => child.toString(selector)).join(separator);
418
- }
419
-
420
- /** 获取所有祖先节点 */
421
- getAncestors() {
422
- const /** @type {this[]} */ ancestors = [];
423
- let {parentNode} = this;
424
- while (parentNode) {
425
- ancestors.push(parentNode);
426
- ({parentNode} = parentNode);
427
- }
428
- return ancestors;
429
- }
430
-
431
- /**
432
- * 比较和另一个节点的相对位置
433
- * @param {this} other 待比较的节点
434
- * @complexity `n`
435
- * @throws `Error` 不在同一个语法树
436
- */
437
- compareDocumentPosition(other) {
438
- if (!(other instanceof AstElement)) {
439
- this.typeError('compareDocumentPosition', 'AstElement');
440
- } else if (this === other) {
441
- return 0;
442
- } else if (this.contains(other)) {
443
- return -1;
444
- } else if (other.contains(this)) {
445
- return 1;
446
- } else if (this.getRootNode() !== other.getRootNode()) {
447
- throw new Error('不在同一个语法树!');
448
- }
449
- const aAncestors = [...this.getAncestors().reverse(), this],
450
- bAncestors = [...other.getAncestors().reverse(), other],
451
- depth = aAncestors.findIndex((ancestor, i) => bAncestors[i] !== ancestor),
452
- commonAncestor = aAncestors[depth - 1],
453
- {childNodes} = commonAncestor;
454
- return childNodes.indexOf(aAncestors[depth]) - childNodes.indexOf(bAncestors[depth]);
455
- }
456
-
457
352
  /**
458
353
  * 最近的祖先节点
459
354
  * @param {string} selector
460
355
  */
461
- closest(selector = '') {
356
+ closest(selector) {
462
357
  let {parentNode} = this;
463
358
  while (parentNode) {
464
359
  if (parentNode.matches(selector)) {
@@ -475,7 +370,7 @@ class AstElement extends AstNode {
475
370
  * @returns {this|undefined}
476
371
  * @complexity `n`
477
372
  */
478
- querySelector(selector = '') {
373
+ querySelector(selector) {
479
374
  for (const child of this.children) {
480
375
  if (child.matches(selector)) {
481
376
  return child;
@@ -493,7 +388,7 @@ class AstElement extends AstNode {
493
388
  * @param {string} selector
494
389
  * @complexity `n`
495
390
  */
496
- querySelectorAll(selector = '') {
391
+ querySelectorAll(selector) {
497
392
  const /** @type {this[]} */ descendants = [];
498
393
  for (const child of this.children) {
499
394
  if (child.matches(selector)) {
@@ -534,16 +429,94 @@ class AstElement extends AstNode {
534
429
  return String(this).split('\n', n + 1).at(-1);
535
430
  }
536
431
 
537
- static lintIgnoredHidden = new Set(['noinclude', 'double-underscore', 'hidden']);
538
- static lintIgnoredSyntax = new Set(['magic-word-name', 'heading-trail', 'table-syntax']);
539
- static lintIgnoredExt = new Set(['nowiki', 'pre', 'syntaxhighlight', 'source', 'math', 'timeline']);
432
+ /**
433
+ * 在开头批量插入子节点
434
+ * @param {...this} elements 插入节点
435
+ * @complexity `n`
436
+ */
437
+ prepend(...elements) {
438
+ for (let i = 0; i < elements.length; i++) {
439
+ this.insertAt(elements[i], i);
440
+ }
441
+ }
442
+
443
+ /**
444
+ * 在末尾批量插入子节点
445
+ * @param {...this} elements 插入节点
446
+ * @complexity `n`
447
+ */
448
+ append(...elements) {
449
+ for (const element of elements) {
450
+ this.insertAt(element);
451
+ }
452
+ }
453
+
454
+ /**
455
+ * 批量替换子节点
456
+ * @param {...this} elements 新的子节点
457
+ * @complexity `n`
458
+ */
459
+ replaceChildren(...elements) {
460
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
461
+ this.removeAt(i);
462
+ }
463
+ this.append(...elements);
464
+ }
465
+
466
+ /**
467
+ * 修改文本子节点
468
+ * @param {string} str 新文本
469
+ * @param {number} i 子节点位置
470
+ * @throws `RangeError` 对应位置的子节点不是文本节点
471
+ */
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}!`);
481
+ }
482
+
483
+ /**
484
+ * 还原为wikitext
485
+ * @param {string} selector
486
+ * @param {string} separator 子节点间的连接符
487
+ * @returns {string}
488
+ */
489
+ toString(selector, separator = '') {
490
+ return selector && this.matches(selector)
491
+ ? ''
492
+ : this.childNodes.map(child => child.toString(selector)).join(separator);
493
+ }
494
+
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
+ ]);
540
512
 
541
513
  /**
542
514
  * Linter
543
515
  * @param {number} start 起始位置
544
516
  */
545
517
  lint(start = 0) {
546
- if (AstElement.lintIgnoredHidden.has(this.type) || AstElement.lintIgnoredSyntax.has(this.type)
518
+ const SyntaxToken = require('../src/syntax');
519
+ if (this instanceof SyntaxToken || this.constructor.hidden
547
520
  || this.type === 'ext-inner' && AstElement.lintIgnoredExt.has(this.name)
548
521
  ) {
549
522
  return [];
@@ -563,15 +536,15 @@ class AstElement extends AstNode {
563
536
  * @returns {string}
564
537
  */
565
538
  print(opt = {}) {
566
- return this.childNodes.length === 0
567
- ? ''
568
- : `<span class="wpb-${opt.class ?? this.type}">${print(this.childNodes, opt)}</span>`;
539
+ return String(this)
540
+ ? `<span class="wpb-${opt.class ?? this.type}">${print(this.childNodes, opt)}</span>`
541
+ : '';
569
542
  }
570
543
 
571
544
  /**
572
545
  * 保存为JSON
573
546
  * @param {string} file 文件名
574
- * @returns {Record<string, any>}
547
+ * @returns {Record<string, *>}
575
548
  */
576
549
  json(file) {
577
550
  const {childNodes, ...prop} = this,
@@ -594,7 +567,7 @@ class AstElement extends AstNode {
594
567
  * @returns {void}
595
568
  */
596
569
  echo(depth = 0) {
597
- if (typeof depth !== 'number') {
570
+ if (!Number.isInteger(depth) || depth < 0) {
598
571
  this.typeError('print', 'Number');
599
572
  }
600
573
  const indent = ' '.repeat(depth),