wikiparser-node 0.11.0-b → 0.11.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 (176) hide show
  1. package/config/.schema.json +18 -0
  2. package/config/default.json +1 -0
  3. package/config/llwiki.json +35 -0
  4. package/config/moegirl.json +44 -0
  5. package/config/zhwiki.json +466 -0
  6. package/dist/index.d.ts +114 -0
  7. package/dist/lib/element.d.ts +162 -0
  8. package/dist/lib/node.d.ts +291 -0
  9. package/dist/lib/ranges.d.ts +37 -0
  10. package/dist/lib/text.d.ts +64 -0
  11. package/dist/lib/title.d.ts +21 -0
  12. package/dist/mixin/attributeParent.d.ts +9 -0
  13. package/dist/mixin/fixedToken.d.ts +8 -0
  14. package/dist/mixin/hidden.d.ts +8 -0
  15. package/dist/mixin/singleLine.d.ts +8 -0
  16. package/dist/mixin/sol.d.ts +8 -0
  17. package/dist/parser/brackets.d.ts +12 -0
  18. package/dist/parser/commentAndExt.d.ts +8 -0
  19. package/dist/parser/converter.d.ts +7 -0
  20. package/dist/parser/externalLinks.d.ts +7 -0
  21. package/dist/parser/hrAndDoubleUnderscore.d.ts +11 -0
  22. package/dist/parser/html.d.ts +7 -0
  23. package/dist/parser/links.d.ts +7 -0
  24. package/dist/parser/list.d.ts +7 -0
  25. package/dist/parser/magicLinks.d.ts +7 -0
  26. package/dist/parser/quotes.d.ts +7 -0
  27. package/dist/parser/selector.d.ts +12 -0
  28. package/dist/parser/table.d.ts +11 -0
  29. package/dist/src/arg.d.ts +54 -0
  30. package/dist/src/atom/hidden.d.ts +5 -0
  31. package/dist/src/atom/index.d.ts +15 -0
  32. package/dist/src/attribute.d.ts +65 -0
  33. package/dist/src/attributes.d.ts +112 -0
  34. package/dist/src/charinsert.d.ts +32 -0
  35. package/dist/src/converter.d.ts +103 -0
  36. package/dist/src/converterFlags.d.ts +83 -0
  37. package/dist/src/converterRule.d.ts +75 -0
  38. package/dist/src/extLink.d.ts +62 -0
  39. package/dist/src/gallery.d.ts +33 -0
  40. package/dist/src/hasNowiki/index.d.ts +14 -0
  41. package/dist/src/hasNowiki/pre.d.ts +13 -0
  42. package/dist/src/heading.d.ts +44 -0
  43. package/dist/src/html.d.ts +56 -0
  44. package/dist/src/imageParameter.d.ts +65 -0
  45. package/dist/src/imagemap.d.ts +37 -0
  46. package/dist/src/imagemapLink.d.ts +21 -0
  47. package/dist/src/index.d.ts +186 -0
  48. package/dist/src/link/category.d.ts +16 -0
  49. package/dist/src/link/file.d.ts +85 -0
  50. package/dist/src/link/galleryImage.d.ts +15 -0
  51. package/dist/src/link/index.d.ts +88 -0
  52. package/dist/src/magicLink.d.ts +36 -0
  53. package/dist/src/nested/choose.d.ts +13 -0
  54. package/dist/src/nested/combobox.d.ts +13 -0
  55. package/dist/src/nested/index.d.ts +18 -0
  56. package/dist/src/nested/references.d.ts +13 -0
  57. package/dist/src/nowiki/comment.d.ts +31 -0
  58. package/dist/src/nowiki/dd.d.ts +17 -0
  59. package/dist/src/nowiki/doubleUnderscore.d.ts +22 -0
  60. package/dist/src/nowiki/hr.d.ts +13 -0
  61. package/dist/src/nowiki/index.d.ts +27 -0
  62. package/dist/src/nowiki/list.d.ts +8 -0
  63. package/dist/src/nowiki/noinclude.d.ts +8 -0
  64. package/dist/src/nowiki/quote.d.ts +13 -0
  65. package/dist/src/onlyinclude.d.ts +24 -0
  66. package/dist/src/paramTag/index.d.ts +29 -0
  67. package/dist/src/paramTag/inputbox.d.ts +8 -0
  68. package/dist/src/parameter.d.ts +75 -0
  69. package/dist/src/syntax.d.ts +20 -0
  70. package/dist/src/table/index.d.ts +273 -0
  71. package/dist/src/table/td.d.ts +100 -0
  72. package/dist/src/table/tr.d.ts +91 -0
  73. package/dist/src/tagPair/ext.d.ts +18 -0
  74. package/dist/src/tagPair/include.d.ts +25 -0
  75. package/dist/src/tagPair/index.d.ts +41 -0
  76. package/dist/src/transclude.d.ts +199 -0
  77. package/dist/tool/index.d.ts +420 -0
  78. package/dist/util/base.d.ts +10 -0
  79. package/dist/util/debug.d.ts +20 -0
  80. package/dist/util/diff.d.ts +8 -0
  81. package/dist/util/lint.d.ts +28 -0
  82. package/dist/util/string.d.ts +55 -0
  83. package/index.js +333 -0
  84. package/lib/element.js +618 -0
  85. package/lib/node.js +730 -0
  86. package/lib/ranges.js +130 -0
  87. package/lib/text.js +265 -0
  88. package/lib/title.js +83 -0
  89. package/mixin/attributeParent.js +117 -0
  90. package/mixin/fixedToken.js +40 -0
  91. package/mixin/hidden.js +21 -0
  92. package/mixin/singleLine.js +31 -0
  93. package/mixin/sol.js +54 -0
  94. package/package.json +17 -13
  95. package/parser/brackets.js +128 -0
  96. package/parser/commentAndExt.js +62 -0
  97. package/parser/converter.js +46 -0
  98. package/parser/externalLinks.js +33 -0
  99. package/parser/hrAndDoubleUnderscore.js +49 -0
  100. package/parser/html.js +42 -0
  101. package/parser/links.js +94 -0
  102. package/parser/list.js +59 -0
  103. package/parser/magicLinks.js +41 -0
  104. package/parser/quotes.js +64 -0
  105. package/parser/selector.js +180 -0
  106. package/parser/table.js +114 -0
  107. package/src/arg.js +207 -0
  108. package/src/atom/hidden.js +13 -0
  109. package/src/atom/index.js +43 -0
  110. package/src/attribute.js +472 -0
  111. package/src/attributes.js +453 -0
  112. package/src/charinsert.js +97 -0
  113. package/src/converter.js +176 -0
  114. package/src/converterFlags.js +284 -0
  115. package/src/converterRule.js +256 -0
  116. package/src/extLink.js +180 -0
  117. package/src/gallery.js +149 -0
  118. package/src/hasNowiki/index.js +44 -0
  119. package/src/hasNowiki/pre.js +40 -0
  120. package/src/heading.js +134 -0
  121. package/src/html.js +254 -0
  122. package/src/imageParameter.js +303 -0
  123. package/src/imagemap.js +199 -0
  124. package/src/imagemapLink.js +41 -0
  125. package/src/index.js +938 -0
  126. package/src/link/category.js +44 -0
  127. package/src/link/file.js +287 -0
  128. package/src/link/galleryImage.js +120 -0
  129. package/src/link/index.js +388 -0
  130. package/src/magicLink.js +151 -0
  131. package/src/nested/choose.js +24 -0
  132. package/src/nested/combobox.js +23 -0
  133. package/src/nested/index.js +96 -0
  134. package/src/nested/references.js +23 -0
  135. package/src/nowiki/comment.js +71 -0
  136. package/src/nowiki/dd.js +59 -0
  137. package/src/nowiki/doubleUnderscore.js +56 -0
  138. package/src/nowiki/hr.js +41 -0
  139. package/src/nowiki/index.js +56 -0
  140. package/src/nowiki/list.js +16 -0
  141. package/src/nowiki/noinclude.js +28 -0
  142. package/src/nowiki/quote.js +69 -0
  143. package/src/onlyinclude.js +64 -0
  144. package/src/paramTag/index.js +89 -0
  145. package/src/paramTag/inputbox.js +35 -0
  146. package/src/parameter.js +239 -0
  147. package/src/syntax.js +91 -0
  148. package/src/table/index.js +985 -0
  149. package/src/table/td.js +343 -0
  150. package/src/table/tr.js +319 -0
  151. package/src/tagPair/ext.js +146 -0
  152. package/src/tagPair/include.js +50 -0
  153. package/src/tagPair/index.js +131 -0
  154. package/src/transclude.js +843 -0
  155. package/tool/index.js +1209 -0
  156. package/typings/api.d.ts +9 -0
  157. package/typings/array.d.ts +29 -0
  158. package/typings/event.d.ts +22 -0
  159. package/typings/index.d.ts +118 -0
  160. package/typings/node.d.ts +35 -0
  161. package/typings/parser.d.ts +12 -0
  162. package/typings/table.d.ts +10 -0
  163. package/typings/token.d.ts +31 -0
  164. package/typings/tool.d.ts +6 -0
  165. package/util/base.js +17 -0
  166. package/util/debug.js +73 -0
  167. package/util/diff.js +76 -0
  168. package/util/lint.js +57 -0
  169. package/util/string.js +126 -0
  170. package/bundle/bundle.min.js +0 -38
  171. package/config/minimum.json +0 -135
  172. package/extensions/base.js +0 -154
  173. package/extensions/editor.css +0 -63
  174. package/extensions/editor.js +0 -183
  175. package/extensions/highlight.js +0 -40
  176. package/extensions/ui.css +0 -119
@@ -0,0 +1,388 @@
1
+ 'use strict';
2
+
3
+ const {generateForChild} = require('../../util/lint'),
4
+ {noWrap} = require('../../util/string'),
5
+ {undo} = require('../../util/debug'),
6
+ Parser = require('../..'),
7
+ AstText = require('../../lib/text'),
8
+ Token = require('..'),
9
+ AtomToken = require('../atom');
10
+
11
+ /**
12
+ * 内链
13
+ * @classdesc `{childNodes: [AtomToken, ?Token]}`
14
+ */
15
+ class LinkToken extends Token {
16
+ type = 'link';
17
+ #bracket = true;
18
+ #delimiter;
19
+ #fragment;
20
+ #encoded = false;
21
+
22
+ /** 完整链接,和FileToken保持一致 */
23
+ get link() {
24
+ return this.#getTitle();
25
+ }
26
+
27
+ set link(link) {
28
+ this.setTarget(link);
29
+ }
30
+
31
+ /** 是否链接到自身 */
32
+ get selfLink() {
33
+ const {title, fragment} = this.#getTitle();
34
+ return !title && Boolean(fragment);
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 === undefined ? '' : `#${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
+
79
+ /**
80
+ * @param {string} link 链接标题
81
+ * @param {string|undefined} linkText 链接显示文字
82
+ * @param {import('../../typings/token').accum} accum
83
+ * @param {string} delimiter `|`
84
+ */
85
+ constructor(link, linkText, config = Parser.getConfig(), accum = [], delimiter = '|') {
86
+ super(undefined, config, true, accum, {
87
+ AtomToken: 0, Token: 1,
88
+ });
89
+ this.insertAt(new AtomToken(link, 'link-target', config, accum, {
90
+ 'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '',
91
+ }));
92
+ if (linkText !== undefined) {
93
+ const inner = new Token(linkText, config, true, accum, {
94
+ 'Stage-5': ':', ConverterToken: ':',
95
+ });
96
+ inner.type = 'link-text';
97
+ this.insertAt(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
98
+ }
99
+ this.#delimiter = delimiter;
100
+ this.getAttribute('protectChildren')(0);
101
+ }
102
+
103
+ /**
104
+ * @override
105
+ * @throws `Error` 非法的内链目标
106
+ * @throws `Error` 不可更改命名空间
107
+ */
108
+ afterBuild() {
109
+ const titleObj = this.normalizeTitle(this.firstChild.text(), 0, false, true, true);
110
+ this.setAttribute('name', titleObj.title);
111
+ this.#fragment = titleObj.fragment;
112
+ this.#encoded = titleObj.encoded;
113
+ if (this.#delimiter?.includes('\0')) {
114
+ this.#delimiter = this.getAttribute('buildFromStr')(this.#delimiter, 'string');
115
+ }
116
+ const /** @type {import('../../typings/event').AstListener} */ linkListener = (e, data) => {
117
+ const {prevTarget} = e;
118
+ if (prevTarget?.type === 'link-target') {
119
+ const name = prevTarget.text(),
120
+ {title, interwiki, ns, valid, fragment, encoded} = this.normalizeTitle(name, 0, false, true, true);
121
+ if (!valid) {
122
+ undo(e, data);
123
+ throw new Error(`非法的内链目标:${name}`);
124
+ } else if (this.type === 'category' && (interwiki || ns !== 14)
125
+ || this.type === 'file' && (interwiki || ns !== 6)
126
+ ) {
127
+ undo(e, data);
128
+ throw new Error(`${this.type === 'file' ? '文件' : '分类'}链接不可更改命名空间:${name}`);
129
+ } else if (this.type === 'link' && !interwiki && (ns === 6 || ns === 14) && name.trim()[0] !== ':') {
130
+ const /** @type {{firstChild: AstText}} */ {firstChild} = prevTarget;
131
+ if (firstChild.type === 'text') {
132
+ firstChild.insertData(0, ':');
133
+ } else {
134
+ prevTarget.prepend(':');
135
+ }
136
+ }
137
+ this.setAttribute('name', title);
138
+ this.#fragment = fragment;
139
+ this.#encoded = encoded;
140
+ }
141
+ };
142
+ this.addEventListener(['remove', 'insert', 'replace', 'text'], linkListener);
143
+ }
144
+
145
+ /**
146
+ * @override
147
+ * @template {string} T
148
+ * @param {T} key 属性键
149
+ * @param {import('../../typings/node').TokenAttribute<T>} value 属性值
150
+ */
151
+ setAttribute(key, value) {
152
+ if (key === 'bracket') {
153
+ this.#bracket = Boolean(value);
154
+ return this;
155
+ }
156
+ return super.setAttribute(key, value);
157
+ }
158
+
159
+ /**
160
+ * @override
161
+ * @param {string} selector
162
+ */
163
+ toString(selector) {
164
+ const str = super.toString(selector, this.#delimiter);
165
+ return this.#bracket && !(selector && this.matches(selector)) ? `[[${str}]]` : str;
166
+ }
167
+
168
+ /** @override */
169
+ text() {
170
+ const str = super.text('|');
171
+ return this.#bracket ? `[[${str}]]` : str;
172
+ }
173
+
174
+ /** @override */
175
+ getPadding() {
176
+ return 2;
177
+ }
178
+
179
+ /** @override */
180
+ getGaps() {
181
+ return this.#delimiter.length;
182
+ }
183
+
184
+ /** @override */
185
+ print() {
186
+ return super.print(this.#bracket ? {pre: '[[', post: ']]', sep: this.#delimiter} : {sep: this.#delimiter});
187
+ }
188
+
189
+ /**
190
+ * @override
191
+ * @param {number} start 起始位置
192
+ */
193
+ lint(start = this.getAbsoluteIndex()) {
194
+ const errors = super.lint(start),
195
+ {childNodes: [target, linkText], type: linkType} = this;
196
+ let rect;
197
+ if (linkType === 'link' && target.childNodes.some(({type}) => type === 'template')) {
198
+ rect = {start, ...this.getRootNode().posFromIndex(start)};
199
+ errors.push(generateForChild(target, rect, 'template in an internal link target', 'warning'));
200
+ }
201
+ if (this.#encoded) {
202
+ rect ||= {start, ...this.getRootNode().posFromIndex(start)};
203
+ errors.push(generateForChild(target, rect, 'unnecessary URL encoding in an internal link'));
204
+ }
205
+ if (linkType === 'link' && linkText?.childNodes?.some(
206
+ /** @param {AstText} */ ({type, data}) => type === 'text' && data.includes('|'),
207
+ )) {
208
+ rect ||= {start, ...this.getRootNode().posFromIndex(start)};
209
+ errors.push(generateForChild(linkText, rect, 'additional "|" in the link text', 'warning'));
210
+ } else if (linkType !== 'link' && this.#fragment !== undefined) {
211
+ rect ||= {start, ...this.getRootNode().posFromIndex(start)};
212
+ errors.push(generateForChild(target, rect, 'useless fragment'));
213
+ }
214
+ return errors;
215
+ }
216
+
217
+ /** 生成Title对象 */
218
+ #getTitle() {
219
+ return this.normalizeTitle(this.firstChild.text(), 0, false, true, true);
220
+ }
221
+
222
+ /**
223
+ * @override
224
+ * @this {LinkToken & {constructor: typeof LinkToken}}
225
+ */
226
+ cloneNode() {
227
+ const [link, ...linkText] = this.cloneChildNodes();
228
+ return Parser.run(() => {
229
+ const token = new this.constructor('', undefined, this.#getTitle(), this.getAttribute('config'));
230
+ token.firstChild.safeReplaceWith(link);
231
+ token.append(...linkText);
232
+ token.afterBuild();
233
+ return token;
234
+ });
235
+ }
236
+
237
+ /**
238
+ * 设置链接目标
239
+ * @param {string} link 链接目标
240
+ * @throws `SyntaxError` 非法的链接目标
241
+ */
242
+ setTarget(link) {
243
+ link = String(link);
244
+ if (this.type === 'link' && !/^\s*[:#]/u.test(link)) {
245
+ link = `:${link}`;
246
+ }
247
+ const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
248
+ {length, firstChild: wikiLink} = root,
249
+ {type, firstChild, length: linkLength} = wikiLink;
250
+ if (length !== 1 || type !== this.type || linkLength !== 1) {
251
+ const msgs = {link: '内链', file: '文件链接', category: '分类'};
252
+ throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
253
+ }
254
+ wikiLink.destroy();
255
+ this.firstChild.safeReplaceWith(firstChild);
256
+ }
257
+
258
+ /**
259
+ * 设置跨语言链接
260
+ * @param {string} lang 语言前缀
261
+ * @param {string} link 页面标题
262
+ * @throws `SyntaxError` 非法的跨语言链接
263
+ */
264
+ setLangLink(lang, link) {
265
+ if (typeof lang !== 'string') {
266
+ this.typeError('setLangLink', 'String');
267
+ }
268
+ link = String(link).trim();
269
+ const [char] = link;
270
+ if (char === '#') {
271
+ throw new SyntaxError('跨语言链接不能仅为fragment!');
272
+ } else if (char === ':') {
273
+ link = link.slice(1);
274
+ }
275
+ const root = Parser.parse(`[[${lang}:${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
276
+ /** @type {Token & {firstChild: LinkToken}} */ {length, firstChild: wikiLink} = root,
277
+ {type, length: linkLength, interwiki, firstChild} = wikiLink;
278
+ if (length !== 1 || type !== 'link' || linkLength !== 1 || interwiki !== lang.toLowerCase()) {
279
+ throw new SyntaxError(`非法的跨语言链接目标:${lang}:${link}`);
280
+ }
281
+ wikiLink.destroy();
282
+ this.firstChild.safeReplaceWith(firstChild);
283
+ }
284
+
285
+ /**
286
+ * 设置fragment
287
+ * @param {string} fragment fragment
288
+ * @param {boolean} page 是否是其他页面
289
+ * @throws `SyntaxError` 非法的fragment
290
+ */
291
+ #setFragment(fragment, page = true) {
292
+ fragment &&= String(fragment).replace(/[<>[\]#|=]/gu, p => encodeURIComponent(p));
293
+ const include = this.getAttribute('include'),
294
+ config = this.getAttribute('config'),
295
+ root = Parser.parse(`[[${page ? `:${this.name}` : ''}${
296
+ fragment === undefined ? '' : `#${fragment}`
297
+ }]]`, include, 6, config),
298
+ {length, firstChild: wikiLink} = root,
299
+ {type, length: linkLength, firstChild} = wikiLink;
300
+ if (length !== 1 || type !== 'link' || linkLength !== 1) {
301
+ throw new SyntaxError(`非法的 fragment:${fragment}`);
302
+ } else if (page) {
303
+ Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
304
+ }
305
+ wikiLink.destroy();
306
+ this.firstChild.safeReplaceWith(firstChild);
307
+ }
308
+
309
+ /**
310
+ * 设置fragment
311
+ * @param {string} fragment fragment
312
+ */
313
+ setFragment(fragment) {
314
+ this.#setFragment(fragment);
315
+ }
316
+
317
+ /**
318
+ * 修改为到自身的链接
319
+ * @param {string} fragment fragment
320
+ * @throws `RangeError` 空fragment
321
+ */
322
+ asSelfLink(fragment = this.fragment) {
323
+ fragment &&= String(fragment);
324
+ if (!fragment?.trim()) {
325
+ throw new RangeError(`${this.constructor.name}.asSelfLink 方法必须指定非空的 fragment!`);
326
+ }
327
+ this.#setFragment(fragment, false);
328
+ }
329
+
330
+ /**
331
+ * 设置链接显示文字
332
+ * @param {string} linkText 链接显示文字
333
+ * @throws `SyntaxError` 非法的链接显示文字
334
+ */
335
+ setLinkText(linkText = '') {
336
+ linkText = String(linkText);
337
+ let lastChild;
338
+ const config = this.getAttribute('config');
339
+ if (linkText) {
340
+ const root = Parser.parse(`[[${
341
+ this.type === 'category' ? 'Category:' : ''
342
+ }L|${linkText}]]`, this.getAttribute('include'), 6, config),
343
+ {length, firstChild: wikiLink} = root;
344
+ if (length !== 1 || wikiLink.type !== this.type || wikiLink.length !== 2) {
345
+ throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${noWrap(linkText)}`);
346
+ }
347
+ ({lastChild} = wikiLink);
348
+ } else {
349
+ lastChild = Parser.run(() => new Token('', config));
350
+ lastChild.setAttribute('stage', 7).type = 'link-text';
351
+ }
352
+ if (this.length === 1) {
353
+ this.insertAt(lastChild);
354
+ } else {
355
+ this.lastChild.safeReplaceWith(lastChild);
356
+ }
357
+ }
358
+
359
+ /**
360
+ * 自动生成管道符后的链接文字
361
+ * @throws `Error` 带有"#"或"%"时不可用
362
+ */
363
+ pipeTrick() {
364
+ const linkText = this.firstChild.text();
365
+ if (linkText.includes('#') || linkText.includes('%')) {
366
+ throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
367
+ }
368
+ const m1 = /^:?(?:[ \w\x80-\xFF-]+:)?([^(]+)\(.+\)$/u.exec(linkText);
369
+ if (m1) {
370
+ this.setLinkText(m1[1].trim());
371
+ return;
372
+ }
373
+ const m2 = /^:?(?:[ \w\x80-\xFF-]+:)?([^(]+)(.+)$/u.exec(linkText);
374
+ if (m2) {
375
+ this.setLinkText(m2[1].trim());
376
+ return;
377
+ }
378
+ const m3 = /^:?(?:[ \w\x80-\xFF-]+:)?(.+?)(?:(?<!\()\(.+\))?(?:, |,|، )./u.exec(linkText);
379
+ if (m3) {
380
+ this.setLinkText(m3[1].trim());
381
+ return;
382
+ }
383
+ this.setLinkText(linkText);
384
+ }
385
+ }
386
+
387
+ Parser.classes.LinkToken = __filename;
388
+ module.exports = LinkToken;
@@ -0,0 +1,151 @@
1
+ 'use strict';
2
+
3
+ const {generateForChild} = require('../util/lint'),
4
+ Parser = require('..'),
5
+ Token = require('.');
6
+
7
+ /**
8
+ * 自由外链
9
+ * @classdesc `{childNodes: [...AstText|CommentToken|IncludeToken|NoincludeToken]}`
10
+ */
11
+ class MagicLinkToken extends Token {
12
+ type = 'free-ext-link';
13
+ #protocolRegex;
14
+
15
+ /** 协议 */
16
+ get protocol() {
17
+ return this.#protocolRegex.exec(this.text())?.[0];
18
+ }
19
+
20
+ set protocol(value) {
21
+ if (typeof value !== 'string') {
22
+ this.typeError('protocol', 'String');
23
+ } else if (!new RegExp(`${this.#protocolRegex.source}$`, 'iu').test(value)) {
24
+ throw new RangeError(`非法的外链协议:${value}`);
25
+ }
26
+ const {link} = this;
27
+ if (!this.#protocolRegex.test(link)) {
28
+ throw new Error(`特殊外链无法更改协议!${link}`);
29
+ }
30
+ this.replaceChildren(link.replace(this.#protocolRegex, value));
31
+ }
32
+
33
+ /** 和内链保持一致 */
34
+ get link() {
35
+ return this.text();
36
+ }
37
+
38
+ set link(url) {
39
+ this.setTarget(url);
40
+ }
41
+
42
+ /**
43
+ * @override
44
+ * @param {number} start 起始位置
45
+ */
46
+ lint(start = this.getAbsoluteIndex()) {
47
+ const errors = super.lint(start),
48
+ source = `[,;。:!?()]+${this.type === 'ext-link-url' ? '|\\|+' : ''}`,
49
+ regex = new RegExp(source, 'u'),
50
+ regexGlobal = new RegExp(source, 'gu');
51
+ let /** @type {{top: number, left: number}} */ rect;
52
+ for (const child of this.childNodes) {
53
+ const str = String(child);
54
+ if (child.type !== 'text' || !regex.test(str)) {
55
+ continue;
56
+ }
57
+ rect ||= {start, ...this.getRootNode().posFromIndex(start)};
58
+ const refError = generateForChild(child, rect, '', 'warning');
59
+ errors.push(...[...str.matchAll(regexGlobal)].map(({index, 0: {0: char, length}}) => {
60
+ const lines = str.slice(0, index).split('\n'),
61
+ {length: top} = lines,
62
+ {length: left} = lines.at(-1),
63
+ startIndex = start + index,
64
+ startLine = refError.startLine + top - 1,
65
+ startCol = (top > 1 ? 0 : refError.startCol) + left;
66
+ return {
67
+ ...refError,
68
+ message: Parser.msg('$1 in URL', char === '|' ? '"|"' : Parser.msg('full-width punctuation')),
69
+ startIndex,
70
+ endIndex: startIndex + length,
71
+ startLine,
72
+ endLine: startLine,
73
+ startCol,
74
+ endCol: startCol + length,
75
+ excerpt: str.slice(Math.max(0, index - 25), index + 25),
76
+ };
77
+ }));
78
+ }
79
+ return errors;
80
+ }
81
+
82
+ /**
83
+ * @param {string} url 网址
84
+ * @param {boolean} doubleSlash 是否接受"//"作为协议
85
+ * @param {import('../typings/token').accum} accum
86
+ */
87
+ constructor(url, doubleSlash, config = Parser.getConfig(), accum = []) {
88
+ super(url, config, true, accum, {
89
+ 'Stage-1': ':', '!ExtToken': '',
90
+ });
91
+ if (doubleSlash) {
92
+ this.type = 'ext-link-url';
93
+ }
94
+ this.#protocolRegex = new RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'iu');
95
+ }
96
+
97
+ /** @override */
98
+ cloneNode() {
99
+ const cloned = this.cloneChildNodes();
100
+ return Parser.run(() => {
101
+ const token = new MagicLinkToken(undefined, this.type === 'ext-link-url', this.getAttribute('config'));
102
+ token.append(...cloned);
103
+ token.afterBuild();
104
+ return token;
105
+ });
106
+ }
107
+
108
+ /**
109
+ * 获取网址
110
+ * @throws `Error` 非标准协议
111
+ */
112
+ getUrl() {
113
+ let url = this.text();
114
+ if (url.startsWith('//')) {
115
+ url = `https:${url}`;
116
+ }
117
+ try {
118
+ return new URL(url);
119
+ } catch (e) {
120
+ if (e instanceof TypeError && e.message === 'Invalid URL') {
121
+ throw new Error(`非标准协议的外部链接:${url}`);
122
+ }
123
+ throw e;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * 设置外链目标
129
+ * @param {string|URL} url 含协议的网址
130
+ * @throws `SyntaxError` 非法的自由外链目标
131
+ */
132
+ setTarget(url) {
133
+ url = String(url);
134
+ const root = Parser.parse(url, this.getAttribute('include'), 9, this.getAttribute('config')),
135
+ {length, firstChild: freeExtLink} = root;
136
+ if (length !== 1 || freeExtLink.type !== 'free-ext-link') {
137
+ throw new SyntaxError(`非法的自由外链目标:${url}`);
138
+ }
139
+ this.replaceChildren(...freeExtLink.childNodes);
140
+ }
141
+
142
+ /** 是否是模板或魔术字参数 */
143
+ isParamValue() {
144
+ const ParameterToken = require('./parameter');
145
+ const /** @type {ParameterToken} */ parameter = this.closest('parameter');
146
+ return parameter?.getValue() === this.text();
147
+ }
148
+ }
149
+
150
+ Parser.classes.MagicLinkToken = __filename;
151
+ module.exports = MagicLinkToken;
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('../..'),
4
+ NestedToken = require('.');
5
+
6
+ /**
7
+ * `<choose>`
8
+ * @classdesc `{childNodes: [...ExtToken|NoincludeToken]}`
9
+ */
10
+ class ChooseToken extends NestedToken {
11
+ name = 'choose';
12
+
13
+ /**
14
+ * @param {string|undefined} wikitext wikitext
15
+ * @param {import('../../typings/token').accum} accum
16
+ */
17
+ constructor(wikitext, config = Parser.getConfig(), accum = []) {
18
+ const regex = /<(option|choicetemplate)(\s[^>]*)?>(.*?)<\/(\1)>/gsu;
19
+ super(wikitext, regex, ['option', 'choicetemplate'], config, accum);
20
+ }
21
+ }
22
+
23
+ Parser.classes.ChooseToken = __filename;
24
+ module.exports = ChooseToken;
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('../..'),
4
+ NestedToken = require('.');
5
+
6
+ /**
7
+ * `<combobox>`
8
+ * @classdesc `{childNodes: [...ExtToken|NoincludeToken]}`
9
+ */
10
+ class ComboboxToken extends NestedToken {
11
+ name = 'combobox';
12
+
13
+ /**
14
+ * @param {string|undefined} wikitext wikitext
15
+ * @param {import('../../typings/token').accum} accum
16
+ */
17
+ constructor(wikitext, config = Parser.getConfig(), accum = []) {
18
+ super(wikitext, /<(combooption)(\s[^>]*)?>(.*?)<\/(combooption\s*)>/gisu, ['combooption'], config, accum);
19
+ }
20
+ }
21
+
22
+ Parser.classes.ComboboxToken = __filename;
23
+ module.exports = ComboboxToken;
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ const {generateForChild} = require('../../util/lint'),
4
+ Parser = require('../..'),
5
+ Token = require('..'),
6
+ ExtToken = require('../tagPair/ext'),
7
+ NoincludeToken = require('../nowiki/noinclude'),
8
+ CommentToken = require('../nowiki/comment');
9
+
10
+ /**
11
+ * 嵌套式的扩展标签
12
+ * @classdesc `{childNodes: [...ExtToken|NoincludeToken|CommentToken]}`
13
+ */
14
+ class NestedToken extends Token {
15
+ type = 'ext-inner';
16
+ #tags;
17
+
18
+ /**
19
+ * @param {string|undefined} wikitext wikitext
20
+ * @param {RegExp} regex 内层正则
21
+ * @param {string[]} tags 内层标签名
22
+ * @param {import('../../typings/token').accum} accum
23
+ */
24
+ constructor(wikitext, regex, tags, config = Parser.getConfig(), accum = []) {
25
+ const text = wikitext?.replace(
26
+ regex,
27
+ /** @type {function(...string): string} */ (comment, name, attr, inner, closing) => {
28
+ const str = `\0${accum.length + 1}${name ? 'e' : 'c'}\x7F`;
29
+ if (name) {
30
+ new ExtToken(name, attr, inner, closing, config, accum);
31
+ } else {
32
+ const closed = comment.endsWith('-->');
33
+ new CommentToken(comment.slice(4, closed ? -3 : undefined), closed, config, accum);
34
+ }
35
+ return str;
36
+ },
37
+ )?.replace(
38
+ /(?<=^|\0\d+[ce]\x7F)[^\0]+(?=$|\0\d+[ce]\x7F)/gu,
39
+ substr => {
40
+ new NoincludeToken(substr, config, accum);
41
+ return `\0${accum.length}c\x7F`;
42
+ },
43
+ );
44
+ super(text, config, true, accum, {
45
+ NoincludeToken: ':', ExtToken: ':',
46
+ });
47
+ this.#tags = tags;
48
+ }
49
+
50
+ /**
51
+ * @override
52
+ * @param {number} start 起始位置
53
+ */
54
+ lint(start = this.getAbsoluteIndex()) {
55
+ let rect;
56
+ return [
57
+ ...super.lint(start),
58
+ ...this.childNodes.filter(child => {
59
+ if (child.type === 'ext' || child.type === 'comment') {
60
+ return false;
61
+ }
62
+ const str = String(child).trim();
63
+ return str && !/^<!--.*-->$/su.test(str);
64
+ }).map(child => {
65
+ rect ||= {start, ...this.getRootNode().posFromIndex(start)};
66
+ return generateForChild(child, rect, Parser.msg('invalid content in <$1>', this.name));
67
+ }),
68
+ ];
69
+ }
70
+
71
+ /**
72
+ * @override
73
+ * @template {string|Token} T
74
+ * @param {T} token 待插入的子节点
75
+ * @param {number} i 插入位置
76
+ */
77
+ insertAt(token, i = this.length) {
78
+ return token.type === 'ext' && !this.#tags.includes(token.name)
79
+ ? this.typeError(`${this.constructor.name}只能以${this.#tags.join('或')}标签作为子节点!`)
80
+ : super.insertAt(token, i);
81
+ }
82
+
83
+ /** @override */
84
+ cloneNode() {
85
+ const cloned = this.cloneChildNodes(),
86
+ config = this.getAttribute('config');
87
+ return Parser.run(() => {
88
+ const token = new this.constructor(undefined, config);
89
+ token.append(...cloned);
90
+ return token;
91
+ });
92
+ }
93
+ }
94
+
95
+ Parser.classes.NestedToken = __filename;
96
+ module.exports = NestedToken;
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('../..'),
4
+ NestedToken = require('.');
5
+
6
+ /**
7
+ * `<references>`
8
+ * @classdesc `{childNodes: [...ExtToken|NoincludeToken|CommentToken]}`
9
+ */
10
+ class ReferencesToken extends NestedToken {
11
+ name = 'references';
12
+
13
+ /**
14
+ * @param {string|undefined} wikitext wikitext
15
+ * @param {import('../../typings/token').accum} accum
16
+ */
17
+ constructor(wikitext, config = Parser.getConfig(), accum = []) {
18
+ super(wikitext, /<!--.*?(?:-->|$)|<(ref)(\s[^>]*)?>(.*?)<\/(ref\s*)>/gisu, ['ref'], config, accum);
19
+ }
20
+ }
21
+
22
+ Parser.classes.ReferencesToken = __filename;
23
+ module.exports = ReferencesToken;