wikiparser-node 0.4.0 → 0.5.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/index.js +25 -2
- package/lib/element.js +69 -185
- package/lib/node.js +159 -1
- package/lib/ranges.js +1 -2
- package/lib/text.js +35 -6
- package/lib/title.js +1 -1
- package/mixin/fixedToken.js +4 -4
- package/mixin/sol.js +17 -7
- package/package.json +11 -1
- package/parser/commentAndExt.js +1 -1
- package/parser/converter.js +1 -1
- package/parser/externalLinks.js +1 -1
- package/parser/hrAndDoubleUnderscore.js +6 -5
- package/parser/links.js +1 -2
- package/parser/magicLinks.js +1 -1
- package/parser/selector.js +5 -5
- package/parser/table.js +12 -12
- package/src/arg.js +44 -20
- package/src/attribute.js +34 -7
- package/src/converter.js +13 -5
- package/src/converterFlags.js +42 -5
- package/src/converterRule.js +25 -19
- package/src/extLink.js +20 -14
- package/src/gallery.js +35 -4
- package/src/heading.js +28 -9
- package/src/html.js +46 -18
- package/src/imageParameter.js +13 -7
- package/src/index.js +22 -15
- package/src/link/category.js +6 -6
- package/src/link/file.js +25 -5
- package/src/link/index.js +36 -33
- package/src/magicLink.js +32 -4
- package/src/nowiki/comment.js +14 -0
- package/src/nowiki/doubleUnderscore.js +5 -0
- package/src/nowiki/quote.js +28 -1
- package/src/onlyinclude.js +5 -0
- package/src/parameter.js +48 -35
- package/src/table/index.js +37 -24
- package/src/table/td.js +23 -17
- package/src/table/tr.js +47 -30
- package/src/tagPair/ext.js +4 -5
- package/src/tagPair/include.js +10 -0
- package/src/tagPair/index.js +8 -0
- package/src/transclude.js +79 -46
- package/tool/index.js +1 -1
- package/{test/util.js → util/diff.js} +14 -18
- package/util/lint.js +40 -0
- package/util/string.js +20 -3
- package/.eslintrc.json +0 -714
- package/errors/README +0 -1
- package/jsconfig.json +0 -7
- package/printed/README +0 -1
- package/printed/example.json +0 -120
- package/test/api.js +0 -83
- package/test/real.js +0 -133
- package/test/test.js +0 -28
- package/typings/api.d.ts +0 -13
- package/typings/array.d.ts +0 -28
- package/typings/event.d.ts +0 -24
- package/typings/index.d.ts +0 -94
- package/typings/node.d.ts +0 -29
- package/typings/parser.d.ts +0 -16
- package/typings/table.d.ts +0 -14
- package/typings/token.d.ts +0 -22
- package/typings/tool.d.ts +0 -11
package/mixin/sol.js
CHANGED
|
@@ -9,25 +9,35 @@ const Parser = require('..'),
|
|
|
9
9
|
* @param {T} Constructor 基类
|
|
10
10
|
* @returns {T}
|
|
11
11
|
*/
|
|
12
|
-
const sol = Constructor => class extends Constructor {
|
|
12
|
+
const sol = Constructor => class SolToken extends Constructor {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* 是否可以视为root节点
|
|
15
15
|
* @this {Token}
|
|
16
|
+
* @param {boolean} includeHeading 是否包括HeadingToken
|
|
17
|
+
*/
|
|
18
|
+
#isRoot(includeHeading) {
|
|
19
|
+
const {parentNode, type} = this;
|
|
20
|
+
return parentNode?.type === 'root'
|
|
21
|
+
|| parentNode?.type === 'ext-inner' && parentNode?.name === 'poem'
|
|
22
|
+
&& (includeHeading || type !== 'heading');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 在前方插入newline
|
|
27
|
+
* @this {SolToken & Token}
|
|
16
28
|
*/
|
|
17
29
|
prependNewLine() {
|
|
18
|
-
|
|
19
|
-
return (previousVisibleSibling || parentNode?.type !== 'root') && String(previousVisibleSibling).at(-1) !== '\n'
|
|
30
|
+
return (this.previousVisibleSibling || !this.#isRoot()) && String(this.previousVisibleSibling).at(-1) !== '\n'
|
|
20
31
|
? '\n'
|
|
21
32
|
: '';
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
/**
|
|
25
36
|
* 在后方插入newline
|
|
26
|
-
* @this {Token}
|
|
37
|
+
* @this {SolToken & Token}
|
|
27
38
|
*/
|
|
28
39
|
appendNewLine() {
|
|
29
|
-
|
|
30
|
-
return (nextVisibleSibling || parentNode?.type !== 'root') && String(nextVisibleSibling ?? '')[0] !== '\n'
|
|
40
|
+
return (this.nextVisibleSibling || !this.#isRoot(true)) && String(this.nextVisibleSibling ?? '')[0] !== '\n'
|
|
31
41
|
? '\n'
|
|
32
42
|
: '';
|
|
33
43
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wikiparser-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A Node.js parser for MediaWiki markup with AST",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mediawiki",
|
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "GPL-3.0",
|
|
15
15
|
"author": "Bhsd",
|
|
16
|
+
"files": [
|
|
17
|
+
"/index.js",
|
|
18
|
+
"/parser/",
|
|
19
|
+
"/util/",
|
|
20
|
+
"/lib/",
|
|
21
|
+
"/src/",
|
|
22
|
+
"/config/",
|
|
23
|
+
"/mixin/",
|
|
24
|
+
"/tool/"
|
|
25
|
+
],
|
|
16
26
|
"repository": {
|
|
17
27
|
"type": "git",
|
|
18
28
|
"url": "git+https://github.com/bhsd-harry/wikiparser-node.git"
|
package/parser/commentAndExt.js
CHANGED
|
@@ -35,7 +35,7 @@ const parseCommentAndExt = (text, config = Parser.getConfig(), accum = [], inclu
|
|
|
35
35
|
+ `<(${noincludeRegex})(\\s[^>]*?)?(?:/>|>(.*?)(?:</(\\5\\s*)>|$))`, // <noinclude>
|
|
36
36
|
'gisu',
|
|
37
37
|
);
|
|
38
|
-
return text.
|
|
38
|
+
return text.replaceAll(
|
|
39
39
|
regex,
|
|
40
40
|
/** @type {function(...string): string} */
|
|
41
41
|
(substr, name, attr, inner, closing, include, includeAttr, includeInner, includeClosing) => {
|
package/parser/converter.js
CHANGED
|
@@ -22,7 +22,7 @@ const parseConverter = (wikitext, config = Parser.getConfig(), accum = []) => {
|
|
|
22
22
|
str = wikitext.slice(top.index + 2, index),
|
|
23
23
|
i = str.indexOf('|'),
|
|
24
24
|
[flags, text] = i === -1 ? [[], str] : [str.slice(0, i).split(';'), str.slice(i + 1)],
|
|
25
|
-
temp = text.
|
|
25
|
+
temp = text.replaceAll(/(&[#a-z\d]+);/giu, '$1\x01'),
|
|
26
26
|
variants = `(?:${config.variants.join('|')})`,
|
|
27
27
|
rules = temp.split(new RegExp(`;(?=\\s*(?:${variants}|[^;]*?=>\\s*${variants})\\s*:)`, 'u'))
|
|
28
28
|
.map(rule => rule.replaceAll('\x01', ';'));
|
package/parser/externalLinks.js
CHANGED
|
@@ -14,7 +14,7 @@ const parseExternalLinks = (wikitext, config = Parser.getConfig(), accum = []) =
|
|
|
14
14
|
`\\[((?:${config.protocol}|//)${extUrlChar})(\\p{Zs}*)([^\\]\x01-\x08\x0A-\x1F\uFFFD]*)\\]`,
|
|
15
15
|
'giu',
|
|
16
16
|
);
|
|
17
|
-
return wikitext.
|
|
17
|
+
return wikitext.replaceAll(regex, /** @type {function(...string): string} */ (_, url, space, text) => {
|
|
18
18
|
const {length} = accum,
|
|
19
19
|
mt = /&[lg]t;/u.exec(url);
|
|
20
20
|
if (mt) {
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Parser = require('..'),
|
|
4
|
-
AstText = require('../lib/text')
|
|
4
|
+
AstText = require('../lib/text'),
|
|
5
|
+
Token = require('../src');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* 解析\<hr\>和状态开关
|
|
8
|
-
* @param {{firstChild: AstText
|
|
9
|
+
* @param {Token & {firstChild: AstText}} root 根节点
|
|
9
10
|
* @param {accum} accum
|
|
10
11
|
*/
|
|
11
|
-
const parseHrAndDoubleUnderscore = ({firstChild: {data}, type}, config = Parser.getConfig(), accum = []) => {
|
|
12
|
+
const parseHrAndDoubleUnderscore = ({firstChild: {data}, type, name}, config = Parser.getConfig(), accum = []) => {
|
|
12
13
|
const HrToken = require('../src/nowiki/hr'),
|
|
13
14
|
DoubleUnderscoreToken = require('../src/nowiki/doubleUnderscore');
|
|
14
15
|
const {doubleUnderscore} = config;
|
|
15
|
-
if (type !== 'root') {
|
|
16
|
+
if (type !== 'root' && (type !== 'ext-inner' || name !== 'poem')) {
|
|
16
17
|
data = `\0${data}`;
|
|
17
18
|
}
|
|
18
19
|
data = data.replaceAll(/^((?:\0\d+c\x7F)*)(-{4,})/gmu, (_, lead, m) => {
|
|
@@ -28,7 +29,7 @@ const parseHrAndDoubleUnderscore = ({firstChild: {data}, type}, config = Parser.
|
|
|
28
29
|
return m;
|
|
29
30
|
},
|
|
30
31
|
);
|
|
31
|
-
return type === 'root' ? data : data.slice(1);
|
|
32
|
+
return type === 'root' || type === 'ext-inner' && name === 'poem' ? data : data.slice(1);
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
Parser.parsers.parseHrAndDoubleUnderscore = __filename;
|
package/parser/links.js
CHANGED
package/parser/magicLinks.js
CHANGED
|
@@ -11,7 +11,7 @@ const {extUrlChar} = require('../util/string'),
|
|
|
11
11
|
const parseMagicLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
|
|
12
12
|
const MagicLinkToken = require('../src/magicLink');
|
|
13
13
|
const regex = new RegExp(`\\b(?:${config.protocol})(${extUrlChar})`, 'giu');
|
|
14
|
-
return wikitext.
|
|
14
|
+
return wikitext.replaceAll(regex, /** @param {string} p1 */ (m, p1) => {
|
|
15
15
|
let trail = '',
|
|
16
16
|
url = m;
|
|
17
17
|
const m2 = /&(?:lt|gt|nbsp|#x0*(?:3[ce]|a0)|#0*(?:6[02]|160));/iu.exec(url);
|
package/parser/selector.js
CHANGED
|
@@ -46,7 +46,7 @@ const /** @type {pseudo[]} */ simplePseudos = [
|
|
|
46
46
|
['&', '&'],
|
|
47
47
|
],
|
|
48
48
|
pseudoRegex = new RegExp(`:(${complexPseudos.join('|')})$`, 'u'),
|
|
49
|
-
regularRegex = /[[(,>+~]|\s+/u,
|
|
49
|
+
regularRegex = /[[(,>+~]|\s+/u,
|
|
50
50
|
attributeRegex = /^\s*(\w+)\s*(?:([~|^$*!]?=)\s*("[^"]*"|'[^']*'|[^\s[\]]+)(?:\s+(i))?\s*)?\]/u,
|
|
51
51
|
functionRegex = /^(\s*"[^"]*"\s*|\s*'[^']*'\s*|[^()]*)\)/u;
|
|
52
52
|
|
|
@@ -164,11 +164,11 @@ const parseSelector = selector => {
|
|
|
164
164
|
}
|
|
165
165
|
mt = regex.exec(sanitized);
|
|
166
166
|
}
|
|
167
|
-
if (regex
|
|
168
|
-
|
|
167
|
+
if (regex === regularRegex) {
|
|
168
|
+
pushSimple(step, sanitized);
|
|
169
|
+
return stack;
|
|
169
170
|
}
|
|
170
|
-
|
|
171
|
-
return stack;
|
|
171
|
+
throw new SyntaxError(`非法的选择器!\n${selector}\n检测到未闭合的'${regex === attributeRegex ? '[' : '('}'`);
|
|
172
172
|
};
|
|
173
173
|
|
|
174
174
|
Parser.parsers.parseSelector = __filename;
|
package/parser/table.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Parser = require('..'),
|
|
4
|
-
AstText = require('../lib/text')
|
|
4
|
+
AstText = require('../lib/text'),
|
|
5
|
+
Token = require('../src'),
|
|
6
|
+
TableToken = require('../src/table'),
|
|
7
|
+
TrToken = require('../src/table/tr'),
|
|
8
|
+
TdToken = require('../src/table/td'),
|
|
9
|
+
DdToken = require('../src/nowiki/dd');
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* 解析表格,注意`tr`和`td`包含开头的换行
|
|
8
|
-
* @param {{firstChild: AstText
|
|
13
|
+
* @param {Token & {firstChild: AstText}} root 根节点
|
|
9
14
|
* @param {accum} accum
|
|
10
15
|
*/
|
|
11
|
-
const parseTable = ({firstChild: {data}, type}, config = Parser.getConfig(), accum = []) => {
|
|
12
|
-
const Token = require('../src'),
|
|
13
|
-
TableToken = require('../src/table'),
|
|
14
|
-
TrToken = require('../src/table/tr'),
|
|
15
|
-
TdToken = require('../src/table/td'),
|
|
16
|
-
DdToken = require('../src/nowiki/dd');
|
|
16
|
+
const parseTable = ({firstChild: {data}, type, name}, config = Parser.getConfig(), accum = []) => {
|
|
17
17
|
const /** @type {TrToken[]} */ stack = [],
|
|
18
18
|
lines = data.split('\n');
|
|
19
|
-
let out = type === 'root' ? '' : `\n${lines.shift()}`;
|
|
19
|
+
let out = type === 'root' || type === 'ext-inner' && name === 'poem' ? '' : `\n${lines.shift()}`;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* 向表格中插入纯文本
|
|
@@ -28,9 +28,9 @@ const parseTable = ({firstChild: {data}, type}, config = Parser.getConfig(), acc
|
|
|
28
28
|
out += str;
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
const {
|
|
32
|
-
if (
|
|
33
|
-
|
|
31
|
+
const /** @type {Token & {firstChild: AstText}} */ {lastChild} = top;
|
|
32
|
+
if (lastChild.isPlain()) {
|
|
33
|
+
lastChild.firstChild.appendData(str);
|
|
34
34
|
} else {
|
|
35
35
|
const token = new Token(str, config, true, accum);
|
|
36
36
|
token.type = 'table-inter';
|
package/src/arg.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {text, noWrap} = require('../util/string'),
|
|
4
|
+
{generateForChild} = require('../util/lint'),
|
|
4
5
|
Parser = require('..'),
|
|
5
6
|
Token = require('.');
|
|
6
7
|
|
|
@@ -20,10 +21,10 @@ class ArgToken extends Token {
|
|
|
20
21
|
super(undefined, config, true, accum, {AtomToken: 0, Token: 1, HiddenToken: '2:'});
|
|
21
22
|
for (let i = 0; i < parts.length; i++) {
|
|
22
23
|
if (i === 0 || i > 1) {
|
|
23
|
-
const AtomToken = i === 0 ? require('./atom') : require('./atom/hidden')
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const AtomToken = i === 0 ? require('./atom') : require('./atom/hidden');
|
|
25
|
+
const token = new AtomToken(parts[i], i === 0 ? 'arg-name' : undefined, config, accum, {
|
|
26
|
+
'Stage-2': ':', '!HeadingToken': '',
|
|
27
|
+
});
|
|
27
28
|
this.appendChild(token);
|
|
28
29
|
} else {
|
|
29
30
|
const token = new Token(parts[i], config, true, accum);
|
|
@@ -39,7 +40,7 @@ class ArgToken extends Token {
|
|
|
39
40
|
const [name, ...cloned] = this.cloneChildNodes();
|
|
40
41
|
return Parser.run(() => {
|
|
41
42
|
const token = new ArgToken([''], this.getAttribute('config'));
|
|
42
|
-
token.
|
|
43
|
+
token.firstChild.safeReplaceWith(name);
|
|
43
44
|
token.append(...cloned);
|
|
44
45
|
return token.afterBuild();
|
|
45
46
|
});
|
|
@@ -47,9 +48,9 @@ class ArgToken extends Token {
|
|
|
47
48
|
|
|
48
49
|
/** @override */
|
|
49
50
|
afterBuild() {
|
|
50
|
-
this.setAttribute('name', this.
|
|
51
|
+
this.setAttribute('name', this.firstChild.text().trim());
|
|
51
52
|
const /** @type {AstListener} */ argListener = ({prevTarget}) => {
|
|
52
|
-
if (prevTarget === this.
|
|
53
|
+
if (prevTarget === this.firstChild) {
|
|
53
54
|
this.setAttribute('name', prevTarget.text().trim());
|
|
54
55
|
}
|
|
55
56
|
};
|
|
@@ -75,12 +76,17 @@ class ArgToken extends Token {
|
|
|
75
76
|
return 1;
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
/** @override */
|
|
80
|
+
print() {
|
|
81
|
+
return super.print({pre: '{{{', post: '}}}', sep: '|'});
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
/**
|
|
79
85
|
* @override
|
|
80
86
|
* @returns {string}
|
|
81
87
|
*/
|
|
82
88
|
text() {
|
|
83
|
-
return `{{{${text(this.
|
|
89
|
+
return `{{{${text(this.childNodes.slice(0, 2), '|')}}}}`;
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
/**
|
|
@@ -95,6 +101,24 @@ class ArgToken extends Token {
|
|
|
95
101
|
});
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
/**
|
|
105
|
+
* @override
|
|
106
|
+
* @param {number} start 起始位置
|
|
107
|
+
* @returns {LintError[]}
|
|
108
|
+
*/
|
|
109
|
+
lint(start = 0) {
|
|
110
|
+
const {childNodes: [argName, argDefault, ...rest]} = this,
|
|
111
|
+
errors = argName.lint(start + 3);
|
|
112
|
+
if (argDefault) {
|
|
113
|
+
errors.push(...argDefault.lint(start + 4 + String(argName).length));
|
|
114
|
+
}
|
|
115
|
+
if (rest.length > 0) {
|
|
116
|
+
const rect = this.getRootNode().posFromIndex(start);
|
|
117
|
+
errors.push(...rest.map(child => generateForChild(child, rect, '三重括号内的不可见部分')));
|
|
118
|
+
}
|
|
119
|
+
return errors;
|
|
120
|
+
}
|
|
121
|
+
|
|
98
122
|
/**
|
|
99
123
|
* 移除子节点,且在移除`arg-default`子节点时自动移除全部多余子节点
|
|
100
124
|
* @param {number} i 移除位置
|
|
@@ -133,13 +157,13 @@ class ArgToken extends Token {
|
|
|
133
157
|
setName(name) {
|
|
134
158
|
name = String(name);
|
|
135
159
|
const root = Parser.parse(`{{{${name}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
136
|
-
{childNodes: {length},
|
|
137
|
-
if (length !== 1 ||
|
|
160
|
+
{childNodes: {length}, firstChild: arg} = root;
|
|
161
|
+
if (length !== 1 || arg.type !== 'arg' || arg.childNodes.length !== 1) {
|
|
138
162
|
throw new SyntaxError(`非法的参数名称:${noWrap(name)}`);
|
|
139
163
|
}
|
|
140
|
-
const {
|
|
141
|
-
|
|
142
|
-
this.
|
|
164
|
+
const {firstChild} = arg;
|
|
165
|
+
arg.destroy(true);
|
|
166
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
143
167
|
}
|
|
144
168
|
|
|
145
169
|
/**
|
|
@@ -150,17 +174,17 @@ class ArgToken extends Token {
|
|
|
150
174
|
setDefault(value) {
|
|
151
175
|
value = String(value);
|
|
152
176
|
const root = Parser.parse(`{{{|${value}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
153
|
-
{childNodes: {length},
|
|
154
|
-
if (length !== 1 ||
|
|
177
|
+
{childNodes: {length}, firstChild: arg} = root;
|
|
178
|
+
if (length !== 1 || arg.type !== 'arg' || arg.childNodes.length !== 2) {
|
|
155
179
|
throw new SyntaxError(`非法的参数预设值:${noWrap(value)}`);
|
|
156
180
|
}
|
|
157
|
-
const {
|
|
158
|
-
{
|
|
159
|
-
|
|
181
|
+
const {childNodes: [, oldDefault]} = this,
|
|
182
|
+
{lastChild} = arg;
|
|
183
|
+
arg.destroy(true);
|
|
160
184
|
if (oldDefault) {
|
|
161
|
-
oldDefault.safeReplaceWith(
|
|
185
|
+
oldDefault.safeReplaceWith(lastChild);
|
|
162
186
|
} else {
|
|
163
|
-
this.appendChild(
|
|
187
|
+
this.appendChild(lastChild);
|
|
164
188
|
}
|
|
165
189
|
}
|
|
166
190
|
}
|
package/src/attribute.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {externalUse} = require('../util/debug'),
|
|
4
|
+
{generateForSelf} = require('../util/lint'),
|
|
4
5
|
{toCase, removeComment, normalizeSpace} = require('../util/string'),
|
|
5
6
|
Parser = require('..'),
|
|
6
7
|
Token = require('.');
|
|
@@ -14,6 +15,7 @@ const stages = {'ext-attr': 0, 'html-attr': 2, 'table-attr': 3};
|
|
|
14
15
|
class AttributeToken extends Token {
|
|
15
16
|
/** @type {Map<string, string|true>} */ #attr = new Map();
|
|
16
17
|
#sanitized = true;
|
|
18
|
+
#quoteBalance = true;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* @override
|
|
@@ -88,11 +90,10 @@ class AttributeToken extends Token {
|
|
|
88
90
|
*/
|
|
89
91
|
#updateFromAttr() {
|
|
90
92
|
let equal = '=';
|
|
91
|
-
const ParameterToken = require('./parameter')
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
) {
|
|
93
|
+
const ParameterToken = require('./parameter'),
|
|
94
|
+
TranscludeToken = require('./transclude');
|
|
95
|
+
const /** @type {ParameterToken & {parentNode: TranscludeToken}} */ parent = this.closest('ext, parameter');
|
|
96
|
+
if (parent instanceof ParameterToken && parent.anon && parent.parentNode?.isTemplate()) {
|
|
96
97
|
equal = '{{=}}';
|
|
97
98
|
}
|
|
98
99
|
return [...this.#attr].map(([k, v]) => {
|
|
@@ -117,6 +118,7 @@ class AttributeToken extends Token {
|
|
|
117
118
|
this.replaceChildren(...token.childNodes, true);
|
|
118
119
|
});
|
|
119
120
|
this.#sanitized = true;
|
|
121
|
+
this.#quoteBalance = true;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
/**
|
|
@@ -146,11 +148,13 @@ class AttributeToken extends Token {
|
|
|
146
148
|
*/
|
|
147
149
|
const build = str =>
|
|
148
150
|
typeof str === 'boolean' || !token ? str : token.getAttribute('buildFromStr')(str).map(String).join('');
|
|
149
|
-
for (const [, key
|
|
150
|
-
.matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(
|
|
151
|
+
for (const [, key, quoteStart, quoted, quoteEnd, unquoted] of string
|
|
152
|
+
.matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(\2|$)|(\S*)))?/gsu)
|
|
151
153
|
) {
|
|
152
154
|
if (!this.setAttr(build(key), build(quoted ?? unquoted ?? true), true)) {
|
|
153
155
|
this.#sanitized = false;
|
|
156
|
+
} else if (quoteStart !== quoteEnd) {
|
|
157
|
+
this.#quoteBalance = false;
|
|
154
158
|
}
|
|
155
159
|
}
|
|
156
160
|
}
|
|
@@ -355,6 +359,29 @@ class AttributeToken extends Token {
|
|
|
355
359
|
return this.#leadingSpace().length;
|
|
356
360
|
}
|
|
357
361
|
|
|
362
|
+
/**
|
|
363
|
+
* @override
|
|
364
|
+
* @this {AttributeToken & {parentNode: HtmlToken}}
|
|
365
|
+
* @param {number} start 起始位置
|
|
366
|
+
*/
|
|
367
|
+
lint(start = 0) {
|
|
368
|
+
const HtmlToken = require('./html');
|
|
369
|
+
const errors = super.lint(start);
|
|
370
|
+
let /** @type {{top: number, left: number}} */ rect;
|
|
371
|
+
if (this.type === 'html-attr' && this.parentNode.closing && this.text().trim()) {
|
|
372
|
+
rect = this.getRootNode().posFromIndex(start);
|
|
373
|
+
errors.push(generateForSelf(this, rect, '位于闭合标签的属性'));
|
|
374
|
+
}
|
|
375
|
+
if (!this.#sanitized) {
|
|
376
|
+
rect ||= this.getRootNode().posFromIndex(start);
|
|
377
|
+
errors.push(generateForSelf(this, rect, '包含无效属性'));
|
|
378
|
+
} else if (!this.#quoteBalance) {
|
|
379
|
+
rect ||= this.getRootNode().posFromIndex(start);
|
|
380
|
+
errors.push(generateForSelf(this, rect, '未闭合的引号', 'warning'));
|
|
381
|
+
}
|
|
382
|
+
return errors;
|
|
383
|
+
}
|
|
384
|
+
|
|
358
385
|
/** @override */
|
|
359
386
|
text() {
|
|
360
387
|
if (this.type === 'table-attr') {
|
package/src/converter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {text} = require('../util/string'),
|
|
3
|
+
const {text, print} = require('../util/string'),
|
|
4
4
|
Parser = require('..'),
|
|
5
5
|
Token = require('.'),
|
|
6
6
|
ConverterFlagsToken = require('./converterFlags'),
|
|
@@ -47,7 +47,7 @@ class ConverterToken extends Token {
|
|
|
47
47
|
cloneNode() {
|
|
48
48
|
const [flags, ...rules] = this.cloneChildNodes(),
|
|
49
49
|
token = Parser.run(() => new ConverterToken([], [], this.getAttribute('config')));
|
|
50
|
-
token.
|
|
50
|
+
token.firstChild.safeReplaceWith(flags);
|
|
51
51
|
token.append(...rules);
|
|
52
52
|
return token;
|
|
53
53
|
}
|
|
@@ -57,7 +57,7 @@ class ConverterToken extends Token {
|
|
|
57
57
|
* @param {string} selector
|
|
58
58
|
*/
|
|
59
59
|
toString(selector) {
|
|
60
|
-
const {
|
|
60
|
+
const {childNodes: [flags, ...rules]} = this;
|
|
61
61
|
return selector && this.matches(selector)
|
|
62
62
|
? ''
|
|
63
63
|
: `-{${flags.toString(selector)}${flags.childNodes.length > 0 ? '|' : ''}${rules.map(String).join(';')}}-`;
|
|
@@ -74,12 +74,20 @@ class ConverterToken extends Token {
|
|
|
74
74
|
*/
|
|
75
75
|
getGaps(i = 0) {
|
|
76
76
|
i = i < 0 ? i + this.childNodes.length : i;
|
|
77
|
-
return i || this.
|
|
77
|
+
return i || this.firstChild.childNodes.length > 0 ? 1 : 0;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** @override */
|
|
81
|
-
|
|
81
|
+
print() {
|
|
82
82
|
const {children: [flags, ...rules]} = this;
|
|
83
|
+
return `<span class="wpb-converter">-{${flags.print()}${
|
|
84
|
+
flags.childNodes.length > 0 ? '|' : ''
|
|
85
|
+
}${print(rules, {sep: ';'})}}-</span>`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @override */
|
|
89
|
+
text() {
|
|
90
|
+
const {childNodes: [flags, ...rules]} = this;
|
|
83
91
|
return `-{${flags.text()}|${text(rules, ';')}}-`;
|
|
84
92
|
}
|
|
85
93
|
|
package/src/converterFlags.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {generateForChild} = require('../util/lint'),
|
|
4
|
+
Parser = require('..'),
|
|
4
5
|
Token = require('.'),
|
|
5
6
|
AtomToken = require('./atom');
|
|
6
7
|
|
|
@@ -35,7 +36,7 @@ class ConverterFlagsToken extends Token {
|
|
|
35
36
|
* @complexity `n`
|
|
36
37
|
*/
|
|
37
38
|
afterBuild() {
|
|
38
|
-
this.#flags = this.
|
|
39
|
+
this.#flags = this.childNodes.map(child => child.text().trim());
|
|
39
40
|
const /** @type {AstListener} */ converterFlagsListener = ({prevTarget}) => {
|
|
40
41
|
if (prevTarget) {
|
|
41
42
|
this.#flags[this.childNodes.indexOf(prevTarget)] = prevTarget.text().trim();
|
|
@@ -94,6 +95,37 @@ class ConverterFlagsToken extends Token {
|
|
|
94
95
|
return 1;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
/** @override */
|
|
99
|
+
print() {
|
|
100
|
+
return super.print({sep: ';'});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @override
|
|
105
|
+
* @param {number} start 起始位置
|
|
106
|
+
*/
|
|
107
|
+
lint(start = 0) {
|
|
108
|
+
const variantFlags = this.getVariantFlags(),
|
|
109
|
+
unknownFlags = this.getUnknownFlags(),
|
|
110
|
+
emptyFlags = this.#flags.filter(flag => !flag),
|
|
111
|
+
validFlags = this.#flags.filter(flag => ['A', 'T', 'R', 'D', '-', 'H', 'N'].includes(flag)),
|
|
112
|
+
knownFlagCount = this.#flags.length - unknownFlags.length - emptyFlags,
|
|
113
|
+
errors = super.lint(start);
|
|
114
|
+
if (variantFlags.length === knownFlagCount || validFlags.length === knownFlagCount) {
|
|
115
|
+
return errors;
|
|
116
|
+
}
|
|
117
|
+
const rect = this.getRootNode().posFromIndex(start);
|
|
118
|
+
for (const child of this.childNodes) {
|
|
119
|
+
const flag = child.text().trim();
|
|
120
|
+
if (flag && !variantFlags.includes(flag) && !unknownFlags.includes(flag)
|
|
121
|
+
&& (variantFlags.length > 0 || !validFlags.includes(flag))
|
|
122
|
+
) {
|
|
123
|
+
errors.push(generateForChild(child, rect, '无效的转换标记'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return errors;
|
|
127
|
+
}
|
|
128
|
+
|
|
97
129
|
/** @override */
|
|
98
130
|
text() {
|
|
99
131
|
return super.text(';');
|
|
@@ -106,7 +138,7 @@ class ConverterFlagsToken extends Token {
|
|
|
106
138
|
* @complexity `n`
|
|
107
139
|
*/
|
|
108
140
|
getFlagToken(flag) {
|
|
109
|
-
return this.#flags.includes(flag) ? this.
|
|
141
|
+
return this.#flags.includes(flag) ? this.childNodes.filter(child => child.text().trim() === flag) : [];
|
|
110
142
|
}
|
|
111
143
|
|
|
112
144
|
/** 获取所有转换类型标记 */
|
|
@@ -122,13 +154,18 @@ class ConverterFlagsToken extends Token {
|
|
|
122
154
|
return this.#flags.filter(flag => /\{\{[^{}]+\}\}/u.test(flag));
|
|
123
155
|
}
|
|
124
156
|
|
|
157
|
+
/** 获取指定语言变体的转换标记 */
|
|
158
|
+
getVariantFlags() {
|
|
159
|
+
const {variants} = this.getAttribute('config');
|
|
160
|
+
return this.#flags.filter(flag => variants.includes(flag));
|
|
161
|
+
}
|
|
162
|
+
|
|
125
163
|
/**
|
|
126
164
|
* 获取有效转换类型标记
|
|
127
165
|
* @complexity `n`
|
|
128
166
|
*/
|
|
129
167
|
getEffectiveFlags() {
|
|
130
|
-
const
|
|
131
|
-
variantFlags = this.#flags.filter(flag => variants.includes(flag)),
|
|
168
|
+
const variantFlags = this.getVariantFlags(),
|
|
132
169
|
unknownFlags = this.getUnknownFlags();
|
|
133
170
|
if (variantFlags.length > 0) {
|
|
134
171
|
return new Set([...variantFlags, ...unknownFlags]);
|