wikiparser-node 0.4.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.
- package/config/default.json +129 -66
- package/config/zhwiki.json +4 -4
- package/index.js +97 -65
- package/lib/element.js +159 -302
- package/lib/node.js +384 -198
- package/lib/ranges.js +3 -4
- package/lib/text.js +65 -36
- package/lib/title.js +9 -8
- package/mixin/fixedToken.js +4 -4
- package/mixin/hidden.js +2 -0
- package/mixin/sol.js +16 -7
- package/package.json +14 -3
- package/parser/brackets.js +8 -2
- package/parser/commentAndExt.js +1 -1
- package/parser/converter.js +1 -1
- package/parser/externalLinks.js +2 -2
- package/parser/hrAndDoubleUnderscore.js +8 -7
- package/parser/links.js +8 -9
- package/parser/magicLinks.js +1 -1
- package/parser/selector.js +5 -5
- package/parser/table.js +18 -16
- package/src/arg.js +71 -42
- package/src/atom/index.js +7 -5
- package/src/attribute.js +102 -64
- package/src/charinsert.js +91 -0
- package/src/converter.js +34 -15
- package/src/converterFlags.js +87 -40
- package/src/converterRule.js +59 -53
- package/src/extLink.js +45 -37
- package/src/gallery.js +71 -16
- package/src/hasNowiki/index.js +42 -0
- package/src/hasNowiki/pre.js +40 -0
- package/src/heading.js +41 -18
- package/src/html.js +76 -48
- package/src/imageParameter.js +73 -51
- package/src/imagemap.js +205 -0
- package/src/imagemapLink.js +43 -0
- package/src/index.js +243 -138
- package/src/link/category.js +10 -14
- package/src/link/file.js +112 -56
- package/src/link/galleryImage.js +74 -10
- package/src/link/index.js +86 -61
- package/src/magicLink.js +48 -21
- package/src/nested/choose.js +24 -0
- package/src/nested/combobox.js +23 -0
- package/src/nested/index.js +88 -0
- package/src/nested/references.js +23 -0
- package/src/nowiki/comment.js +18 -4
- package/src/nowiki/dd.js +2 -2
- package/src/nowiki/doubleUnderscore.js +16 -11
- package/src/nowiki/index.js +12 -0
- package/src/nowiki/quote.js +28 -1
- package/src/onlyinclude.js +15 -8
- package/src/paramTag/index.js +83 -0
- package/src/paramTag/inputbox.js +42 -0
- package/src/parameter.js +73 -46
- package/src/syntax.js +9 -1
- package/src/table/index.js +58 -44
- package/src/table/td.js +63 -63
- package/src/table/tr.js +52 -35
- package/src/tagPair/ext.js +60 -43
- package/src/tagPair/include.js +11 -1
- package/src/tagPair/index.js +29 -20
- package/src/transclude.js +214 -166
- package/tool/index.js +720 -439
- package/util/base.js +17 -0
- package/util/debug.js +1 -1
- package/{test/util.js → util/diff.js} +15 -19
- package/util/lint.js +40 -0
- package/util/string.js +37 -20
- package/.eslintrc.json +0 -714
- package/errors/README +0 -1
- package/jsconfig.json +0 -7
- package/printed/README +0 -1
- package/printed/example.json +0 -120
- package/test/api.js +0 -83
- package/test/real.js +0 -133
- package/test/test.js +0 -28
- package/typings/api.d.ts +0 -13
- package/typings/array.d.ts +0 -28
- package/typings/event.d.ts +0 -24
- package/typings/index.d.ts +0 -94
- package/typings/node.d.ts +0 -29
- package/typings/parser.d.ts +0 -16
- package/typings/table.d.ts +0 -14
- package/typings/token.d.ts +0 -22
- package/typings/tool.d.ts +0 -11
package/src/tagPair/index.js
CHANGED
|
@@ -13,6 +13,15 @@ class TagPairToken extends fixedToken(Token) {
|
|
|
13
13
|
#closed;
|
|
14
14
|
#tags;
|
|
15
15
|
|
|
16
|
+
/** getter */
|
|
17
|
+
get closed() {
|
|
18
|
+
return this.#closed;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set closed(value) {
|
|
22
|
+
this.#closed ||= Boolean(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
/** getter */
|
|
17
26
|
get selfClosing() {
|
|
18
27
|
return this.#selfClosing;
|
|
@@ -26,15 +35,6 @@ class TagPairToken extends fixedToken(Token) {
|
|
|
26
35
|
this.#selfClosing = value;
|
|
27
36
|
}
|
|
28
37
|
|
|
29
|
-
/** getter */
|
|
30
|
-
get closed() {
|
|
31
|
-
return this.#closed;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
set closed(value) {
|
|
35
|
-
this.#closed ||= Boolean(value);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
38
|
/** 内部wikitext */
|
|
39
39
|
get innerText() {
|
|
40
40
|
return this.#selfClosing ? undefined : this.lastChild.text();
|
|
@@ -49,7 +49,8 @@ class TagPairToken extends fixedToken(Token) {
|
|
|
49
49
|
*/
|
|
50
50
|
constructor(name, attr, inner, closed, config = Parser.getConfig(), accum = []) {
|
|
51
51
|
super(undefined, config, true);
|
|
52
|
-
this.setAttribute('name', name.toLowerCase())
|
|
52
|
+
this.setAttribute('name', name.toLowerCase());
|
|
53
|
+
this.#tags = [name, closed || name];
|
|
53
54
|
this.#selfClosing = closed === undefined;
|
|
54
55
|
this.#closed = closed !== '';
|
|
55
56
|
this.append(attr, inner);
|
|
@@ -63,16 +64,6 @@ class TagPairToken extends fixedToken(Token) {
|
|
|
63
64
|
accum.splice(index, 0, this);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
/**
|
|
67
|
-
* @override
|
|
68
|
-
* @template {string} T
|
|
69
|
-
* @param {T} key 属性键
|
|
70
|
-
* @returns {TokenAttribute<T>}
|
|
71
|
-
*/
|
|
72
|
-
getAttribute(key) {
|
|
73
|
-
return key === 'tags' ? [...this.#tags] : super.getAttribute(key);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
67
|
/**
|
|
77
68
|
* @override
|
|
78
69
|
* @param {string} selector
|
|
@@ -101,6 +92,24 @@ class TagPairToken extends fixedToken(Token) {
|
|
|
101
92
|
return 1;
|
|
102
93
|
}
|
|
103
94
|
|
|
95
|
+
/** @override */
|
|
96
|
+
print() {
|
|
97
|
+
const [opening, closing] = this.#tags;
|
|
98
|
+
return super.print(this.#selfClosing
|
|
99
|
+
? {pre: `<${opening}`, post: '/>'}
|
|
100
|
+
: {pre: `<${opening}`, sep: '>', post: this.#closed ? `</${closing}>` : ''});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @override
|
|
105
|
+
* @template {string} T
|
|
106
|
+
* @param {T} key 属性键
|
|
107
|
+
* @returns {TokenAttribute<T>}
|
|
108
|
+
*/
|
|
109
|
+
getAttribute(key) {
|
|
110
|
+
return key === 'tags' ? [...this.#tags] : super.getAttribute(key);
|
|
111
|
+
}
|
|
112
|
+
|
|
104
113
|
/**
|
|
105
114
|
* @override
|
|
106
115
|
* @returns {string}
|
package/src/transclude.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {removeComment, escapeRegExp, text, noWrap} = require('../util/string'),
|
|
3
|
+
const {removeComment, escapeRegExp, text, noWrap, print} = require('../util/string'),
|
|
4
4
|
{externalUse} = require('../util/debug'),
|
|
5
|
+
{generateForChild} = require('../util/lint'),
|
|
5
6
|
Parser = require('..'),
|
|
6
7
|
Token = require('.'),
|
|
7
8
|
ParameterToken = require('./parameter');
|
|
@@ -13,8 +14,13 @@ const {removeComment, escapeRegExp, text, noWrap} = require('../util/string'),
|
|
|
13
14
|
class TranscludeToken extends Token {
|
|
14
15
|
type = 'template';
|
|
15
16
|
modifier = '';
|
|
16
|
-
/** @type {Set<string>} */ #keys = new Set();
|
|
17
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
|
+
}
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* 设置引用修饰符
|
|
@@ -64,11 +70,11 @@ class TranscludeToken extends Token {
|
|
|
64
70
|
isSensitive = sensitive.includes(name);
|
|
65
71
|
if (isSensitive || insensitive.includes(name.toLowerCase())) {
|
|
66
72
|
this.setAttribute('name', name.toLowerCase().replace(/^#/u, '')).type = 'magic-word';
|
|
67
|
-
const pattern = new RegExp(`^\\s*${name}\\s*$`, isSensitive ? '' : '
|
|
73
|
+
const pattern = new RegExp(`^\\s*${name}\\s*$`, isSensitive ? 'u' : 'iu'),
|
|
68
74
|
token = new SyntaxToken(magicWord, pattern, 'magic-word-name', config, accum, {
|
|
69
75
|
'Stage-1': ':', '!ExtToken': '',
|
|
70
76
|
});
|
|
71
|
-
this.
|
|
77
|
+
this.insertAt(token);
|
|
72
78
|
if (arg.length > 0) {
|
|
73
79
|
parts.unshift([arg.join(':')]);
|
|
74
80
|
}
|
|
@@ -82,7 +88,7 @@ class TranscludeToken extends Token {
|
|
|
82
88
|
const invoke = new AtomToken(part.join('='), `invoke-${
|
|
83
89
|
i ? 'function' : 'module'
|
|
84
90
|
}`, config, accum, {'Stage-1': ':', '!ExtToken': ''});
|
|
85
|
-
this.
|
|
91
|
+
this.insertAt(invoke);
|
|
86
92
|
}
|
|
87
93
|
this.getAttribute('protectChildren')('1:3');
|
|
88
94
|
}
|
|
@@ -94,11 +100,10 @@ class TranscludeToken extends Token {
|
|
|
94
100
|
accum.pop();
|
|
95
101
|
throw new SyntaxError(`非法的模板名称:${name}`);
|
|
96
102
|
}
|
|
97
|
-
this.setAttribute('name', this.normalizeTitle(name, 10, true).title);
|
|
98
103
|
const token = new AtomToken(title, 'template-name', config, accum, {'Stage-2': ':', '!HeadingToken': ''});
|
|
99
|
-
this.
|
|
104
|
+
this.insertAt(token);
|
|
100
105
|
}
|
|
101
|
-
const templateLike = this.
|
|
106
|
+
const templateLike = this.isTemplate();
|
|
102
107
|
let i = 1;
|
|
103
108
|
for (const part of parts) {
|
|
104
109
|
if (!templateLike) {
|
|
@@ -109,85 +114,11 @@ class TranscludeToken extends Token {
|
|
|
109
114
|
part.unshift(i);
|
|
110
115
|
i++;
|
|
111
116
|
}
|
|
112
|
-
this.
|
|
117
|
+
this.insertAt(new ParameterToken(...part, config, accum));
|
|
113
118
|
}
|
|
114
119
|
this.getAttribute('protectChildren')(0);
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
/** @override */
|
|
118
|
-
cloneNode() {
|
|
119
|
-
const [first, ...cloned] = this.cloneChildNodes(),
|
|
120
|
-
config = this.getAttribute('config');
|
|
121
|
-
return Parser.run(() => {
|
|
122
|
-
const token = new TranscludeToken(this.type === 'template' ? '' : first.text(), [], config);
|
|
123
|
-
token.setModifier(this.modifier);
|
|
124
|
-
token.firstElementChild.safeReplaceWith(first);
|
|
125
|
-
token.afterBuild();
|
|
126
|
-
token.append(...cloned);
|
|
127
|
-
return token;
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** @override */
|
|
132
|
-
afterBuild() {
|
|
133
|
-
if (this.name.includes('\0')) {
|
|
134
|
-
this.setAttribute('name', text(this.getAttribute('buildFromStr')(this.name)));
|
|
135
|
-
}
|
|
136
|
-
if (this.matches('template, magic-word#invoke')) {
|
|
137
|
-
/**
|
|
138
|
-
* 当事件bubble到`parameter`时,将`oldKey`和`newKey`保存进AstEventData。
|
|
139
|
-
* 当继续bubble到`template`时,处理并删除`oldKey`和`newKey`。
|
|
140
|
-
* @type {AstListener}
|
|
141
|
-
*/
|
|
142
|
-
const transcludeListener = (e, data) => {
|
|
143
|
-
const {prevTarget} = e,
|
|
144
|
-
{oldKey, newKey} = data ?? {};
|
|
145
|
-
if (typeof oldKey === 'string') {
|
|
146
|
-
delete data.oldKey;
|
|
147
|
-
delete data.newKey;
|
|
148
|
-
}
|
|
149
|
-
if (prevTarget === this.firstElementChild && this.type === 'template') {
|
|
150
|
-
this.setAttribute('name', this.normalizeTitle(prevTarget.text(), 10).title);
|
|
151
|
-
} else if (oldKey !== newKey && prevTarget instanceof ParameterToken) {
|
|
152
|
-
const oldArgs = this.getArgs(oldKey, false, false);
|
|
153
|
-
oldArgs.delete(prevTarget);
|
|
154
|
-
this.getArgs(newKey, false, false).add(prevTarget);
|
|
155
|
-
this.#keys.add(newKey);
|
|
156
|
-
if (oldArgs.size === 0) {
|
|
157
|
-
this.#keys.delete(oldKey);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
this.addEventListener(['remove', 'insert', 'replace', 'text'], transcludeListener);
|
|
162
|
-
}
|
|
163
|
-
return this;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/** 替换引用 */
|
|
167
|
-
subst() {
|
|
168
|
-
this.setModifier('subst');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** 安全的替换引用 */
|
|
172
|
-
safesubst() {
|
|
173
|
-
this.setModifier('safesubst');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* @override
|
|
178
|
-
* @template {string} T
|
|
179
|
-
* @param {T} key 属性键
|
|
180
|
-
* @returns {TokenAttribute<T>}
|
|
181
|
-
*/
|
|
182
|
-
getAttribute(key) {
|
|
183
|
-
if (key === 'args') {
|
|
184
|
-
return {...this.#args};
|
|
185
|
-
} else if (key === 'keys') {
|
|
186
|
-
return !Parser.debugging && externalUse('getAttribute') ? new Set(this.#keys) : this.#keys;
|
|
187
|
-
}
|
|
188
|
-
return super.getAttribute(key);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
122
|
/**
|
|
192
123
|
* @override
|
|
193
124
|
* @param {string} selector
|
|
@@ -196,10 +127,10 @@ class TranscludeToken extends Token {
|
|
|
196
127
|
if (selector && this.matches(selector)) {
|
|
197
128
|
return '';
|
|
198
129
|
}
|
|
199
|
-
const {
|
|
130
|
+
const {childNodes, firstChild, modifier} = this;
|
|
200
131
|
return `{{${modifier}${modifier && ':'}${
|
|
201
132
|
this.type === 'magic-word'
|
|
202
|
-
? `${String(firstChild)}${length > 1 ? ':' : ''}${
|
|
133
|
+
? `${String(firstChild)}${childNodes.length > 1 ? ':' : ''}${childNodes.slice(1).map(String).join('|')}`
|
|
203
134
|
: super.toString(selector, '|')
|
|
204
135
|
}}}`;
|
|
205
136
|
}
|
|
@@ -214,18 +145,38 @@ class TranscludeToken extends Token {
|
|
|
214
145
|
return 1;
|
|
215
146
|
}
|
|
216
147
|
|
|
148
|
+
/** @override */
|
|
149
|
+
print() {
|
|
150
|
+
const {childNodes, firstChild, modifier} = this;
|
|
151
|
+
return `<span class="wpb-${this.type}">{{${modifier}${modifier && ':'}${
|
|
152
|
+
this.type === 'magic-word'
|
|
153
|
+
? `${firstChild.print()}${childNodes.length > 1 ? ':' : ''}${print(childNodes.slice(1), {sep: '|'})}`
|
|
154
|
+
: print(childNodes, {sep: '|'})
|
|
155
|
+
}}}</span>`;
|
|
156
|
+
}
|
|
157
|
+
|
|
217
158
|
/**
|
|
218
159
|
* @override
|
|
219
|
-
* @
|
|
220
|
-
* @complexity `n`
|
|
160
|
+
* @param {number} start 起始位置
|
|
221
161
|
*/
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
162
|
+
lint(start = 0) {
|
|
163
|
+
const errors = super.lint(start);
|
|
164
|
+
if (!this.isTemplate()) {
|
|
165
|
+
return errors;
|
|
166
|
+
}
|
|
167
|
+
const duplicatedArgs = this.getDuplicatedArgs();
|
|
168
|
+
if (duplicatedArgs.length > 0) {
|
|
169
|
+
const rect = this.getRootNode().posFromIndex(start);
|
|
170
|
+
errors.push(...duplicatedArgs.flatMap(([, args]) => [...args]).map(
|
|
171
|
+
arg => generateForChild(arg, rect, '重复参数'),
|
|
172
|
+
));
|
|
173
|
+
}
|
|
174
|
+
return errors;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** 是否是模板 */
|
|
178
|
+
isTemplate() {
|
|
179
|
+
return this.type === 'template' || this.type === 'magic-word' && this.name === 'invoke';
|
|
229
180
|
}
|
|
230
181
|
|
|
231
182
|
/**
|
|
@@ -256,25 +207,6 @@ class TranscludeToken extends Token {
|
|
|
256
207
|
}
|
|
257
208
|
}
|
|
258
209
|
|
|
259
|
-
/**
|
|
260
|
-
* @override
|
|
261
|
-
* @param {number} i 移除位置
|
|
262
|
-
* @complexity `n`
|
|
263
|
-
*/
|
|
264
|
-
removeAt(i) {
|
|
265
|
-
const /** @type {ParameterToken} */ token = super.removeAt(i);
|
|
266
|
-
if (token.anon) {
|
|
267
|
-
this.#handleAnonArgChange(Number(token.name));
|
|
268
|
-
} else {
|
|
269
|
-
const args = this.getArgs(token.name, false, false);
|
|
270
|
-
args.delete(token);
|
|
271
|
-
if (args.size === 0) {
|
|
272
|
-
this.#keys.delete(token.name);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return token;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
210
|
/**
|
|
279
211
|
* @override
|
|
280
212
|
* @param {ParameterToken} token 待插入的子节点
|
|
@@ -298,7 +230,7 @@ class TranscludeToken extends Token {
|
|
|
298
230
|
* @complexity `n`
|
|
299
231
|
*/
|
|
300
232
|
getAllArgs() {
|
|
301
|
-
return this.
|
|
233
|
+
return this.childNodes.filter(child => child instanceof ParameterToken);
|
|
302
234
|
}
|
|
303
235
|
|
|
304
236
|
/**
|
|
@@ -322,8 +254,10 @@ class TranscludeToken extends Token {
|
|
|
322
254
|
}
|
|
323
255
|
copy ||= !Parser.debugging && externalUse('getArgs');
|
|
324
256
|
const keyStr = String(key).trim();
|
|
325
|
-
let args
|
|
326
|
-
if (
|
|
257
|
+
let args;
|
|
258
|
+
if (Object.hasOwn(this.#args, keyStr)) {
|
|
259
|
+
args = this.#args[keyStr];
|
|
260
|
+
} else {
|
|
327
261
|
args = new Set(this.getAllArgs().filter(({name}) => keyStr === name));
|
|
328
262
|
this.#args[keyStr] = args;
|
|
329
263
|
}
|
|
@@ -335,6 +269,135 @@ class TranscludeToken extends Token {
|
|
|
335
269
|
return args;
|
|
336
270
|
}
|
|
337
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
|
+
|
|
338
401
|
/**
|
|
339
402
|
* 是否具有某参数
|
|
340
403
|
* @param {string|number} key 参数名
|
|
@@ -396,7 +459,7 @@ class TranscludeToken extends Token {
|
|
|
396
459
|
* 获取生效的参数值
|
|
397
460
|
* @template {string|number|undefined} T
|
|
398
461
|
* @param {T} key 参数名
|
|
399
|
-
* @returns {T extends undefined ?
|
|
462
|
+
* @returns {T extends undefined ? Record<string, string> : string}
|
|
400
463
|
* @complexity `n`
|
|
401
464
|
*/
|
|
402
465
|
getValue(key) {
|
|
@@ -414,14 +477,16 @@ class TranscludeToken extends Token {
|
|
|
414
477
|
*/
|
|
415
478
|
newAnonArg(val) {
|
|
416
479
|
val = String(val);
|
|
417
|
-
const templateLike = this.
|
|
480
|
+
const templateLike = this.isTemplate(),
|
|
418
481
|
wikitext = `{{${templateLike ? ':T|' : 'lc:'}${val}}}`,
|
|
419
482
|
root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
420
|
-
{
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
483
|
+
{length, firstChild: transclude} = root,
|
|
484
|
+
/** @type {Token & {lastChild: ParameterToken}} */
|
|
485
|
+
{type, name, length: transcludeLength, lastChild} = transclude,
|
|
486
|
+
targetType = templateLike ? 'template' : 'magic-word',
|
|
487
|
+
targetName = templateLike ? 'T' : 'lc';
|
|
488
|
+
if (length === 1 && type === targetType && name === targetName && transcludeLength === 2 && lastChild.anon) {
|
|
489
|
+
return this.insertAt(lastChild);
|
|
425
490
|
}
|
|
426
491
|
throw new SyntaxError(`非法的匿名参数:${noWrap(val)}`);
|
|
427
492
|
}
|
|
@@ -437,7 +502,7 @@ class TranscludeToken extends Token {
|
|
|
437
502
|
setValue(key, value) {
|
|
438
503
|
if (typeof key !== 'string') {
|
|
439
504
|
this.typeError('setValue', 'String');
|
|
440
|
-
} else if (!this.
|
|
505
|
+
} else if (!this.isTemplate()) {
|
|
441
506
|
throw new Error(`${this.constructor.name}.setValue 方法仅供模板使用!`);
|
|
442
507
|
}
|
|
443
508
|
const token = this.getArg(key);
|
|
@@ -448,13 +513,12 @@ class TranscludeToken extends Token {
|
|
|
448
513
|
}
|
|
449
514
|
const wikitext = `{{:T|${key}=${value}}}`,
|
|
450
515
|
root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
451
|
-
{
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
) {
|
|
516
|
+
{length, firstChild: template} = root,
|
|
517
|
+
{type, name, length: templateLength, lastChild: parameter} = template;
|
|
518
|
+
if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2 || parameter.name !== key) {
|
|
455
519
|
throw new SyntaxError(`非法的命名参数:${key}=${noWrap(value)}`);
|
|
456
520
|
}
|
|
457
|
-
this.
|
|
521
|
+
this.insertAt(parameter);
|
|
458
522
|
}
|
|
459
523
|
|
|
460
524
|
/**
|
|
@@ -463,11 +527,11 @@ class TranscludeToken extends Token {
|
|
|
463
527
|
* @throws `Error` 仅用于模板
|
|
464
528
|
*/
|
|
465
529
|
anonToNamed() {
|
|
466
|
-
if (!this.
|
|
530
|
+
if (!this.isTemplate()) {
|
|
467
531
|
throw new Error(`${this.constructor.name}.anonToNamed 方法仅供模板使用!`);
|
|
468
532
|
}
|
|
469
533
|
for (const token of this.getAnonArgs()) {
|
|
470
|
-
token.
|
|
534
|
+
token.firstChild.replaceChildren(token.name);
|
|
471
535
|
}
|
|
472
536
|
}
|
|
473
537
|
|
|
@@ -484,11 +548,11 @@ class TranscludeToken extends Token {
|
|
|
484
548
|
this.typeError('replaceTemplate', 'String');
|
|
485
549
|
}
|
|
486
550
|
const root = Parser.parse(`{{${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
487
|
-
{
|
|
488
|
-
if (length !== 1 ||
|
|
551
|
+
{length, firstChild: template} = root;
|
|
552
|
+
if (length !== 1 || template.type !== 'template' || template.childNodes.length !== 1) {
|
|
489
553
|
throw new SyntaxError(`非法的模板名称:${title}`);
|
|
490
554
|
}
|
|
491
|
-
this.
|
|
555
|
+
this.firstChild.replaceChildren(...template.firstChild.childNodes);
|
|
492
556
|
}
|
|
493
557
|
|
|
494
558
|
/**
|
|
@@ -504,17 +568,15 @@ class TranscludeToken extends Token {
|
|
|
504
568
|
this.typeError('replaceModule', 'String');
|
|
505
569
|
}
|
|
506
570
|
const root = Parser.parse(`{{#invoke:${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
507
|
-
{
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
) {
|
|
571
|
+
{length, firstChild: invoke} = root,
|
|
572
|
+
{type, name, length: invokeLength, lastChild} = invoke;
|
|
573
|
+
if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 2) {
|
|
511
574
|
throw new SyntaxError(`非法的模块名称:${title}`);
|
|
512
575
|
} else if (this.childNodes.length > 1) {
|
|
513
|
-
this.
|
|
576
|
+
this.childNodes[1].replaceChildren(...lastChild.childNodes);
|
|
514
577
|
} else {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
this.appendChild(lastChild);
|
|
578
|
+
invoke.destroy(true);
|
|
579
|
+
this.insertAt(lastChild);
|
|
518
580
|
}
|
|
519
581
|
}
|
|
520
582
|
|
|
@@ -536,17 +598,15 @@ class TranscludeToken extends Token {
|
|
|
536
598
|
const root = Parser.parse(
|
|
537
599
|
`{{#invoke:M|${func}}}`, this.getAttribute('include'), 2, this.getAttribute('config'),
|
|
538
600
|
),
|
|
539
|
-
{
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
) {
|
|
601
|
+
{length, firstChild: invoke} = root,
|
|
602
|
+
{type, name, length: invokeLength, lastChild} = invoke;
|
|
603
|
+
if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 3) {
|
|
543
604
|
throw new SyntaxError(`非法的模块函数名:${func}`);
|
|
544
605
|
} else if (this.childNodes.length > 2) {
|
|
545
|
-
this.
|
|
606
|
+
this.childNodes[2].replaceChildren(...lastChild.childNodes);
|
|
546
607
|
} else {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
this.appendChild(lastChild);
|
|
608
|
+
invoke.destroy(true);
|
|
609
|
+
this.insertAt(lastChild);
|
|
550
610
|
}
|
|
551
611
|
}
|
|
552
612
|
|
|
@@ -556,24 +616,12 @@ class TranscludeToken extends Token {
|
|
|
556
616
|
* @throws `Error` 仅用于模板
|
|
557
617
|
*/
|
|
558
618
|
hasDuplicatedArgs() {
|
|
559
|
-
if (this.
|
|
619
|
+
if (this.isTemplate()) {
|
|
560
620
|
return this.getAllArgs().length - this.getKeys().length;
|
|
561
621
|
}
|
|
562
622
|
throw new Error(`${this.constructor.name}.hasDuplicatedArgs 方法仅供模板使用!`);
|
|
563
623
|
}
|
|
564
624
|
|
|
565
|
-
/**
|
|
566
|
-
* 获取重名参数
|
|
567
|
-
* @complexity `n`
|
|
568
|
-
* @throws `Error` 仅用于模板
|
|
569
|
-
*/
|
|
570
|
-
getDuplicatedArgs() {
|
|
571
|
-
if (this.matches('template, magic-word#invoke')) {
|
|
572
|
-
return Object.entries(this.#args).filter(([, {size}]) => size > 1).map(([key, args]) => [key, new Set(args)]);
|
|
573
|
-
}
|
|
574
|
-
throw new Error(`${this.constructor.name}.getDuplicatedArgs 方法仅供模板使用!`);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
625
|
/**
|
|
578
626
|
* 修复重名参数:
|
|
579
627
|
* `aggressive = false`时只移除空参数和全同参数,优先保留匿名参数,否则将所有匿名参数更改为命名。
|
|
@@ -594,7 +642,7 @@ class TranscludeToken extends Token {
|
|
|
594
642
|
const /** @type {Record<string, ParameterToken[]>} */ values = {};
|
|
595
643
|
for (const arg of args) {
|
|
596
644
|
const val = arg.getValue().trim();
|
|
597
|
-
if (val
|
|
645
|
+
if (Object.hasOwn(values, val)) {
|
|
598
646
|
values[val].push(arg);
|
|
599
647
|
} else {
|
|
600
648
|
values[val] = [arg];
|
|
@@ -652,7 +700,7 @@ class TranscludeToken extends Token {
|
|
|
652
700
|
if (remaining > 1) {
|
|
653
701
|
Parser.error(`${this.type === 'template'
|
|
654
702
|
? this.name
|
|
655
|
-
: this.normalizeTitle(this.
|
|
703
|
+
: this.normalizeTitle(this.childNodes[1]?.text() ?? '', 828).title
|
|
656
704
|
} 还留有 ${remaining} 个重复的 ${key} 参数:${[...this.getArgs(key)].map(arg => {
|
|
657
705
|
const {top, left} = arg.getBoundingClientRect();
|
|
658
706
|
return `第 ${top} 行第 ${left} 列`;
|
|
@@ -680,7 +728,7 @@ class TranscludeToken extends Token {
|
|
|
680
728
|
config = this.getAttribute('config'),
|
|
681
729
|
parsed = Parser.parse(stripped, include, 4, config);
|
|
682
730
|
const TableToken = require('./table');
|
|
683
|
-
for (const table of parsed.
|
|
731
|
+
for (const table of parsed.childNodes) {
|
|
684
732
|
if (table instanceof TableToken) {
|
|
685
733
|
table.escape();
|
|
686
734
|
}
|