wikiparser-node 0.7.0-m → 0.7.0
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/README.md +39 -0
- package/config/llwiki.json +35 -0
- package/config/moegirl.json +44 -0
- package/config/zhwiki.json +466 -0
- package/index.js +259 -11
- package/lib/element.js +481 -7
- package/lib/node.js +552 -6
- package/lib/ranges.js +130 -0
- package/lib/text.js +112 -21
- package/lib/title.js +27 -0
- package/mixin/attributeParent.js +117 -0
- package/mixin/fixedToken.js +40 -0
- package/mixin/hidden.js +3 -0
- package/mixin/singleLine.js +31 -0
- package/mixin/sol.js +65 -0
- package/package.json +5 -4
- package/parser/brackets.js +1 -0
- package/parser/commentAndExt.js +4 -3
- package/parser/converter.js +1 -0
- package/parser/externalLinks.js +1 -0
- package/parser/hrAndDoubleUnderscore.js +1 -0
- package/parser/html.js +1 -0
- package/parser/links.js +5 -4
- package/parser/list.js +1 -0
- package/parser/magicLinks.js +5 -4
- package/parser/quotes.js +2 -1
- package/parser/selector.js +177 -0
- package/parser/table.js +1 -0
- package/src/arg.js +116 -2
- package/src/atom/hidden.js +2 -0
- package/src/atom/index.js +17 -0
- package/src/attribute.js +171 -4
- package/src/attributes.js +306 -4
- package/src/charinsert.js +57 -1
- package/src/converter.js +108 -2
- package/src/converterFlags.js +187 -0
- package/src/converterRule.js +184 -1
- package/src/extLink.js +120 -1
- package/src/gallery.js +55 -5
- package/src/hasNowiki/index.js +12 -0
- package/src/hasNowiki/pre.js +12 -0
- package/src/heading.js +55 -4
- package/src/html.js +118 -3
- package/src/imageParameter.js +176 -5
- package/src/imagemap.js +60 -1
- package/src/imagemapLink.js +13 -1
- package/src/index.js +527 -3
- package/src/link/category.js +37 -1
- package/src/link/file.js +159 -2
- package/src/link/galleryImage.js +59 -1
- package/src/link/index.js +269 -1
- package/src/magicLink.js +90 -9
- package/src/nested/choose.js +1 -0
- package/src/nested/combobox.js +1 -0
- package/src/nested/index.js +30 -3
- package/src/nested/references.js +1 -0
- package/src/nowiki/comment.js +25 -1
- package/src/nowiki/dd.js +47 -1
- package/src/nowiki/doubleUnderscore.js +31 -1
- package/src/nowiki/hr.js +20 -1
- package/src/nowiki/index.js +23 -1
- package/src/nowiki/list.js +5 -2
- package/src/nowiki/noinclude.js +14 -0
- package/src/nowiki/quote.js +16 -2
- package/src/onlyinclude.js +26 -1
- package/src/paramTag/index.js +24 -1
- package/src/paramTag/inputbox.js +44 -0
- package/src/parameter.js +148 -6
- package/src/syntax.js +68 -0
- package/src/table/index.js +940 -2
- package/src/table/td.js +225 -5
- package/src/table/tr.js +247 -2
- package/src/tagPair/ext.js +24 -4
- package/src/tagPair/include.js +24 -0
- package/src/tagPair/index.js +52 -2
- package/src/transclude.js +512 -11
- package/tool/index.js +1202 -0
- package/util/debug.js +73 -0
- package/util/string.js +48 -1
- package/config/minimum.json +0 -142
package/src/link/file.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {explode} = require('../../util/string'),
|
|
3
|
+
const {explode, noWrap} = require('../../util/string'),
|
|
4
4
|
{generateForChild} = require('../../util/lint'),
|
|
5
5
|
Parser = require('../..'),
|
|
6
|
+
Title = require('../../lib/title'),
|
|
6
7
|
LinkToken = require('.'),
|
|
7
8
|
ImageParameterToken = require('../imageParameter');
|
|
8
9
|
|
|
@@ -17,16 +18,64 @@ const frame = new Set(['manualthumb', 'frameless', 'framed', 'thumbnail']),
|
|
|
17
18
|
class FileToken extends LinkToken {
|
|
18
19
|
type = 'file';
|
|
19
20
|
|
|
21
|
+
/** 图片链接 */
|
|
22
|
+
get link() {
|
|
23
|
+
return this.getArg('link')?.link;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
set link(value) {
|
|
27
|
+
this.setValue('link', value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 图片大小 */
|
|
31
|
+
get size() {
|
|
32
|
+
return this.getArg('width')?.size;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 图片宽度 */
|
|
36
|
+
get width() {
|
|
37
|
+
return this.size?.width;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
set width(width) {
|
|
41
|
+
const arg = this.getArg('width');
|
|
42
|
+
if (arg) {
|
|
43
|
+
arg.width = width;
|
|
44
|
+
} else {
|
|
45
|
+
this.setValue('width', width);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 图片高度 */
|
|
50
|
+
get height() {
|
|
51
|
+
return this.size?.height;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
set height(height) {
|
|
55
|
+
const arg = this.getArg('width');
|
|
56
|
+
if (arg) {
|
|
57
|
+
arg.height = height;
|
|
58
|
+
} else {
|
|
59
|
+
this.setValue('width', `x${height}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
20
63
|
/**
|
|
21
64
|
* @param {string} link 文件名
|
|
22
65
|
* @param {string|undefined} text 图片参数
|
|
66
|
+
* @param {Title} title 文件标题对象
|
|
23
67
|
* @param {accum} accum
|
|
24
68
|
* @param {string} delimiter `|`
|
|
25
69
|
* @complexity `n`
|
|
26
70
|
*/
|
|
27
71
|
constructor(link, text, title, config = Parser.getConfig(), accum = [], delimiter = '|') {
|
|
28
72
|
super(link, undefined, title, config, accum, delimiter);
|
|
73
|
+
this.setAttribute('acceptable', {AtomToken: 0, ImageParameterToken: '1:'});
|
|
29
74
|
this.append(...explode('-{', '}-', '|', text).map(part => new ImageParameterToken(part, config, accum)));
|
|
75
|
+
this.seal(
|
|
76
|
+
['selfLink', 'interwiki', 'setLangLink', 'setFragment', 'asSelfLink', 'setLinkText', 'pipeTrick'],
|
|
77
|
+
true,
|
|
78
|
+
);
|
|
30
79
|
}
|
|
31
80
|
|
|
32
81
|
/**
|
|
@@ -92,7 +141,9 @@ class FileToken extends LinkToken {
|
|
|
92
141
|
* @complexity `n`
|
|
93
142
|
*/
|
|
94
143
|
getArgs(key) {
|
|
95
|
-
return
|
|
144
|
+
return typeof key === 'string'
|
|
145
|
+
? this.getAllArgs().filter(({name}) => key === name)
|
|
146
|
+
: this.typeError('getArgs', 'String');
|
|
96
147
|
}
|
|
97
148
|
|
|
98
149
|
/**
|
|
@@ -103,6 +154,9 @@ class FileToken extends LinkToken {
|
|
|
103
154
|
*/
|
|
104
155
|
#getTypedArgs(keys, type) {
|
|
105
156
|
const args = this.getAllArgs().filter(({name}) => keys.has(name));
|
|
157
|
+
if (args.length > 1) {
|
|
158
|
+
Parser.warn(`图片 ${this.name} 带有 ${args.length} 个${type}参数,只有最后 1 个 ${args[0].name} 会生效!`);
|
|
159
|
+
}
|
|
106
160
|
return args;
|
|
107
161
|
}
|
|
108
162
|
|
|
@@ -120,6 +174,109 @@ class FileToken extends LinkToken {
|
|
|
120
174
|
getVertAlignArgs() {
|
|
121
175
|
return this.#getTypedArgs(vertAlign, '垂直对齐');
|
|
122
176
|
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 获取生效的指定图片参数
|
|
180
|
+
* @param {string} key 参数名
|
|
181
|
+
* @complexity `n`
|
|
182
|
+
*/
|
|
183
|
+
getArg(key) {
|
|
184
|
+
return this.getArgs(key).at(-1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 是否具有指定图片参数
|
|
189
|
+
* @param {string} key 参数名
|
|
190
|
+
* @complexity `n`
|
|
191
|
+
*/
|
|
192
|
+
hasArg(key) {
|
|
193
|
+
return this.getArgs(key).length > 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 移除指定图片参数
|
|
198
|
+
* @param {string} key 参数名
|
|
199
|
+
* @complexity `n`
|
|
200
|
+
*/
|
|
201
|
+
removeArg(key) {
|
|
202
|
+
for (const token of this.getArgs(key)) {
|
|
203
|
+
this.removeChild(token);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 获取图片参数名
|
|
209
|
+
* @complexity `n`
|
|
210
|
+
*/
|
|
211
|
+
getKeys() {
|
|
212
|
+
return this.getAllArgs().map(({name}) => name);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 获取指定的图片参数值
|
|
217
|
+
* @param {string} key 参数名
|
|
218
|
+
* @complexity `n`
|
|
219
|
+
*/
|
|
220
|
+
getValues(key) {
|
|
221
|
+
return this.getArgs(key).map(token => token.getValue());
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 获取生效的指定图片参数值
|
|
226
|
+
* @param {string} key 参数名
|
|
227
|
+
* @complexity `n`
|
|
228
|
+
*/
|
|
229
|
+
getValue(key) {
|
|
230
|
+
return this.getArg(key)?.getValue();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 设置图片参数
|
|
235
|
+
* @param {string} key 参数名
|
|
236
|
+
* @param {string|boolean} value 参数值
|
|
237
|
+
* @complexity `n`
|
|
238
|
+
* @throws `RangeError` 未定义的图片参数
|
|
239
|
+
* @throws `SyntaxError` 非法的参数
|
|
240
|
+
*/
|
|
241
|
+
setValue(key, value) {
|
|
242
|
+
if (typeof key !== 'string') {
|
|
243
|
+
this.typeError('setValue', 'String');
|
|
244
|
+
} else if (value === false) {
|
|
245
|
+
this.removeArg(key);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const token = this.getArg(key);
|
|
249
|
+
value = value === true ? value : String(value);
|
|
250
|
+
if (token) {
|
|
251
|
+
token.setValue(value);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
let syntax = '';
|
|
255
|
+
const config = this.getAttribute('config');
|
|
256
|
+
if (key !== 'caption') {
|
|
257
|
+
syntax = Object.entries(config.img).find(([, name]) => name === key)?.[0];
|
|
258
|
+
if (!syntax) {
|
|
259
|
+
throw new RangeError(`未定义的图片参数: ${key}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (value === true) {
|
|
263
|
+
if (syntax.includes('$1')) {
|
|
264
|
+
this.typeError('setValue', 'Boolean');
|
|
265
|
+
}
|
|
266
|
+
const newArg = Parser.run(() => new ImageParameterToken(syntax, config));
|
|
267
|
+
this.insertAt(newArg);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const wikitext = `[[File:F|${syntax ? syntax.replace('$1', value) : value}]]`,
|
|
271
|
+
root = Parser.parse(wikitext, this.getAttribute('include'), 6, config),
|
|
272
|
+
{length, firstChild: file} = root,
|
|
273
|
+
{name, type, length: fileLength, lastChild: imageParameter} = file;
|
|
274
|
+
if (length !== 1 || type !== 'file' || name !== 'File:F' || fileLength !== 2 || imageParameter.name !== key) {
|
|
275
|
+
throw new SyntaxError(`非法的 ${key} 参数:${noWrap(value)}`);
|
|
276
|
+
}
|
|
277
|
+
this.insertAt(imageParameter);
|
|
278
|
+
}
|
|
123
279
|
}
|
|
124
280
|
|
|
281
|
+
Parser.classes.FileToken = __filename;
|
|
125
282
|
module.exports = FileToken;
|
package/src/link/galleryImage.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {generateForSelf} = require('../../util/lint'),
|
|
4
|
+
{undo} = require('../../util/debug'),
|
|
5
|
+
singleLine = require('../../mixin/singleLine'),
|
|
6
|
+
Title = require('../../lib/title'),
|
|
4
7
|
Parser = require('../..'),
|
|
5
8
|
Token = require('..'),
|
|
6
9
|
FileToken = require('./file');
|
|
@@ -9,13 +12,23 @@ const {generateForSelf} = require('../../util/lint'),
|
|
|
9
12
|
* 图片
|
|
10
13
|
* @classdesc `{childNodes: [AtomToken, ...ImageParameterToken]}`
|
|
11
14
|
*/
|
|
12
|
-
class GalleryImageToken extends FileToken {
|
|
15
|
+
class GalleryImageToken extends singleLine(FileToken) {
|
|
13
16
|
type = 'gallery-image';
|
|
14
17
|
#invalid = false;
|
|
15
18
|
|
|
19
|
+
/** 图片链接 */
|
|
20
|
+
get link() {
|
|
21
|
+
return this.type === 'imagemap-image' ? undefined : super.link;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
set link(value) {
|
|
25
|
+
super.link = value;
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
/**
|
|
17
29
|
* @param {string} link 图片文件名
|
|
18
30
|
* @param {string|undefined} text 图片参数
|
|
31
|
+
* @param {Title} title 图片文件标题对象
|
|
19
32
|
* @param {accum} accum
|
|
20
33
|
*/
|
|
21
34
|
constructor(link, text, title, config = Parser.getConfig(), accum = []) {
|
|
@@ -30,15 +43,39 @@ class GalleryImageToken extends FileToken {
|
|
|
30
43
|
}
|
|
31
44
|
super(link, token?.toString(), title, config, accum);
|
|
32
45
|
this.setAttribute('bracket', false);
|
|
46
|
+
if (!Object.values(config.img).includes('width')) {
|
|
47
|
+
this.seal(['size', 'width', 'height'], true);
|
|
48
|
+
}
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
/**
|
|
36
52
|
* @override
|
|
53
|
+
* @throws `Error` 非法的内链目标
|
|
54
|
+
* @throws `Error` 不可更改命名空间
|
|
37
55
|
*/
|
|
38
56
|
afterBuild() {
|
|
39
57
|
const initImagemap = this.type === 'imagemap-image',
|
|
40
58
|
titleObj = this.normalizeTitle(String(this.firstChild), initImagemap ? 0 : 6, true, !initImagemap);
|
|
59
|
+
this.setAttribute('name', titleObj.title);
|
|
41
60
|
this.#invalid = titleObj.interwiki || titleObj.ns !== 6; // 只用于gallery-image的首次解析
|
|
61
|
+
const /** @type {AstListener} */ linkListener = (e, data) => {
|
|
62
|
+
const {prevTarget} = e;
|
|
63
|
+
if (prevTarget?.type === 'link-target') {
|
|
64
|
+
const name = String(prevTarget),
|
|
65
|
+
imagemap = this.type === 'imagemap-image',
|
|
66
|
+
{title, interwiki, ns, valid} = this.normalizeTitle(name, imagemap ? 0 : 6, true, !imagemap);
|
|
67
|
+
if (!valid) {
|
|
68
|
+
undo(e, data);
|
|
69
|
+
throw new Error(`非法的图片文件名:${name}`);
|
|
70
|
+
} else if (interwiki || ns !== 6) {
|
|
71
|
+
undo(e, data);
|
|
72
|
+
throw new Error(`图片链接不可更改命名空间:${name}`);
|
|
73
|
+
}
|
|
74
|
+
this.setAttribute('name', title);
|
|
75
|
+
this.#invalid = false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
this.addEventListener(['remove', 'insert', 'replace', 'text'], linkListener);
|
|
42
79
|
}
|
|
43
80
|
|
|
44
81
|
/** @override */
|
|
@@ -57,6 +94,27 @@ class GalleryImageToken extends FileToken {
|
|
|
57
94
|
}
|
|
58
95
|
return errors;
|
|
59
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @override
|
|
100
|
+
* @param {string} link 链接目标
|
|
101
|
+
* @throws `SyntaxError` 非法的链接目标
|
|
102
|
+
*/
|
|
103
|
+
setTarget(link) {
|
|
104
|
+
link = String(link);
|
|
105
|
+
const include = this.getAttribute('include'),
|
|
106
|
+
config = this.getAttribute('config'),
|
|
107
|
+
root = Parser.parse(`<gallery>${link}</gallery>`, include, 1, config),
|
|
108
|
+
{length, firstChild: gallery} = root,
|
|
109
|
+
{type, lastChild: {length: galleryLength, firstChild: image}} = gallery;
|
|
110
|
+
if (length !== 1 || type !== 'ext' || galleryLength !== 1 || image.type !== 'gallery-image') {
|
|
111
|
+
throw new SyntaxError(`非法的图库文件名:${link}`);
|
|
112
|
+
}
|
|
113
|
+
const {firstChild} = image;
|
|
114
|
+
image.destroy(true);
|
|
115
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
116
|
+
}
|
|
60
117
|
}
|
|
61
118
|
|
|
119
|
+
Parser.classes.GalleryImageToken = __filename;
|
|
62
120
|
module.exports = GalleryImageToken;
|
package/src/link/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {generateForChild} = require('../../util/lint'),
|
|
4
|
+
{noWrap} = require('../../util/string'),
|
|
5
|
+
{undo} = require('../../util/debug'),
|
|
4
6
|
Parser = require('../..'),
|
|
7
|
+
Title = require('../../lib/title'),
|
|
5
8
|
AstText = require('../../lib/text'),
|
|
6
9
|
Token = require('..'),
|
|
7
10
|
AtomToken = require('../atom');
|
|
@@ -17,36 +20,127 @@ class LinkToken extends Token {
|
|
|
17
20
|
#fragment = '';
|
|
18
21
|
#encoded = false;
|
|
19
22
|
|
|
23
|
+
/** 完整链接,和FileToken保持一致 */
|
|
24
|
+
get link() {
|
|
25
|
+
return String(this.#getTitle());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set link(link) {
|
|
29
|
+
this.setTarget(link);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 是否链接到自身 */
|
|
33
|
+
get selfLink() {
|
|
34
|
+
return !this.#getTitle().title;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set selfLink(selfLink) {
|
|
38
|
+
if (selfLink === true) {
|
|
39
|
+
this.asSelfLink();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** fragment */
|
|
44
|
+
get fragment() {
|
|
45
|
+
return this.#getTitle().fragment;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
set fragment(fragment) {
|
|
49
|
+
this.setFragment(fragment);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** interwiki */
|
|
53
|
+
get interwiki() {
|
|
54
|
+
return this.#getTitle().interwiki;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set interwiki(interwiki) {
|
|
58
|
+
if (typeof interwiki !== 'string') {
|
|
59
|
+
this.typeError('set interwiki', 'String');
|
|
60
|
+
}
|
|
61
|
+
const {prefix, main, fragment} = this.#getTitle(),
|
|
62
|
+
link = `${interwiki}:${prefix}${main}${fragment && '#'}${fragment}`;
|
|
63
|
+
if (interwiki && !this.isInterwiki(link)) {
|
|
64
|
+
throw new RangeError(`${interwiki} 不是合法的跨维基前缀!`);
|
|
65
|
+
}
|
|
66
|
+
this.setTarget(link);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 链接显示文字 */
|
|
70
|
+
get innerText() {
|
|
71
|
+
if (this.type === 'link') {
|
|
72
|
+
return this.length > 1
|
|
73
|
+
? this.lastChild.text()
|
|
74
|
+
: this.firstChild.text().replace(/^\s*:/u, '');
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
20
79
|
/**
|
|
21
80
|
* @param {string} link 链接标题
|
|
22
81
|
* @param {string|undefined} linkText 链接显示文字
|
|
82
|
+
* @param {Title} title 链接标题对象
|
|
23
83
|
* @param {accum} accum
|
|
24
84
|
* @param {string} delimiter `|`
|
|
25
85
|
*/
|
|
26
86
|
constructor(link, linkText, title, config = Parser.getConfig(), accum = [], delimiter = '|') {
|
|
27
87
|
super(undefined, config, true, accum, {
|
|
88
|
+
AtomToken: 0, Token: 1,
|
|
28
89
|
});
|
|
29
90
|
this.insertAt(new AtomToken(link, 'link-target', config, accum, {
|
|
91
|
+
'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '',
|
|
30
92
|
}));
|
|
31
93
|
if (linkText !== undefined) {
|
|
32
94
|
const inner = new Token(linkText, config, true, accum, {
|
|
95
|
+
'Stage-5': ':', ConverterToken: ':',
|
|
33
96
|
});
|
|
34
97
|
inner.type = 'link-text';
|
|
35
98
|
this.insertAt(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
|
|
36
99
|
}
|
|
37
100
|
this.#delimiter = delimiter;
|
|
101
|
+
this.getAttribute('protectChildren')(0);
|
|
38
102
|
}
|
|
39
103
|
|
|
40
104
|
/**
|
|
41
105
|
* @override
|
|
106
|
+
* @throws `Error` 非法的内链目标
|
|
107
|
+
* @throws `Error` 不可更改命名空间
|
|
42
108
|
*/
|
|
43
109
|
afterBuild() {
|
|
44
110
|
const titleObj = this.normalizeTitle(this.firstChild.text(), 0, false, true, true);
|
|
111
|
+
this.setAttribute('name', titleObj.title);
|
|
45
112
|
this.#fragment = titleObj.fragment;
|
|
46
113
|
this.#encoded = titleObj.encoded;
|
|
47
114
|
if (this.#delimiter?.includes('\0')) {
|
|
48
115
|
this.#delimiter = this.getAttribute('buildFromStr')(this.#delimiter, 'string');
|
|
49
116
|
}
|
|
117
|
+
const /** @type {AstListener} */ linkListener = (e, data) => {
|
|
118
|
+
const {prevTarget} = e;
|
|
119
|
+
if (prevTarget?.type === 'link-target') {
|
|
120
|
+
const name = prevTarget.text(),
|
|
121
|
+
{title, interwiki, ns, valid, fragment, encoded} = this.normalizeTitle(name, 0, false, true, true);
|
|
122
|
+
if (!valid) {
|
|
123
|
+
undo(e, data);
|
|
124
|
+
throw new Error(`非法的内链目标:${name}`);
|
|
125
|
+
} else if (this.type === 'category' && (interwiki || ns !== 14)
|
|
126
|
+
|| this.type === 'file' && (interwiki || ns !== 6)
|
|
127
|
+
) {
|
|
128
|
+
undo(e, data);
|
|
129
|
+
throw new Error(`${this.type === 'file' ? '文件' : '分类'}链接不可更改命名空间:${name}`);
|
|
130
|
+
} else if (this.type === 'link' && !interwiki && (ns === 6 || ns === 14) && name.trim()[0] !== ':') {
|
|
131
|
+
const /** @type {{firstChild: AstText}} */ {firstChild} = prevTarget;
|
|
132
|
+
if (firstChild.type === 'text') {
|
|
133
|
+
firstChild.insertData(0, ':');
|
|
134
|
+
} else {
|
|
135
|
+
prevTarget.prepend(':');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
this.setAttribute('name', title);
|
|
139
|
+
this.#fragment = fragment;
|
|
140
|
+
this.#encoded = encoded;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
this.addEventListener(['remove', 'insert', 'replace', 'text'], linkListener);
|
|
50
144
|
}
|
|
51
145
|
|
|
52
146
|
/**
|
|
@@ -65,10 +159,11 @@ class LinkToken extends Token {
|
|
|
65
159
|
|
|
66
160
|
/**
|
|
67
161
|
* @override
|
|
162
|
+
* @param {string} selector
|
|
68
163
|
*/
|
|
69
164
|
toString(selector) {
|
|
70
165
|
const str = super.toString(selector, this.#delimiter);
|
|
71
|
-
return this.#bracket ? `[[${str}]]` : str;
|
|
166
|
+
return this.#bracket && !(selector && this.matches(selector)) ? `[[${str}]]` : str;
|
|
72
167
|
}
|
|
73
168
|
|
|
74
169
|
/** @override */
|
|
@@ -87,6 +182,11 @@ class LinkToken extends Token {
|
|
|
87
182
|
return this.#delimiter.length;
|
|
88
183
|
}
|
|
89
184
|
|
|
185
|
+
/** @override */
|
|
186
|
+
print() {
|
|
187
|
+
return super.print(this.#bracket ? {pre: '[[', post: ']]', sep: this.#delimiter} : {sep: this.#delimiter});
|
|
188
|
+
}
|
|
189
|
+
|
|
90
190
|
/**
|
|
91
191
|
* @override
|
|
92
192
|
* @param {number} start 起始位置
|
|
@@ -110,6 +210,174 @@ class LinkToken extends Token {
|
|
|
110
210
|
}
|
|
111
211
|
return errors;
|
|
112
212
|
}
|
|
213
|
+
|
|
214
|
+
/** 生成Title对象 */
|
|
215
|
+
#getTitle() {
|
|
216
|
+
return this.normalizeTitle(this.firstChild.text(), 0, false, true, true);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @override
|
|
221
|
+
* @this {LinkToken & {constructor: typeof LinkToken}}
|
|
222
|
+
*/
|
|
223
|
+
cloneNode() {
|
|
224
|
+
const [link, ...linkText] = this.cloneChildNodes();
|
|
225
|
+
return Parser.run(() => {
|
|
226
|
+
const token = new this.constructor('', undefined, this.#getTitle(), this.getAttribute('config'));
|
|
227
|
+
token.firstChild.safeReplaceWith(link);
|
|
228
|
+
token.append(...linkText);
|
|
229
|
+
token.afterBuild();
|
|
230
|
+
return token;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 设置链接目标
|
|
236
|
+
* @param {string} link 链接目标
|
|
237
|
+
* @throws `SyntaxError` 非法的链接目标
|
|
238
|
+
*/
|
|
239
|
+
setTarget(link) {
|
|
240
|
+
link = String(link);
|
|
241
|
+
if (this.type === 'link' && !/^\s*[:#]/u.test(link)) {
|
|
242
|
+
link = `:${link}`;
|
|
243
|
+
}
|
|
244
|
+
const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
245
|
+
{length, firstChild: wikiLink} = root,
|
|
246
|
+
{type, firstChild, length: linkLength} = wikiLink;
|
|
247
|
+
if (length !== 1 || type !== this.type || linkLength !== 1) {
|
|
248
|
+
const msgs = {link: '内链', file: '文件链接', category: '分类'};
|
|
249
|
+
throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
|
|
250
|
+
}
|
|
251
|
+
wikiLink.destroy(true);
|
|
252
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 设置跨语言链接
|
|
257
|
+
* @param {string} lang 语言前缀
|
|
258
|
+
* @param {string} link 页面标题
|
|
259
|
+
* @throws `SyntaxError` 非法的跨语言链接
|
|
260
|
+
*/
|
|
261
|
+
setLangLink(lang, link) {
|
|
262
|
+
if (typeof lang !== 'string') {
|
|
263
|
+
this.typeError('setLangLink', 'String');
|
|
264
|
+
}
|
|
265
|
+
link = String(link).trim();
|
|
266
|
+
const [char] = link;
|
|
267
|
+
if (char === '#') {
|
|
268
|
+
throw new SyntaxError('跨语言链接不能仅为fragment!');
|
|
269
|
+
} else if (char === ':') {
|
|
270
|
+
link = link.slice(1);
|
|
271
|
+
}
|
|
272
|
+
const root = Parser.parse(`[[${lang}:${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
273
|
+
/** @type {Token & {firstChild: LinkToken}} */ {length, firstChild: wikiLink} = root,
|
|
274
|
+
{type, length: linkLength, interwiki, firstChild} = wikiLink;
|
|
275
|
+
if (length !== 1 || type !== 'link' || linkLength !== 1 || interwiki !== lang.toLowerCase()) {
|
|
276
|
+
throw new SyntaxError(`非法的跨语言链接目标:${lang}:${link}`);
|
|
277
|
+
}
|
|
278
|
+
wikiLink.destroy(true);
|
|
279
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 设置fragment
|
|
284
|
+
* @param {string} fragment fragment
|
|
285
|
+
* @param {boolean} page 是否是其他页面
|
|
286
|
+
* @throws `SyntaxError` 非法的fragment
|
|
287
|
+
*/
|
|
288
|
+
#setFragment(fragment, page = true) {
|
|
289
|
+
fragment = String(fragment).replace(/[<>[\]#|=]/gu, p => encodeURIComponent(p));
|
|
290
|
+
const include = this.getAttribute('include'),
|
|
291
|
+
config = this.getAttribute('config'),
|
|
292
|
+
root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
|
|
293
|
+
{length, firstChild: wikiLink} = root,
|
|
294
|
+
{type, length: linkLength, firstChild} = wikiLink;
|
|
295
|
+
if (length !== 1 || type !== 'link' || linkLength !== 1) {
|
|
296
|
+
throw new SyntaxError(`非法的 fragment:${fragment}`);
|
|
297
|
+
} else if (page) {
|
|
298
|
+
Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
|
|
299
|
+
}
|
|
300
|
+
wikiLink.destroy(true);
|
|
301
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* 设置fragment
|
|
306
|
+
* @param {string} fragment fragment
|
|
307
|
+
*/
|
|
308
|
+
setFragment(fragment) {
|
|
309
|
+
this.#setFragment(fragment);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 修改为到自身的链接
|
|
314
|
+
* @param {string} fragment fragment
|
|
315
|
+
* @throws `RangeError` 空fragment
|
|
316
|
+
*/
|
|
317
|
+
asSelfLink(fragment = this.fragment) {
|
|
318
|
+
fragment = String(fragment);
|
|
319
|
+
if (!fragment.trim()) {
|
|
320
|
+
throw new RangeError(`${this.constructor.name}.asSelfLink 方法必须指定非空的 fragment!`);
|
|
321
|
+
}
|
|
322
|
+
this.#setFragment(fragment, false);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 设置链接显示文字
|
|
327
|
+
* @param {string} linkText 链接显示文字
|
|
328
|
+
* @throws `SyntaxError` 非法的链接显示文字
|
|
329
|
+
*/
|
|
330
|
+
setLinkText(linkText = '') {
|
|
331
|
+
linkText = String(linkText);
|
|
332
|
+
let lastChild;
|
|
333
|
+
const config = this.getAttribute('config');
|
|
334
|
+
if (linkText) {
|
|
335
|
+
const root = Parser.parse(`[[${
|
|
336
|
+
this.type === 'category' ? 'Category:' : ''
|
|
337
|
+
}L|${linkText}]]`, this.getAttribute('include'), 6, config),
|
|
338
|
+
{length, firstChild: wikiLink} = root;
|
|
339
|
+
if (length !== 1 || wikiLink.type !== this.type || wikiLink.length !== 2) {
|
|
340
|
+
throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${noWrap(linkText)}`);
|
|
341
|
+
}
|
|
342
|
+
({lastChild} = wikiLink);
|
|
343
|
+
} else {
|
|
344
|
+
lastChild = Parser.run(() => new Token('', config));
|
|
345
|
+
lastChild.setAttribute('stage', 7).type = 'link-text';
|
|
346
|
+
}
|
|
347
|
+
if (this.length === 1) {
|
|
348
|
+
this.insertAt(lastChild);
|
|
349
|
+
} else {
|
|
350
|
+
this.lastChild.safeReplaceWith(lastChild);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 自动生成管道符后的链接文字
|
|
356
|
+
* @throws `Error` 带有"#"或"%"时不可用
|
|
357
|
+
*/
|
|
358
|
+
pipeTrick() {
|
|
359
|
+
const linkText = this.firstChild.text();
|
|
360
|
+
if (linkText.includes('#') || linkText.includes('%')) {
|
|
361
|
+
throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
|
|
362
|
+
}
|
|
363
|
+
const m1 = /^:?(?:[ \w\x80-\xFF-]+:)?([^(]+)\(.+\)$/u.exec(linkText);
|
|
364
|
+
if (m1) {
|
|
365
|
+
this.setLinkText(m1[1].trim());
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const m2 = /^:?(?:[ \w\x80-\xFF-]+:)?([^(]+)(.+)$/u.exec(linkText);
|
|
369
|
+
if (m2) {
|
|
370
|
+
this.setLinkText(m2[1].trim());
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const m3 = /^:?(?:[ \w\x80-\xFF-]+:)?(.+?)(?:(?<!\()\(.+\))?(?:, |,|، )./u.exec(linkText);
|
|
374
|
+
if (m3) {
|
|
375
|
+
this.setLinkText(m3[1].trim());
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.setLinkText(linkText);
|
|
379
|
+
}
|
|
113
380
|
}
|
|
114
381
|
|
|
382
|
+
Parser.classes.LinkToken = __filename;
|
|
115
383
|
module.exports = LinkToken;
|