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