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/link/file.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Title = require('../../lib/title'),
|
|
4
4
|
{explode, noWrap} = require('../../util/string'),
|
|
5
5
|
{externalUse} = require('../../util/debug'),
|
|
6
|
+
{generateForChild} = require('../../util/lint'),
|
|
6
7
|
Parser = require('../..'),
|
|
7
8
|
LinkToken = require('.'),
|
|
8
9
|
ImageParameterToken = require('../imageParameter');
|
|
@@ -16,12 +17,6 @@ class FileToken extends LinkToken {
|
|
|
16
17
|
/** @type {Set<string>} */ #keys = new Set();
|
|
17
18
|
/** @type {Record<string, Set<ImageParameterToken>>} */ #args = {};
|
|
18
19
|
|
|
19
|
-
setLangLink = undefined;
|
|
20
|
-
setFragment = undefined;
|
|
21
|
-
asSelfLink = undefined;
|
|
22
|
-
setLinkText = undefined;
|
|
23
|
-
pipeTrick = undefined;
|
|
24
|
-
|
|
25
20
|
/** 图片链接 */
|
|
26
21
|
get link() {
|
|
27
22
|
return this.getArg('link')?.link;
|
|
@@ -69,42 +64,45 @@ class FileToken extends LinkToken {
|
|
|
69
64
|
* @param {string|undefined} text 图片参数
|
|
70
65
|
* @param {Title} title 文件标题对象
|
|
71
66
|
* @param {accum} accum
|
|
67
|
+
* @param {string} delimiter `|`
|
|
72
68
|
* @complexity `n`
|
|
73
69
|
*/
|
|
74
|
-
constructor(link, text, title, config = Parser.getConfig(), accum = []) {
|
|
75
|
-
super(link, undefined, title, config, accum);
|
|
70
|
+
constructor(link, text, title, config = Parser.getConfig(), accum = [], delimiter = '|') {
|
|
71
|
+
super(link, undefined, title, config, accum, delimiter);
|
|
76
72
|
this.setAttribute('acceptable', {AtomToken: 0, ImageParameterToken: '1:'});
|
|
77
73
|
this.append(...explode('-{', '}-', '|', text).map(part => new ImageParameterToken(part, config, accum)));
|
|
78
|
-
this.seal(
|
|
74
|
+
this.seal(
|
|
75
|
+
['selfLink', 'interwiki', 'setLangLink', 'setFragment', 'asSelfLink', 'setLinkText', 'pipeTrick'],
|
|
76
|
+
true,
|
|
77
|
+
);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
/**
|
|
82
81
|
* @override
|
|
83
|
-
* @param {number}
|
|
84
|
-
* @complexity `n`
|
|
82
|
+
* @param {number} start 起始位置
|
|
85
83
|
*/
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this
|
|
84
|
+
lint(start = 0) {
|
|
85
|
+
const errors = super.lint(start),
|
|
86
|
+
frameArgs = this.getFrameArgs(),
|
|
87
|
+
horizAlignArgs = this.getHorizAlignArgs(),
|
|
88
|
+
vertAlignArgs = this.getVertAlignArgs(),
|
|
89
|
+
captions = this.getArgs('caption');
|
|
90
|
+
if (frameArgs.length > 1 || horizAlignArgs.length > 1 || vertAlignArgs.length > 1 || captions.size > 1) {
|
|
91
|
+
const rect = this.getRootNode().posFromIndex(start);
|
|
92
|
+
if (frameArgs.length > 1) {
|
|
93
|
+
errors.push(...frameArgs.map(arg => generateForChild(arg, rect, '重复或冲突的图片框架参数')));
|
|
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
|
+
}
|
|
101
|
+
if (captions.size > 1) {
|
|
102
|
+
errors.push(...[...captions].map(arg => generateForChild(arg, rect, '重复的图片说明')));
|
|
103
|
+
}
|
|
92
104
|
}
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @override
|
|
98
|
-
* @param {ImageParameterToken} token 待插入的子节点
|
|
99
|
-
* @param {number} i 插入位置
|
|
100
|
-
* @complexity `n`
|
|
101
|
-
*/
|
|
102
|
-
insertAt(token, i = this.childNodes.length) {
|
|
103
|
-
if (!Parser.running) {
|
|
104
|
-
this.getArgs(token.name, false, false).add(token);
|
|
105
|
-
this.#keys.add(token.name);
|
|
106
|
-
}
|
|
107
|
-
return super.insertAt(token, i);
|
|
105
|
+
return errors;
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
/**
|
|
@@ -115,6 +113,27 @@ class FileToken extends LinkToken {
|
|
|
115
113
|
return this.childNodes.slice(1);
|
|
116
114
|
}
|
|
117
115
|
|
|
116
|
+
/**
|
|
117
|
+
* 获取指定图片参数
|
|
118
|
+
* @param {string} key 参数名
|
|
119
|
+
* @param {boolean} copy 是否返回备份
|
|
120
|
+
* @complexity `n`
|
|
121
|
+
*/
|
|
122
|
+
getArgs(key, copy = true) {
|
|
123
|
+
if (typeof key !== 'string') {
|
|
124
|
+
this.typeError('getArgs', 'String');
|
|
125
|
+
}
|
|
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;
|
|
135
|
+
}
|
|
136
|
+
|
|
118
137
|
/**
|
|
119
138
|
* 获取图片框架属性参数节点
|
|
120
139
|
* @complexity `n`
|
|
@@ -129,31 +148,31 @@ class FileToken extends LinkToken {
|
|
|
129
148
|
}
|
|
130
149
|
|
|
131
150
|
/**
|
|
132
|
-
*
|
|
133
|
-
* @param {string} key 参数名
|
|
134
|
-
* @param {boolean} copy 是否返回备份
|
|
151
|
+
* 获取图片水平对齐参数节点
|
|
135
152
|
* @complexity `n`
|
|
136
153
|
*/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
let args = this.#args[key];
|
|
143
|
-
if (!args) {
|
|
144
|
-
args = new Set(this.getAllArgs().filter(({name}) => key === name));
|
|
145
|
-
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} 会生效!`);
|
|
146
159
|
}
|
|
147
|
-
return
|
|
160
|
+
return args;
|
|
148
161
|
}
|
|
149
162
|
|
|
150
163
|
/**
|
|
151
|
-
*
|
|
152
|
-
* @param {string} key 参数名
|
|
164
|
+
* 获取图片垂直对齐参数节点
|
|
153
165
|
* @complexity `n`
|
|
154
166
|
*/
|
|
155
|
-
|
|
156
|
-
|
|
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;
|
|
157
176
|
}
|
|
158
177
|
|
|
159
178
|
/**
|
|
@@ -165,6 +184,15 @@ class FileToken extends LinkToken {
|
|
|
165
184
|
return [...this.getArgs(key, false)].sort((a, b) => a.compareDocumentPosition(b)).at(-1);
|
|
166
185
|
}
|
|
167
186
|
|
|
187
|
+
/**
|
|
188
|
+
* 是否具有指定图片参数
|
|
189
|
+
* @param {string} key 参数名
|
|
190
|
+
* @complexity `n`
|
|
191
|
+
*/
|
|
192
|
+
hasArg(key) {
|
|
193
|
+
return this.getArgs(key, false).size > 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
168
196
|
/**
|
|
169
197
|
* 移除指定图片参数
|
|
170
198
|
* @param {string} key 参数名
|
|
@@ -203,7 +231,7 @@ class FileToken extends LinkToken {
|
|
|
203
231
|
* 获取生效的指定图片参数值
|
|
204
232
|
* @template {string|undefined} T
|
|
205
233
|
* @param {T} key 参数名
|
|
206
|
-
* @returns {T extends undefined ?
|
|
234
|
+
* @returns {T extends undefined ? Record<string, string> : string|true}
|
|
207
235
|
* @complexity `n`
|
|
208
236
|
*/
|
|
209
237
|
getValue(key) {
|
|
@@ -246,18 +274,46 @@ class FileToken extends LinkToken {
|
|
|
246
274
|
this.typeError('setValue', 'Boolean');
|
|
247
275
|
}
|
|
248
276
|
const newArg = Parser.run(() => new ImageParameterToken(syntax, config));
|
|
249
|
-
this.
|
|
277
|
+
this.insertAt(newArg);
|
|
250
278
|
return;
|
|
251
279
|
}
|
|
252
280
|
const wikitext = `[[File:F|${syntax ? syntax.replace('$1', value) : value}]]`,
|
|
253
281
|
root = Parser.parse(wikitext, this.getAttribute('include'), 6, config),
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
) {
|
|
282
|
+
{length, firstChild: file} = root,
|
|
283
|
+
{name, type, length: fileLength, lastChild: imageParameter} = file;
|
|
284
|
+
if (length !== 1 || type !== 'file' || name !== 'File:F' || fileLength !== 2 || imageParameter.name !== key) {
|
|
258
285
|
throw new SyntaxError(`非法的 ${key} 参数:${noWrap(value)}`);
|
|
259
286
|
}
|
|
260
|
-
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);
|
|
261
317
|
}
|
|
262
318
|
}
|
|
263
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() {
|
|
@@ -62,12 +64,12 @@ class LinkToken extends Token {
|
|
|
62
64
|
|
|
63
65
|
/** 链接显示文字 */
|
|
64
66
|
get innerText() {
|
|
65
|
-
if (this.type
|
|
66
|
-
return
|
|
67
|
+
if (this.type === 'link') {
|
|
68
|
+
return this.childNodes.length > 1
|
|
69
|
+
? this.lastChild.text()
|
|
70
|
+
: this.firstChild.text().replace(/^\s*:/u, '');
|
|
67
71
|
}
|
|
68
|
-
return
|
|
69
|
-
? this.lastElementChild.text()
|
|
70
|
-
: this.firstElementChild.text().replace(/^\s*:/u, '');
|
|
72
|
+
return undefined;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/**
|
|
@@ -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.firstElementChild.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.firstElementChild.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,13 +162,37 @@ class LinkToken extends Token {
|
|
|
158
162
|
|
|
159
163
|
/** @override */
|
|
160
164
|
getGaps() {
|
|
161
|
-
return
|
|
165
|
+
return this.#delimiter.length;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** @override */
|
|
169
|
+
print() {
|
|
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
|
+
});
|
|
162
190
|
}
|
|
163
191
|
|
|
164
192
|
/** @override */
|
|
165
193
|
text() {
|
|
166
194
|
const str = super.text('|');
|
|
167
|
-
return this
|
|
195
|
+
return this.#bracket ? `[[${str}]]` : str;
|
|
168
196
|
}
|
|
169
197
|
|
|
170
198
|
/**
|
|
@@ -178,14 +206,14 @@ class LinkToken extends Token {
|
|
|
178
206
|
link = `:${link}`;
|
|
179
207
|
}
|
|
180
208
|
const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
181
|
-
{
|
|
182
|
-
|
|
183
|
-
|
|
209
|
+
{length, firstChild: wikiLink} = root,
|
|
210
|
+
{type, firstChild, length: linkLength} = wikiLink;
|
|
211
|
+
if (length !== 1 || type !== this.type || linkLength !== 1) {
|
|
212
|
+
const msgs = {link: '内链', file: '文件链接', category: '分类'};
|
|
184
213
|
throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
|
|
185
214
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.firstElementChild.safeReplaceWith(firstChild);
|
|
215
|
+
wikiLink.destroy(true);
|
|
216
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
189
217
|
}
|
|
190
218
|
|
|
191
219
|
/**
|
|
@@ -201,20 +229,18 @@ class LinkToken extends Token {
|
|
|
201
229
|
link = String(link).trim();
|
|
202
230
|
const [char] = link;
|
|
203
231
|
if (char === '#') {
|
|
204
|
-
throw new SyntaxError(
|
|
232
|
+
throw new SyntaxError('跨语言链接不能仅为fragment!');
|
|
205
233
|
} else if (char === ':') {
|
|
206
234
|
link = link.slice(1);
|
|
207
235
|
}
|
|
208
236
|
const root = Parser.parse(`[[${lang}:${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
209
|
-
/** @type {Token & {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
) {
|
|
237
|
+
/** @type {Token & {firstChild: LinkToken}} */ {length, firstChild: wikiLink} = root,
|
|
238
|
+
{type, length: linkLength, interwiki, firstChild} = wikiLink;
|
|
239
|
+
if (length !== 1 || type !== 'link' || linkLength !== 1 || interwiki !== lang.toLowerCase()) {
|
|
213
240
|
throw new SyntaxError(`非法的跨语言链接目标:${lang}:${link}`);
|
|
214
241
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.firstElementChild.safeReplaceWith(firstChild);
|
|
242
|
+
wikiLink.destroy(true);
|
|
243
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
218
244
|
}
|
|
219
245
|
|
|
220
246
|
/**
|
|
@@ -228,16 +254,15 @@ class LinkToken extends Token {
|
|
|
228
254
|
const include = this.getAttribute('include'),
|
|
229
255
|
config = this.getAttribute('config'),
|
|
230
256
|
root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
|
|
231
|
-
{
|
|
232
|
-
|
|
257
|
+
{length, firstChild: wikiLink} = root,
|
|
258
|
+
{type, length: linkLength, firstChild} = wikiLink;
|
|
259
|
+
if (length !== 1 || type !== 'link' || linkLength !== 1) {
|
|
233
260
|
throw new SyntaxError(`非法的 fragment:${fragment}`);
|
|
234
|
-
}
|
|
235
|
-
if (page) {
|
|
261
|
+
} else if (page) {
|
|
236
262
|
Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
|
|
237
263
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.firstElementChild.safeReplaceWith(firstChild);
|
|
264
|
+
wikiLink.destroy(true);
|
|
265
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
241
266
|
}
|
|
242
267
|
|
|
243
268
|
/**
|
|
@@ -268,25 +293,25 @@ class LinkToken extends Token {
|
|
|
268
293
|
*/
|
|
269
294
|
setLinkText(linkText = '') {
|
|
270
295
|
linkText = String(linkText);
|
|
271
|
-
let
|
|
296
|
+
let lastChild;
|
|
272
297
|
const config = this.getAttribute('config');
|
|
273
298
|
if (linkText) {
|
|
274
299
|
const root = Parser.parse(`[[${
|
|
275
300
|
this.type === 'category' ? 'Category:' : ''
|
|
276
301
|
}L|${linkText}]]`, this.getAttribute('include'), 6, config),
|
|
277
|
-
{
|
|
278
|
-
if (length !== 1 ||
|
|
302
|
+
{length, firstChild: wikiLink} = root;
|
|
303
|
+
if (length !== 1 || wikiLink.type !== this.type || wikiLink.childNodes.length !== 2) {
|
|
279
304
|
throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${noWrap(linkText)}`);
|
|
280
305
|
}
|
|
281
|
-
({
|
|
306
|
+
({lastChild} = wikiLink);
|
|
282
307
|
} else {
|
|
283
|
-
|
|
284
|
-
|
|
308
|
+
lastChild = Parser.run(() => new Token('', config));
|
|
309
|
+
lastChild.setAttribute('stage', 7).type = 'link-text';
|
|
285
310
|
}
|
|
286
311
|
if (this.childNodes.length === 1) {
|
|
287
|
-
this.
|
|
312
|
+
this.insertAt(lastChild);
|
|
288
313
|
} else {
|
|
289
|
-
this.
|
|
314
|
+
this.lastChild.safeReplaceWith(lastChild);
|
|
290
315
|
}
|
|
291
316
|
}
|
|
292
317
|
|
|
@@ -295,7 +320,7 @@ class LinkToken extends Token {
|
|
|
295
320
|
* @throws `Error` 带有"#"或"%"时不可用
|
|
296
321
|
*/
|
|
297
322
|
pipeTrick() {
|
|
298
|
-
const linkText = this.
|
|
323
|
+
const linkText = this.firstChild.text();
|
|
299
324
|
if (linkText.includes('#') || linkText.includes('%')) {
|
|
300
325
|
throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
|
|
301
326
|
}
|