wikiparser-node 0.8.1-m → 0.9.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 (81) hide show
  1. package/README.md +39 -0
  2. package/config/moegirl.json +1 -0
  3. package/i18n/zh-hans.json +44 -0
  4. package/i18n/zh-hant.json +44 -0
  5. package/index.js +264 -10
  6. package/lib/element.js +507 -33
  7. package/lib/node.js +550 -6
  8. package/lib/ranges.js +130 -0
  9. package/lib/text.js +111 -41
  10. package/lib/title.js +28 -5
  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 +54 -0
  16. package/package.json +9 -8
  17. package/parser/brackets.js +9 -2
  18. package/parser/commentAndExt.js +3 -5
  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 +6 -5
  24. package/parser/list.js +1 -0
  25. package/parser/magicLinks.js +5 -4
  26. package/parser/quotes.js +1 -0
  27. package/parser/selector.js +177 -0
  28. package/parser/table.js +1 -0
  29. package/src/arg.js +123 -5
  30. package/src/atom/hidden.js +2 -0
  31. package/src/atom/index.js +17 -0
  32. package/src/attribute.js +191 -8
  33. package/src/attributes.js +311 -8
  34. package/src/charinsert.js +97 -0
  35. package/src/converter.js +108 -2
  36. package/src/converterFlags.js +190 -3
  37. package/src/converterRule.js +185 -4
  38. package/src/extLink.js +122 -2
  39. package/src/gallery.js +59 -11
  40. package/src/hasNowiki/index.js +12 -0
  41. package/src/hasNowiki/pre.js +12 -0
  42. package/src/heading.js +57 -6
  43. package/src/html.js +133 -12
  44. package/src/imageParameter.js +232 -38
  45. package/src/imagemap.js +65 -6
  46. package/src/imagemapLink.js +14 -2
  47. package/src/index.js +537 -8
  48. package/src/link/category.js +32 -1
  49. package/src/link/file.js +173 -11
  50. package/src/link/galleryImage.js +63 -5
  51. package/src/link/index.js +268 -9
  52. package/src/magicLink.js +92 -11
  53. package/src/nested/choose.js +1 -0
  54. package/src/nested/combobox.js +1 -0
  55. package/src/nested/index.js +31 -7
  56. package/src/nested/references.js +1 -0
  57. package/src/nowiki/comment.js +27 -3
  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 +25 -3
  62. package/src/nowiki/list.js +5 -2
  63. package/src/nowiki/noinclude.js +14 -0
  64. package/src/nowiki/quote.js +17 -3
  65. package/src/onlyinclude.js +26 -1
  66. package/src/paramTag/index.js +27 -4
  67. package/src/paramTag/inputbox.js +4 -1
  68. package/src/parameter.js +150 -8
  69. package/src/syntax.js +68 -0
  70. package/src/table/index.js +941 -4
  71. package/src/table/td.js +229 -10
  72. package/src/table/tr.js +249 -4
  73. package/src/tagPair/ext.js +36 -9
  74. package/src/tagPair/include.js +24 -0
  75. package/src/tagPair/index.js +51 -2
  76. package/src/transclude.js +547 -29
  77. package/tool/index.js +1202 -0
  78. package/util/debug.js +73 -0
  79. package/util/lint.js +8 -7
  80. package/util/string.js +67 -1
  81. package/config/minimum.json +0 -142
@@ -1,53 +1,124 @@
1
1
  'use strict';
2
2
 
3
- const {extUrlChar, extUrlCharFirst} = require('../util/string'),
3
+ const {text, noWrap, print, extUrlChar, extUrlCharFirst} = require('../util/string'),
4
+ {generateForSelf} = require('../util/lint'),
5
+ Title = require('../lib/title'),
4
6
  Parser = require('..'),
7
+ AstText = require('../lib/text'),
5
8
  Token = require('.');
6
9
 
10
+ const params = new Set(['alt', 'link', 'lang', 'page', 'caption']);
11
+
12
+ /**
13
+ * 检查图片参数是否合法
14
+ * @template {string} T
15
+ * @param {T} key 参数名
16
+ * @param {string} value 参数值
17
+ * @returns {T extends 'link' ? string|Title : boolean}
18
+ */
19
+ const validate = (key, value, config = Parser.getConfig(), halfParsed = false) => {
20
+ value = value.replace(/\0\d+t\x7F/gu, '').trim();
21
+ switch (key) {
22
+ case 'width':
23
+ return /^(?:\d+x?|\d*x\d+)$/u.test(value);
24
+ case 'link': {
25
+ if (!value) {
26
+ return '';
27
+ }
28
+ const regex = new RegExp(`(?:(?:${config.protocol}|//)${extUrlCharFirst}|\0\\d+m\x7F)${
29
+ extUrlChar
30
+ }(?=\0\\d+t\x7F|$)`, 'iu');
31
+ if (regex.test(value)) {
32
+ return value;
33
+ } else if (value.startsWith('[[') && value.endsWith(']]')) {
34
+ value = value.slice(2, -2);
35
+ }
36
+ const title = Parser.normalizeTitle(value, 0, false, config, halfParsed, true, true);
37
+ return title.valid && title;
38
+ }
39
+ case 'lang':
40
+ return config.variants.includes(value);
41
+ case 'alt':
42
+ case 'class':
43
+ case 'manualthumb':
44
+ return true;
45
+ default:
46
+ return !isNaN(value);
47
+ }
48
+ };
49
+
7
50
  /**
8
51
  * 图片参数
9
52
  * @classdesc `{childNodes: ...(AstText|Token)}`
10
53
  */
11
54
  class ImageParameterToken extends Token {
12
- /**
13
- * 检查图片参数是否合法
14
- * @template {string} T
15
- * @param {T} key 参数名
16
- * @param {string} value 参数值
17
- */
18
- static #validate(key, value, config = Parser.getConfig(), halfParsed = false) {
19
- value = value.replace(/\0\d+t\x7F/gu, '').trim();
20
- switch (key) {
21
- case 'width':
22
- return /^\d*(?:x\d*)?$/u.test(value);
23
- case 'link': {
24
- if (!value) {
25
- return true;
26
- }
27
- const regex = new RegExp(`(?:(?:${config.protocol}|//)${extUrlCharFirst}|\0\\d+m\x7F)${
28
- extUrlChar
29
- }(?=\0\\d+t\x7F|$)`, 'iu');
30
- if (regex.test(value)) {
31
- return true;
32
- } else if (value.startsWith('[[') && value.endsWith(']]')) {
33
- value = value.slice(2, -2);
34
- }
35
- const title = Parser.normalizeTitle(value, 0, false, config, halfParsed, true, true);
36
- return title.valid;
55
+ type = 'image-parameter';
56
+ #syntax = '';
57
+
58
+ /** 图片链接 */
59
+ get link() {
60
+ return this.name === 'link' ? validate('link', super.text(), this.getAttribute('config')) : undefined;
61
+ }
62
+
63
+ set link(value) {
64
+ if (this.name === 'link') {
65
+ this.setValue(value);
66
+ }
67
+ }
68
+
69
+ /** getValue()的getter */
70
+ get value() {
71
+ return this.getValue();
72
+ }
73
+
74
+ set value(value) {
75
+ this.setValue(value);
76
+ }
77
+
78
+ /** 图片大小 */
79
+ get size() {
80
+ if (this.name === 'width') {
81
+ const /** @type {string} */ size = this.getValue().trim();
82
+ if (!size.includes('{{')) {
83
+ const [width, height = ''] = size.split('x');
84
+ return {width, height};
85
+ }
86
+ const /** @type {{childNodes: AstText[]}} */ token = Parser.parse(size, false, 2, this.getAttribute('config')),
87
+ i = token.childNodes.findIndex(({type, data}) => type === 'text' && data.includes('x')),
88
+ str = token.childNodes[i];
89
+ if (i === -1) {
90
+ return {width: size, height: ''};
37
91
  }
38
- case 'lang':
39
- return config.variants.includes(value);
40
- case 'alt':
41
- case 'class':
42
- case 'manualthumb':
43
- return true;
44
- default:
45
- return !isNaN(value);
92
+ str.splitText(str.data.indexOf('x'));
93
+ str.nextSibling.splitText(1);
94
+ return {width: text(token.childNodes.slice(0, i + 1)), height: text(token.childNodes.slice(i + 2))};
46
95
  }
96
+ return undefined;
47
97
  }
48
98
 
49
- type = 'image-parameter';
50
- #syntax = '';
99
+ /** 图片宽度 */
100
+ get width() {
101
+ return this.size?.width;
102
+ }
103
+
104
+ set width(width) {
105
+ if (this.name === 'width') {
106
+ const {height} = this;
107
+ this.setValue(`${String(width || '')}${height && 'x'}${height}`);
108
+ }
109
+ }
110
+
111
+ /** 图片高度 */
112
+ get height() {
113
+ return this.size?.height;
114
+ }
115
+
116
+ set height(height) {
117
+ height = String(height || '');
118
+ if (this.name === 'width') {
119
+ this.setValue(`${this.width}${height && 'x'}${height}`);
120
+ }
121
+ }
51
122
 
52
123
  /**
53
124
  * @param {string} str 图片参数
@@ -61,7 +132,7 @@ class ImageParameterToken extends Token {
61
132
  ),
62
133
  param = regexes.find(([, key, regex]) => {
63
134
  mt = regex.exec(str);
64
- return mt && (mt.length !== 4 || ImageParameterToken.#validate(key, mt[2], config, true));
135
+ return mt && (mt.length !== 4 || validate(key, mt[2], config, true) !== false);
65
136
  });
66
137
  if (param) {
67
138
  if (mt.length === 3) {
@@ -69,6 +140,7 @@ class ImageParameterToken extends Token {
69
140
  this.#syntax = str;
70
141
  } else {
71
142
  super(mt[2], config, true, accum, {
143
+ 'Stage-2': ':', '!HeadingToken': ':',
72
144
  });
73
145
  this.#syntax = `${mt[1]}${param[0]}${mt[3]}`;
74
146
  }
@@ -79,6 +151,13 @@ class ImageParameterToken extends Token {
79
151
  this.setAttribute('name', 'caption').setAttribute('stage', 7);
80
152
  }
81
153
 
154
+ /** @override */
155
+ afterBuild() {
156
+ if (this.parentNode.type === 'gallery-image' && !params.has(this.name)) {
157
+ this.setAttribute('name', 'invalid');
158
+ }
159
+ }
160
+
82
161
  /** @override */
83
162
  isPlain() {
84
163
  return this.name === 'caption';
@@ -86,9 +165,10 @@ class ImageParameterToken extends Token {
86
165
 
87
166
  /**
88
167
  * @override
168
+ * @param {string} selector
89
169
  */
90
170
  toString(selector) {
91
- return this.#syntax
171
+ return this.#syntax && !(selector && this.matches(selector))
92
172
  ? this.#syntax.replace('$1', super.toString(selector))
93
173
  : super.toString(selector);
94
174
  }
@@ -102,6 +182,120 @@ class ImageParameterToken extends Token {
102
182
  getPadding() {
103
183
  return Math.max(0, this.#syntax.indexOf('$1'));
104
184
  }
185
+
186
+ /**
187
+ * @override
188
+ * @this {ImageParameterToken & {link: Title}}
189
+ * @param {number} start 起始位置
190
+ */
191
+ lint(start = this.getAbsoluteIndex()) {
192
+ const errors = super.lint(start);
193
+ if (this.name === 'invalid') {
194
+ errors.push(generateForSelf(this, {start}, 'invalid gallery image parameter'));
195
+ } else if (this.link?.encoded) {
196
+ errors.push(generateForSelf(this, {start}, 'unnecessary URL encoding in an internal link'));
197
+ }
198
+ return errors;
199
+ }
200
+
201
+ /** @override */
202
+ print() {
203
+ return this.#syntax
204
+ ? `<span class="wpb-image-parameter">${
205
+ this.#syntax.replace('$1', `<span class="wpb-image-caption">${print(this.childNodes)}</span>`)
206
+ }</span>`
207
+ : super.print({class: 'image-caption'});
208
+ }
209
+
210
+ /** @override */
211
+ cloneNode() {
212
+ const cloned = this.cloneChildNodes(),
213
+ config = this.getAttribute('config');
214
+ return Parser.run(() => {
215
+ const token = new ImageParameterToken(this.#syntax.replace('$1', ''), config);
216
+ token.replaceChildren(...cloned);
217
+ return token;
218
+ });
219
+ }
220
+
221
+ /**
222
+ * @override
223
+ * @template {string} T
224
+ * @param {T} key 属性键
225
+ * @returns {TokenAttribute<T>}
226
+ */
227
+ getAttribute(key) {
228
+ return key === 'syntax' ? this.#syntax : super.getAttribute(key);
229
+ }
230
+
231
+ /**
232
+ * @override
233
+ * @param {PropertyKey} key 属性键
234
+ */
235
+ hasAttribute(key) {
236
+ return key === 'syntax' || super.hasAttribute(key);
237
+ }
238
+
239
+ /** 是否是不可变参数 */
240
+ #isVoid() {
241
+ return this.#syntax && !this.#syntax.includes('$1');
242
+ }
243
+
244
+ /**
245
+ * @override
246
+ * @template {Token} T
247
+ * @param {T} token 待插入的子节点
248
+ * @param {number} i 插入位置
249
+ * @complexity `n`
250
+ * @throws `Error` 不接受自定义输入的图片参数
251
+ */
252
+ insertAt(token, i = this.length) {
253
+ if (!Parser.running && this.#isVoid()) {
254
+ throw new Error(`图片参数 ${this.name} 不接受自定义输入!`);
255
+ }
256
+ return super.insertAt(token, i);
257
+ }
258
+
259
+ /**
260
+ * 获取参数值
261
+ * @complexity `n`
262
+ */
263
+ getValue() {
264
+ return this.name === 'invalid' ? this.text() : this.#isVoid() || super.text();
265
+ }
266
+
267
+ /**
268
+ * 设置参数值
269
+ * @param {string|boolean} value 参数值
270
+ * @complexity `n`
271
+ * @throws SyntaxError` 非法的参数值
272
+ */
273
+ setValue(value) {
274
+ if (this.name === 'invalid') {
275
+ throw new Error('无效的图片参数!');
276
+ } else if (this.#isVoid()) {
277
+ if (typeof value !== 'boolean') {
278
+ this.typeError('setValue', 'Boolean');
279
+ } else if (value === false) {
280
+ this.remove();
281
+ }
282
+ return;
283
+ } else if (typeof value !== 'string') {
284
+ this.typeError('setValue', 'String');
285
+ }
286
+ const root = Parser.parse(`[[File:F|${
287
+ this.#syntax ? this.#syntax.replace('$1', value) : value
288
+ }]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
289
+ {length, firstChild: file} = root,
290
+ {lastChild: imageParameter, type, name, length: fileLength} = file;
291
+ if (length !== 1 || type !== 'file' || name !== 'File:F' || fileLength !== 2
292
+ || imageParameter.name !== this.name
293
+ ) {
294
+ throw new SyntaxError(`非法的 ${this.name} 参数:${noWrap(value)}`);
295
+ }
296
+ this.replaceChildren(...imageParameter.childNodes);
297
+ }
105
298
  }
106
299
 
300
+ Parser.classes.ImageParameterToken = __filename;
107
301
  module.exports = ImageParameterToken;
package/src/imagemap.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {generateForSelf, generateForChild} = require('../util/lint'),
4
+ singleLine = require('../mixin/singleLine'),
4
5
  Parser = require('..'),
5
6
  Token = require('.'),
6
7
  NoincludeToken = require('./nowiki/noinclude'),
@@ -23,6 +24,14 @@ class ImagemapToken extends Token {
23
24
  return this.childNodes.find(({type}) => type === 'imagemap-image');
24
25
  }
25
26
 
27
+ /**
28
+ * 链接
29
+ * @returns {ImagemapLinkToken[]}
30
+ */
31
+ get links() {
32
+ return this.childNodes.filter(({type}) => type === 'imagemap-link');
33
+ }
34
+
26
35
  /**
27
36
  * @param {string} inner 标签内部wikitext
28
37
  * @param {accum} accum
@@ -30,13 +39,14 @@ class ImagemapToken extends Token {
30
39
  */
31
40
  constructor(inner, config = Parser.getConfig(), accum = []) {
32
41
  super(undefined, config, true, accum, {
42
+ GalleryImageToken: ':', ImagemapLinkToken: ':', SingleLineNoincludeToken: ':', AstText: ':',
33
43
  });
34
44
  if (!inner) {
35
45
  return;
36
46
  }
37
47
  const lines = inner.split('\n'),
38
48
  protocols = new Set(config.protocol.split('|')),
39
- SingleLineNoincludeToken = NoincludeToken,
49
+ SingleLineNoincludeToken = singleLine(NoincludeToken),
40
50
  fallback = /** @param {string} line 一行文本 */ line => {
41
51
  super.insertAt(new SingleLineNoincludeToken(line, config, accum));
42
52
  };
@@ -51,13 +61,14 @@ class ImagemapToken extends Token {
51
61
  title = this.normalizeTitle(file, 0, true);
52
62
  if (title.valid && !title.interwiki && title.ns === 6) {
53
63
  const token = new GalleryImageToken(
54
- file, options.length > 0 ? options.join('|') : undefined, title, config, accum,
64
+ file, options.length > 0 ? options.join('|') : undefined, config, accum,
55
65
  );
56
66
  token.type = 'imagemap-image';
57
67
  super.insertAt(token);
58
68
  first = false;
59
69
  continue;
60
70
  } else {
71
+ Parser.error('<imagemap>标签内必须先包含一张合法图片!', line);
61
72
  error = true;
62
73
  }
63
74
  } else if (line.trim().split(/[\t ]/u)[0] === 'desc') {
@@ -72,7 +83,7 @@ class ImagemapToken extends Token {
72
83
  if (title.valid) {
73
84
  super.insertAt(new ImagemapLinkToken(
74
85
  line.slice(0, i),
75
- [...mtIn.slice(1), title],
86
+ mtIn.slice(1),
76
87
  substr.slice(substr.indexOf(']]') + 2),
77
88
  config,
78
89
  accum,
@@ -101,6 +112,7 @@ class ImagemapToken extends Token {
101
112
 
102
113
  /**
103
114
  * @override
115
+ * @param {string} selector
104
116
  */
105
117
  toString(selector) {
106
118
  return super.toString(selector, '\n');
@@ -116,11 +128,16 @@ class ImagemapToken extends Token {
116
128
  return 1;
117
129
  }
118
130
 
131
+ /** @override */
132
+ print() {
133
+ return super.print({sep: '\n'});
134
+ }
135
+
119
136
  /**
120
137
  * @override
121
138
  * @param {number} start 起始位置
122
139
  */
123
- lint(start = 0) {
140
+ lint(start = this.getAbsoluteIndex()) {
124
141
  const errors = super.lint(start),
125
142
  rect = {start, ...this.getRootNode().posFromIndex(start)};
126
143
  if (this.image) {
@@ -128,13 +145,55 @@ class ImagemapToken extends Token {
128
145
  ...this.childNodes.filter(child => {
129
146
  const str = String(child).trim();
130
147
  return child.type === 'noinclude' && str && str[0] !== '#';
131
- }).map(child => generateForChild(child, rect, '无效的<imagemap>链接')),
148
+ }).map(child => generateForChild(child, rect, 'invalid link in <imagemap>')),
132
149
  );
133
150
  } else {
134
- errors.push(generateForSelf(this, rect, '缺少图片的<imagemap>'));
151
+ errors.push(generateForSelf(this, rect, '<imagemap> without an image'));
135
152
  }
136
153
  return errors;
137
154
  }
155
+
156
+ /**
157
+ * @override
158
+ * @template {string|Token} T
159
+ * @param {T} token 待插入的节点
160
+ * @param {number} i 插入位置
161
+ * @throws `Error` 当前缺少合法图片
162
+ * @throws `RangeError` 已有一张合法图片
163
+ */
164
+ insertAt(token, i = 0) {
165
+ const {image} = this;
166
+ if (!image && (token.type === 'imagemap-link' || token.type === 'text')) {
167
+ throw new Error('当前缺少一张合法图片!');
168
+ } else if (image && token.type === 'imagemap-image') {
169
+ throw new RangeError('已有一张合法图片!');
170
+ }
171
+ return super.insertAt(token, i);
172
+ }
173
+
174
+ /**
175
+ * @override
176
+ * @param {number} i 移除位置
177
+ * @throws `Error` 禁止移除图片
178
+ */
179
+ removeAt(i) {
180
+ const child = this.childNodes[i];
181
+ if (child.type === 'imagemap-image') {
182
+ throw new Error('禁止移除<imagemap>内的图片!');
183
+ }
184
+ return super.removeAt(i);
185
+ }
186
+
187
+ /** @override */
188
+ cloneNode() {
189
+ const cloned = this.cloneChildNodes();
190
+ return Parser.run(() => {
191
+ const token = new ImagemapToken(undefined, this.getAttribute('config'));
192
+ token.append(...cloned);
193
+ return token;
194
+ });
195
+ }
138
196
  }
139
197
 
198
+ Parser.classes.ImagemapToken = __filename;
140
199
  module.exports = ImagemapToken;
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const Title = require('../lib/title'),
4
+ fixedToken = require('../mixin/fixedToken'),
5
+ singleLine = require('../mixin/singleLine'),
6
+ Parser = require('..'),
4
7
  Token = require('.'),
5
8
  NoincludeToken = require('./nowiki/noinclude'),
6
9
  LinkToken = require('./link'),
@@ -10,9 +13,17 @@ const Title = require('../lib/title'),
10
13
  * `<imagemap>`内的链接
11
14
  * @classdesc `{childNodes: [AstText, LinkToken|ExtLinkToken, NoincludeToken]}`
12
15
  */
13
- class ImagemapLinkToken extends Token {
16
+ class ImagemapLinkToken extends fixedToken(singleLine(Token)) {
14
17
  type = 'imagemap-link';
15
18
 
19
+ /**
20
+ * 内外链接
21
+ * @this {{childNodes: (LinkToken|ExtLinkToken)[]}}
22
+ */
23
+ get link() {
24
+ return this.childNodes[1].link;
25
+ }
26
+
16
27
  /**
17
28
  * @param {string} pre 链接前的文本
18
29
  * @param {[string, string, string|Title]} linkStuff 内外链接
@@ -20,10 +31,11 @@ class ImagemapLinkToken extends Token {
20
31
  * @param {accum} accum
21
32
  */
22
33
  constructor(pre, linkStuff, post, config, accum) {
23
- const SomeLinkToken = linkStuff[2] instanceof Title ? LinkToken : ExtLinkToken;
34
+ const SomeLinkToken = linkStuff.length === 2 ? LinkToken : ExtLinkToken;
24
35
  super(undefined, config, true, accum);
25
36
  this.append(pre, new SomeLinkToken(...linkStuff, config, accum), new NoincludeToken(post, config, accum));
26
37
  }
27
38
  }
28
39
 
40
+ Parser.classes.ImagemapLinkToken = __filename;
29
41
  module.exports = ImagemapLinkToken;