wikiparser-node 0.8.1 → 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.
- package/config/moegirl.json +1 -0
- package/i18n/zh-hans.json +44 -0
- package/i18n/zh-hant.json +44 -0
- package/index.js +15 -3
- package/lib/element.js +27 -27
- package/lib/node.js +3 -5
- package/lib/text.js +11 -33
- package/lib/title.js +8 -6
- package/mixin/sol.js +3 -14
- package/package.json +7 -6
- package/parser/brackets.js +8 -2
- package/parser/commentAndExt.js +1 -4
- package/parser/links.js +1 -1
- package/parser/quotes.js +1 -1
- package/parser/selector.js +5 -5
- package/src/arg.js +7 -3
- package/src/attribute.js +14 -8
- package/src/attributes.js +6 -7
- package/src/converterFlags.js +3 -3
- package/src/converterRule.js +4 -6
- package/src/extLink.js +4 -3
- package/src/gallery.js +6 -9
- package/src/heading.js +8 -8
- package/src/html.js +15 -9
- package/src/imageParameter.js +78 -55
- package/src/imagemap.js +5 -5
- package/src/imagemapLink.js +1 -1
- package/src/index.js +9 -6
- package/src/link/category.js +4 -9
- package/src/link/file.js +16 -11
- package/src/link/galleryImage.js +7 -7
- package/src/link/index.js +19 -18
- package/src/magicLink.js +2 -2
- package/src/nested/index.js +3 -6
- package/src/nowiki/comment.js +2 -2
- package/src/nowiki/index.js +2 -2
- package/src/nowiki/quote.js +3 -3
- package/src/paramTag/index.js +3 -3
- package/src/parameter.js +2 -2
- package/src/table/index.js +5 -6
- package/src/table/td.js +5 -6
- package/src/table/tr.js +3 -3
- package/src/tagPair/ext.js +14 -8
- package/src/transclude.js +43 -26
- package/util/lint.js +8 -7
- package/util/string.js +20 -1
package/src/attribute.js
CHANGED
|
@@ -302,7 +302,7 @@ class AttributeToken extends fixedToken(Token) {
|
|
|
302
302
|
* @override
|
|
303
303
|
* @param {number} start 起始位置
|
|
304
304
|
*/
|
|
305
|
-
lint(start =
|
|
305
|
+
lint(start = this.getAbsoluteIndex()) {
|
|
306
306
|
const errors = super.lint(start),
|
|
307
307
|
{balanced, firstChild, lastChild, type, name, parentNode, value} = this,
|
|
308
308
|
tagName = parentNode?.name;
|
|
@@ -310,7 +310,7 @@ class AttributeToken extends fixedToken(Token) {
|
|
|
310
310
|
if (!balanced) {
|
|
311
311
|
const root = this.getRootNode();
|
|
312
312
|
rect = {start, ...root.posFromIndex(start)};
|
|
313
|
-
const e = generateForChild(lastChild, rect, '
|
|
313
|
+
const e = generateForChild(lastChild, rect, 'unclosed quotes', 'warning'),
|
|
314
314
|
startIndex = e.startIndex - 1,
|
|
315
315
|
startCol = e.startCol - 1;
|
|
316
316
|
errors.push({...e, startIndex, startCol, excerpt: String(root).slice(startIndex, startIndex + 50)});
|
|
@@ -322,10 +322,10 @@ class AttributeToken extends fixedToken(Token) {
|
|
|
322
322
|
&& (tagName === 'meta' || tagName === 'link' || !commonHtmlAttrs.has(name))
|
|
323
323
|
)) {
|
|
324
324
|
rect ||= {start, ...this.getRootNode().posFromIndex(start)};
|
|
325
|
-
errors.push(generateForChild(firstChild, rect, '
|
|
325
|
+
errors.push(generateForChild(firstChild, rect, 'illegal attribute name'));
|
|
326
326
|
} else if (name === 'style' && typeof value === 'string' && insecureStyle.test(value)) {
|
|
327
327
|
rect ||= {start, ...this.getRootNode().posFromIndex(start)};
|
|
328
|
-
errors.push(generateForChild(lastChild, rect, '
|
|
328
|
+
errors.push(generateForChild(lastChild, rect, 'insecure style'));
|
|
329
329
|
}
|
|
330
330
|
return errors;
|
|
331
331
|
}
|
|
@@ -355,6 +355,14 @@ class AttributeToken extends fixedToken(Token) {
|
|
|
355
355
|
return key === 'quotes' ? this.#quotes : super.getAttribute(key);
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
/**
|
|
359
|
+
* @override
|
|
360
|
+
* @param {PropertyKey} key 属性键
|
|
361
|
+
*/
|
|
362
|
+
hasAttribute(key) {
|
|
363
|
+
return key === 'equal' || key === 'quotes' || super.hasAttribute(key);
|
|
364
|
+
}
|
|
365
|
+
|
|
358
366
|
/** @override */
|
|
359
367
|
cloneNode() {
|
|
360
368
|
const [key, value] = this.cloneChildNodes(),
|
|
@@ -401,8 +409,7 @@ class AttributeToken extends fixedToken(Token) {
|
|
|
401
409
|
if (length !== 1 || tag.type !== type.slice(0, -5)) {
|
|
402
410
|
throw new SyntaxError(`非法的标签属性:${noWrap(value)}`);
|
|
403
411
|
} else if (type === 'table-attr') {
|
|
404
|
-
|
|
405
|
-
if (tableLength !== 2) {
|
|
412
|
+
if (tag.length !== 2) {
|
|
406
413
|
throw new SyntaxError(`非法的标签属性:${noWrap(value)}`);
|
|
407
414
|
}
|
|
408
415
|
attrs = tag.lastChild;
|
|
@@ -442,8 +449,7 @@ class AttributeToken extends fixedToken(Token) {
|
|
|
442
449
|
if (length !== 1 || tag.type !== type.slice(0, -5)) {
|
|
443
450
|
throw new SyntaxError(`非法的标签属性名:${noWrap(key)}`);
|
|
444
451
|
} else if (type === 'table-attr') {
|
|
445
|
-
|
|
446
|
-
if (tableLength !== 2) {
|
|
452
|
+
if (tag.length !== 2) {
|
|
447
453
|
throw new SyntaxError(`非法的标签属性名:${noWrap(key)}`);
|
|
448
454
|
}
|
|
449
455
|
attrs = tag.lastChild;
|
package/src/attributes.js
CHANGED
|
@@ -183,7 +183,7 @@ class AttributesToken extends Token {
|
|
|
183
183
|
* @this {AttributesToken & {parentNode: HtmlToken}}
|
|
184
184
|
* @param {number} start 起始位置
|
|
185
185
|
*/
|
|
186
|
-
lint(start =
|
|
186
|
+
lint(start = this.getAbsoluteIndex()) {
|
|
187
187
|
const HtmlToken = require('./html');
|
|
188
188
|
const errors = super.lint(start),
|
|
189
189
|
{parentNode: {closing}, length, childNodes} = this,
|
|
@@ -192,14 +192,14 @@ class AttributesToken extends Token {
|
|
|
192
192
|
let rect;
|
|
193
193
|
if (closing && this.text().trim()) {
|
|
194
194
|
rect = {start, ...this.getRootNode().posFromIndex(start)};
|
|
195
|
-
errors.push(generateForSelf(this, rect, '
|
|
195
|
+
errors.push(generateForSelf(this, rect, 'attributes of a closing tag'));
|
|
196
196
|
}
|
|
197
197
|
for (let i = 0; i < length; i++) {
|
|
198
198
|
const /** @type {AtomToken|AttributeToken} */ attr = childNodes[i];
|
|
199
199
|
if (attr instanceof AtomToken && attr.text().trim()) {
|
|
200
200
|
rect ||= {start, ...this.getRootNode().posFromIndex(start)};
|
|
201
201
|
errors.push({
|
|
202
|
-
...generateForChild(attr, rect, '
|
|
202
|
+
...generateForChild(attr, rect, 'containing invalid attribute'),
|
|
203
203
|
excerpt: childNodes.slice(i).map(String).join('').slice(0, 50),
|
|
204
204
|
});
|
|
205
205
|
} else if (attr instanceof AttributeToken) {
|
|
@@ -215,7 +215,7 @@ class AttributesToken extends Token {
|
|
|
215
215
|
if (duplicated.size > 0) {
|
|
216
216
|
rect ||= {start, ...this.getRootNode().posFromIndex(start)};
|
|
217
217
|
for (const key of duplicated) {
|
|
218
|
-
errors.push(...attrs[key].map(attr => generateForChild(attr, rect,
|
|
218
|
+
errors.push(...attrs[key].map(attr => generateForChild(attr, rect, Parser.msg('duplicated $1 attribute', key))));
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
return errors;
|
|
@@ -236,10 +236,9 @@ class AttributesToken extends Token {
|
|
|
236
236
|
|
|
237
237
|
/** 清理标签属性 */
|
|
238
238
|
sanitize() {
|
|
239
|
-
const {childNodes, length} = this;
|
|
240
239
|
let dirty = false;
|
|
241
|
-
for (let i = length - 1; i >= 0; i--) {
|
|
242
|
-
const child = childNodes[i];
|
|
240
|
+
for (let i = this.length - 1; i >= 0; i--) {
|
|
241
|
+
const child = this.childNodes[i];
|
|
243
242
|
if (child instanceof AtomToken && child.text().trim()) {
|
|
244
243
|
dirty = true;
|
|
245
244
|
this.removeAt(i);
|
package/src/converterFlags.js
CHANGED
|
@@ -67,7 +67,7 @@ class ConverterFlagsToken extends Token {
|
|
|
67
67
|
* @override
|
|
68
68
|
* @param {number} start 起始位置
|
|
69
69
|
*/
|
|
70
|
-
lint(start =
|
|
70
|
+
lint(start = this.getAbsoluteIndex()) {
|
|
71
71
|
const variantFlags = this.getVariantFlags(),
|
|
72
72
|
unknownFlags = this.getUnknownFlags(),
|
|
73
73
|
validFlags = new Set(this.#flags.filter(flag => definedFlags.has(flag))),
|
|
@@ -85,7 +85,7 @@ class ConverterFlagsToken extends Token {
|
|
|
85
85
|
if (flag && !variantFlags.has(flag) && !unknownFlags.has(flag)
|
|
86
86
|
&& (variantFlags.size > 0 || !validFlags.has(flag))
|
|
87
87
|
) {
|
|
88
|
-
const error = generateForChild(child, rect, '
|
|
88
|
+
const error = generateForChild(child, rect, 'invalid conversion flag');
|
|
89
89
|
errors.push({...error, excerpt: childNodes.slice(0, i + 1).map(String).join(';').slice(-50)});
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -97,7 +97,7 @@ class ConverterFlagsToken extends Token {
|
|
|
97
97
|
* @complexity `n`
|
|
98
98
|
*/
|
|
99
99
|
getUnknownFlags() {
|
|
100
|
-
return new Set(this.#flags.filter(flag => /\{
|
|
100
|
+
return new Set(this.#flags.filter(flag => /\{{3}[^{}]+\}{3}/u.test(flag)));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/** 获取指定语言变体的转换标记 */
|
package/src/converterRule.js
CHANGED
|
@@ -48,9 +48,8 @@ class ConverterRuleToken extends Token {
|
|
|
48
48
|
if (hasColon) {
|
|
49
49
|
const i = rule.indexOf(':'),
|
|
50
50
|
j = rule.slice(0, i).indexOf('=>'),
|
|
51
|
-
v = j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i)
|
|
52
|
-
|
|
53
|
-
if (variants.includes(v.trim())) {
|
|
51
|
+
v = j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i);
|
|
52
|
+
if (config.variants.includes(v.trim())) {
|
|
54
53
|
super.insertAt(new AtomToken(v, 'converter-rule-variant', config, accum));
|
|
55
54
|
super.insertAt(new AtomToken(rule.slice(i + 1), 'converter-rule-to', config, accum));
|
|
56
55
|
if (j !== -1) {
|
|
@@ -127,9 +126,8 @@ class ConverterRuleToken extends Token {
|
|
|
127
126
|
/** @override */
|
|
128
127
|
afterBuild() {
|
|
129
128
|
const /** @type {AstListener} */ converterRuleListener = (e, data) => {
|
|
130
|
-
const {
|
|
131
|
-
|
|
132
|
-
if (length > 1 && childNodes.at(-2) === prevTarget) {
|
|
129
|
+
const {prevTarget} = e;
|
|
130
|
+
if (this.length > 1 && this.childNodes.at(-2) === prevTarget) {
|
|
133
131
|
const v = prevTarget.text().trim(),
|
|
134
132
|
{variants} = this.getAttribute('config');
|
|
135
133
|
if (!variants.includes(v)) {
|
package/src/extLink.js
CHANGED
|
@@ -51,7 +51,7 @@ class ExtLinkToken extends Token {
|
|
|
51
51
|
* @param {string} text 链接文字
|
|
52
52
|
* @param {accum} accum
|
|
53
53
|
*/
|
|
54
|
-
constructor(url, space, text, config = Parser.getConfig(), accum = []) {
|
|
54
|
+
constructor(url, space = '', text = '', config = Parser.getConfig(), accum = []) {
|
|
55
55
|
super(undefined, config, true, accum, {
|
|
56
56
|
MagicLinkToken: 0, Token: 1,
|
|
57
57
|
});
|
|
@@ -101,8 +101,9 @@ class ExtLinkToken extends Token {
|
|
|
101
101
|
|
|
102
102
|
/** @override */
|
|
103
103
|
print() {
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
return super.print(
|
|
105
|
+
this.length > 1 ? {pre: '[', sep: this.#space, post: ']'} : {pre: '[', post: `${this.#space}]`},
|
|
106
|
+
);
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
/** @override */
|
package/src/gallery.js
CHANGED
|
@@ -26,14 +26,11 @@ class GalleryToken extends Token {
|
|
|
26
26
|
super(undefined, config, true, accum, {
|
|
27
27
|
AstText: ':', GalleryImageToken: ':', HiddenToken: ':',
|
|
28
28
|
});
|
|
29
|
-
const /** @type {ParserConfig} */ newConfig = {
|
|
30
|
-
...config, img: Object.fromEntries(Object.entries(config.img).filter(([, param]) => param !== 'width')),
|
|
31
|
-
};
|
|
32
29
|
for (const line of inner?.split('\n') ?? []) {
|
|
33
30
|
const matches = /^([^|]+)(?:\|(.*))?/u.exec(line);
|
|
34
31
|
if (!matches) {
|
|
35
32
|
super.insertAt(line.trim()
|
|
36
|
-
? new HiddenToken(line, undefined,
|
|
33
|
+
? new HiddenToken(line, undefined, config, [], {
|
|
37
34
|
AstText: ':',
|
|
38
35
|
})
|
|
39
36
|
: line);
|
|
@@ -42,9 +39,9 @@ class GalleryToken extends Token {
|
|
|
42
39
|
const [, file, alt] = matches,
|
|
43
40
|
title = this.normalizeTitle(file, 6, true, true);
|
|
44
41
|
if (title.valid) {
|
|
45
|
-
super.insertAt(new GalleryImageToken(file, alt,
|
|
42
|
+
super.insertAt(new GalleryImageToken(file, alt, config, accum));
|
|
46
43
|
} else {
|
|
47
|
-
super.insertAt(new HiddenToken(line, undefined,
|
|
44
|
+
super.insertAt(new HiddenToken(line, undefined, config, [], {
|
|
48
45
|
AstText: ':',
|
|
49
46
|
}));
|
|
50
47
|
}
|
|
@@ -78,7 +75,7 @@ class GalleryToken extends Token {
|
|
|
78
75
|
* @override
|
|
79
76
|
* @param {number} start 起始位置
|
|
80
77
|
*/
|
|
81
|
-
lint(start =
|
|
78
|
+
lint(start = this.getAbsoluteIndex()) {
|
|
82
79
|
const {top, left} = this.getRootNode().posFromIndex(start),
|
|
83
80
|
/** @type {LintError[]} */ errors = [];
|
|
84
81
|
for (let i = 0, startIndex = start; i < this.length; i++) {
|
|
@@ -90,7 +87,7 @@ class GalleryToken extends Token {
|
|
|
90
87
|
startCol = i ? 0 : left;
|
|
91
88
|
if (child.type === 'hidden' && trimmed && !/^<!--.*-->$/u.test(trimmed)) {
|
|
92
89
|
errors.push({
|
|
93
|
-
message: '
|
|
90
|
+
message: Parser.msg('invalid content in <$1>', 'gallery'),
|
|
94
91
|
severity: 'error',
|
|
95
92
|
startIndex,
|
|
96
93
|
endIndex: startIndex + length,
|
|
@@ -127,7 +124,7 @@ class GalleryToken extends Token {
|
|
|
127
124
|
insertImage(file, i = this.length) {
|
|
128
125
|
const title = this.normalizeTitle(file, 6, true, true);
|
|
129
126
|
if (title.valid) {
|
|
130
|
-
const token = Parser.run(() => new GalleryImageToken(file, undefined,
|
|
127
|
+
const token = Parser.run(() => new GalleryImageToken(file, undefined, this.getAttribute('config')));
|
|
131
128
|
return this.insertAt(token, i);
|
|
132
129
|
}
|
|
133
130
|
throw new SyntaxError(`非法的文件名:${file}`);
|
package/src/heading.js
CHANGED
|
@@ -39,7 +39,7 @@ class HeadingToken extends fixedToken(sol(Token)) {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* @override
|
|
42
|
-
* @this {{prependNewLine(): ''|'\n'
|
|
42
|
+
* @this {{prependNewLine(): ''|'\n'} & HeadingToken}
|
|
43
43
|
* @param {string} selector
|
|
44
44
|
* @returns {string}
|
|
45
45
|
*/
|
|
@@ -49,17 +49,17 @@ class HeadingToken extends fixedToken(sol(Token)) {
|
|
|
49
49
|
? ''
|
|
50
50
|
: `${this.prependNewLine()}${equals}${
|
|
51
51
|
this.firstChild.toString(selector)
|
|
52
|
-
}${equals}${this.lastChild.toString(selector)}
|
|
52
|
+
}${equals}${this.lastChild.toString(selector)}`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* @override
|
|
57
|
-
* @this {HeadingToken & {prependNewLine(): ''|'\n'
|
|
57
|
+
* @this {HeadingToken & {prependNewLine(): ''|'\n'}}
|
|
58
58
|
* @returns {string}
|
|
59
59
|
*/
|
|
60
60
|
text() {
|
|
61
61
|
const equals = '='.repeat(Number(this.name));
|
|
62
|
-
return `${this.prependNewLine()}${equals}${this.firstChild.text()}${equals}
|
|
62
|
+
return `${this.prependNewLine()}${equals}${this.firstChild.text()}${equals}`;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/** @override */
|
|
@@ -82,7 +82,7 @@ class HeadingToken extends fixedToken(sol(Token)) {
|
|
|
82
82
|
* @override
|
|
83
83
|
* @param {number} start 起始位置
|
|
84
84
|
*/
|
|
85
|
-
lint(start =
|
|
85
|
+
lint(start = this.getAbsoluteIndex()) {
|
|
86
86
|
const errors = super.lint(start),
|
|
87
87
|
innerText = String(this.firstChild);
|
|
88
88
|
let refError;
|
|
@@ -90,13 +90,13 @@ class HeadingToken extends fixedToken(sol(Token)) {
|
|
|
90
90
|
refError = generateForSelf(this, {start}, '<h1>');
|
|
91
91
|
errors.push(refError);
|
|
92
92
|
}
|
|
93
|
-
if (innerText[0] === '=' || innerText.
|
|
93
|
+
if (innerText[0] === '=' || innerText.endsWith('=')) {
|
|
94
94
|
refError ||= generateForSelf(this, {start}, '');
|
|
95
|
-
errors.push({...refError, message: '
|
|
95
|
+
errors.push({...refError, message: Parser.msg('unbalanced "=" in a section header')});
|
|
96
96
|
}
|
|
97
97
|
if (this.closest('html-attrs, table-attrs')) {
|
|
98
98
|
refError ||= generateForSelf(this, {start}, '');
|
|
99
|
-
errors.push({...refError, message: 'HTML
|
|
99
|
+
errors.push({...refError, message: Parser.msg('section header in a HTML tag')});
|
|
100
100
|
}
|
|
101
101
|
return errors;
|
|
102
102
|
}
|
package/src/html.js
CHANGED
|
@@ -7,6 +7,8 @@ const {generateForSelf} = require('../util/lint'),
|
|
|
7
7
|
Parser = require('..'),
|
|
8
8
|
Token = require('.');
|
|
9
9
|
|
|
10
|
+
const magicWords = new Set(['if', 'ifeq', 'ifexpr', 'ifexist', 'iferror', 'switch']);
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* HTML标签
|
|
12
14
|
* @classdesc `{childNodes: [AttributesToken]}`
|
|
@@ -107,7 +109,7 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
|
107
109
|
* @override
|
|
108
110
|
* @param {number} start 起始位置
|
|
109
111
|
*/
|
|
110
|
-
lint(start =
|
|
112
|
+
lint(start = this.getAbsoluteIndex()) {
|
|
111
113
|
const errors = super.lint(start);
|
|
112
114
|
let wikitext, /** @type {LintError} */ refError;
|
|
113
115
|
if (this.name === 'h1' && !this.#closing) {
|
|
@@ -119,20 +121,24 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
|
119
121
|
wikitext ||= String(this.getRootNode());
|
|
120
122
|
refError ||= generateForSelf(this, {start}, '');
|
|
121
123
|
const excerpt = wikitext.slice(Math.max(0, start - 25), start + 25);
|
|
122
|
-
errors.push({...refError, message: '
|
|
124
|
+
errors.push({...refError, message: Parser.msg('HTML tag in table attributes'), excerpt});
|
|
123
125
|
}
|
|
124
126
|
try {
|
|
125
127
|
this.findMatchingTag();
|
|
126
128
|
} catch ({message: errorMsg}) {
|
|
127
129
|
wikitext ||= String(this.getRootNode());
|
|
128
130
|
refError ||= generateForSelf(this, {start}, '');
|
|
129
|
-
const [
|
|
130
|
-
error = {...refError, message
|
|
131
|
-
if (
|
|
131
|
+
const [msg] = errorMsg.split(':'),
|
|
132
|
+
error = {...refError, message: Parser.msg(msg)};
|
|
133
|
+
if (msg === 'unclosed tag') {
|
|
134
|
+
error.severity = 'warning';
|
|
132
135
|
error.excerpt = wikitext.slice(start, start + 50);
|
|
133
|
-
} else if (
|
|
136
|
+
} else if (msg === 'unmatched closing tag') {
|
|
134
137
|
const end = start + String(this).length;
|
|
135
138
|
error.excerpt = wikitext.slice(Math.max(0, end - 50), end);
|
|
139
|
+
if (magicWords.has(this.closest('magic-word')?.name)) {
|
|
140
|
+
error.severity = 'warning';
|
|
141
|
+
}
|
|
136
142
|
}
|
|
137
143
|
errors.push(error);
|
|
138
144
|
}
|
|
@@ -151,11 +157,11 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
|
151
157
|
{name: tagName, parentNode} = this,
|
|
152
158
|
string = noWrap(String(this));
|
|
153
159
|
if (this.#closing && (this.#selfClosing || html[2].includes(tagName))) {
|
|
154
|
-
throw new SyntaxError(
|
|
160
|
+
throw new SyntaxError(`tag that is both closing and self-closing: ${string}`);
|
|
155
161
|
} else if (html[2].includes(tagName) || this.#selfClosing && html[1].includes(tagName)) { // 自封闭标签
|
|
156
162
|
return this;
|
|
157
163
|
} else if (this.#selfClosing && html[0].includes(tagName)) {
|
|
158
|
-
throw new SyntaxError(
|
|
164
|
+
throw new SyntaxError(`invalid self-closing tag: ${string}`);
|
|
159
165
|
} else if (!parentNode) {
|
|
160
166
|
return undefined;
|
|
161
167
|
}
|
|
@@ -175,7 +181,7 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
|
175
181
|
return token;
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
|
-
throw new SyntaxError(
|
|
184
|
+
throw new SyntaxError(`${this.#closing ? 'unmatched closing' : 'unclosed'} tag: ${string}`);
|
|
179
185
|
}
|
|
180
186
|
|
|
181
187
|
/** @override */
|
package/src/imageParameter.js
CHANGED
|
@@ -1,81 +1,80 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {text, noWrap, print, extUrlChar, extUrlCharFirst} = require('../util/string'),
|
|
4
|
+
{generateForSelf} = require('../util/lint'),
|
|
5
|
+
Title = require('../lib/title'),
|
|
4
6
|
Parser = require('..'),
|
|
5
7
|
AstText = require('../lib/text'),
|
|
6
8
|
Token = require('.');
|
|
7
9
|
|
|
10
|
+
const params = new Set(['alt', 'link', 'lang', 'page', 'caption']);
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
13
|
+
* 检查图片参数是否合法
|
|
14
|
+
* @template {string} T
|
|
15
|
+
* @param {T} key 参数名
|
|
16
|
+
* @param {string} value 参数值
|
|
17
|
+
* @returns {T extends 'link' ? string|Title : boolean}
|
|
11
18
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!value) {
|
|
29
|
-
return this.noLink;
|
|
30
|
-
}
|
|
31
|
-
const regex = new RegExp(`(?:(?:${config.protocol}|//)${extUrlCharFirst}|\0\\d+m\x7F)${
|
|
32
|
-
extUrlChar
|
|
33
|
-
}(?=\0\\d+t\x7F|$)`, 'iu');
|
|
34
|
-
if (regex.test(value)) {
|
|
35
|
-
return value;
|
|
36
|
-
} else if (value.startsWith('[[') && value.endsWith(']]')) {
|
|
37
|
-
value = value.slice(2, -2);
|
|
38
|
-
}
|
|
39
|
-
const title = Parser.normalizeTitle(value, 0, false, config, halfParsed, true, true);
|
|
40
|
-
return title.valid && String(title);
|
|
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);
|
|
41
35
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
case 'alt':
|
|
45
|
-
case 'class':
|
|
46
|
-
case 'manualthumb':
|
|
47
|
-
return true;
|
|
48
|
-
default:
|
|
49
|
-
return !isNaN(value);
|
|
36
|
+
const title = Parser.normalizeTitle(value, 0, false, config, halfParsed, true, true);
|
|
37
|
+
return title.valid && title;
|
|
50
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);
|
|
51
47
|
}
|
|
48
|
+
};
|
|
52
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 图片参数
|
|
52
|
+
* @classdesc `{childNodes: ...(AstText|Token)}`
|
|
53
|
+
*/
|
|
54
|
+
class ImageParameterToken extends Token {
|
|
53
55
|
type = 'image-parameter';
|
|
54
56
|
#syntax = '';
|
|
55
57
|
|
|
56
|
-
/** getValue()的getter */
|
|
57
|
-
get value() {
|
|
58
|
-
return this.getValue();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
set value(value) {
|
|
62
|
-
this.setValue(value);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
58
|
/** 图片链接 */
|
|
66
59
|
get link() {
|
|
67
|
-
return this.name === 'link'
|
|
68
|
-
? ImageParameterToken.#validate('link', this.getValue(), this.getAttribute('config'))
|
|
69
|
-
: undefined;
|
|
60
|
+
return this.name === 'link' ? validate('link', super.text(), this.getAttribute('config')) : undefined;
|
|
70
61
|
}
|
|
71
62
|
|
|
72
63
|
set link(value) {
|
|
73
64
|
if (this.name === 'link') {
|
|
74
|
-
value = value === ImageParameterToken.noLink ? '' : value;
|
|
75
65
|
this.setValue(value);
|
|
76
66
|
}
|
|
77
67
|
}
|
|
78
68
|
|
|
69
|
+
/** getValue()的getter */
|
|
70
|
+
get value() {
|
|
71
|
+
return this.getValue();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
set value(value) {
|
|
75
|
+
this.setValue(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
79
78
|
/** 图片大小 */
|
|
80
79
|
get size() {
|
|
81
80
|
if (this.name === 'width') {
|
|
@@ -133,7 +132,7 @@ class ImageParameterToken extends Token {
|
|
|
133
132
|
),
|
|
134
133
|
param = regexes.find(([, key, regex]) => {
|
|
135
134
|
mt = regex.exec(str);
|
|
136
|
-
return mt && (mt.length !== 4 ||
|
|
135
|
+
return mt && (mt.length !== 4 || validate(key, mt[2], config, true) !== false);
|
|
137
136
|
});
|
|
138
137
|
if (param) {
|
|
139
138
|
if (mt.length === 3) {
|
|
@@ -152,6 +151,13 @@ class ImageParameterToken extends Token {
|
|
|
152
151
|
this.setAttribute('name', 'caption').setAttribute('stage', 7);
|
|
153
152
|
}
|
|
154
153
|
|
|
154
|
+
/** @override */
|
|
155
|
+
afterBuild() {
|
|
156
|
+
if (this.parentNode.type === 'gallery-image' && !params.has(this.name)) {
|
|
157
|
+
this.setAttribute('name', 'invalid');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
155
161
|
/** @override */
|
|
156
162
|
isPlain() {
|
|
157
163
|
return this.name === 'caption';
|
|
@@ -177,6 +183,21 @@ class ImageParameterToken extends Token {
|
|
|
177
183
|
return Math.max(0, this.#syntax.indexOf('$1'));
|
|
178
184
|
}
|
|
179
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
|
+
|
|
180
201
|
/** @override */
|
|
181
202
|
print() {
|
|
182
203
|
return this.#syntax
|
|
@@ -240,7 +261,7 @@ class ImageParameterToken extends Token {
|
|
|
240
261
|
* @complexity `n`
|
|
241
262
|
*/
|
|
242
263
|
getValue() {
|
|
243
|
-
return this.#isVoid() || super.text();
|
|
264
|
+
return this.name === 'invalid' ? this.text() : this.#isVoid() || super.text();
|
|
244
265
|
}
|
|
245
266
|
|
|
246
267
|
/**
|
|
@@ -250,7 +271,9 @@ class ImageParameterToken extends Token {
|
|
|
250
271
|
* @throws SyntaxError` 非法的参数值
|
|
251
272
|
*/
|
|
252
273
|
setValue(value) {
|
|
253
|
-
if (this
|
|
274
|
+
if (this.name === 'invalid') {
|
|
275
|
+
throw new Error('无效的图片参数!');
|
|
276
|
+
} else if (this.#isVoid()) {
|
|
254
277
|
if (typeof value !== 'boolean') {
|
|
255
278
|
this.typeError('setValue', 'Boolean');
|
|
256
279
|
} else if (value === false) {
|
package/src/imagemap.js
CHANGED
|
@@ -61,7 +61,7 @@ class ImagemapToken extends Token {
|
|
|
61
61
|
title = this.normalizeTitle(file, 0, true);
|
|
62
62
|
if (title.valid && !title.interwiki && title.ns === 6) {
|
|
63
63
|
const token = new GalleryImageToken(
|
|
64
|
-
file, options.length > 0 ? options.join('|') : undefined,
|
|
64
|
+
file, options.length > 0 ? options.join('|') : undefined, config, accum,
|
|
65
65
|
);
|
|
66
66
|
token.type = 'imagemap-image';
|
|
67
67
|
super.insertAt(token);
|
|
@@ -83,7 +83,7 @@ class ImagemapToken extends Token {
|
|
|
83
83
|
if (title.valid) {
|
|
84
84
|
super.insertAt(new ImagemapLinkToken(
|
|
85
85
|
line.slice(0, i),
|
|
86
|
-
|
|
86
|
+
mtIn.slice(1),
|
|
87
87
|
substr.slice(substr.indexOf(']]') + 2),
|
|
88
88
|
config,
|
|
89
89
|
accum,
|
|
@@ -137,7 +137,7 @@ class ImagemapToken extends Token {
|
|
|
137
137
|
* @override
|
|
138
138
|
* @param {number} start 起始位置
|
|
139
139
|
*/
|
|
140
|
-
lint(start =
|
|
140
|
+
lint(start = this.getAbsoluteIndex()) {
|
|
141
141
|
const errors = super.lint(start),
|
|
142
142
|
rect = {start, ...this.getRootNode().posFromIndex(start)};
|
|
143
143
|
if (this.image) {
|
|
@@ -145,10 +145,10 @@ class ImagemapToken extends Token {
|
|
|
145
145
|
...this.childNodes.filter(child => {
|
|
146
146
|
const str = String(child).trim();
|
|
147
147
|
return child.type === 'noinclude' && str && str[0] !== '#';
|
|
148
|
-
}).map(child => generateForChild(child, rect, '
|
|
148
|
+
}).map(child => generateForChild(child, rect, 'invalid link in <imagemap>')),
|
|
149
149
|
);
|
|
150
150
|
} else {
|
|
151
|
-
errors.push(generateForSelf(this, rect, '
|
|
151
|
+
errors.push(generateForSelf(this, rect, '<imagemap> without an image'));
|
|
152
152
|
}
|
|
153
153
|
return errors;
|
|
154
154
|
}
|
package/src/imagemapLink.js
CHANGED
|
@@ -31,7 +31,7 @@ class ImagemapLinkToken extends fixedToken(singleLine(Token)) {
|
|
|
31
31
|
* @param {accum} accum
|
|
32
32
|
*/
|
|
33
33
|
constructor(pre, linkStuff, post, config, accum) {
|
|
34
|
-
const SomeLinkToken = linkStuff
|
|
34
|
+
const SomeLinkToken = linkStuff.length === 2 ? LinkToken : ExtLinkToken;
|
|
35
35
|
super(undefined, config, true, accum);
|
|
36
36
|
this.append(pre, new SomeLinkToken(...linkStuff, config, accum), new NoincludeToken(post, config, accum));
|
|
37
37
|
}
|
package/src/index.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* -: `{{!-}}`专用
|
|
30
30
|
* +: `{{!!}}`专用
|
|
31
31
|
* ~: `{{=}}`专用
|
|
32
|
+
* s: `{{{|subst:}}}`
|
|
32
33
|
* m: `{{fullurl:}}`、`{{canonicalurl:}}`或`{{filepath:}}`
|
|
33
34
|
* t: ArgToken或TranscludeToken
|
|
34
35
|
* h: HeadingToken
|
|
@@ -135,7 +136,7 @@ class Token extends AstElement {
|
|
|
135
136
|
#buildFromStr = (str, type) => {
|
|
136
137
|
const nodes = str.split(/[\0\x7F]/u).map((s, i) => {
|
|
137
138
|
if (i % 2 === 0) {
|
|
138
|
-
return new AstText(s
|
|
139
|
+
return new AstText(s);
|
|
139
140
|
} else if (isNaN(s.at(-1))) {
|
|
140
141
|
return this.#accum[Number(s.slice(0, -1))];
|
|
141
142
|
}
|
|
@@ -308,7 +309,7 @@ class Token extends AstElement {
|
|
|
308
309
|
*/
|
|
309
310
|
insertAt(token, i = this.length) {
|
|
310
311
|
if (typeof token === 'string') {
|
|
311
|
-
token = new AstText(token
|
|
312
|
+
token = new AstText(token);
|
|
312
313
|
}
|
|
313
314
|
if (!Parser.running && this.#acceptable) {
|
|
314
315
|
const acceptableIndices = Object.fromEntries(
|
|
@@ -449,7 +450,7 @@ class Token extends AstElement {
|
|
|
449
450
|
* @param {string} data 文本内容
|
|
450
451
|
*/
|
|
451
452
|
createTextNode(data = '') {
|
|
452
|
-
return typeof data === 'string' ? new AstText(data
|
|
453
|
+
return typeof data === 'string' ? new AstText(data) : this.typeError('createComment', 'String');
|
|
453
454
|
}
|
|
454
455
|
|
|
455
456
|
/**
|
|
@@ -915,7 +916,7 @@ class Token extends AstElement {
|
|
|
915
916
|
}
|
|
916
917
|
const parseList = require('../parser/list');
|
|
917
918
|
const lines = String(this.firstChild).split('\n');
|
|
918
|
-
let i = this.type === 'root' || this.type === 'ext-inner' && this.
|
|
919
|
+
let i = this.type === 'root' || this.type === 'ext-inner' && this.name === 'poem' ? 0 : 1;
|
|
919
920
|
for (; i < lines.length; i++) {
|
|
920
921
|
lines[i] = parseList(lines[i], this.#config, this.#accum);
|
|
921
922
|
}
|
|
@@ -924,8 +925,10 @@ class Token extends AstElement {
|
|
|
924
925
|
|
|
925
926
|
/** 解析语言变体转换 */
|
|
926
927
|
#parseConverter() {
|
|
927
|
-
|
|
928
|
-
|
|
928
|
+
if (this.#config.variants?.length > 0) {
|
|
929
|
+
const parseConverter = require('../parser/converter');
|
|
930
|
+
this.setText(parseConverter(String(this.firstChild), this.#config, this.#accum));
|
|
931
|
+
}
|
|
929
932
|
}
|
|
930
933
|
}
|
|
931
934
|
|