wikiparser-node 0.8.0 → 0.8.1-m

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 (77) hide show
  1. package/config/minimum.json +142 -0
  2. package/index.js +11 -253
  3. package/lib/element.js +7 -481
  4. package/lib/node.js +6 -552
  5. package/lib/text.js +70 -110
  6. package/lib/title.js +0 -21
  7. package/mixin/hidden.js +0 -3
  8. package/package.json +5 -5
  9. package/parser/brackets.js +0 -1
  10. package/parser/commentAndExt.js +3 -4
  11. package/parser/converter.js +0 -1
  12. package/parser/externalLinks.js +0 -1
  13. package/parser/hrAndDoubleUnderscore.js +0 -1
  14. package/parser/html.js +0 -1
  15. package/parser/links.js +4 -5
  16. package/parser/list.js +0 -1
  17. package/parser/magicLinks.js +4 -5
  18. package/parser/quotes.js +1 -2
  19. package/parser/table.js +0 -1
  20. package/src/arg.js +2 -116
  21. package/src/atom/hidden.js +0 -2
  22. package/src/atom/index.js +0 -17
  23. package/src/attribute.js +15 -182
  24. package/src/attributes.js +4 -308
  25. package/src/converter.js +2 -108
  26. package/src/converterFlags.js +0 -187
  27. package/src/converterRule.js +1 -184
  28. package/src/extLink.js +1 -120
  29. package/src/gallery.js +6 -57
  30. package/src/hasNowiki/index.js +0 -12
  31. package/src/hasNowiki/pre.js +0 -12
  32. package/src/heading.js +4 -55
  33. package/src/html.js +3 -118
  34. package/src/imageParameter.js +19 -189
  35. package/src/imagemap.js +1 -60
  36. package/src/imagemapLink.js +1 -13
  37. package/src/index.js +3 -529
  38. package/src/link/category.js +1 -37
  39. package/src/link/file.js +2 -159
  40. package/src/link/galleryImage.js +1 -59
  41. package/src/link/index.js +1 -259
  42. package/src/magicLink.js +9 -90
  43. package/src/nested/choose.js +0 -1
  44. package/src/nested/combobox.js +0 -1
  45. package/src/nested/index.js +3 -30
  46. package/src/nested/references.js +0 -1
  47. package/src/nowiki/comment.js +1 -25
  48. package/src/nowiki/dd.js +1 -47
  49. package/src/nowiki/doubleUnderscore.js +1 -31
  50. package/src/nowiki/hr.js +1 -20
  51. package/src/nowiki/index.js +1 -23
  52. package/src/nowiki/list.js +2 -5
  53. package/src/nowiki/noinclude.js +0 -14
  54. package/src/nowiki/quote.js +2 -16
  55. package/src/onlyinclude.js +1 -26
  56. package/src/paramTag/index.js +1 -24
  57. package/src/paramTag/inputbox.js +1 -4
  58. package/src/parameter.js +6 -148
  59. package/src/syntax.js +0 -68
  60. package/src/table/index.js +2 -940
  61. package/src/table/td.js +5 -225
  62. package/src/table/tr.js +2 -247
  63. package/src/tagPair/ext.js +3 -24
  64. package/src/tagPair/include.js +0 -24
  65. package/src/tagPair/index.js +2 -51
  66. package/src/transclude.js +20 -519
  67. package/util/string.js +1 -48
  68. package/README.md +0 -39
  69. package/lib/ranges.js +0 -130
  70. package/mixin/attributeParent.js +0 -117
  71. package/mixin/fixedToken.js +0 -40
  72. package/mixin/singleLine.js +0 -31
  73. package/mixin/sol.js +0 -65
  74. package/parser/selector.js +0 -177
  75. package/src/charinsert.js +0 -97
  76. package/tool/index.js +0 -1202
  77. package/util/debug.js +0 -73
package/src/transclude.js CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {removeComment, escapeRegExp, text, noWrap, print} = require('../util/string'),
4
- {externalUse} = require('../util/debug'),
3
+ const {removeComment, text} = require('../util/string'),
5
4
  {generateForChild} = require('../util/lint'),
6
5
  Parser = require('..'),
7
6
  Token = require('.'),
@@ -17,32 +16,23 @@ class TranscludeToken extends Token {
17
16
  type = 'template';
18
17
  modifier = '';
19
18
  /** @type {Record<string, Set<ParameterToken>>} */ #args = {};
20
- /** @type {Set<string>} */ #keys = new Set();
21
19
  #fragment = '';
22
20
  #valid = true;
23
21
 
24
- /** 是否存在重复参数 */
25
- get duplication() {
26
- return this.isTemplate() && Boolean(this.hasDuplicatedArgs());
27
- }
28
-
29
22
  /**
30
23
  * 设置引用修饰符
31
24
  * @param {string} modifier 引用修饰符
32
25
  * @complexity `n`
33
26
  */
34
27
  setModifier(modifier = '') {
35
- if (typeof modifier !== 'string') {
36
- this.typeError('setModifier', 'String');
28
+ if (/\s$/u.test(modifier)) {
29
+ return false;
37
30
  }
38
31
  const {parserFunction: [,, raw, subst]} = this.getAttribute('config'),
39
- lcModifier = modifier.trimStart().toLowerCase(),
32
+ lcModifier = modifier.trim().toLowerCase(),
40
33
  isRaw = raw.includes(lcModifier),
41
- isSubst = subst.includes(lcModifier),
42
- wasRaw = raw.includes(this.modifier.trimStart().toLowerCase());
43
- if (wasRaw && isRaw || !wasRaw && (isSubst || modifier === '')
44
- || (Parser.running || this.length > 1) && (isRaw || isSubst || modifier === '')
45
- ) {
34
+ isSubst = subst.includes(lcModifier);
35
+ if (isRaw || isSubst || modifier === '') {
46
36
  this.setAttribute('modifier', modifier);
47
37
  return Boolean(modifier);
48
38
  }
@@ -58,10 +48,8 @@ class TranscludeToken extends Token {
58
48
  */
59
49
  constructor(title, parts, config = Parser.getConfig(), accum = []) {
60
50
  super(undefined, config, true, accum, {
61
- AtomToken: 0, SyntaxToken: 0, ParameterToken: '1:',
62
51
  });
63
52
  const {parserFunction: [insensitive, sensitive, raw]} = config;
64
- this.seal('modifier');
65
53
  if (title.includes(':')) {
66
54
  const [modifier, ...arg] = title.split(':');
67
55
  if (this.setModifier(modifier)) {
@@ -71,21 +59,19 @@ class TranscludeToken extends Token {
71
59
  if (title.includes(':') || parts.length === 0 && !raw.includes(this.modifier.toLowerCase())) {
72
60
  const [magicWord, ...arg] = title.split(':'),
73
61
  cleaned = removeComment(magicWord),
74
- name = cleaned[arg.length > 0 ? 'trimStart' : 'trim'](),
62
+ name = cleaned.trim(),
75
63
  isSensitive = sensitive.includes(name),
76
64
  canonicalCame = insensitive[name.toLowerCase()];
77
- if (isSensitive || canonicalCame) {
65
+ if (!(arg.length > 0 && /\s$/u.test(cleaned)) && (isSensitive || canonicalCame)) {
78
66
  this.setAttribute('name', canonicalCame || name.toLowerCase()).type = 'magic-word';
79
67
  const pattern = new RegExp(`^\\s*${name}\\s*$`, isSensitive ? 'u' : 'iu'),
80
68
  token = new SyntaxToken(magicWord, pattern, 'magic-word-name', config, accum, {
81
- 'Stage-1': ':', '!ExtToken': '',
82
69
  });
83
70
  this.insertAt(token);
84
71
  if (arg.length > 0) {
85
72
  parts.unshift([arg.join(':')]);
86
73
  }
87
74
  if (this.name === 'invoke') {
88
- this.setAttribute('acceptable', {SyntaxToken: 0, AtomToken: '1:3', ParameterToken: '3:'});
89
75
  for (let i = 0; i < 2; i++) {
90
76
  const part = parts.shift();
91
77
  if (!part) {
@@ -94,11 +80,9 @@ class TranscludeToken extends Token {
94
80
  const invoke = new AtomToken(part.join('='), `invoke-${
95
81
  i ? 'function' : 'module'
96
82
  }`, config, accum, {
97
- 'Stage-1': ':', '!ExtToken': '',
98
83
  });
99
84
  this.insertAt(invoke);
100
85
  }
101
- this.getAttribute('protectChildren')('1:3');
102
86
  }
103
87
  }
104
88
  }
@@ -106,10 +90,9 @@ class TranscludeToken extends Token {
106
90
  const name = removeComment(title).split('#')[0].trim();
107
91
  if (!name || /\0\d+[eh!+-]\x7F|[<>[\]{}\n]|%[\da-f]{2}/u.test(name)) {
108
92
  accum.pop();
109
- throw new SyntaxError(`非法的模板名称:${noWrap(name)}`);
93
+ throw new SyntaxError(`非法的模板名称:${name}`);
110
94
  }
111
95
  const token = new AtomToken(title, 'template-name', config, accum, {
112
- 'Stage-2': ':', '!HeadingToken': '',
113
96
  });
114
97
  this.insertAt(token);
115
98
  }
@@ -127,7 +110,6 @@ class TranscludeToken extends Token {
127
110
  }
128
111
  this.insertAt(new ParameterToken(...part, config, accum));
129
112
  }
130
- this.getAttribute('protectChildren')(0);
131
113
  }
132
114
 
133
115
  /** @override */
@@ -135,52 +117,15 @@ class TranscludeToken extends Token {
135
117
  if (this.isTemplate()) {
136
118
  const isTemplate = this.type === 'template',
137
119
  titleObj = this.normalizeTitle(this.childNodes[isTemplate ? 0 : 1].text(), isTemplate ? 10 : 828);
138
- this.setAttribute(isTemplate ? 'name' : 'module', titleObj.title);
139
120
  this.#fragment = titleObj.fragment;
140
121
  this.#valid = titleObj.valid;
141
-
142
- /**
143
- * 当事件bubble到`parameter`时,将`oldKey`和`newKey`保存进AstEventData。
144
- * 当继续bubble到`template`时,处理并删除`oldKey`和`newKey`。
145
- * @type {AstListener}
146
- */
147
- const transcludeListener = (e, data) => {
148
- const {prevTarget} = e,
149
- {oldKey, newKey} = data ?? {};
150
- if (typeof oldKey === 'string') {
151
- delete data.oldKey;
152
- delete data.newKey;
153
- }
154
- if (prevTarget === this.firstChild && isTemplate
155
- || prevTarget === this.childNodes[1] && !isTemplate && this.name === 'invoke'
156
- ) {
157
- const name = prevTarget.text(),
158
- {title, fragment, valid} = this.normalizeTitle(name, 10);
159
- this.setAttribute(isTemplate ? 'name' : 'module', title);
160
- this.#fragment = fragment;
161
- this.#valid = valid;
162
- } else if (oldKey !== newKey && prevTarget instanceof ParameterToken) {
163
- const oldArgs = this.getArgs(oldKey, false, false);
164
- oldArgs.delete(prevTarget);
165
- this.getArgs(newKey, false, false).add(prevTarget);
166
- this.#keys.add(newKey);
167
- if (oldArgs.size === 0) {
168
- this.#keys.delete(oldKey);
169
- }
170
- }
171
- };
172
- this.addEventListener(['remove', 'insert', 'replace', 'text'], transcludeListener);
173
122
  }
174
123
  }
175
124
 
176
125
  /**
177
126
  * @override
178
- * @param {string} selector
179
127
  */
180
128
  toString(selector) {
181
- if (selector && this.matches(selector)) {
182
- return '';
183
- }
184
129
  const {childNodes, firstChild, modifier} = this;
185
130
  return `{{${modifier}${modifier && ':'}${
186
131
  this.type === 'magic-word'
@@ -195,12 +140,14 @@ class TranscludeToken extends Token {
195
140
  * @complexity `n`
196
141
  */
197
142
  text() {
198
- const {childNodes, firstChild, modifier} = this;
199
- return `{{${modifier}${modifier && ':'}${
200
- this.type === 'magic-word'
201
- ? `${firstChild.text()}${childNodes.length > 1 ? ':' : ''}${text(childNodes.slice(1), '|')}`
202
- : super.text('|')
203
- }}}`;
143
+ const {childNodes, firstChild, modifier, type, name} = this;
144
+ return type === 'magic-word' && name === 'vardefine'
145
+ ? ''
146
+ : `{{${modifier}${modifier && ':'}${
147
+ this.type === 'magic-word'
148
+ ? `${firstChild.text()}${childNodes.length > 1 ? ':' : ''}${text(childNodes.slice(1), '|')}`
149
+ : super.text('|')
150
+ }}}`;
204
151
  }
205
152
 
206
153
  /** @override */
@@ -213,16 +160,6 @@ class TranscludeToken extends Token {
213
160
  return 1;
214
161
  }
215
162
 
216
- /** @override */
217
- print() {
218
- const {childNodes, firstChild, modifier} = this;
219
- return `<span class="wpb-${this.type}">{{${modifier}${modifier && ':'}${
220
- this.type === 'magic-word'
221
- ? `${firstChild.print()}${childNodes.length > 1 ? ':' : ''}${print(childNodes.slice(1), {sep: '|'})}`
222
- : print(childNodes, {sep: '|'})
223
- }}}</span>`;
224
- }
225
-
226
163
  /**
227
164
  * @override
228
165
  * @param {number} start 起始位置
@@ -263,23 +200,13 @@ class TranscludeToken extends Token {
263
200
  */
264
201
  #handleAnonArgChange(addedToken) {
265
202
  const args = this.getAnonArgs(),
266
- added = typeof addedToken !== 'number',
267
- maxAnon = String(args.length + (added ? 0 : 1));
268
- if (added) {
269
- this.#keys.add(maxAnon);
270
- } else if (!this.hasArg(maxAnon, true)) {
271
- this.#keys.delete(maxAnon);
272
- }
273
- const j = added ? args.indexOf(addedToken) : addedToken - 1;
203
+ j = args.indexOf(addedToken);
274
204
  for (let i = j; i < args.length; i++) {
275
205
  const token = args[i],
276
206
  {name} = token,
277
207
  newName = String(i + 1);
278
208
  if (name !== newName) {
279
209
  this.getArgs(newName, false, false).add(token.setAttribute('name', newName));
280
- if (name) {
281
- this.getArgs(name, false, false).delete(token);
282
- }
283
210
  }
284
211
  }
285
212
  }
@@ -296,7 +223,6 @@ class TranscludeToken extends Token {
296
223
  this.#handleAnonArgChange(token);
297
224
  } else if (token.name) {
298
225
  this.getArgs(token.name, false, false).add(token);
299
- this.#keys.add(token.name);
300
226
  }
301
227
  return token;
302
228
  }
@@ -326,11 +252,7 @@ class TranscludeToken extends Token {
326
252
  * @complexity `n`
327
253
  */
328
254
  getArgs(key, exact, copy = true) {
329
- if (typeof key !== 'string' && typeof key !== 'number') {
330
- this.typeError('getArgs', 'String', 'Number');
331
- }
332
- copy ||= !Parser.debugging && externalUse('getArgs');
333
- const keyStr = String(key).trim();
255
+ const keyStr = String(key).replace(/^[ \t\n\0\v]+|([^ \t\n\0\v])[ \t\n\0\v]+$/gu, '$1');
334
256
  let args;
335
257
  if (Object.hasOwn(this.#args, keyStr)) {
336
258
  args = this.#args[keyStr];
@@ -338,11 +260,6 @@ class TranscludeToken extends Token {
338
260
  args = new Set(this.getAllArgs().filter(({name}) => keyStr === name));
339
261
  this.#args[keyStr] = args;
340
262
  }
341
- if (exact && !isNaN(keyStr)) {
342
- args = new Set([...args].filter(({anon}) => typeof key === 'number' === anon));
343
- } else if (copy) {
344
- args = new Set(args);
345
- }
346
263
  return args;
347
264
  }
348
265
 
@@ -350,14 +267,13 @@ class TranscludeToken extends Token {
350
267
  * 获取重名参数
351
268
  * @complexity `n`
352
269
  * @returns {[string, ParameterToken[]][]}
353
- * @throws `Error` 仅用于模板
354
270
  */
355
271
  getDuplicatedArgs() {
356
272
  if (this.isTemplate()) {
357
273
  return Object.entries(this.#args).filter(([, {size}]) => size > 1)
358
274
  .map(([key, args]) => [key, [...args]]);
359
275
  }
360
- throw new Error(`${this.constructor.name}.getDuplicatedArgs 方法仅供模板使用!`);
276
+ return [];
361
277
  }
362
278
 
363
279
  /**
@@ -404,421 +320,6 @@ class TranscludeToken extends Token {
404
320
  }
405
321
  return queue;
406
322
  }
407
-
408
- /** @override */
409
- cloneNode() {
410
- const [first, ...cloned] = this.cloneChildNodes(),
411
- config = this.getAttribute('config');
412
- return Parser.run(() => {
413
- const token = new TranscludeToken(this.type === 'template' ? '' : first.text(), [], config);
414
- token.setModifier(this.modifier);
415
- token.firstChild.safeReplaceWith(first);
416
- token.afterBuild();
417
- token.append(...cloned);
418
- return token;
419
- });
420
- }
421
-
422
- /** 替换引用 */
423
- subst() {
424
- this.setModifier('subst');
425
- }
426
-
427
- /** 安全的替换引用 */
428
- safesubst() {
429
- this.setModifier('safesubst');
430
- }
431
-
432
- /**
433
- * @override
434
- * @param {PropertyKey} key 属性键
435
- */
436
- hasAttribute(key) {
437
- return key === 'keys' || super.hasAttribute(key);
438
- }
439
-
440
- /**
441
- * @override
442
- * @template {string} T
443
- * @param {T} key 属性键
444
- * @returns {TokenAttribute<T>}
445
- */
446
- getAttribute(key) {
447
- if (key === 'args') {
448
- return {...this.#args};
449
- } else if (key === 'keys') {
450
- return !Parser.debugging && externalUse('getAttribute') ? new Set(this.#keys) : this.#keys;
451
- }
452
- return super.getAttribute(key);
453
- }
454
-
455
- /**
456
- * @override
457
- * @param {number} i 移除位置
458
- * @complexity `n`
459
- */
460
- removeAt(i) {
461
- const /** @type {ParameterToken} */ token = super.removeAt(i);
462
- if (token.anon) {
463
- this.#handleAnonArgChange(Number(token.name));
464
- } else {
465
- const args = this.getArgs(token.name, false, false);
466
- args.delete(token);
467
- if (args.size === 0) {
468
- this.#keys.delete(token.name);
469
- }
470
- }
471
- return token;
472
- }
473
-
474
- /**
475
- * 是否具有某参数
476
- * @param {string|number} key 参数名
477
- * @param {boolean} exact 是否匹配匿名性
478
- * @complexity `n`
479
- */
480
- hasArg(key, exact) {
481
- return this.getArgs(key, exact, false).size > 0;
482
- }
483
-
484
- /**
485
- * 获取生效的指定参数
486
- * @param {string|number} key 参数名
487
- * @param {boolean} exact 是否匹配匿名性
488
- * @complexity `n`
489
- */
490
- getArg(key, exact) {
491
- return [...this.getArgs(key, exact, false)].sort((a, b) => a.compareDocumentPosition(b)).at(-1);
492
- }
493
-
494
- /**
495
- * 移除指定参数
496
- * @param {string|number} key 参数名
497
- * @param {boolean} exact 是否匹配匿名性
498
- * @complexity `n`
499
- */
500
- removeArg(key, exact) {
501
- Parser.run(() => {
502
- for (const token of this.getArgs(key, exact, false)) {
503
- this.removeChild(token);
504
- }
505
- });
506
- }
507
-
508
- /**
509
- * 获取所有参数名
510
- * @complexity `n`
511
- */
512
- getKeys() {
513
- const args = this.getAllArgs();
514
- if (this.#keys.size === 0 && args.length > 0) {
515
- for (const {name} of args) {
516
- this.#keys.add(name);
517
- }
518
- }
519
- return [...this.#keys];
520
- }
521
-
522
- /**
523
- * 获取参数值
524
- * @param {string|number} key 参数名
525
- * @complexity `n`
526
- */
527
- getValues(key) {
528
- return [...this.getArgs(key, false, false)].map(token => token.getValue());
529
- }
530
-
531
- /**
532
- * 获取生效的参数值
533
- * @template {string|number|undefined} T
534
- * @param {T} key 参数名
535
- * @returns {T extends undefined ? Record<string, string> : string}
536
- * @complexity `n`
537
- */
538
- getValue(key) {
539
- return key === undefined
540
- ? Object.fromEntries(this.getKeys().map(k => [k, this.getValue(k)]))
541
- : this.getArg(key)?.getValue();
542
- }
543
-
544
- /**
545
- * 插入匿名参数
546
- * @param {string} val 参数值
547
- * @returns {ParameterToken}
548
- * @complexity `n`
549
- * @throws `SyntaxError` 非法的匿名参数
550
- */
551
- newAnonArg(val) {
552
- val = String(val);
553
- const templateLike = this.isTemplate(),
554
- wikitext = `{{${templateLike ? ':T|' : 'lc:'}${val}}}`,
555
- root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
556
- {length, firstChild: transclude} = root,
557
- /** @type {Token & {lastChild: ParameterToken}} */
558
- {type, name, length: transcludeLength, lastChild} = transclude,
559
- targetType = templateLike ? 'template' : 'magic-word',
560
- targetName = templateLike ? 'T' : 'lc';
561
- if (length === 1 && type === targetType && name === targetName && transcludeLength === 2 && lastChild.anon) {
562
- return this.insertAt(lastChild);
563
- }
564
- throw new SyntaxError(`非法的匿名参数:${noWrap(val)}`);
565
- }
566
-
567
- /**
568
- * 设置参数值
569
- * @param {string} key 参数名
570
- * @param {string} value 参数值
571
- * @complexity `n`
572
- * @throws `Error` 仅用于模板
573
- * @throws `SyntaxError` 非法的命名参数
574
- */
575
- setValue(key, value) {
576
- if (typeof key !== 'string') {
577
- this.typeError('setValue', 'String');
578
- } else if (!this.isTemplate()) {
579
- throw new Error(`${this.constructor.name}.setValue 方法仅供模板使用!`);
580
- }
581
- const token = this.getArg(key);
582
- value = String(value);
583
- if (token) {
584
- token.setValue(value);
585
- return;
586
- }
587
- const wikitext = `{{:T|${key}=${value}}}`,
588
- root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
589
- {length, firstChild: template} = root,
590
- {type, name, length: templateLength, lastChild: parameter} = template;
591
- if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2 || parameter.name !== key) {
592
- throw new SyntaxError(`非法的命名参数:${key}=${noWrap(value)}`);
593
- }
594
- this.insertAt(parameter);
595
- }
596
-
597
- /**
598
- * 将匿名参数改写为命名参数
599
- * @complexity `n`
600
- * @throws `Error` 仅用于模板
601
- */
602
- anonToNamed() {
603
- if (!this.isTemplate()) {
604
- throw new Error(`${this.constructor.name}.anonToNamed 方法仅供模板使用!`);
605
- }
606
- for (const token of this.getAnonArgs()) {
607
- token.firstChild.replaceChildren(token.name);
608
- }
609
- }
610
-
611
- /**
612
- * 替换模板名
613
- * @param {string} title 模板名
614
- * @throws `Error` 仅用于模板
615
- * @throws `SyntaxError` 非法的模板名称
616
- */
617
- replaceTemplate(title) {
618
- if (this.type === 'magic-word') {
619
- throw new Error(`${this.constructor.name}.replaceTemplate 方法仅用于更换模板!`);
620
- } else if (typeof title !== 'string') {
621
- this.typeError('replaceTemplate', 'String');
622
- }
623
- const root = Parser.parse(`{{${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
624
- {length, firstChild: template} = root;
625
- if (length !== 1 || template.type !== 'template' || template.length !== 1) {
626
- throw new SyntaxError(`非法的模板名称:${title}`);
627
- }
628
- this.firstChild.replaceChildren(...template.firstChild.childNodes);
629
- }
630
-
631
- /**
632
- * 替换模块名
633
- * @param {string} title 模块名
634
- * @throws `Error` 仅用于模块
635
- * @throws `SyntaxError` 非法的模块名称
636
- */
637
- replaceModule(title) {
638
- if (this.type !== 'magic-word' || this.name !== 'invoke') {
639
- throw new Error(`${this.constructor.name}.replaceModule 方法仅用于更换模块!`);
640
- } else if (typeof title !== 'string') {
641
- this.typeError('replaceModule', 'String');
642
- }
643
- const root = Parser.parse(`{{#invoke:${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
644
- {length, firstChild: invoke} = root,
645
- {type, name, length: invokeLength, lastChild} = invoke;
646
- if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 2) {
647
- throw new SyntaxError(`非法的模块名称:${title}`);
648
- } else if (this.length > 1) {
649
- this.childNodes[1].replaceChildren(...lastChild.childNodes);
650
- } else {
651
- invoke.destroy(true);
652
- this.insertAt(lastChild);
653
- }
654
- }
655
-
656
- /**
657
- * 替换模块函数
658
- * @param {string} func 模块函数名
659
- * @throws `Error` 仅用于模块
660
- * @throws `Error` 尚未指定模块名称
661
- * @throws `SyntaxError` 非法的模块函数名
662
- */
663
- replaceFunction(func) {
664
- if (this.type !== 'magic-word' || this.name !== 'invoke') {
665
- throw new Error(`${this.constructor.name}.replaceModule 方法仅用于更换模块!`);
666
- } else if (typeof func !== 'string') {
667
- this.typeError('replaceFunction', 'String');
668
- } else if (this.length < 2) {
669
- throw new Error('尚未指定模块名称!');
670
- }
671
- const root = Parser.parse(
672
- `{{#invoke:M|${func}}}`, this.getAttribute('include'), 2, this.getAttribute('config'),
673
- ),
674
- {length, firstChild: invoke} = root,
675
- {type, name, length: invokeLength, lastChild} = invoke;
676
- if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 3) {
677
- throw new SyntaxError(`非法的模块函数名:${func}`);
678
- } else if (this.length > 2) {
679
- this.childNodes[2].replaceChildren(...lastChild.childNodes);
680
- } else {
681
- invoke.destroy(true);
682
- this.insertAt(lastChild);
683
- }
684
- }
685
-
686
- /**
687
- * 是否存在重名参数
688
- * @complexity `n`
689
- * @throws `Error` 仅用于模板
690
- */
691
- hasDuplicatedArgs() {
692
- if (this.isTemplate()) {
693
- return this.getAllArgs().length - this.getKeys().length;
694
- }
695
- throw new Error(`${this.constructor.name}.hasDuplicatedArgs 方法仅供模板使用!`);
696
- }
697
-
698
- /**
699
- * 修复重名参数:
700
- * `aggressive = false`时只移除空参数和全同参数,优先保留匿名参数,否则将所有匿名参数更改为命名。
701
- * `aggressive = true`时还会尝试处理连续的以数字编号的参数。
702
- * @param {boolean} aggressive 是否使用有更大风险的修复手段
703
- * @complexity `n²`
704
- */
705
- fixDuplication(aggressive) {
706
- if (!this.hasDuplicatedArgs()) {
707
- return [];
708
- }
709
- const /** @type {string[]} */ duplicatedKeys = [];
710
- let {length: anonCount} = this.getAnonArgs();
711
- for (const [key, args] of this.getDuplicatedArgs()) {
712
- if (args.length <= 1) {
713
- continue;
714
- }
715
- const /** @type {Record<string, ParameterToken[]>} */ values = {};
716
- for (const arg of args) {
717
- const val = arg.getValue().trim();
718
- if (Object.hasOwn(values, val)) {
719
- values[val].push(arg);
720
- } else {
721
- values[val] = [arg];
722
- }
723
- }
724
- let noMoreAnon = anonCount === 0 || isNaN(key);
725
- const emptyArgs = values[''] ?? [],
726
- duplicatedArgs = Object.entries(values).filter(([val, {length}]) => val && length > 1)
727
- .flatMap(([, curArgs]) => {
728
- const anonIndex = noMoreAnon ? -1 : curArgs.findIndex(({anon}) => anon);
729
- if (anonIndex !== -1) {
730
- noMoreAnon = true;
731
- }
732
- curArgs.splice(anonIndex, 1);
733
- return curArgs;
734
- }),
735
- badArgs = [...emptyArgs, ...duplicatedArgs],
736
- index = noMoreAnon ? -1 : emptyArgs.findIndex(({anon}) => anon);
737
- if (badArgs.length === args.length) {
738
- badArgs.splice(index, 1);
739
- } else if (index !== -1) {
740
- this.anonToNamed();
741
- anonCount = 0;
742
- }
743
- for (const arg of badArgs) {
744
- arg.remove();
745
- }
746
- let remaining = args.length - badArgs.length;
747
- if (remaining === 1) {
748
- continue;
749
- } else if (aggressive && (anonCount ? /\D\d+$/u : /(?:^|\D)\d+$/u).test(key)) {
750
- let /** @type {number} */ last;
751
- const str = key.slice(0, -/(?<!\d)\d+$/u.exec(key)[0].length),
752
- regex = new RegExp(`^${escapeRegExp(str)}\\d+$`, 'u'),
753
- series = this.getAllArgs().filter(({name}) => regex.test(name)),
754
- ordered = series.every(({name}, i) => {
755
- const j = Number(name.slice(str.length)),
756
- cmp = j <= i + 1 && (i === 0 || j >= last || name === key);
757
- last = j;
758
- return cmp;
759
- });
760
- if (ordered) {
761
- for (let i = 0; i < series.length; i++) {
762
- const name = `${str}${i + 1}`,
763
- arg = series[i];
764
- if (arg.name !== name) {
765
- if (arg.name === key) {
766
- remaining--;
767
- }
768
- arg.rename(name, true);
769
- }
770
- }
771
- }
772
- }
773
- if (remaining > 1) {
774
- Parser.error(`${this.type === 'template'
775
- ? this.name
776
- : this.normalizeTitle(this.childNodes[1]?.text() ?? '', 828).title
777
- } 还留有 ${remaining} 个重复的 ${key} 参数:${[...this.getArgs(key)].map(arg => {
778
- const {top, left} = arg.getBoundingClientRect();
779
- return `第 ${top} 行第 ${left} 列`;
780
- }).join('、')}`);
781
- duplicatedKeys.push(key);
782
- continue;
783
- }
784
- }
785
- return duplicatedKeys;
786
- }
787
-
788
- /**
789
- * 转义模板内的表格
790
- * @returns {TranscludeToken}
791
- * @complexity `n`
792
- * @throws `Error` 转义失败
793
- */
794
- escapeTables() {
795
- const count = this.hasDuplicatedArgs();
796
- if (!/\n[^\S\n]*(?::+\s*)?\{\|[^\n]*\n\s*(?:\S[^\n]*\n\s*)*\|\}/u.test(this.text()) || !count) {
797
- return this;
798
- }
799
- const stripped = String(this).slice(2, -2),
800
- include = this.getAttribute('include'),
801
- config = this.getAttribute('config'),
802
- parsed = Parser.parse(stripped, include, 4, config);
803
- const TableToken = require('./table');
804
- for (const table of parsed.childNodes) {
805
- if (table instanceof TableToken) {
806
- table.escape();
807
- }
808
- }
809
- const {firstChild, childNodes} = Parser.parse(`{{${String(parsed)}}}`, include, 2, config);
810
- if (childNodes.length !== 1 || !(firstChild instanceof TranscludeToken)) {
811
- throw new Error('转义表格失败!');
812
- }
813
- const newCount = firstChild.hasDuplicatedArgs();
814
- if (newCount === count) {
815
- return this;
816
- }
817
- Parser.info(`共修复了 ${count - newCount} 个重复参数。`);
818
- this.safeReplaceWith(firstChild);
819
- return firstChild;
820
- }
821
323
  }
822
324
 
823
- Parser.classes.TranscludeToken = __filename;
824
325
  module.exports = TranscludeToken;