wikiparser-node 0.5.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 +74 -65
- package/lib/element.js +125 -152
- package/lib/node.js +251 -223
- package/lib/ranges.js +2 -2
- package/lib/text.js +64 -64
- package/lib/title.js +8 -7
- package/mixin/hidden.js +2 -0
- package/mixin/sol.js +1 -2
- package/package.json +4 -3
- package/parser/brackets.js +8 -2
- package/parser/externalLinks.js +1 -1
- package/parser/hrAndDoubleUnderscore.js +4 -4
- package/parser/links.js +7 -7
- package/parser/table.js +12 -10
- package/src/arg.js +53 -48
- package/src/atom/index.js +7 -5
- package/src/attribute.js +91 -80
- package/src/charinsert.js +91 -0
- package/src/converter.js +22 -11
- package/src/converterFlags.js +72 -62
- package/src/converterRule.js +49 -49
- package/src/extLink.js +30 -28
- package/src/gallery.js +56 -32
- package/src/hasNowiki/index.js +42 -0
- package/src/hasNowiki/pre.js +40 -0
- package/src/heading.js +15 -11
- package/src/html.js +38 -38
- package/src/imageParameter.js +64 -48
- package/src/imagemap.js +205 -0
- package/src/imagemapLink.js +43 -0
- package/src/index.js +222 -124
- package/src/link/category.js +4 -8
- package/src/link/file.js +95 -59
- package/src/link/galleryImage.js +74 -10
- package/src/link/index.js +61 -39
- package/src/magicLink.js +21 -22
- 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 +17 -17
- package/src/nowiki/dd.js +2 -2
- package/src/nowiki/doubleUnderscore.js +14 -14
- package/src/nowiki/index.js +12 -0
- package/src/onlyinclude.js +10 -8
- package/src/paramTag/index.js +83 -0
- package/src/paramTag/inputbox.js +42 -0
- package/src/parameter.js +32 -18
- package/src/syntax.js +9 -1
- package/src/table/index.js +33 -32
- package/src/table/td.js +51 -57
- package/src/table/tr.js +6 -6
- package/src/tagPair/ext.js +58 -40
- package/src/tagPair/include.js +1 -1
- package/src/tagPair/index.js +21 -20
- package/src/transclude.js +158 -143
- package/tool/index.js +720 -439
- package/util/base.js +17 -0
- package/util/debug.js +1 -1
- package/util/diff.js +1 -1
- package/util/string.js +20 -20
package/src/link/category.js
CHANGED
|
@@ -11,11 +11,6 @@ const Title = require('../../lib/title'),
|
|
|
11
11
|
class CategoryToken extends LinkToken {
|
|
12
12
|
type = 'category';
|
|
13
13
|
|
|
14
|
-
setLangLink = undefined;
|
|
15
|
-
setFragment = undefined;
|
|
16
|
-
asSelfLink = undefined;
|
|
17
|
-
pipeTrick = undefined;
|
|
18
|
-
|
|
19
14
|
/** 分类排序关键字 */
|
|
20
15
|
get sortkey() {
|
|
21
16
|
return this.childNodes[1]?.text()?.replaceAll(
|
|
@@ -34,10 +29,11 @@ class CategoryToken extends LinkToken {
|
|
|
34
29
|
* @param {string|undefined} text 排序关键字
|
|
35
30
|
* @param {Title} title 分类页面标题对象
|
|
36
31
|
* @param {accum} accum
|
|
32
|
+
* @param {string} delimiter `|`
|
|
37
33
|
*/
|
|
38
|
-
constructor(link, text, title, config = Parser.getConfig(), accum = []) {
|
|
39
|
-
super(link, text, title, config, accum);
|
|
40
|
-
this.seal(['setFragment', 'asSelfLink', 'pipeTrick'], true);
|
|
34
|
+
constructor(link, text, title, config = Parser.getConfig(), accum = [], delimiter = '|') {
|
|
35
|
+
super(link, text, title, config, accum, delimiter);
|
|
36
|
+
this.seal(['selfLink', 'interwiki', 'setLangLink', 'setFragment', 'asSelfLink', 'pipeTrick'], true);
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
/**
|
package/src/link/file.js
CHANGED
|
@@ -17,12 +17,6 @@ class FileToken extends LinkToken {
|
|
|
17
17
|
/** @type {Set<string>} */ #keys = new Set();
|
|
18
18
|
/** @type {Record<string, Set<ImageParameterToken>>} */ #args = {};
|
|
19
19
|
|
|
20
|
-
setLangLink = undefined;
|
|
21
|
-
setFragment = undefined;
|
|
22
|
-
asSelfLink = undefined;
|
|
23
|
-
setLinkText = undefined;
|
|
24
|
-
pipeTrick = undefined;
|
|
25
|
-
|
|
26
20
|
/** 图片链接 */
|
|
27
21
|
get link() {
|
|
28
22
|
return this.getArg('link')?.link;
|
|
@@ -70,13 +64,17 @@ class FileToken extends LinkToken {
|
|
|
70
64
|
* @param {string|undefined} text 图片参数
|
|
71
65
|
* @param {Title} title 文件标题对象
|
|
72
66
|
* @param {accum} accum
|
|
67
|
+
* @param {string} delimiter `|`
|
|
73
68
|
* @complexity `n`
|
|
74
69
|
*/
|
|
75
|
-
constructor(link, text, title, config = Parser.getConfig(), accum = []) {
|
|
76
|
-
super(link, undefined, title, config, accum);
|
|
70
|
+
constructor(link, text, title, config = Parser.getConfig(), accum = [], delimiter = '|') {
|
|
71
|
+
super(link, undefined, title, config, accum, delimiter);
|
|
77
72
|
this.setAttribute('acceptable', {AtomToken: 0, ImageParameterToken: '1:'});
|
|
78
73
|
this.append(...explode('-{', '}-', '|', text).map(part => new ImageParameterToken(part, config, accum)));
|
|
79
|
-
this.seal(
|
|
74
|
+
this.seal(
|
|
75
|
+
['selfLink', 'interwiki', 'setLangLink', 'setFragment', 'asSelfLink', 'setLinkText', 'pipeTrick'],
|
|
76
|
+
true,
|
|
77
|
+
);
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
/**
|
|
@@ -86,12 +84,20 @@ class FileToken extends LinkToken {
|
|
|
86
84
|
lint(start = 0) {
|
|
87
85
|
const errors = super.lint(start),
|
|
88
86
|
frameArgs = this.getFrameArgs(),
|
|
87
|
+
horizAlignArgs = this.getHorizAlignArgs(),
|
|
88
|
+
vertAlignArgs = this.getVertAlignArgs(),
|
|
89
89
|
captions = this.getArgs('caption');
|
|
90
|
-
if (frameArgs.length > 1 || captions.size > 1) {
|
|
90
|
+
if (frameArgs.length > 1 || horizAlignArgs.length > 1 || vertAlignArgs.length > 1 || captions.size > 1) {
|
|
91
91
|
const rect = this.getRootNode().posFromIndex(start);
|
|
92
92
|
if (frameArgs.length > 1) {
|
|
93
93
|
errors.push(...frameArgs.map(arg => generateForChild(arg, rect, '重复或冲突的图片框架参数')));
|
|
94
94
|
}
|
|
95
|
+
if (horizAlignArgs.length > 1) {
|
|
96
|
+
errors.push(...horizAlignArgs.map(arg => generateForChild(arg, rect, '重复或冲突的图片水平对齐参数')));
|
|
97
|
+
}
|
|
98
|
+
if (vertAlignArgs.length > 1) {
|
|
99
|
+
errors.push(...vertAlignArgs.map(arg => generateForChild(arg, rect, '重复或冲突的图片垂直对齐参数')));
|
|
100
|
+
}
|
|
95
101
|
if (captions.size > 1) {
|
|
96
102
|
errors.push(...[...captions].map(arg => generateForChild(arg, rect, '重复的图片说明')));
|
|
97
103
|
}
|
|
@@ -100,40 +106,32 @@ class FileToken extends LinkToken {
|
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
/**
|
|
103
|
-
*
|
|
104
|
-
* @
|
|
105
|
-
* @complexity `n`
|
|
109
|
+
* 获取所有图片参数节点
|
|
110
|
+
* @returns {ImageParameterToken[]}
|
|
106
111
|
*/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
args = this.getArgs(token.name, false, false);
|
|
110
|
-
args.delete(token);
|
|
111
|
-
if (args.size === 0) {
|
|
112
|
-
this.#keys.delete(token.name);
|
|
113
|
-
}
|
|
114
|
-
return token;
|
|
112
|
+
getAllArgs() {
|
|
113
|
+
return this.childNodes.slice(1);
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
/**
|
|
118
|
-
*
|
|
119
|
-
* @param {
|
|
120
|
-
* @param {
|
|
117
|
+
* 获取指定图片参数
|
|
118
|
+
* @param {string} key 参数名
|
|
119
|
+
* @param {boolean} copy 是否返回备份
|
|
121
120
|
* @complexity `n`
|
|
122
121
|
*/
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
this.getArgs
|
|
126
|
-
this.#keys.add(token.name);
|
|
122
|
+
getArgs(key, copy = true) {
|
|
123
|
+
if (typeof key !== 'string') {
|
|
124
|
+
this.typeError('getArgs', 'String');
|
|
127
125
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return
|
|
126
|
+
copy ||= !Parser.debugging && externalUse('getArgs');
|
|
127
|
+
let args;
|
|
128
|
+
if (Object.hasOwn(this.#args, key)) {
|
|
129
|
+
args = this.#args[key];
|
|
130
|
+
} else {
|
|
131
|
+
args = new Set(this.getAllArgs().filter(({name}) => key === name));
|
|
132
|
+
this.#args[key] = args;
|
|
133
|
+
}
|
|
134
|
+
return copy ? new Set(args) : args;
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
/**
|
|
@@ -150,31 +148,31 @@ class FileToken extends LinkToken {
|
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
/**
|
|
153
|
-
*
|
|
154
|
-
* @param {string} key 参数名
|
|
155
|
-
* @param {boolean} copy 是否返回备份
|
|
151
|
+
* 获取图片水平对齐参数节点
|
|
156
152
|
* @complexity `n`
|
|
157
153
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
let args = this.#args[key];
|
|
164
|
-
if (!args) {
|
|
165
|
-
args = new Set(this.getAllArgs().filter(({name}) => key === name));
|
|
166
|
-
this.#args[key] = args;
|
|
154
|
+
getHorizAlignArgs() {
|
|
155
|
+
const args = this.getAllArgs()
|
|
156
|
+
.filter(({name}) => ['left', 'right', 'center', 'none'].includes(name));
|
|
157
|
+
if (args.length > 1) {
|
|
158
|
+
Parser.error(`图片 ${this.name} 带有 ${args.length} 个水平对齐参数,只有第 1 个 ${args[0].name} 会生效!`);
|
|
167
159
|
}
|
|
168
|
-
return
|
|
160
|
+
return args;
|
|
169
161
|
}
|
|
170
162
|
|
|
171
163
|
/**
|
|
172
|
-
*
|
|
173
|
-
* @param {string} key 参数名
|
|
164
|
+
* 获取图片垂直对齐参数节点
|
|
174
165
|
* @complexity `n`
|
|
175
166
|
*/
|
|
176
|
-
|
|
177
|
-
|
|
167
|
+
getVertAlignArgs() {
|
|
168
|
+
const args = this.getAllArgs().filter(
|
|
169
|
+
({name}) => ['baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom']
|
|
170
|
+
.includes(name),
|
|
171
|
+
);
|
|
172
|
+
if (args.length > 1) {
|
|
173
|
+
Parser.error(`图片 ${this.name} 带有 ${args.length} 个垂直对齐架参数,只有第 1 个 ${args[0].name} 会生效!`);
|
|
174
|
+
}
|
|
175
|
+
return args;
|
|
178
176
|
}
|
|
179
177
|
|
|
180
178
|
/**
|
|
@@ -186,6 +184,15 @@ class FileToken extends LinkToken {
|
|
|
186
184
|
return [...this.getArgs(key, false)].sort((a, b) => a.compareDocumentPosition(b)).at(-1);
|
|
187
185
|
}
|
|
188
186
|
|
|
187
|
+
/**
|
|
188
|
+
* 是否具有指定图片参数
|
|
189
|
+
* @param {string} key 参数名
|
|
190
|
+
* @complexity `n`
|
|
191
|
+
*/
|
|
192
|
+
hasArg(key) {
|
|
193
|
+
return this.getArgs(key, false).size > 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
189
196
|
/**
|
|
190
197
|
* 移除指定图片参数
|
|
191
198
|
* @param {string} key 参数名
|
|
@@ -224,7 +231,7 @@ class FileToken extends LinkToken {
|
|
|
224
231
|
* 获取生效的指定图片参数值
|
|
225
232
|
* @template {string|undefined} T
|
|
226
233
|
* @param {T} key 参数名
|
|
227
|
-
* @returns {T extends undefined ?
|
|
234
|
+
* @returns {T extends undefined ? Record<string, string> : string|true}
|
|
228
235
|
* @complexity `n`
|
|
229
236
|
*/
|
|
230
237
|
getValue(key) {
|
|
@@ -267,17 +274,46 @@ class FileToken extends LinkToken {
|
|
|
267
274
|
this.typeError('setValue', 'Boolean');
|
|
268
275
|
}
|
|
269
276
|
const newArg = Parser.run(() => new ImageParameterToken(syntax, config));
|
|
270
|
-
this.
|
|
277
|
+
this.insertAt(newArg);
|
|
271
278
|
return;
|
|
272
279
|
}
|
|
273
280
|
const wikitext = `[[File:F|${syntax ? syntax.replace('$1', value) : value}]]`,
|
|
274
281
|
root = Parser.parse(wikitext, this.getAttribute('include'), 6, config),
|
|
275
|
-
{
|
|
276
|
-
{name, type,
|
|
282
|
+
{length, firstChild: file} = root,
|
|
283
|
+
{name, type, length: fileLength, lastChild: imageParameter} = file;
|
|
277
284
|
if (length !== 1 || type !== 'file' || name !== 'File:F' || fileLength !== 2 || imageParameter.name !== key) {
|
|
278
285
|
throw new SyntaxError(`非法的 ${key} 参数:${noWrap(value)}`);
|
|
279
286
|
}
|
|
280
|
-
this.
|
|
287
|
+
this.insertAt(imageParameter);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @override
|
|
292
|
+
* @param {number} i 移除位置
|
|
293
|
+
* @complexity `n`
|
|
294
|
+
*/
|
|
295
|
+
removeAt(i) {
|
|
296
|
+
const /** @type {ImageParameterToken} */ token = super.removeAt(i),
|
|
297
|
+
args = this.getArgs(token.name, false, false);
|
|
298
|
+
args.delete(token);
|
|
299
|
+
if (args.size === 0) {
|
|
300
|
+
this.#keys.delete(token.name);
|
|
301
|
+
}
|
|
302
|
+
return token;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @override
|
|
307
|
+
* @param {ImageParameterToken} token 待插入的子节点
|
|
308
|
+
* @param {number} i 插入位置
|
|
309
|
+
* @complexity `n`
|
|
310
|
+
*/
|
|
311
|
+
insertAt(token, i = this.childNodes.length) {
|
|
312
|
+
if (!Parser.running) {
|
|
313
|
+
this.getArgs(token.name, false, false).add(token);
|
|
314
|
+
this.#keys.add(token.name);
|
|
315
|
+
}
|
|
316
|
+
return super.insertAt(token, i);
|
|
281
317
|
}
|
|
282
318
|
}
|
|
283
319
|
|
package/src/link/galleryImage.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {undo} = require('../../util/debug'),
|
|
4
|
+
{generateForSelf} = require('../../util/lint'),
|
|
5
|
+
Title = require('../../lib/title'),
|
|
4
6
|
Parser = require('../..'),
|
|
5
|
-
Token = require('..'),
|
|
6
7
|
FileToken = require('./file');
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -11,10 +12,7 @@ const Title = require('../../lib/title'),
|
|
|
11
12
|
*/
|
|
12
13
|
class GalleryImageToken extends FileToken {
|
|
13
14
|
type = 'gallery-image';
|
|
14
|
-
|
|
15
|
-
size = undefined;
|
|
16
|
-
width = undefined;
|
|
17
|
-
height = undefined;
|
|
15
|
+
#invalid = false;
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* @param {string} link 图片文件名
|
|
@@ -25,6 +23,7 @@ class GalleryImageToken extends FileToken {
|
|
|
25
23
|
constructor(link, text, title, config = Parser.getConfig(), accum = []) {
|
|
26
24
|
let token;
|
|
27
25
|
if (text !== undefined) {
|
|
26
|
+
const Token = require('..');
|
|
28
27
|
token = new Token(text, config, true, accum);
|
|
29
28
|
token.type = 'temp';
|
|
30
29
|
for (let n = 1; n < Parser.MAX_STAGE; n++) {
|
|
@@ -32,10 +31,43 @@ class GalleryImageToken extends FileToken {
|
|
|
32
31
|
}
|
|
33
32
|
accum.splice(accum.indexOf(token), 1);
|
|
34
33
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
super(link, token?.toString(), title, config, accum);
|
|
35
|
+
this.setAttribute('bracket', false);
|
|
36
|
+
if (!Object.values(config.img).includes('width')) {
|
|
37
|
+
this.seal(['size', 'width', 'height'], true);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @override
|
|
43
|
+
* @throws `Error` 非法的内链目标
|
|
44
|
+
* @throws `Error` 不可更改命名空间
|
|
45
|
+
*/
|
|
46
|
+
afterBuild() {
|
|
47
|
+
const {
|
|
48
|
+
title: initTitle, interwiki: initInterwiki, ns: initNs,
|
|
49
|
+
} = this.normalizeTitle(String(this.firstChild), this.type === 'imagemap-image' ? 0 : 6, true);
|
|
50
|
+
this.setAttribute('name', initTitle);
|
|
51
|
+
this.#invalid = initInterwiki || initNs !== 6; // 只用于gallery-image的首次解析
|
|
52
|
+
const /** @type {AstListener} */ linkListener = (e, data) => {
|
|
53
|
+
const {prevTarget} = e;
|
|
54
|
+
if (prevTarget?.type === 'link-target') {
|
|
55
|
+
const name = String(prevTarget),
|
|
56
|
+
defaultNs = this.type === 'imagemap-image' ? 0 : 6,
|
|
57
|
+
{title, interwiki, ns, valid} = this.normalizeTitle(name, defaultNs, true);
|
|
58
|
+
if (!valid) {
|
|
59
|
+
undo(e, data);
|
|
60
|
+
throw new Error(`非法的图片文件名:${name}`);
|
|
61
|
+
} else if (interwiki || ns !== 6) {
|
|
62
|
+
undo(e, data);
|
|
63
|
+
throw new Error(`图片链接不可更改命名空间:${name}`);
|
|
64
|
+
}
|
|
65
|
+
this.setAttribute('name', title);
|
|
66
|
+
this.#invalid = false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
this.addEventListener(['remove', 'insert', 'replace', 'text'], linkListener);
|
|
70
|
+
return this;
|
|
39
71
|
}
|
|
40
72
|
|
|
41
73
|
/** @override */
|
|
@@ -43,6 +75,18 @@ class GalleryImageToken extends FileToken {
|
|
|
43
75
|
return 0;
|
|
44
76
|
}
|
|
45
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @override
|
|
80
|
+
* @param {number} start 起始位置
|
|
81
|
+
*/
|
|
82
|
+
lint(start = 0) {
|
|
83
|
+
const errors = super.lint(start);
|
|
84
|
+
if (this.#invalid) {
|
|
85
|
+
errors.push(generateForSelf(this, this.getRootNode().posFromIndex(start), '无效的图库图片'));
|
|
86
|
+
}
|
|
87
|
+
return errors;
|
|
88
|
+
}
|
|
89
|
+
|
|
46
90
|
/**
|
|
47
91
|
* @override
|
|
48
92
|
* @param {string} selector
|
|
@@ -55,6 +99,26 @@ class GalleryImageToken extends FileToken {
|
|
|
55
99
|
text() {
|
|
56
100
|
return super.text().replaceAll('\n', ' ');
|
|
57
101
|
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @override
|
|
105
|
+
* @param {string} link 链接目标
|
|
106
|
+
* @throws `SyntaxError` 非法的链接目标
|
|
107
|
+
*/
|
|
108
|
+
setTarget(link) {
|
|
109
|
+
link = String(link);
|
|
110
|
+
const include = this.getAttribute('include'),
|
|
111
|
+
config = this.getAttribute('config'),
|
|
112
|
+
root = Parser.parse(`<gallery>${link}</gallery>`, include, 1, config),
|
|
113
|
+
{length, firstChild: gallery} = root,
|
|
114
|
+
{type, lastChild: {length: galleryLength, firstChild: image}} = gallery;
|
|
115
|
+
if (length !== 1 || type !== 'ext' || galleryLength !== 1 || image.type !== 'gallery-image') {
|
|
116
|
+
throw new SyntaxError(`非法的图库文件名:${link}`);
|
|
117
|
+
}
|
|
118
|
+
const {firstChild} = image;
|
|
119
|
+
image.destroy(true);
|
|
120
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
121
|
+
}
|
|
58
122
|
}
|
|
59
123
|
|
|
60
124
|
Parser.classes.GalleryImageToken = __filename;
|
package/src/link/index.js
CHANGED
|
@@ -13,6 +13,8 @@ const Title = require('../../lib/title'),
|
|
|
13
13
|
*/
|
|
14
14
|
class LinkToken extends Token {
|
|
15
15
|
type = 'link';
|
|
16
|
+
#bracket = true;
|
|
17
|
+
#delimiter;
|
|
16
18
|
|
|
17
19
|
/** 完整链接,和FileToken保持一致 */
|
|
18
20
|
get link() {
|
|
@@ -75,37 +77,21 @@ class LinkToken extends Token {
|
|
|
75
77
|
* @param {string|undefined} linkText 链接显示文字
|
|
76
78
|
* @param {Title} title 链接标题对象
|
|
77
79
|
* @param {accum} accum
|
|
80
|
+
* @param {string} delimiter `|`
|
|
78
81
|
*/
|
|
79
|
-
constructor(link, linkText, title, config = Parser.getConfig(), accum = []) {
|
|
82
|
+
constructor(link, linkText, title, config = Parser.getConfig(), accum = [], delimiter = '|') {
|
|
80
83
|
super(undefined, config, true, accum, {AtomToken: 0, Token: 1});
|
|
81
84
|
const AtomToken = require('../atom');
|
|
82
|
-
this.
|
|
85
|
+
this.insertAt(new AtomToken(link, 'link-target', config, accum, {
|
|
83
86
|
'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '',
|
|
84
87
|
}));
|
|
85
88
|
if (linkText !== undefined) {
|
|
86
89
|
const inner = new Token(linkText, config, true, accum, {'Stage-5': ':', ConverterToken: ':'});
|
|
87
90
|
inner.type = 'link-text';
|
|
88
|
-
this.
|
|
91
|
+
this.insertAt(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
|
|
89
92
|
}
|
|
90
|
-
this
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
/** 生成Title对象 */
|
|
94
|
-
#getTitle() {
|
|
95
|
-
return this.normalizeTitle(this.firstChild.text());
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** @override */
|
|
99
|
-
cloneNode() {
|
|
100
|
-
const [link, ...linkText] = this.cloneChildNodes();
|
|
101
|
-
return Parser.run(() => {
|
|
102
|
-
/** @type {this & {constructor: typeof LinkToken}} */
|
|
103
|
-
const {constructor} = this,
|
|
104
|
-
token = new constructor('', undefined, this.#getTitle(), this.getAttribute('config'));
|
|
105
|
-
token.firstChild.safeReplaceWith(link);
|
|
106
|
-
token.append(...linkText);
|
|
107
|
-
return token.afterBuild();
|
|
108
|
-
});
|
|
93
|
+
this.#delimiter = delimiter;
|
|
94
|
+
this.getAttribute('protectChildren')(0);
|
|
109
95
|
}
|
|
110
96
|
|
|
111
97
|
/**
|
|
@@ -114,6 +100,10 @@ class LinkToken extends Token {
|
|
|
114
100
|
* @throws `Error` 不可更改命名空间
|
|
115
101
|
*/
|
|
116
102
|
afterBuild() {
|
|
103
|
+
this.setAttribute('name', this.normalizeTitle(this.firstChild.text()).title);
|
|
104
|
+
if (this.#delimiter?.includes('\0')) {
|
|
105
|
+
this.#delimiter = this.getAttribute('buildFromStr')(this.#delimiter).map(String).join('');
|
|
106
|
+
}
|
|
117
107
|
const /** @type {AstListener} */ linkListener = (e, data) => {
|
|
118
108
|
const {prevTarget} = e;
|
|
119
109
|
if (prevTarget?.type === 'link-target') {
|
|
@@ -142,13 +132,27 @@ class LinkToken extends Token {
|
|
|
142
132
|
return this;
|
|
143
133
|
}
|
|
144
134
|
|
|
135
|
+
/**
|
|
136
|
+
* @override
|
|
137
|
+
* @template {string} T
|
|
138
|
+
* @param {T} key 属性键
|
|
139
|
+
* @param {TokenAttribute<T>} value 属性值
|
|
140
|
+
*/
|
|
141
|
+
setAttribute(key, value) {
|
|
142
|
+
if (key === 'bracket') {
|
|
143
|
+
this.#bracket = Boolean(value);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
return super.setAttribute(key, value);
|
|
147
|
+
}
|
|
148
|
+
|
|
145
149
|
/**
|
|
146
150
|
* @override
|
|
147
151
|
* @param {string} selector
|
|
148
152
|
*/
|
|
149
153
|
toString(selector) {
|
|
150
|
-
const str = super.toString(selector,
|
|
151
|
-
return this
|
|
154
|
+
const str = super.toString(selector, this.#delimiter);
|
|
155
|
+
return this.#bracket && !(selector && this.matches(selector)) ? `[[${str}]]` : str;
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
/** @override */
|
|
@@ -158,18 +162,37 @@ class LinkToken extends Token {
|
|
|
158
162
|
|
|
159
163
|
/** @override */
|
|
160
164
|
getGaps() {
|
|
161
|
-
return
|
|
165
|
+
return this.#delimiter.length;
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
/** @override */
|
|
165
169
|
print() {
|
|
166
|
-
return super.print(this
|
|
170
|
+
return super.print(this.#bracket ? {pre: '[[', post: ']]', sep: this.#delimiter} : {sep: this.#delimiter});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** 生成Title对象 */
|
|
174
|
+
#getTitle() {
|
|
175
|
+
return this.normalizeTitle(this.firstChild.text());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @override
|
|
180
|
+
* @this {LinkToken & {constructor: typeof LinkToken}}
|
|
181
|
+
*/
|
|
182
|
+
cloneNode() {
|
|
183
|
+
const [link, ...linkText] = this.cloneChildNodes();
|
|
184
|
+
return Parser.run(() => {
|
|
185
|
+
const token = new this.constructor('', undefined, this.#getTitle(), this.getAttribute('config'));
|
|
186
|
+
token.firstChild.safeReplaceWith(link);
|
|
187
|
+
token.append(...linkText);
|
|
188
|
+
return token.afterBuild();
|
|
189
|
+
});
|
|
167
190
|
}
|
|
168
191
|
|
|
169
192
|
/** @override */
|
|
170
193
|
text() {
|
|
171
194
|
const str = super.text('|');
|
|
172
|
-
return this
|
|
195
|
+
return this.#bracket ? `[[${str}]]` : str;
|
|
173
196
|
}
|
|
174
197
|
|
|
175
198
|
/**
|
|
@@ -183,10 +206,10 @@ class LinkToken extends Token {
|
|
|
183
206
|
link = `:${link}`;
|
|
184
207
|
}
|
|
185
208
|
const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
186
|
-
{
|
|
187
|
-
{type, firstChild,
|
|
209
|
+
{length, firstChild: wikiLink} = root,
|
|
210
|
+
{type, firstChild, length: linkLength} = wikiLink;
|
|
188
211
|
if (length !== 1 || type !== this.type || linkLength !== 1) {
|
|
189
|
-
const msgs = {link: '内链', file: '文件链接', category: '分类'
|
|
212
|
+
const msgs = {link: '内链', file: '文件链接', category: '分类'};
|
|
190
213
|
throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
|
|
191
214
|
}
|
|
192
215
|
wikiLink.destroy(true);
|
|
@@ -206,13 +229,13 @@ class LinkToken extends Token {
|
|
|
206
229
|
link = String(link).trim();
|
|
207
230
|
const [char] = link;
|
|
208
231
|
if (char === '#') {
|
|
209
|
-
throw new SyntaxError(
|
|
232
|
+
throw new SyntaxError('跨语言链接不能仅为fragment!');
|
|
210
233
|
} else if (char === ':') {
|
|
211
234
|
link = link.slice(1);
|
|
212
235
|
}
|
|
213
236
|
const root = Parser.parse(`[[${lang}:${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
214
|
-
/** @type {Token & {firstChild: LinkToken}} */ {
|
|
215
|
-
{type,
|
|
237
|
+
/** @type {Token & {firstChild: LinkToken}} */ {length, firstChild: wikiLink} = root,
|
|
238
|
+
{type, length: linkLength, interwiki, firstChild} = wikiLink;
|
|
216
239
|
if (length !== 1 || type !== 'link' || linkLength !== 1 || interwiki !== lang.toLowerCase()) {
|
|
217
240
|
throw new SyntaxError(`非法的跨语言链接目标:${lang}:${link}`);
|
|
218
241
|
}
|
|
@@ -231,12 +254,11 @@ class LinkToken extends Token {
|
|
|
231
254
|
const include = this.getAttribute('include'),
|
|
232
255
|
config = this.getAttribute('config'),
|
|
233
256
|
root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
|
|
234
|
-
{
|
|
235
|
-
{type,
|
|
257
|
+
{length, firstChild: wikiLink} = root,
|
|
258
|
+
{type, length: linkLength, firstChild} = wikiLink;
|
|
236
259
|
if (length !== 1 || type !== 'link' || linkLength !== 1) {
|
|
237
260
|
throw new SyntaxError(`非法的 fragment:${fragment}`);
|
|
238
|
-
}
|
|
239
|
-
if (page) {
|
|
261
|
+
} else if (page) {
|
|
240
262
|
Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
|
|
241
263
|
}
|
|
242
264
|
wikiLink.destroy(true);
|
|
@@ -277,7 +299,7 @@ class LinkToken extends Token {
|
|
|
277
299
|
const root = Parser.parse(`[[${
|
|
278
300
|
this.type === 'category' ? 'Category:' : ''
|
|
279
301
|
}L|${linkText}]]`, this.getAttribute('include'), 6, config),
|
|
280
|
-
{
|
|
302
|
+
{length, firstChild: wikiLink} = root;
|
|
281
303
|
if (length !== 1 || wikiLink.type !== this.type || wikiLink.childNodes.length !== 2) {
|
|
282
304
|
throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${noWrap(linkText)}`);
|
|
283
305
|
}
|
|
@@ -287,7 +309,7 @@ class LinkToken extends Token {
|
|
|
287
309
|
lastChild.setAttribute('stage', 7).type = 'link-text';
|
|
288
310
|
}
|
|
289
311
|
if (this.childNodes.length === 1) {
|
|
290
|
-
this.
|
|
312
|
+
this.insertAt(lastChild);
|
|
291
313
|
} else {
|
|
292
314
|
this.lastChild.safeReplaceWith(lastChild);
|
|
293
315
|
}
|
package/src/magicLink.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const {generateForChild} = require('../util/lint'),
|
|
4
4
|
Parser = require('..'),
|
|
5
|
-
AstText = require('../lib/text'),
|
|
6
5
|
Token = require('.');
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -21,11 +20,14 @@ class MagicLinkToken extends Token {
|
|
|
21
20
|
set protocol(value) {
|
|
22
21
|
if (typeof value !== 'string') {
|
|
23
22
|
this.typeError('protocol', 'String');
|
|
24
|
-
}
|
|
25
|
-
if (!new RegExp(`${this.#protocolRegex.source}$`, 'iu').test(value)) {
|
|
23
|
+
} else if (!new RegExp(`${this.#protocolRegex.source}$`, 'iu').test(value)) {
|
|
26
24
|
throw new RangeError(`非法的外链协议:${value}`);
|
|
27
25
|
}
|
|
28
|
-
this
|
|
26
|
+
const {link} = this;
|
|
27
|
+
if (!this.#protocolRegex.test(link)) {
|
|
28
|
+
throw new Error(`特殊外链无法更改协议!${link}`);
|
|
29
|
+
}
|
|
30
|
+
this.replaceChildren(link.replace(this.#protocolRegex, value));
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/** 和内链保持一致 */
|
|
@@ -76,25 +78,15 @@ class MagicLinkToken extends Token {
|
|
|
76
78
|
return errors;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
/** @override */
|
|
80
|
-
afterBuild() {
|
|
81
|
-
const ParameterToken = require('./parameter');
|
|
82
|
-
const /** @type {ParameterToken} */ parameter = this.closest('parameter');
|
|
83
|
-
if (parameter?.getValue() === this.text()) {
|
|
84
|
-
this.replaceWith(...this.childNodes);
|
|
85
|
-
}
|
|
86
|
-
return this;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
81
|
/** @override */
|
|
90
82
|
cloneNode() {
|
|
91
|
-
const cloned = this.cloneChildNodes()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
const cloned = this.cloneChildNodes();
|
|
84
|
+
return Parser.run(() => {
|
|
85
|
+
const token = new MagicLinkToken(undefined, this.type === 'ext-link-url', this.getAttribute('config'));
|
|
86
|
+
token.append(...cloned);
|
|
87
|
+
token.afterBuild();
|
|
88
|
+
return token;
|
|
89
|
+
});
|
|
98
90
|
}
|
|
99
91
|
|
|
100
92
|
/**
|
|
@@ -124,12 +116,19 @@ class MagicLinkToken extends Token {
|
|
|
124
116
|
setTarget(url) {
|
|
125
117
|
url = String(url);
|
|
126
118
|
const root = Parser.parse(url, this.getAttribute('include'), 9, this.getAttribute('config')),
|
|
127
|
-
{
|
|
119
|
+
{length, firstChild: freeExtLink} = root;
|
|
128
120
|
if (length !== 1 || freeExtLink.type !== 'free-ext-link') {
|
|
129
121
|
throw new SyntaxError(`非法的自由外链目标:${url}`);
|
|
130
122
|
}
|
|
131
123
|
this.replaceChildren(...freeExtLink.childNodes);
|
|
132
124
|
}
|
|
125
|
+
|
|
126
|
+
/** 是否是模板或魔术字参数 */
|
|
127
|
+
isParamValue() {
|
|
128
|
+
const ParameterToken = require('./parameter');
|
|
129
|
+
const /** @type {ParameterToken} */ parameter = this.closest('parameter');
|
|
130
|
+
return parameter?.getValue() === this.text();
|
|
131
|
+
}
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
Parser.classes.MagicLinkToken = __filename;
|