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.
Files changed (80) hide show
  1. package/README.md +39 -0
  2. package/config/llwiki.json +35 -0
  3. package/config/moegirl.json +44 -0
  4. package/config/zhwiki.json +466 -0
  5. package/index.js +259 -11
  6. package/lib/element.js +481 -7
  7. package/lib/node.js +552 -6
  8. package/lib/ranges.js +130 -0
  9. package/lib/text.js +112 -21
  10. package/lib/title.js +27 -0
  11. package/mixin/attributeParent.js +117 -0
  12. package/mixin/fixedToken.js +40 -0
  13. package/mixin/hidden.js +3 -0
  14. package/mixin/singleLine.js +31 -0
  15. package/mixin/sol.js +65 -0
  16. package/package.json +5 -4
  17. package/parser/brackets.js +1 -0
  18. package/parser/commentAndExt.js +4 -3
  19. package/parser/converter.js +1 -0
  20. package/parser/externalLinks.js +1 -0
  21. package/parser/hrAndDoubleUnderscore.js +1 -0
  22. package/parser/html.js +1 -0
  23. package/parser/links.js +5 -4
  24. package/parser/list.js +1 -0
  25. package/parser/magicLinks.js +5 -4
  26. package/parser/quotes.js +2 -1
  27. package/parser/selector.js +177 -0
  28. package/parser/table.js +1 -0
  29. package/src/arg.js +116 -2
  30. package/src/atom/hidden.js +2 -0
  31. package/src/atom/index.js +17 -0
  32. package/src/attribute.js +171 -4
  33. package/src/attributes.js +306 -4
  34. package/src/charinsert.js +57 -1
  35. package/src/converter.js +108 -2
  36. package/src/converterFlags.js +187 -0
  37. package/src/converterRule.js +184 -1
  38. package/src/extLink.js +120 -1
  39. package/src/gallery.js +55 -5
  40. package/src/hasNowiki/index.js +12 -0
  41. package/src/hasNowiki/pre.js +12 -0
  42. package/src/heading.js +55 -4
  43. package/src/html.js +118 -3
  44. package/src/imageParameter.js +176 -5
  45. package/src/imagemap.js +60 -1
  46. package/src/imagemapLink.js +13 -1
  47. package/src/index.js +527 -3
  48. package/src/link/category.js +37 -1
  49. package/src/link/file.js +159 -2
  50. package/src/link/galleryImage.js +59 -1
  51. package/src/link/index.js +269 -1
  52. package/src/magicLink.js +90 -9
  53. package/src/nested/choose.js +1 -0
  54. package/src/nested/combobox.js +1 -0
  55. package/src/nested/index.js +30 -3
  56. package/src/nested/references.js +1 -0
  57. package/src/nowiki/comment.js +25 -1
  58. package/src/nowiki/dd.js +47 -1
  59. package/src/nowiki/doubleUnderscore.js +31 -1
  60. package/src/nowiki/hr.js +20 -1
  61. package/src/nowiki/index.js +23 -1
  62. package/src/nowiki/list.js +5 -2
  63. package/src/nowiki/noinclude.js +14 -0
  64. package/src/nowiki/quote.js +16 -2
  65. package/src/onlyinclude.js +26 -1
  66. package/src/paramTag/index.js +24 -1
  67. package/src/paramTag/inputbox.js +44 -0
  68. package/src/parameter.js +148 -6
  69. package/src/syntax.js +68 -0
  70. package/src/table/index.js +940 -2
  71. package/src/table/td.js +225 -5
  72. package/src/table/tr.js +247 -2
  73. package/src/tagPair/ext.js +24 -4
  74. package/src/tagPair/include.js +24 -0
  75. package/src/tagPair/index.js +52 -2
  76. package/src/transclude.js +512 -11
  77. package/tool/index.js +1202 -0
  78. package/util/debug.js +73 -0
  79. package/util/string.js +48 -1
  80. 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 this.getAllArgs().filter(({name}) => key === name);
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;
@@ -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;