wikiparser-node 0.5.0 → 0.6.2-b

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 (70) hide show
  1. package/bundle/bundle.min.js +34 -0
  2. package/package.json +10 -13
  3. package/README.md +0 -39
  4. package/config/default.json +0 -769
  5. package/config/llwiki.json +0 -630
  6. package/config/moegirl.json +0 -727
  7. package/config/zhwiki.json +0 -1269
  8. package/index.js +0 -268
  9. package/lib/element.js +0 -639
  10. package/lib/node.js +0 -743
  11. package/lib/ranges.js +0 -130
  12. package/lib/text.js +0 -175
  13. package/lib/title.js +0 -69
  14. package/mixin/attributeParent.js +0 -113
  15. package/mixin/fixedToken.js +0 -40
  16. package/mixin/hidden.js +0 -19
  17. package/mixin/sol.js +0 -69
  18. package/parser/brackets.js +0 -112
  19. package/parser/commentAndExt.js +0 -63
  20. package/parser/converter.js +0 -45
  21. package/parser/externalLinks.js +0 -31
  22. package/parser/hrAndDoubleUnderscore.js +0 -36
  23. package/parser/html.js +0 -42
  24. package/parser/links.js +0 -97
  25. package/parser/list.js +0 -59
  26. package/parser/magicLinks.js +0 -41
  27. package/parser/quotes.js +0 -64
  28. package/parser/selector.js +0 -175
  29. package/parser/table.js +0 -112
  30. package/src/arg.js +0 -193
  31. package/src/atom/hidden.js +0 -13
  32. package/src/atom/index.js +0 -41
  33. package/src/attribute.js +0 -449
  34. package/src/converter.js +0 -165
  35. package/src/converterFlags.js +0 -269
  36. package/src/converterRule.js +0 -259
  37. package/src/extLink.js +0 -173
  38. package/src/gallery.js +0 -122
  39. package/src/heading.js +0 -119
  40. package/src/html.js +0 -230
  41. package/src/imageParameter.js +0 -260
  42. package/src/index.js +0 -744
  43. package/src/link/category.js +0 -53
  44. package/src/link/file.js +0 -285
  45. package/src/link/galleryImage.js +0 -61
  46. package/src/link/index.js +0 -325
  47. package/src/magicLink.js +0 -136
  48. package/src/nowiki/comment.js +0 -71
  49. package/src/nowiki/dd.js +0 -59
  50. package/src/nowiki/doubleUnderscore.js +0 -56
  51. package/src/nowiki/hr.js +0 -41
  52. package/src/nowiki/index.js +0 -44
  53. package/src/nowiki/list.js +0 -16
  54. package/src/nowiki/noinclude.js +0 -28
  55. package/src/nowiki/quote.js +0 -63
  56. package/src/onlyinclude.js +0 -59
  57. package/src/parameter.js +0 -200
  58. package/src/syntax.js +0 -83
  59. package/src/table/index.js +0 -980
  60. package/src/table/td.js +0 -314
  61. package/src/table/tr.js +0 -299
  62. package/src/tagPair/ext.js +0 -104
  63. package/src/tagPair/include.js +0 -60
  64. package/src/tagPair/index.js +0 -125
  65. package/src/transclude.js +0 -736
  66. package/tool/index.js +0 -918
  67. package/util/debug.js +0 -73
  68. package/util/diff.js +0 -76
  69. package/util/lint.js +0 -40
  70. package/util/string.js +0 -105
package/src/transclude.js DELETED
@@ -1,736 +0,0 @@
1
- 'use strict';
2
-
3
- const {removeComment, escapeRegExp, text, noWrap, print} = require('../util/string'),
4
- {externalUse} = require('../util/debug'),
5
- {generateForChild} = require('../util/lint'),
6
- Parser = require('..'),
7
- Token = require('.'),
8
- ParameterToken = require('./parameter');
9
-
10
- /**
11
- * 模板或魔术字
12
- * @classdesc `{childNodes: [AtomToken|SyntaxToken, ...ParameterToken]}`
13
- */
14
- class TranscludeToken extends Token {
15
- type = 'template';
16
- modifier = '';
17
- /** @type {Set<string>} */ #keys = new Set();
18
- /** @type {Record<string, Set<ParameterToken>>} */ #args = {};
19
-
20
- /**
21
- * 设置引用修饰符
22
- * @param {string} modifier 引用修饰符
23
- * @complexity `n`
24
- */
25
- setModifier(modifier = '') {
26
- if (typeof modifier !== 'string') {
27
- this.typeError('setModifier', 'String');
28
- }
29
- const {parserFunction: [,, raw, subst]} = this.getAttribute('config'),
30
- lcModifier = modifier.trim().toLowerCase(),
31
- isRaw = raw.includes(lcModifier),
32
- isSubst = subst.includes(lcModifier),
33
- wasRaw = raw.includes(this.modifier.trim().toLowerCase());
34
- if (wasRaw && isRaw || !wasRaw && (isSubst || modifier === '')
35
- || (Parser.running || this.childNodes.length > 1) && (isRaw || isSubst || modifier === '')
36
- ) {
37
- this.setAttribute('modifier', modifier);
38
- return Boolean(modifier);
39
- }
40
- return false;
41
- }
42
-
43
- /**
44
- * @param {string} title 模板标题或魔术字
45
- * @param {[string, string|undefined][]} parts 参数各部分
46
- * @param {accum} accum
47
- * @complexity `n`
48
- * @throws `SyntaxError` 非法的模板名称
49
- */
50
- constructor(title, parts, config = Parser.getConfig(), accum = []) {
51
- super(undefined, config, true, accum, {AtomToken: 0, SyntaxToken: 0, ParameterToken: '1:'});
52
- const AtomToken = require('./atom'),
53
- SyntaxToken = require('./syntax');
54
- const {parserFunction: [insensitive, sensitive, raw]} = config;
55
- this.seal('modifier');
56
- if (title.includes(':')) {
57
- const [modifier, ...arg] = title.split(':');
58
- if (this.setModifier(modifier)) {
59
- title = arg.join(':');
60
- }
61
- }
62
- if (title.includes(':') || parts.length === 0 && !raw.includes(this.modifier.toLowerCase())) {
63
- const [magicWord, ...arg] = title.split(':'),
64
- name = removeComment(magicWord),
65
- isSensitive = sensitive.includes(name);
66
- if (isSensitive || insensitive.includes(name.toLowerCase())) {
67
- this.setAttribute('name', name.toLowerCase().replace(/^#/u, '')).type = 'magic-word';
68
- const pattern = new RegExp(`^\\s*${name}\\s*$`, isSensitive ? 'u' : 'iu'),
69
- token = new SyntaxToken(magicWord, pattern, 'magic-word-name', config, accum, {
70
- 'Stage-1': ':', '!ExtToken': '',
71
- });
72
- this.appendChild(token);
73
- if (arg.length > 0) {
74
- parts.unshift([arg.join(':')]);
75
- }
76
- if (this.name === 'invoke') {
77
- this.setAttribute('acceptable', {SyntaxToken: 0, AtomToken: '1:3', ParameterToken: '3:'});
78
- for (let i = 0; i < 2; i++) {
79
- const part = parts.shift();
80
- if (!part) {
81
- break;
82
- }
83
- const invoke = new AtomToken(part.join('='), `invoke-${
84
- i ? 'function' : 'module'
85
- }`, config, accum, {'Stage-1': ':', '!ExtToken': ''});
86
- this.appendChild(invoke);
87
- }
88
- this.getAttribute('protectChildren')('1:3');
89
- }
90
- }
91
- }
92
- if (this.type === 'template') {
93
- const [name] = removeComment(title).split('#');
94
- if (/\0\d+[eh!+-]\x7F|[<>[\]{}]/u.test(name)) {
95
- accum.pop();
96
- throw new SyntaxError(`非法的模板名称:${name}`);
97
- }
98
- this.setAttribute('name', this.normalizeTitle(name, 10, true).title);
99
- const token = new AtomToken(title, 'template-name', config, accum, {'Stage-2': ':', '!HeadingToken': ''});
100
- this.appendChild(token);
101
- }
102
- const templateLike = this.isTemplate();
103
- let i = 1;
104
- for (const part of parts) {
105
- if (!templateLike) {
106
- part[0] = part.join('=');
107
- part.length = 1;
108
- }
109
- if (part.length === 1) {
110
- part.unshift(i);
111
- i++;
112
- }
113
- this.appendChild(new ParameterToken(...part, config, accum));
114
- }
115
- this.getAttribute('protectChildren')(0);
116
- }
117
-
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
- /**
193
- * @override
194
- * @param {string} selector
195
- */
196
- toString(selector) {
197
- if (selector && this.matches(selector)) {
198
- return '';
199
- }
200
- const {childNodes, firstChild, modifier} = this;
201
- return `{{${modifier}${modifier && ':'}${
202
- this.type === 'magic-word'
203
- ? `${String(firstChild)}${childNodes.length > 1 ? ':' : ''}${childNodes.slice(1).map(String).join('|')}`
204
- : super.toString(selector, '|')
205
- }}}`;
206
- }
207
-
208
- /** @override */
209
- getPadding() {
210
- return this.modifier ? this.modifier.length + 3 : 2;
211
- }
212
-
213
- /** @override */
214
- getGaps() {
215
- return 1;
216
- }
217
-
218
- /** @override */
219
- print() {
220
- const {childNodes, firstChild, modifier} = this;
221
- return `<span class="wpb-${this.type}">{{${modifier}${modifier && ':'}${
222
- this.type === 'magic-word'
223
- ? `${firstChild.print()}${childNodes.length > 1 ? ':' : ''}${print(childNodes.slice(1), {sep: '|'})}`
224
- : print(childNodes, {sep: '|'})
225
- }}}</span>`;
226
- }
227
-
228
- /**
229
- * @override
230
- * @param {number} start 起始位置
231
- */
232
- lint(start = 0) {
233
- const errors = super.lint(start);
234
- if (!this.isTemplate()) {
235
- return errors;
236
- }
237
- const duplicatedArgs = this.getDuplicatedArgs();
238
- if (duplicatedArgs.length > 0) {
239
- const rect = this.getRootNode().posFromIndex(start);
240
- errors.push(...duplicatedArgs.flatMap(([, args]) => [...args]).map(
241
- arg => generateForChild(arg, rect, '重复参数'),
242
- ));
243
- }
244
- return errors;
245
- }
246
-
247
- /** 是否是模板 */
248
- isTemplate() {
249
- return this.type === 'template' || this.type === 'magic-word' && this.name === 'invoke';
250
- }
251
-
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
- /**
267
- * 处理匿名参数更改
268
- * @param {number|ParameterToken} addedToken 新增的参数
269
- * @complexity `n`
270
- */
271
- #handleAnonArgChange(addedToken) {
272
- const args = this.getAnonArgs(),
273
- added = typeof addedToken !== 'number',
274
- maxAnon = String(args.length + (added ? 0 : 1));
275
- if (added) {
276
- this.#keys.add(maxAnon);
277
- } else if (!this.hasArg(maxAnon, true)) {
278
- this.#keys.delete(maxAnon);
279
- }
280
- const j = added ? args.indexOf(addedToken) : addedToken - 1;
281
- for (let i = j; i < args.length; i++) {
282
- const token = args[i],
283
- {name} = token,
284
- newName = String(i + 1);
285
- if (name !== newName) {
286
- this.getArgs(newName, false, false).add(token.setAttribute('name', newName));
287
- if (name) {
288
- this.getArgs(name, false, false).delete(token);
289
- }
290
- }
291
- }
292
- }
293
-
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
- /**
314
- * @override
315
- * @param {ParameterToken} token 待插入的子节点
316
- * @param {number} i 插入位置
317
- * @complexity `n`
318
- */
319
- insertAt(token, i = this.childNodes.length) {
320
- super.insertAt(token, i);
321
- if (token.anon) {
322
- this.#handleAnonArgChange(token);
323
- } else if (token.name) {
324
- this.getArgs(token.name, false, false).add(token);
325
- this.#keys.add(token.name);
326
- }
327
- return token;
328
- }
329
-
330
- /**
331
- * 获取所有参数
332
- * @returns {ParameterToken[]}
333
- * @complexity `n`
334
- */
335
- getAllArgs() {
336
- return this.childNodes.filter(child => child instanceof ParameterToken);
337
- }
338
-
339
- /**
340
- * 获取匿名参数
341
- * @complexity `n`
342
- */
343
- getAnonArgs() {
344
- return this.getAllArgs().filter(({anon}) => anon);
345
- }
346
-
347
- /**
348
- * 获取指定参数
349
- * @param {string|number} key 参数名
350
- * @param {boolean} exact 是否匹配匿名性
351
- * @param {boolean} copy 是否返回一个备份
352
- * @complexity `n`
353
- */
354
- getArgs(key, exact, copy = true) {
355
- if (typeof key !== 'string' && typeof key !== 'number') {
356
- this.typeError('getArgs', 'String', 'Number');
357
- }
358
- copy ||= !Parser.debugging && externalUse('getArgs');
359
- const keyStr = String(key).trim();
360
- let args = this.#args[keyStr];
361
- if (!args) {
362
- args = new Set(this.getAllArgs().filter(({name}) => keyStr === name));
363
- this.#args[keyStr] = args;
364
- }
365
- if (exact && !isNaN(keyStr)) {
366
- args = new Set([...args].filter(({anon}) => typeof key === 'number' === anon));
367
- } else if (copy) {
368
- args = new Set(args);
369
- }
370
- return args;
371
- }
372
-
373
- /**
374
- * 是否具有某参数
375
- * @param {string|number} key 参数名
376
- * @param {boolean} exact 是否匹配匿名性
377
- * @complexity `n`
378
- */
379
- hasArg(key, exact) {
380
- return this.getArgs(key, exact, false).size > 0;
381
- }
382
-
383
- /**
384
- * 获取生效的指定参数
385
- * @param {string|number} key 参数名
386
- * @param {boolean} exact 是否匹配匿名性
387
- * @complexity `n`
388
- */
389
- getArg(key, exact) {
390
- return [...this.getArgs(key, exact, false)].sort((a, b) => a.compareDocumentPosition(b)).at(-1);
391
- }
392
-
393
- /**
394
- * 移除指定参数
395
- * @param {string|number} key 参数名
396
- * @param {boolean} exact 是否匹配匿名性
397
- * @complexity `n`
398
- */
399
- removeArg(key, exact) {
400
- Parser.run(() => {
401
- for (const token of this.getArgs(key, exact, false)) {
402
- this.removeChild(token);
403
- }
404
- });
405
- }
406
-
407
- /**
408
- * 获取所有参数名
409
- * @complexity `n`
410
- */
411
- getKeys() {
412
- const args = this.getAllArgs();
413
- if (this.#keys.size === 0 && args.length > 0) {
414
- for (const {name} of args) {
415
- this.#keys.add(name);
416
- }
417
- }
418
- return [...this.#keys];
419
- }
420
-
421
- /**
422
- * 获取参数值
423
- * @param {string|number} key 参数名
424
- * @complexity `n`
425
- */
426
- getValues(key) {
427
- return [...this.getArgs(key, false, false)].map(token => token.getValue());
428
- }
429
-
430
- /**
431
- * 获取生效的参数值
432
- * @template {string|number|undefined} T
433
- * @param {T} key 参数名
434
- * @returns {T extends undefined ? Object<string, string> : string}
435
- * @complexity `n`
436
- */
437
- getValue(key) {
438
- return key === undefined
439
- ? Object.fromEntries(this.getKeys().map(k => [k, this.getValue(k)]))
440
- : this.getArg(key)?.getValue();
441
- }
442
-
443
- /**
444
- * 插入匿名参数
445
- * @param {string} val 参数值
446
- * @returns {ParameterToken}
447
- * @complexity `n`
448
- * @throws `SyntaxError` 非法的匿名参数
449
- */
450
- newAnonArg(val) {
451
- val = String(val);
452
- const templateLike = this.isTemplate(),
453
- wikitext = `{{${templateLike ? ':T|' : 'lc:'}${val}}}`,
454
- root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
455
- {childNodes: {length}, firstChild: transclude} = root,
456
- /** @type {Token & {lastChild: ParameterToken}} */
457
- {type, name, childNodes: {length: transcludeLength}, lastChild} = transclude,
458
- targetType = templateLike ? 'template' : 'magic-word',
459
- targetName = templateLike ? 'T' : 'lc';
460
- if (length === 1 && type === targetType && name === targetName && transcludeLength === 2 && lastChild.anon) {
461
- return this.appendChild(lastChild);
462
- }
463
- throw new SyntaxError(`非法的匿名参数:${noWrap(val)}`);
464
- }
465
-
466
- /**
467
- * 设置参数值
468
- * @param {string} key 参数名
469
- * @param {string} value 参数值
470
- * @complexity `n`
471
- * @throws `Error` 仅用于模板
472
- * @throws `SyntaxError` 非法的命名参数
473
- */
474
- setValue(key, value) {
475
- if (typeof key !== 'string') {
476
- this.typeError('setValue', 'String');
477
- } else if (!this.isTemplate()) {
478
- throw new Error(`${this.constructor.name}.setValue 方法仅供模板使用!`);
479
- }
480
- const token = this.getArg(key);
481
- value = String(value);
482
- if (token) {
483
- token.setValue(value);
484
- return;
485
- }
486
- const wikitext = `{{:T|${key}=${value}}}`,
487
- 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;
490
- if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2 || parameter.name !== key) {
491
- throw new SyntaxError(`非法的命名参数:${key}=${noWrap(value)}`);
492
- }
493
- this.appendChild(parameter);
494
- }
495
-
496
- /**
497
- * 将匿名参数改写为命名参数
498
- * @complexity `n`
499
- * @throws `Error` 仅用于模板
500
- */
501
- anonToNamed() {
502
- if (!this.isTemplate()) {
503
- throw new Error(`${this.constructor.name}.anonToNamed 方法仅供模板使用!`);
504
- }
505
- for (const token of this.getAnonArgs()) {
506
- token.firstChild.replaceChildren(token.name);
507
- }
508
- }
509
-
510
- /**
511
- * 替换模板名
512
- * @param {string} title 模板名
513
- * @throws `Error` 仅用于模板
514
- * @throws `SyntaxError` 非法的模板名称
515
- */
516
- replaceTemplate(title) {
517
- if (this.type === 'magic-word') {
518
- throw new Error(`${this.constructor.name}.replaceTemplate 方法仅用于更换模板!`);
519
- } else if (typeof title !== 'string') {
520
- this.typeError('replaceTemplate', 'String');
521
- }
522
- const root = Parser.parse(`{{${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
523
- {childNodes: {length}, firstChild: template} = root;
524
- if (length !== 1 || template.type !== 'template' || template.childNodes.length !== 1) {
525
- throw new SyntaxError(`非法的模板名称:${title}`);
526
- }
527
- this.firstChild.replaceChildren(...template.firstChild.childNodes);
528
- }
529
-
530
- /**
531
- * 替换模块名
532
- * @param {string} title 模块名
533
- * @throws `Error` 仅用于模块
534
- * @throws `SyntaxError` 非法的模块名称
535
- */
536
- replaceModule(title) {
537
- if (this.type !== 'magic-word' || this.name !== 'invoke') {
538
- throw new Error(`${this.constructor.name}.replaceModule 方法仅用于更换模块!`);
539
- } else if (typeof title !== 'string') {
540
- this.typeError('replaceModule', 'String');
541
- }
542
- 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;
545
- if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 2) {
546
- throw new SyntaxError(`非法的模块名称:${title}`);
547
- } else if (this.childNodes.length > 1) {
548
- this.childNodes[1].replaceChildren(...lastChild.childNodes);
549
- } else {
550
- invoke.destroy(true);
551
- this.appendChild(lastChild);
552
- }
553
- }
554
-
555
- /**
556
- * 替换模块函数
557
- * @param {string} func 模块函数名
558
- * @throws `Error` 仅用于模块
559
- * @throws `Error` 尚未指定模块名称
560
- * @throws `SyntaxError` 非法的模块函数名
561
- */
562
- replaceFunction(func) {
563
- if (this.type !== 'magic-word' || this.name !== 'invoke') {
564
- throw new Error(`${this.constructor.name}.replaceModule 方法仅用于更换模块!`);
565
- } else if (typeof func !== 'string') {
566
- this.typeError('replaceFunction', 'String');
567
- } else if (this.childNodes.length < 2) {
568
- throw new Error('尚未指定模块名称!');
569
- }
570
- const root = Parser.parse(
571
- `{{#invoke:M|${func}}}`, this.getAttribute('include'), 2, this.getAttribute('config'),
572
- ),
573
- {childNodes: {length}, firstChild: invoke} = root,
574
- {type, name, childNodes: {length: invokeLength}, lastChild} = invoke;
575
- if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 3) {
576
- throw new SyntaxError(`非法的模块函数名:${func}`);
577
- } else if (this.childNodes.length > 2) {
578
- this.childNodes[2].replaceChildren(...lastChild.childNodes);
579
- } else {
580
- invoke.destroy(true);
581
- this.appendChild(lastChild);
582
- }
583
- }
584
-
585
- /**
586
- * 是否存在重名参数
587
- * @complexity `n`
588
- * @throws `Error` 仅用于模板
589
- */
590
- hasDuplicatedArgs() {
591
- if (this.isTemplate()) {
592
- return this.getAllArgs().length - this.getKeys().length;
593
- }
594
- throw new Error(`${this.constructor.name}.hasDuplicatedArgs 方法仅供模板使用!`);
595
- }
596
-
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
- /**
611
- * 修复重名参数:
612
- * `aggressive = false`时只移除空参数和全同参数,优先保留匿名参数,否则将所有匿名参数更改为命名。
613
- * `aggressive = true`时还会尝试处理连续的以数字编号的参数。
614
- * @param {boolean} aggressive 是否使用有更大风险的修复手段
615
- * @complexity `n²`
616
- */
617
- fixDuplication(aggressive) {
618
- if (!this.hasDuplicatedArgs()) {
619
- return [];
620
- }
621
- const /** @type {string[]} */ duplicatedKeys = [];
622
- let {length: anonCount} = this.getAnonArgs();
623
- for (const [key, args] of this.getDuplicatedArgs()) {
624
- if (args.size <= 1) {
625
- continue;
626
- }
627
- const /** @type {Record<string, ParameterToken[]>} */ values = {};
628
- for (const arg of args) {
629
- const val = arg.getValue().trim();
630
- if (val in values) {
631
- values[val].push(arg);
632
- } else {
633
- values[val] = [arg];
634
- }
635
- }
636
- let noMoreAnon = anonCount === 0 || isNaN(key);
637
- const emptyArgs = values[''] ?? [],
638
- duplicatedArgs = Object.entries(values).filter(([val, {length}]) => val && length > 1)
639
- .flatMap(([, curArgs]) => {
640
- const anonIndex = noMoreAnon ? -1 : curArgs.findIndex(({anon}) => anon);
641
- if (anonIndex !== -1) {
642
- noMoreAnon = true;
643
- }
644
- curArgs.splice(anonIndex, 1);
645
- return curArgs;
646
- }),
647
- badArgs = [...emptyArgs, ...duplicatedArgs],
648
- index = noMoreAnon ? -1 : emptyArgs.findIndex(({anon}) => anon);
649
- if (badArgs.length === args.size) {
650
- badArgs.splice(index, 1);
651
- } else if (index !== -1) {
652
- this.anonToNamed();
653
- anonCount = 0;
654
- }
655
- for (const arg of badArgs) {
656
- arg.remove();
657
- }
658
- let remaining = args.size - badArgs.length;
659
- if (remaining === 1) {
660
- continue;
661
- } else if (aggressive && (anonCount ? /\D\d+$/u : /(?:^|\D)\d+$/u).test(key)) {
662
- let /** @type {number} */ last;
663
- const str = key.slice(0, -/(?<!\d)\d+$/u.exec(key)[0].length),
664
- regex = new RegExp(`^${escapeRegExp(str)}\\d+$`, 'u'),
665
- series = this.getAllArgs().filter(({name}) => regex.test(name)),
666
- ordered = series.every(({name}, i) => {
667
- const j = Number(name.slice(str.length)),
668
- cmp = j <= i + 1 && (i === 0 || j >= last || name === key);
669
- last = j;
670
- return cmp;
671
- });
672
- if (ordered) {
673
- for (let i = 0; i < series.length; i++) {
674
- const name = `${str}${i + 1}`,
675
- arg = series[i];
676
- if (arg.name !== name) {
677
- if (arg.name === key) {
678
- remaining--;
679
- }
680
- arg.rename(name, true);
681
- }
682
- }
683
- }
684
- }
685
- if (remaining > 1) {
686
- Parser.error(`${this.type === 'template'
687
- ? this.name
688
- : this.normalizeTitle(this.childNodes[1]?.text() ?? '', 828).title
689
- } 还留有 ${remaining} 个重复的 ${key} 参数:${[...this.getArgs(key)].map(arg => {
690
- const {top, left} = arg.getBoundingClientRect();
691
- return `第 ${top} 行第 ${left} 列`;
692
- }).join('、')}`);
693
- duplicatedKeys.push(key);
694
- continue;
695
- }
696
- }
697
- return duplicatedKeys;
698
- }
699
-
700
- /**
701
- * 转义模板内的表格
702
- * @returns {TranscludeToken}
703
- * @complexity `n`
704
- * @throws `Error` 转义失败
705
- */
706
- escapeTables() {
707
- const count = this.hasDuplicatedArgs();
708
- if (!/\n[^\S\n]*(?::+\s*)?\{\|[^\n]*\n\s*(?:\S[^\n]*\n\s*)*\|\}/u.test(this.text()) || !count) {
709
- return this;
710
- }
711
- const stripped = String(this).slice(2, -2),
712
- include = this.getAttribute('include'),
713
- config = this.getAttribute('config'),
714
- parsed = Parser.parse(stripped, include, 4, config);
715
- const TableToken = require('./table');
716
- for (const table of parsed.childNodes) {
717
- if (table instanceof TableToken) {
718
- table.escape();
719
- }
720
- }
721
- const {firstChild, childNodes} = Parser.parse(`{{${String(parsed)}}}`, include, 2, config);
722
- if (childNodes.length !== 1 || !(firstChild instanceof TranscludeToken)) {
723
- throw new Error('转义表格失败!');
724
- }
725
- const newCount = firstChild.hasDuplicatedArgs();
726
- if (newCount === count) {
727
- return this;
728
- }
729
- Parser.info(`共修复了 ${count - newCount} 个重复参数。`);
730
- this.safeReplaceWith(firstChild);
731
- return firstChild;
732
- }
733
- }
734
-
735
- Parser.classes.TranscludeToken = __filename;
736
- module.exports = TranscludeToken;