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/src/transclude.js CHANGED
@@ -14,8 +14,13 @@ const {removeComment, escapeRegExp, text, noWrap, print} = require('../util/stri
14
14
  class TranscludeToken extends Token {
15
15
  type = 'template';
16
16
  modifier = '';
17
- /** @type {Set<string>} */ #keys = new Set();
18
17
  /** @type {Record<string, Set<ParameterToken>>} */ #args = {};
18
+ /** @type {Set<string>} */ #keys = new Set();
19
+
20
+ /** 是否存在重复参数 */
21
+ get duplication() {
22
+ return this.isTemplate() && Boolean(this.hasDuplicatedArgs());
23
+ }
19
24
 
20
25
  /**
21
26
  * 设置引用修饰符
@@ -69,7 +74,7 @@ class TranscludeToken extends Token {
69
74
  token = new SyntaxToken(magicWord, pattern, 'magic-word-name', config, accum, {
70
75
  'Stage-1': ':', '!ExtToken': '',
71
76
  });
72
- this.appendChild(token);
77
+ this.insertAt(token);
73
78
  if (arg.length > 0) {
74
79
  parts.unshift([arg.join(':')]);
75
80
  }
@@ -83,7 +88,7 @@ class TranscludeToken extends Token {
83
88
  const invoke = new AtomToken(part.join('='), `invoke-${
84
89
  i ? 'function' : 'module'
85
90
  }`, config, accum, {'Stage-1': ':', '!ExtToken': ''});
86
- this.appendChild(invoke);
91
+ this.insertAt(invoke);
87
92
  }
88
93
  this.getAttribute('protectChildren')('1:3');
89
94
  }
@@ -95,9 +100,8 @@ class TranscludeToken extends Token {
95
100
  accum.pop();
96
101
  throw new SyntaxError(`非法的模板名称:${name}`);
97
102
  }
98
- this.setAttribute('name', this.normalizeTitle(name, 10, true).title);
99
103
  const token = new AtomToken(title, 'template-name', config, accum, {'Stage-2': ':', '!HeadingToken': ''});
100
- this.appendChild(token);
104
+ this.insertAt(token);
101
105
  }
102
106
  const templateLike = this.isTemplate();
103
107
  let i = 1;
@@ -110,85 +114,11 @@ class TranscludeToken extends Token {
110
114
  part.unshift(i);
111
115
  i++;
112
116
  }
113
- this.appendChild(new ParameterToken(...part, config, accum));
117
+ this.insertAt(new ParameterToken(...part, config, accum));
114
118
  }
115
119
  this.getAttribute('protectChildren')(0);
116
120
  }
117
121
 
118
- /** @override */
119
- cloneNode() {
120
- const [first, ...cloned] = this.cloneChildNodes(),
121
- config = this.getAttribute('config');
122
- return Parser.run(() => {
123
- const token = new TranscludeToken(this.type === 'template' ? '' : first.text(), [], config);
124
- token.setModifier(this.modifier);
125
- token.firstChild.safeReplaceWith(first);
126
- token.afterBuild();
127
- token.append(...cloned);
128
- return token;
129
- });
130
- }
131
-
132
- /** @override */
133
- afterBuild() {
134
- if (this.name.includes('\0')) {
135
- this.setAttribute('name', text(this.getAttribute('buildFromStr')(this.name)));
136
- }
137
- if (this.isTemplate()) {
138
- /**
139
- * 当事件bubble到`parameter`时,将`oldKey`和`newKey`保存进AstEventData。
140
- * 当继续bubble到`template`时,处理并删除`oldKey`和`newKey`。
141
- * @type {AstListener}
142
- */
143
- const transcludeListener = (e, data) => {
144
- const {prevTarget} = e,
145
- {oldKey, newKey} = data ?? {};
146
- if (typeof oldKey === 'string') {
147
- delete data.oldKey;
148
- delete data.newKey;
149
- }
150
- if (prevTarget === this.firstChild && this.type === 'template') {
151
- this.setAttribute('name', this.normalizeTitle(prevTarget.text(), 10).title);
152
- } else if (oldKey !== newKey && prevTarget instanceof ParameterToken) {
153
- const oldArgs = this.getArgs(oldKey, false, false);
154
- oldArgs.delete(prevTarget);
155
- this.getArgs(newKey, false, false).add(prevTarget);
156
- this.#keys.add(newKey);
157
- if (oldArgs.size === 0) {
158
- this.#keys.delete(oldKey);
159
- }
160
- }
161
- };
162
- this.addEventListener(['remove', 'insert', 'replace', 'text'], transcludeListener);
163
- }
164
- return this;
165
- }
166
-
167
- /** 替换引用 */
168
- subst() {
169
- this.setModifier('subst');
170
- }
171
-
172
- /** 安全的替换引用 */
173
- safesubst() {
174
- this.setModifier('safesubst');
175
- }
176
-
177
- /**
178
- * @override
179
- * @template {string} T
180
- * @param {T} key 属性键
181
- * @returns {TokenAttribute<T>}
182
- */
183
- getAttribute(key) {
184
- if (key === 'args') {
185
- return {...this.#args};
186
- } else if (key === 'keys') {
187
- return !Parser.debugging && externalUse('getAttribute') ? new Set(this.#keys) : this.#keys;
188
- }
189
- return super.getAttribute(key);
190
- }
191
-
192
122
  /**
193
123
  * @override
194
124
  * @param {string} selector
@@ -249,20 +179,6 @@ class TranscludeToken extends Token {
249
179
  return this.type === 'template' || this.type === 'magic-word' && this.name === 'invoke';
250
180
  }
251
181
 
252
- /**
253
- * @override
254
- * @returns {string}
255
- * @complexity `n`
256
- */
257
- text() {
258
- const {childNodes, firstChild, modifier} = this;
259
- return `{{${modifier}${modifier && ':'}${
260
- this.type === 'magic-word'
261
- ? `${firstChild.text()}${childNodes.length > 1 ? ':' : ''}${text(childNodes.slice(1), '|')}`
262
- : super.text('|')
263
- }}}`;
264
- }
265
-
266
182
  /**
267
183
  * 处理匿名参数更改
268
184
  * @param {number|ParameterToken} addedToken 新增的参数
@@ -291,25 +207,6 @@ class TranscludeToken extends Token {
291
207
  }
292
208
  }
293
209
 
294
- /**
295
- * @override
296
- * @param {number} i 移除位置
297
- * @complexity `n`
298
- */
299
- removeAt(i) {
300
- const /** @type {ParameterToken} */ token = super.removeAt(i);
301
- if (token.anon) {
302
- this.#handleAnonArgChange(Number(token.name));
303
- } else {
304
- const args = this.getArgs(token.name, false, false);
305
- args.delete(token);
306
- if (args.size === 0) {
307
- this.#keys.delete(token.name);
308
- }
309
- }
310
- return token;
311
- }
312
-
313
210
  /**
314
211
  * @override
315
212
  * @param {ParameterToken} token 待插入的子节点
@@ -357,8 +254,10 @@ class TranscludeToken extends Token {
357
254
  }
358
255
  copy ||= !Parser.debugging && externalUse('getArgs');
359
256
  const keyStr = String(key).trim();
360
- let args = this.#args[keyStr];
361
- if (!args) {
257
+ let args;
258
+ if (Object.hasOwn(this.#args, keyStr)) {
259
+ args = this.#args[keyStr];
260
+ } else {
362
261
  args = new Set(this.getAllArgs().filter(({name}) => keyStr === name));
363
262
  this.#args[keyStr] = args;
364
263
  }
@@ -370,6 +269,135 @@ class TranscludeToken extends Token {
370
269
  return args;
371
270
  }
372
271
 
272
+ /**
273
+ * 获取重名参数
274
+ * @complexity `n`
275
+ * @returns {[string, Set<ParameterToken>][]}
276
+ * @throws `Error` 仅用于模板
277
+ */
278
+ getDuplicatedArgs() {
279
+ if (this.isTemplate()) {
280
+ return Object.entries(this.#args).filter(([, {size}]) => size > 1)
281
+ .map(([key, args]) => [key, new Set(args)]);
282
+ }
283
+ throw new Error(`${this.constructor.name}.getDuplicatedArgs 方法仅供模板使用!`);
284
+ }
285
+
286
+ /** @override */
287
+ cloneNode() {
288
+ const [first, ...cloned] = this.cloneChildNodes(),
289
+ config = this.getAttribute('config');
290
+ return Parser.run(() => {
291
+ const token = new TranscludeToken(this.type === 'template' ? '' : first.text(), [], config);
292
+ token.setModifier(this.modifier);
293
+ token.firstChild.safeReplaceWith(first);
294
+ token.afterBuild();
295
+ token.append(...cloned);
296
+ return token;
297
+ });
298
+ }
299
+
300
+ /** @override */
301
+ afterBuild() {
302
+ if (this.type === 'template') {
303
+ this.setAttribute('name', this.normalizeTitle(this.firstChild.text(), 10).title);
304
+ }
305
+ if (this.isTemplate()) {
306
+ /**
307
+ * 当事件bubble到`parameter`时,将`oldKey`和`newKey`保存进AstEventData。
308
+ * 当继续bubble到`template`时,处理并删除`oldKey`和`newKey`。
309
+ * @type {AstListener}
310
+ */
311
+ const transcludeListener = (e, data) => {
312
+ const {prevTarget} = e,
313
+ {oldKey, newKey} = data ?? {};
314
+ if (typeof oldKey === 'string') {
315
+ delete data.oldKey;
316
+ delete data.newKey;
317
+ }
318
+ if (prevTarget === this.firstChild && this.type === 'template') {
319
+ this.setAttribute('name', this.normalizeTitle(prevTarget.text(), 10).title);
320
+ } else if (oldKey !== newKey && prevTarget instanceof ParameterToken) {
321
+ const oldArgs = this.getArgs(oldKey, false, false);
322
+ oldArgs.delete(prevTarget);
323
+ this.getArgs(newKey, false, false).add(prevTarget);
324
+ this.#keys.add(newKey);
325
+ if (oldArgs.size === 0) {
326
+ this.#keys.delete(oldKey);
327
+ }
328
+ }
329
+ };
330
+ this.addEventListener(['remove', 'insert', 'replace', 'text'], transcludeListener);
331
+ }
332
+ return this;
333
+ }
334
+
335
+ /** 替换引用 */
336
+ subst() {
337
+ this.setModifier('subst');
338
+ }
339
+
340
+ /** 安全的替换引用 */
341
+ safesubst() {
342
+ this.setModifier('safesubst');
343
+ }
344
+
345
+ /**
346
+ * @override
347
+ * @param {PropertyKey} key 属性键
348
+ */
349
+ hasAttribute(key) {
350
+ return key === 'keys' || super.hasAttribute(key);
351
+ }
352
+
353
+ /**
354
+ * @override
355
+ * @template {string} T
356
+ * @param {T} key 属性键
357
+ * @returns {TokenAttribute<T>}
358
+ */
359
+ getAttribute(key) {
360
+ if (key === 'args') {
361
+ return {...this.#args};
362
+ } else if (key === 'keys') {
363
+ return !Parser.debugging && externalUse('getAttribute') ? new Set(this.#keys) : this.#keys;
364
+ }
365
+ return super.getAttribute(key);
366
+ }
367
+
368
+ /**
369
+ * @override
370
+ * @returns {string}
371
+ * @complexity `n`
372
+ */
373
+ text() {
374
+ const {childNodes, firstChild, modifier} = this;
375
+ return `{{${modifier}${modifier && ':'}${
376
+ this.type === 'magic-word'
377
+ ? `${firstChild.text()}${childNodes.length > 1 ? ':' : ''}${text(childNodes.slice(1), '|')}`
378
+ : super.text('|')
379
+ }}}`;
380
+ }
381
+
382
+ /**
383
+ * @override
384
+ * @param {number} i 移除位置
385
+ * @complexity `n`
386
+ */
387
+ removeAt(i) {
388
+ const /** @type {ParameterToken} */ token = super.removeAt(i);
389
+ if (token.anon) {
390
+ this.#handleAnonArgChange(Number(token.name));
391
+ } else {
392
+ const args = this.getArgs(token.name, false, false);
393
+ args.delete(token);
394
+ if (args.size === 0) {
395
+ this.#keys.delete(token.name);
396
+ }
397
+ }
398
+ return token;
399
+ }
400
+
373
401
  /**
374
402
  * 是否具有某参数
375
403
  * @param {string|number} key 参数名
@@ -431,7 +459,7 @@ class TranscludeToken extends Token {
431
459
  * 获取生效的参数值
432
460
  * @template {string|number|undefined} T
433
461
  * @param {T} key 参数名
434
- * @returns {T extends undefined ? Object<string, string> : string}
462
+ * @returns {T extends undefined ? Record<string, string> : string}
435
463
  * @complexity `n`
436
464
  */
437
465
  getValue(key) {
@@ -452,13 +480,13 @@ class TranscludeToken extends Token {
452
480
  const templateLike = this.isTemplate(),
453
481
  wikitext = `{{${templateLike ? ':T|' : 'lc:'}${val}}}`,
454
482
  root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
455
- {childNodes: {length}, firstChild: transclude} = root,
483
+ {length, firstChild: transclude} = root,
456
484
  /** @type {Token & {lastChild: ParameterToken}} */
457
- {type, name, childNodes: {length: transcludeLength}, lastChild} = transclude,
485
+ {type, name, length: transcludeLength, lastChild} = transclude,
458
486
  targetType = templateLike ? 'template' : 'magic-word',
459
487
  targetName = templateLike ? 'T' : 'lc';
460
488
  if (length === 1 && type === targetType && name === targetName && transcludeLength === 2 && lastChild.anon) {
461
- return this.appendChild(lastChild);
489
+ return this.insertAt(lastChild);
462
490
  }
463
491
  throw new SyntaxError(`非法的匿名参数:${noWrap(val)}`);
464
492
  }
@@ -485,12 +513,12 @@ class TranscludeToken extends Token {
485
513
  }
486
514
  const wikitext = `{{:T|${key}=${value}}}`,
487
515
  root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
488
- {childNodes: {length}, firstChild: template} = root,
489
- {type, name, childNodes: {length: templateLength}, lastChild: parameter} = template;
516
+ {length, firstChild: template} = root,
517
+ {type, name, length: templateLength, lastChild: parameter} = template;
490
518
  if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2 || parameter.name !== key) {
491
519
  throw new SyntaxError(`非法的命名参数:${key}=${noWrap(value)}`);
492
520
  }
493
- this.appendChild(parameter);
521
+ this.insertAt(parameter);
494
522
  }
495
523
 
496
524
  /**
@@ -520,7 +548,7 @@ class TranscludeToken extends Token {
520
548
  this.typeError('replaceTemplate', 'String');
521
549
  }
522
550
  const root = Parser.parse(`{{${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
523
- {childNodes: {length}, firstChild: template} = root;
551
+ {length, firstChild: template} = root;
524
552
  if (length !== 1 || template.type !== 'template' || template.childNodes.length !== 1) {
525
553
  throw new SyntaxError(`非法的模板名称:${title}`);
526
554
  }
@@ -540,15 +568,15 @@ class TranscludeToken extends Token {
540
568
  this.typeError('replaceModule', 'String');
541
569
  }
542
570
  const root = Parser.parse(`{{#invoke:${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
543
- {childNodes: {length}, firstChild: invoke} = root,
544
- {type, name, childNodes: {length: invokeLength}, lastChild} = invoke;
571
+ {length, firstChild: invoke} = root,
572
+ {type, name, length: invokeLength, lastChild} = invoke;
545
573
  if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 2) {
546
574
  throw new SyntaxError(`非法的模块名称:${title}`);
547
575
  } else if (this.childNodes.length > 1) {
548
576
  this.childNodes[1].replaceChildren(...lastChild.childNodes);
549
577
  } else {
550
578
  invoke.destroy(true);
551
- this.appendChild(lastChild);
579
+ this.insertAt(lastChild);
552
580
  }
553
581
  }
554
582
 
@@ -570,15 +598,15 @@ class TranscludeToken extends Token {
570
598
  const root = Parser.parse(
571
599
  `{{#invoke:M|${func}}}`, this.getAttribute('include'), 2, this.getAttribute('config'),
572
600
  ),
573
- {childNodes: {length}, firstChild: invoke} = root,
574
- {type, name, childNodes: {length: invokeLength}, lastChild} = invoke;
601
+ {length, firstChild: invoke} = root,
602
+ {type, name, length: invokeLength, lastChild} = invoke;
575
603
  if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 3) {
576
604
  throw new SyntaxError(`非法的模块函数名:${func}`);
577
605
  } else if (this.childNodes.length > 2) {
578
606
  this.childNodes[2].replaceChildren(...lastChild.childNodes);
579
607
  } else {
580
608
  invoke.destroy(true);
581
- this.appendChild(lastChild);
609
+ this.insertAt(lastChild);
582
610
  }
583
611
  }
584
612
 
@@ -594,19 +622,6 @@ class TranscludeToken extends Token {
594
622
  throw new Error(`${this.constructor.name}.hasDuplicatedArgs 方法仅供模板使用!`);
595
623
  }
596
624
 
597
- /**
598
- * 获取重名参数
599
- * @complexity `n`
600
- * @returns {[string, Set<ParameterToken>][]}
601
- * @throws `Error` 仅用于模板
602
- */
603
- getDuplicatedArgs() {
604
- if (this.isTemplate()) {
605
- return Object.entries(this.#args).filter(([, {size}]) => size > 1).map(([key, args]) => [key, new Set(args)]);
606
- }
607
- throw new Error(`${this.constructor.name}.getDuplicatedArgs 方法仅供模板使用!`);
608
- }
609
-
610
625
  /**
611
626
  * 修复重名参数:
612
627
  * `aggressive = false`时只移除空参数和全同参数,优先保留匿名参数,否则将所有匿名参数更改为命名。
@@ -627,7 +642,7 @@ class TranscludeToken extends Token {
627
642
  const /** @type {Record<string, ParameterToken[]>} */ values = {};
628
643
  for (const arg of args) {
629
644
  const val = arg.getValue().trim();
630
- if (val in values) {
645
+ if (Object.hasOwn(values, val)) {
631
646
  values[val].push(arg);
632
647
  } else {
633
648
  values[val] = [arg];