wikiparser-node 0.4.0 → 0.6.1
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/default.json +129 -66
- package/config/zhwiki.json +4 -4
- package/index.js +97 -65
- package/lib/element.js +159 -302
- package/lib/node.js +384 -198
- package/lib/ranges.js +3 -4
- package/lib/text.js +65 -36
- package/lib/title.js +9 -8
- package/mixin/fixedToken.js +4 -4
- package/mixin/hidden.js +2 -0
- package/mixin/sol.js +16 -7
- package/package.json +14 -3
- package/parser/brackets.js +8 -2
- package/parser/commentAndExt.js +1 -1
- package/parser/converter.js +1 -1
- package/parser/externalLinks.js +2 -2
- package/parser/hrAndDoubleUnderscore.js +8 -7
- package/parser/links.js +8 -9
- package/parser/magicLinks.js +1 -1
- package/parser/selector.js +5 -5
- package/parser/table.js +18 -16
- package/src/arg.js +71 -42
- package/src/atom/index.js +7 -5
- package/src/attribute.js +102 -64
- package/src/charinsert.js +91 -0
- package/src/converter.js +34 -15
- package/src/converterFlags.js +87 -40
- package/src/converterRule.js +59 -53
- package/src/extLink.js +45 -37
- package/src/gallery.js +71 -16
- package/src/hasNowiki/index.js +42 -0
- package/src/hasNowiki/pre.js +40 -0
- package/src/heading.js +41 -18
- package/src/html.js +76 -48
- package/src/imageParameter.js +73 -51
- package/src/imagemap.js +205 -0
- package/src/imagemapLink.js +43 -0
- package/src/index.js +243 -138
- package/src/link/category.js +10 -14
- package/src/link/file.js +112 -56
- package/src/link/galleryImage.js +74 -10
- package/src/link/index.js +86 -61
- package/src/magicLink.js +48 -21
- package/src/nested/choose.js +24 -0
- package/src/nested/combobox.js +23 -0
- package/src/nested/index.js +88 -0
- package/src/nested/references.js +23 -0
- package/src/nowiki/comment.js +18 -4
- package/src/nowiki/dd.js +2 -2
- package/src/nowiki/doubleUnderscore.js +16 -11
- package/src/nowiki/index.js +12 -0
- package/src/nowiki/quote.js +28 -1
- package/src/onlyinclude.js +15 -8
- package/src/paramTag/index.js +83 -0
- package/src/paramTag/inputbox.js +42 -0
- package/src/parameter.js +73 -46
- package/src/syntax.js +9 -1
- package/src/table/index.js +58 -44
- package/src/table/td.js +63 -63
- package/src/table/tr.js +52 -35
- package/src/tagPair/ext.js +60 -43
- package/src/tagPair/include.js +11 -1
- package/src/tagPair/index.js +29 -20
- package/src/transclude.js +214 -166
- package/tool/index.js +720 -439
- package/util/base.js +17 -0
- package/util/debug.js +1 -1
- package/{test/util.js → util/diff.js} +15 -19
- package/util/lint.js +40 -0
- package/util/string.js +37 -20
- 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/src/arg.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
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
|
|
|
7
8
|
/**
|
|
8
9
|
* `{{{}}}`包裹的参数
|
|
9
|
-
* @classdesc `{childNodes: [AtomToken, Token, ...HiddenToken]}`
|
|
10
|
+
* @classdesc `{childNodes: [AtomToken, ?Token, ...HiddenToken]}`
|
|
10
11
|
*/
|
|
11
12
|
class ArgToken extends Token {
|
|
12
13
|
type = 'arg';
|
|
13
14
|
|
|
15
|
+
/** default */
|
|
16
|
+
get default() {
|
|
17
|
+
return this.childNodes[1]?.text() ?? false;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* @param {string[]} parts 以'|'分隔的各部分
|
|
16
22
|
* @param {accum} accum
|
|
@@ -20,26 +26,67 @@ class ArgToken extends Token {
|
|
|
20
26
|
super(undefined, config, true, accum, {AtomToken: 0, Token: 1, HiddenToken: '2:'});
|
|
21
27
|
for (let i = 0; i < parts.length; i++) {
|
|
22
28
|
if (i === 0 || i > 1) {
|
|
23
|
-
const AtomToken = i === 0 ? require('./atom') : require('./atom/hidden')
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
const AtomToken = i === 0 ? require('./atom') : require('./atom/hidden');
|
|
30
|
+
const token = new AtomToken(parts[i], i === 0 ? 'arg-name' : undefined, config, accum, {
|
|
31
|
+
'Stage-2': ':', '!HeadingToken': '',
|
|
32
|
+
});
|
|
33
|
+
super.insertAt(token);
|
|
28
34
|
} else {
|
|
29
35
|
const token = new Token(parts[i], config, true, accum);
|
|
30
36
|
token.type = 'arg-default';
|
|
31
|
-
|
|
37
|
+
super.insertAt(token.setAttribute('stage', 2));
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
this.getAttribute('protectChildren')(0);
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @override
|
|
45
|
+
* @param {string} selector
|
|
46
|
+
*/
|
|
47
|
+
toString(selector) {
|
|
48
|
+
return selector && this.matches(selector) ? '' : `{{{${super.toString(selector, '|')}}}}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @override */
|
|
52
|
+
getPadding() {
|
|
53
|
+
return 3;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @override */
|
|
57
|
+
getGaps() {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** @override */
|
|
62
|
+
print() {
|
|
63
|
+
return super.print({pre: '{{{', post: '}}}', sep: '|'});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @override
|
|
68
|
+
* @param {number} start 起始位置
|
|
69
|
+
* @returns {LintError[]}
|
|
70
|
+
*/
|
|
71
|
+
lint(start = 0) {
|
|
72
|
+
const {childNodes: [argName, argDefault, ...rest]} = this,
|
|
73
|
+
errors = argName.lint(start + 3);
|
|
74
|
+
if (argDefault) {
|
|
75
|
+
errors.push(...argDefault.lint(start + 4 + String(argName).length));
|
|
76
|
+
}
|
|
77
|
+
if (rest.length > 0) {
|
|
78
|
+
const rect = this.getRootNode().posFromIndex(start);
|
|
79
|
+
errors.push(...rest.map(child => generateForChild(child, rect, '三重括号内的不可见部分')));
|
|
80
|
+
}
|
|
81
|
+
return errors;
|
|
82
|
+
}
|
|
83
|
+
|
|
37
84
|
/** @override */
|
|
38
85
|
cloneNode() {
|
|
39
86
|
const [name, ...cloned] = this.cloneChildNodes();
|
|
40
87
|
return Parser.run(() => {
|
|
41
88
|
const token = new ArgToken([''], this.getAttribute('config'));
|
|
42
|
-
token.
|
|
89
|
+
token.firstChild.safeReplaceWith(name);
|
|
43
90
|
token.append(...cloned);
|
|
44
91
|
return token.afterBuild();
|
|
45
92
|
});
|
|
@@ -47,9 +94,9 @@ class ArgToken extends Token {
|
|
|
47
94
|
|
|
48
95
|
/** @override */
|
|
49
96
|
afterBuild() {
|
|
50
|
-
this.setAttribute('name', this.
|
|
97
|
+
this.setAttribute('name', this.firstChild.text().trim());
|
|
51
98
|
const /** @type {AstListener} */ argListener = ({prevTarget}) => {
|
|
52
|
-
if (prevTarget === this.
|
|
99
|
+
if (prevTarget === this.firstChild) {
|
|
53
100
|
this.setAttribute('name', prevTarget.text().trim());
|
|
54
101
|
}
|
|
55
102
|
};
|
|
@@ -57,30 +104,12 @@ class ArgToken extends Token {
|
|
|
57
104
|
return this;
|
|
58
105
|
}
|
|
59
106
|
|
|
60
|
-
/**
|
|
61
|
-
* @override
|
|
62
|
-
* @param {string} selector
|
|
63
|
-
*/
|
|
64
|
-
toString(selector) {
|
|
65
|
-
return selector && this.matches(selector) ? '' : `{{{${super.toString(selector, '|')}}}}`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** @override */
|
|
69
|
-
getPadding() {
|
|
70
|
-
return 3;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** @override */
|
|
74
|
-
getGaps() {
|
|
75
|
-
return 1;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
107
|
/**
|
|
79
108
|
* @override
|
|
80
109
|
* @returns {string}
|
|
81
110
|
*/
|
|
82
111
|
text() {
|
|
83
|
-
return `{{{${text(this.
|
|
112
|
+
return `{{{${text(this.childNodes.slice(0, 2), '|')}}}}`;
|
|
84
113
|
}
|
|
85
114
|
|
|
86
115
|
/**
|
|
@@ -115,7 +144,7 @@ class ArgToken extends Token {
|
|
|
115
144
|
*/
|
|
116
145
|
insertAt(token, i = this.childNodes.length) {
|
|
117
146
|
const j = i < 0 ? i + this.childNodes.length : i;
|
|
118
|
-
if (j > 1
|
|
147
|
+
if (j > 1) {
|
|
119
148
|
throw new RangeError(`${this.constructor.name} 不可插入多余的子节点!`);
|
|
120
149
|
}
|
|
121
150
|
super.insertAt(token, i);
|
|
@@ -133,13 +162,13 @@ class ArgToken extends Token {
|
|
|
133
162
|
setName(name) {
|
|
134
163
|
name = String(name);
|
|
135
164
|
const root = Parser.parse(`{{{${name}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
136
|
-
{
|
|
137
|
-
if (length !== 1 ||
|
|
165
|
+
{length, firstChild: arg} = root;
|
|
166
|
+
if (length !== 1 || arg.type !== 'arg' || arg.childNodes.length !== 1) {
|
|
138
167
|
throw new SyntaxError(`非法的参数名称:${noWrap(name)}`);
|
|
139
168
|
}
|
|
140
|
-
const {
|
|
141
|
-
|
|
142
|
-
this.
|
|
169
|
+
const {firstChild} = arg;
|
|
170
|
+
arg.destroy(true);
|
|
171
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
143
172
|
}
|
|
144
173
|
|
|
145
174
|
/**
|
|
@@ -150,17 +179,17 @@ class ArgToken extends Token {
|
|
|
150
179
|
setDefault(value) {
|
|
151
180
|
value = String(value);
|
|
152
181
|
const root = Parser.parse(`{{{|${value}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
153
|
-
{
|
|
154
|
-
if (length !== 1 ||
|
|
182
|
+
{length, firstChild: arg} = root;
|
|
183
|
+
if (length !== 1 || arg.type !== 'arg' || arg.childNodes.length !== 2) {
|
|
155
184
|
throw new SyntaxError(`非法的参数预设值:${noWrap(value)}`);
|
|
156
185
|
}
|
|
157
|
-
const {
|
|
158
|
-
{
|
|
159
|
-
|
|
186
|
+
const {childNodes: [, oldDefault]} = this,
|
|
187
|
+
{lastChild} = arg;
|
|
188
|
+
arg.destroy(true);
|
|
160
189
|
if (oldDefault) {
|
|
161
|
-
oldDefault.safeReplaceWith(
|
|
190
|
+
oldDefault.safeReplaceWith(lastChild);
|
|
162
191
|
} else {
|
|
163
|
-
this.
|
|
192
|
+
this.insertAt(lastChild);
|
|
164
193
|
}
|
|
165
194
|
}
|
|
166
195
|
}
|
package/src/atom/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const Parser = require('../..'),
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* 不会被继续解析的plain Token
|
|
8
|
-
* @classdesc `{childNodes:
|
|
8
|
+
* @classdesc `{childNodes: ...AstText|Token}`
|
|
9
9
|
*/
|
|
10
10
|
class AtomToken extends Token {
|
|
11
11
|
type = 'plain';
|
|
@@ -30,10 +30,12 @@ class AtomToken extends Token {
|
|
|
30
30
|
cloneNode() {
|
|
31
31
|
const cloned = this.cloneChildNodes(),
|
|
32
32
|
config = this.getAttribute('config'),
|
|
33
|
-
acceptable = this.getAttribute('acceptable')
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
acceptable = this.getAttribute('acceptable');
|
|
34
|
+
return Parser.run(() => {
|
|
35
|
+
const token = new this.constructor(undefined, this.type, config, [], acceptable);
|
|
36
|
+
token.append(...cloned);
|
|
37
|
+
return token;
|
|
38
|
+
});
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
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
|
|
@@ -82,17 +84,26 @@ class AttributeToken extends Token {
|
|
|
82
84
|
this.setAttr('id', id);
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
/** #sanitized */
|
|
88
|
+
get sanitized() {
|
|
89
|
+
return this.#sanitized;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** #quoteBalance */
|
|
93
|
+
get quoteBalance() {
|
|
94
|
+
return this.#quoteBalance;
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
/**
|
|
86
98
|
* 从`this.#attr`更新`childNodes`
|
|
87
99
|
* @complexity `n`
|
|
88
100
|
*/
|
|
89
101
|
#updateFromAttr() {
|
|
90
102
|
let equal = '=';
|
|
91
|
-
const ParameterToken = require('./parameter')
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
) {
|
|
103
|
+
const ParameterToken = require('./parameter'),
|
|
104
|
+
TranscludeToken = require('./transclude');
|
|
105
|
+
const /** @type {ParameterToken & {parentNode: TranscludeToken}} */ parent = this.closest('ext, parameter');
|
|
106
|
+
if (parent instanceof ParameterToken && parent.anon && parent.parentNode?.isTemplate()) {
|
|
96
107
|
equal = '{{=}}';
|
|
97
108
|
}
|
|
98
109
|
return [...this.#attr].map(([k, v]) => {
|
|
@@ -117,6 +128,7 @@ class AttributeToken extends Token {
|
|
|
117
128
|
this.replaceChildren(...token.childNodes, true);
|
|
118
129
|
});
|
|
119
130
|
this.#sanitized = true;
|
|
131
|
+
this.#quoteBalance = true;
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
/**
|
|
@@ -146,11 +158,13 @@ class AttributeToken extends Token {
|
|
|
146
158
|
*/
|
|
147
159
|
const build = str =>
|
|
148
160
|
typeof str === 'boolean' || !token ? str : token.getAttribute('buildFromStr')(str).map(String).join('');
|
|
149
|
-
for (const [, key
|
|
150
|
-
.matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(
|
|
161
|
+
for (const [, key, quoteStart, quoted, quoteEnd, unquoted] of string
|
|
162
|
+
.matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(\2|$)|(\S*)))?/gsu)
|
|
151
163
|
) {
|
|
152
164
|
if (!this.setAttr(build(key), build(quoted ?? unquoted ?? true), true)) {
|
|
153
165
|
this.#sanitized = false;
|
|
166
|
+
} else if (quoteStart !== quoteEnd) {
|
|
167
|
+
this.#quoteBalance = false;
|
|
154
168
|
}
|
|
155
169
|
}
|
|
156
170
|
}
|
|
@@ -164,7 +178,87 @@ class AttributeToken extends Token {
|
|
|
164
178
|
constructor(attr, type, name, config = Parser.getConfig(), accum = []) {
|
|
165
179
|
super(attr, config, true, accum, {[`Stage-${stages[type]}`]: ':'});
|
|
166
180
|
this.type = type;
|
|
167
|
-
this
|
|
181
|
+
this.#parseAttr();
|
|
182
|
+
this.setAttribute('name', name);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 获取标签属性
|
|
187
|
+
* @template {string|undefined} T
|
|
188
|
+
* @param {T} key 属性键
|
|
189
|
+
* @returns {T extends string ? string|true : Record<string, string|true>}
|
|
190
|
+
*/
|
|
191
|
+
getAttr(key) {
|
|
192
|
+
if (key === undefined) {
|
|
193
|
+
return Object.fromEntries(this.#attr);
|
|
194
|
+
}
|
|
195
|
+
return typeof key === 'string' ? this.#attr.get(key.toLowerCase().trim()) : this.typeError('getAttr', 'String');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 设置标签属性
|
|
200
|
+
* @param {string} key 属性键
|
|
201
|
+
* @param {string|boolean} value 属性值
|
|
202
|
+
* @param {boolean} init 是否是初次解析
|
|
203
|
+
* @complexity `n`
|
|
204
|
+
* @throws `RangeError` 扩展标签属性不能包含">"
|
|
205
|
+
* @throws `RangeError` 无效的属性名
|
|
206
|
+
*/
|
|
207
|
+
setAttr(key, value, init) {
|
|
208
|
+
init &&= !externalUse('setAttr');
|
|
209
|
+
if (typeof key !== 'string' || typeof value !== 'string' && typeof value !== 'boolean') {
|
|
210
|
+
this.typeError('setAttr', 'String', 'Boolean');
|
|
211
|
+
} else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
|
|
212
|
+
throw new RangeError('扩展标签属性不能包含 ">"!');
|
|
213
|
+
}
|
|
214
|
+
key = key.toLowerCase().trim();
|
|
215
|
+
const config = this.getAttribute('config'),
|
|
216
|
+
include = this.getAttribute('include'),
|
|
217
|
+
parsedKey = this.type === 'ext-attr' || init
|
|
218
|
+
? key
|
|
219
|
+
: Parser.run(() => {
|
|
220
|
+
const token = new Token(key, config),
|
|
221
|
+
parseOnce = token.getAttribute('parseOnce');
|
|
222
|
+
parseOnce(0, include);
|
|
223
|
+
return String(parseOnce());
|
|
224
|
+
});
|
|
225
|
+
if (!/^(?:[\w:]|\0\d+[t!~{}+-]\x7F)(?:[\w:.-]|\0\d+[t!~{}+-]\x7F)*$/u.test(parsedKey)) {
|
|
226
|
+
if (init) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
throw new RangeError(`无效的属性名:${key}!`);
|
|
230
|
+
} else if (value === false) {
|
|
231
|
+
this.#attr.delete(key);
|
|
232
|
+
} else {
|
|
233
|
+
this.#attr.set(key, value === true ? true : value.replaceAll(/\s/gu, ' ').trim());
|
|
234
|
+
}
|
|
235
|
+
if (!init) {
|
|
236
|
+
this.sanitize();
|
|
237
|
+
}
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @override
|
|
243
|
+
* @this {AttributeToken & {parentNode: HtmlToken}}
|
|
244
|
+
* @param {number} start 起始位置
|
|
245
|
+
*/
|
|
246
|
+
lint(start = 0) {
|
|
247
|
+
const HtmlToken = require('./html');
|
|
248
|
+
const errors = super.lint(start);
|
|
249
|
+
let /** @type {{top: number, left: number}} */ rect;
|
|
250
|
+
if (this.type === 'html-attr' && this.parentNode.closing && this.text().trim()) {
|
|
251
|
+
rect = this.getRootNode().posFromIndex(start);
|
|
252
|
+
errors.push(generateForSelf(this, rect, '位于闭合标签的属性'));
|
|
253
|
+
}
|
|
254
|
+
if (!this.#sanitized) {
|
|
255
|
+
rect ||= this.getRootNode().posFromIndex(start);
|
|
256
|
+
errors.push(generateForSelf(this, rect, '包含无效属性'));
|
|
257
|
+
} else if (!this.#quoteBalance) {
|
|
258
|
+
rect ||= this.getRootNode().posFromIndex(start);
|
|
259
|
+
errors.push(generateForSelf(this, rect, '未闭合的引号', 'warning'));
|
|
260
|
+
}
|
|
261
|
+
return errors;
|
|
168
262
|
}
|
|
169
263
|
|
|
170
264
|
/** @override */
|
|
@@ -227,19 +321,6 @@ class AttributeToken extends Token {
|
|
|
227
321
|
return typeof key === 'string' ? this.#attr.has(key.toLowerCase().trim()) : this.typeError('hasAttr', 'String');
|
|
228
322
|
}
|
|
229
323
|
|
|
230
|
-
/**
|
|
231
|
-
* 获取标签属性
|
|
232
|
-
* @template {string|undefined} T
|
|
233
|
-
* @param {T} key 属性键
|
|
234
|
-
* @returns {T extends string ? string|true : Record<string, string|true>}
|
|
235
|
-
*/
|
|
236
|
-
getAttr(key) {
|
|
237
|
-
if (key === undefined) {
|
|
238
|
-
return Object.fromEntries(this.#attr);
|
|
239
|
-
}
|
|
240
|
-
return typeof key === 'string' ? this.#attr.get(key.toLowerCase().trim()) : this.typeError('getAttr', 'String');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
324
|
/** 获取全部的标签属性名 */
|
|
244
325
|
getAttrNames() {
|
|
245
326
|
return [...this.#attr.keys()];
|
|
@@ -250,49 +331,6 @@ class AttributeToken extends Token {
|
|
|
250
331
|
return this.getAttrNames().length > 0;
|
|
251
332
|
}
|
|
252
333
|
|
|
253
|
-
/**
|
|
254
|
-
* 设置标签属性
|
|
255
|
-
* @param {string} key 属性键
|
|
256
|
-
* @param {string|boolean} value 属性值
|
|
257
|
-
* @param {boolean} init 是否是初次解析
|
|
258
|
-
* @complexity `n`
|
|
259
|
-
* @throws `RangeError` 扩展标签属性不能包含">"
|
|
260
|
-
* @throws `RangeError` 无效的属性名
|
|
261
|
-
*/
|
|
262
|
-
setAttr(key, value, init) {
|
|
263
|
-
init &&= !externalUse('setAttr');
|
|
264
|
-
if (typeof key !== 'string' || typeof value !== 'string' && typeof value !== 'boolean') {
|
|
265
|
-
this.typeError('setAttr', 'String', 'Boolean');
|
|
266
|
-
} else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
|
|
267
|
-
throw new RangeError('扩展标签属性不能包含 ">"!');
|
|
268
|
-
}
|
|
269
|
-
key = key.toLowerCase().trim();
|
|
270
|
-
const config = this.getAttribute('config'),
|
|
271
|
-
include = this.getAttribute('include'),
|
|
272
|
-
parsedKey = this.type === 'ext-attr' || init
|
|
273
|
-
? key
|
|
274
|
-
: Parser.run(() => {
|
|
275
|
-
const token = new Token(key, config),
|
|
276
|
-
parseOnce = token.getAttribute('parseOnce');
|
|
277
|
-
parseOnce(0, include);
|
|
278
|
-
return String(parseOnce());
|
|
279
|
-
});
|
|
280
|
-
if (!/^(?:[\w:]|\0\d+[t!~{}+-]\x7F)(?:[\w:.-]|\0\d+[t!~{}+-]\x7F)*$/u.test(parsedKey)) {
|
|
281
|
-
if (init) {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
throw new RangeError(`无效的属性名:${key}!`);
|
|
285
|
-
} else if (value === false) {
|
|
286
|
-
this.#attr.delete(key);
|
|
287
|
-
} else {
|
|
288
|
-
this.#attr.set(key, value === true ? true : value.replaceAll(/\s/gu, ' ').trim());
|
|
289
|
-
}
|
|
290
|
-
if (!init) {
|
|
291
|
-
this.sanitize();
|
|
292
|
-
}
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
334
|
/**
|
|
297
335
|
* 移除标签属性
|
|
298
336
|
* @param {string} key 属性键
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Parser = require('..'),
|
|
4
|
+
Token = require('.'),
|
|
5
|
+
HasNowikiToken = require('./hasNowiki');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `<charinsert>`
|
|
9
|
+
* @classdesc `{childNodes: [...HasNowikiToken]}`
|
|
10
|
+
*/
|
|
11
|
+
class CharinsertToken extends Token {
|
|
12
|
+
type = 'ext-inner';
|
|
13
|
+
name = 'charinsert';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} wikitext wikitext
|
|
17
|
+
* @param {accum} accum
|
|
18
|
+
*/
|
|
19
|
+
constructor(wikitext, config = Parser.getConfig(), accum = []) {
|
|
20
|
+
super(undefined, config, true, accum, {HasNowikiToken: ':'});
|
|
21
|
+
this.append(...wikitext.split('\n').map(str => new HasNowikiToken(str, 'charinsert-line', config, accum)));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @override
|
|
26
|
+
* @param {string} selector
|
|
27
|
+
*/
|
|
28
|
+
toString(selector) {
|
|
29
|
+
return super.toString(selector, '\n');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @override */
|
|
33
|
+
getGaps() {
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @override */
|
|
38
|
+
print() {
|
|
39
|
+
return super.print({sep: '\n'});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @override */
|
|
43
|
+
cloneNode() {
|
|
44
|
+
const cloned = this.cloneChildNodes();
|
|
45
|
+
return Parser.run(() => {
|
|
46
|
+
const token = new CharinsertToken(undefined, this.getAttribute('config'));
|
|
47
|
+
token.append(...cloned);
|
|
48
|
+
return token;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 获取某一行的插入选项
|
|
54
|
+
* @param {number|HasNowikiToken} child 行号或子节点
|
|
55
|
+
*/
|
|
56
|
+
getLineItems(child) {
|
|
57
|
+
if (Number.isInteger(child)) {
|
|
58
|
+
child = this.childNodes.at(child);
|
|
59
|
+
}
|
|
60
|
+
if (!(child instanceof HasNowikiToken)) {
|
|
61
|
+
this.typeError('getLineItems', 'Number', 'HasNowikiToken');
|
|
62
|
+
}
|
|
63
|
+
const entities = {'\t': '	', '\r': '', ' ': ' '};
|
|
64
|
+
return String(child).replaceAll(
|
|
65
|
+
/<nowiki>(.+?)<\/nowiki>/giu,
|
|
66
|
+
/** @type {function(...string): string} */ (_, inner) => inner.replaceAll(/[\t\r ]/gu, s => entities[s]),
|
|
67
|
+
).split(/\s/u).filter(Boolean)
|
|
68
|
+
.map(item => {
|
|
69
|
+
const parts = item.split('+');
|
|
70
|
+
if (parts.length === 1) {
|
|
71
|
+
return parts[0];
|
|
72
|
+
}
|
|
73
|
+
return parts[0] ? parts.slice(0, 2) : '+';
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 获取所有插入选项 */
|
|
78
|
+
getAllItems() {
|
|
79
|
+
return this.childNodes.flatMap(child => this.getLineItems(child));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** @override */
|
|
83
|
+
text() {
|
|
84
|
+
return this.childNodes.map(
|
|
85
|
+
child => this.getLineItems(child).map(str => Array.isArray(str) ? str.join('+') : str).join(' '),
|
|
86
|
+
).join('\n');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Parser.classes.CharinsertToken = __filename;
|
|
91
|
+
module.exports = CharinsertToken;
|
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'),
|
|
@@ -21,6 +21,11 @@ class ConverterToken extends Token {
|
|
|
21
21
|
return this.hasFlag('R') || this.childNodes.length === 2 && !this.lastChild.variant;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** flags */
|
|
25
|
+
get flags() {
|
|
26
|
+
return this.getAllFlags();
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
30
|
* @param {string[]} flags 转换类型标记
|
|
26
31
|
* @param {string[]} rules 转换规则
|
|
@@ -33,7 +38,7 @@ class ConverterToken extends Token {
|
|
|
33
38
|
hasColon = firstRule.includes(':'),
|
|
34
39
|
firstRuleToken = new ConverterRuleToken(firstRule, hasColon, config, accum);
|
|
35
40
|
if (hasColon && firstRuleToken.childNodes.length === 1) {
|
|
36
|
-
this.
|
|
41
|
+
this.insertAt(new ConverterRuleToken(rules.join(';'), false, config, accum));
|
|
37
42
|
} else {
|
|
38
43
|
this.append(
|
|
39
44
|
firstRuleToken,
|
|
@@ -43,21 +48,13 @@ class ConverterToken extends Token {
|
|
|
43
48
|
this.getAttribute('protectChildren')(0);
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
/** @override */
|
|
47
|
-
cloneNode() {
|
|
48
|
-
const [flags, ...rules] = this.cloneChildNodes(),
|
|
49
|
-
token = Parser.run(() => new ConverterToken([], [], this.getAttribute('config')));
|
|
50
|
-
token.firstElementChild.safeReplaceWith(flags);
|
|
51
|
-
token.append(...rules);
|
|
52
|
-
return token;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
51
|
/**
|
|
56
52
|
* @override
|
|
57
53
|
* @param {string} selector
|
|
54
|
+
* @returns {string}
|
|
58
55
|
*/
|
|
59
56
|
toString(selector) {
|
|
60
|
-
const {
|
|
57
|
+
const {childNodes: [flags, ...rules]} = this;
|
|
61
58
|
return selector && this.matches(selector)
|
|
62
59
|
? ''
|
|
63
60
|
: `-{${flags.toString(selector)}${flags.childNodes.length > 0 ? '|' : ''}${rules.map(String).join(';')}}-`;
|
|
@@ -69,17 +66,39 @@ class ConverterToken extends Token {
|
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
/**
|
|
72
|
-
*
|
|
69
|
+
* @override
|
|
73
70
|
* @param {number} i 子节点位置
|
|
74
71
|
*/
|
|
75
72
|
getGaps(i = 0) {
|
|
76
73
|
i = i < 0 ? i + this.childNodes.length : i;
|
|
77
|
-
return i || this.
|
|
74
|
+
return i || this.firstChild.childNodes.length > 0 ? 1 : 0;
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
/** @override */
|
|
81
|
-
|
|
78
|
+
print() {
|
|
82
79
|
const {children: [flags, ...rules]} = this;
|
|
80
|
+
return `<span class="wpb-converter">-{${flags.print()}${
|
|
81
|
+
flags.childNodes.length > 0 ? '|' : ''
|
|
82
|
+
}${print(rules, {sep: ';'})}}-</span>`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @override */
|
|
86
|
+
cloneNode() {
|
|
87
|
+
const [flags, ...rules] = this.cloneChildNodes();
|
|
88
|
+
return Parser.run(() => {
|
|
89
|
+
const token = new ConverterToken([], [], this.getAttribute('config'));
|
|
90
|
+
token.firstChild.safeReplaceWith(flags);
|
|
91
|
+
token.append(...rules);
|
|
92
|
+
return token;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @override
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
text() {
|
|
101
|
+
const {childNodes: [flags, ...rules]} = this;
|
|
83
102
|
return `-{${flags.text()}|${text(rules, ';')}}-`;
|
|
84
103
|
}
|
|
85
104
|
|