wikiparser-node 0.3.0 → 0.4.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/.eslintrc.json +472 -34
- package/README.md +1 -1
- package/config/default.json +58 -30
- package/config/llwiki.json +22 -90
- package/config/moegirl.json +51 -13
- package/config/zhwiki.json +1269 -0
- package/index.js +114 -104
- package/lib/element.js +448 -440
- package/lib/node.js +335 -115
- package/lib/ranges.js +27 -18
- package/lib/text.js +146 -0
- package/lib/title.js +13 -5
- package/mixin/attributeParent.js +70 -24
- package/mixin/fixedToken.js +14 -6
- package/mixin/hidden.js +6 -4
- package/mixin/sol.js +27 -10
- package/package.json +9 -3
- package/parser/brackets.js +22 -17
- package/parser/commentAndExt.js +18 -16
- package/parser/converter.js +14 -13
- package/parser/externalLinks.js +12 -11
- package/parser/hrAndDoubleUnderscore.js +23 -14
- package/parser/html.js +10 -9
- package/parser/links.js +15 -14
- package/parser/list.js +12 -11
- package/parser/magicLinks.js +12 -11
- package/parser/quotes.js +6 -5
- package/parser/selector.js +175 -0
- package/parser/table.js +25 -18
- package/printed/example.json +120 -0
- package/src/arg.js +56 -32
- package/src/atom/hidden.js +5 -2
- package/src/atom/index.js +17 -9
- package/src/attribute.js +182 -100
- package/src/converter.js +68 -41
- package/src/converterFlags.js +67 -45
- package/src/converterRule.js +117 -65
- package/src/extLink.js +66 -18
- package/src/gallery.js +42 -15
- package/src/heading.js +34 -15
- package/src/html.js +97 -35
- package/src/imageParameter.js +83 -54
- package/src/index.js +299 -178
- package/src/link/category.js +20 -52
- package/src/link/file.js +59 -28
- package/src/link/galleryImage.js +21 -7
- package/src/link/index.js +146 -60
- package/src/magicLink.js +34 -12
- package/src/nowiki/comment.js +22 -10
- package/src/nowiki/dd.js +37 -22
- package/src/nowiki/doubleUnderscore.js +16 -7
- package/src/nowiki/hr.js +11 -7
- package/src/nowiki/index.js +16 -9
- package/src/nowiki/list.js +2 -2
- package/src/nowiki/noinclude.js +8 -4
- package/src/nowiki/quote.js +11 -7
- package/src/onlyinclude.js +19 -7
- package/src/parameter.js +65 -38
- package/src/syntax.js +26 -20
- package/src/table/index.js +260 -165
- package/src/table/td.js +98 -52
- package/src/table/tr.js +102 -58
- package/src/tagPair/ext.js +27 -19
- package/src/tagPair/include.js +16 -11
- package/src/tagPair/index.js +64 -29
- package/src/transclude.js +170 -93
- package/test/api.js +83 -0
- package/test/real.js +133 -0
- package/test/test.js +28 -0
- package/test/util.js +80 -0
- package/tool/index.js +41 -31
- package/typings/api.d.ts +13 -0
- package/typings/array.d.ts +28 -0
- package/typings/event.d.ts +24 -0
- package/typings/index.d.ts +46 -4
- package/typings/node.d.ts +15 -9
- package/typings/parser.d.ts +7 -0
- package/typings/tool.d.ts +3 -2
- package/util/debug.js +21 -18
- package/util/string.js +40 -27
- package/typings/element.d.ts +0 -28
package/src/attribute.js
CHANGED
|
@@ -2,27 +2,94 @@
|
|
|
2
2
|
|
|
3
3
|
const {externalUse} = require('../util/debug'),
|
|
4
4
|
{toCase, removeComment, normalizeSpace} = require('../util/string'),
|
|
5
|
-
|
|
5
|
+
Parser = require('..'),
|
|
6
6
|
Token = require('.');
|
|
7
7
|
|
|
8
8
|
const stages = {'ext-attr': 0, 'html-attr': 2, 'table-attr': 3};
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 扩展和HTML标签属性
|
|
12
|
-
* @classdesc `{childNodes: [
|
|
12
|
+
* @classdesc `{childNodes: [AstText]|(AstText|ArgToken|TranscludeToken)[]}`
|
|
13
13
|
*/
|
|
14
14
|
class AttributeToken extends Token {
|
|
15
15
|
/** @type {Map<string, string|true>} */ #attr = new Map();
|
|
16
16
|
#sanitized = true;
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @override
|
|
20
|
+
* @param {string} key 属性键
|
|
21
|
+
* @param {string|undefined} equal 属性规则运算符,`equal`存在时`val`和`i`也一定存在
|
|
22
|
+
* @param {string|undefined} val 属性值
|
|
23
|
+
* @param {string|undefined} i 是否对大小写不敏感
|
|
24
|
+
*/
|
|
25
|
+
#matchesAttr = (key, equal, val, i) => {
|
|
26
|
+
if (!equal) {
|
|
27
|
+
return this.hasAttr(key);
|
|
28
|
+
} else if (!this.hasAttr(key)) {
|
|
29
|
+
return equal === '!=';
|
|
30
|
+
}
|
|
31
|
+
val = toCase(val, i);
|
|
32
|
+
const attr = this.getAttr(key),
|
|
33
|
+
thisVal = toCase(attr === true ? '' : attr, i);
|
|
34
|
+
switch (equal) {
|
|
35
|
+
case '~=':
|
|
36
|
+
return attr !== true && thisVal.split(/\s/u).includes(val);
|
|
37
|
+
case '|=': // 允许`val === ''`
|
|
38
|
+
return thisVal === val || thisVal.startsWith(`${val}-`);
|
|
39
|
+
case '^=':
|
|
40
|
+
return attr !== true && thisVal.startsWith(val);
|
|
41
|
+
case '$=':
|
|
42
|
+
return attr !== true && thisVal.endsWith(val);
|
|
43
|
+
case '*=':
|
|
44
|
+
return attr !== true && thisVal.includes(val);
|
|
45
|
+
case '!=':
|
|
46
|
+
return thisVal !== val;
|
|
47
|
+
default: // `=`
|
|
48
|
+
return thisVal === val;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* getAttr()方法的getter写法
|
|
54
|
+
* @returns {Record<string, string|true>}
|
|
55
|
+
*/
|
|
56
|
+
get attributes() {
|
|
57
|
+
return this.getAttr();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 以字符串表示的class属性 */
|
|
61
|
+
get className() {
|
|
62
|
+
const attr = this.getAttr('class');
|
|
63
|
+
return typeof attr === 'string' ? attr : '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
set className(className) {
|
|
67
|
+
this.setAttr('class', className);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 以Set表示的class属性 */
|
|
71
|
+
get classList() {
|
|
72
|
+
return new Set(this.className.split(/\s/u));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** id属性 */
|
|
76
|
+
get id() {
|
|
77
|
+
const attr = this.getAttr('id');
|
|
78
|
+
return typeof attr === 'string' ? attr : '';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
set id(id) {
|
|
82
|
+
this.setAttr('id', id);
|
|
83
|
+
}
|
|
84
|
+
|
|
18
85
|
/**
|
|
19
86
|
* 从`this.#attr`更新`childNodes`
|
|
20
87
|
* @complexity `n`
|
|
21
88
|
*/
|
|
22
89
|
#updateFromAttr() {
|
|
23
90
|
let equal = '=';
|
|
24
|
-
const ParameterToken = require('./parameter')
|
|
25
|
-
|
|
91
|
+
const ParameterToken = require('./parameter');
|
|
92
|
+
const parent = this.closest('ext, parameter');
|
|
26
93
|
if (parent instanceof ParameterToken && parent.anon
|
|
27
94
|
&& parent.parentNode?.matches('template, magic-word#invoke')
|
|
28
95
|
) {
|
|
@@ -37,7 +104,10 @@ class AttributeToken extends Token {
|
|
|
37
104
|
}).join(' ');
|
|
38
105
|
}
|
|
39
106
|
|
|
40
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* 清理标签属性
|
|
109
|
+
* @complexity `n`
|
|
110
|
+
*/
|
|
41
111
|
sanitize() {
|
|
42
112
|
if (!Parser.running && !this.#sanitized) {
|
|
43
113
|
Parser.warn(`${this.constructor.name}.sanitize 方法将清理无效属性!`);
|
|
@@ -55,19 +125,29 @@ class AttributeToken extends Token {
|
|
|
55
125
|
*/
|
|
56
126
|
#parseAttr() {
|
|
57
127
|
this.#attr.clear();
|
|
58
|
-
let string = this.toString(),
|
|
59
|
-
|
|
128
|
+
let string = this.toString('comment, include, noinclude, heading, html'),
|
|
129
|
+
token;
|
|
60
130
|
if (this.type !== 'ext-attr' && !Parser.running) {
|
|
61
131
|
const config = this.getAttribute('config'),
|
|
62
132
|
include = this.getAttribute('include');
|
|
63
|
-
token = Parser.run(() =>
|
|
64
|
-
|
|
133
|
+
token = Parser.run(() => {
|
|
134
|
+
const newToken = new Token(string, config),
|
|
135
|
+
parseOnce = newToken.getAttribute('parseOnce');
|
|
136
|
+
parseOnce(0, include);
|
|
137
|
+
return parseOnce();
|
|
138
|
+
});
|
|
139
|
+
string = String(token);
|
|
65
140
|
}
|
|
66
|
-
string = removeComment(string).
|
|
67
|
-
|
|
68
|
-
|
|
141
|
+
string = removeComment(string).replaceAll(/\0\d+~\x7F/gu, '=');
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 解析并重建标签属性
|
|
145
|
+
* @param {string|boolean} str 半解析的标签属性文本
|
|
146
|
+
*/
|
|
147
|
+
const build = str =>
|
|
148
|
+
typeof str === 'boolean' || !token ? str : token.getAttribute('buildFromStr')(str).map(String).join('');
|
|
69
149
|
for (const [, key,, quoted, unquoted] of string
|
|
70
|
-
.matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(?:\2|$)|(\S*)))?/
|
|
150
|
+
.matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(?:\2|$)|(\S*)))?/gsu)
|
|
71
151
|
) {
|
|
72
152
|
if (!this.setAttr(build(key), build(quoted ?? unquoted ?? true), true)) {
|
|
73
153
|
this.#sanitized = false;
|
|
@@ -76,9 +156,9 @@ class AttributeToken extends Token {
|
|
|
76
156
|
}
|
|
77
157
|
|
|
78
158
|
/**
|
|
79
|
-
* @param {string} attr
|
|
80
|
-
* @param {'ext-attr'|'html-attr'|'table-attr'} type
|
|
81
|
-
* @param {string} name
|
|
159
|
+
* @param {string} attr 标签属性
|
|
160
|
+
* @param {'ext-attr'|'html-attr'|'table-attr'} type 标签类型
|
|
161
|
+
* @param {string} name 标签名
|
|
82
162
|
* @param {accum} accum
|
|
83
163
|
*/
|
|
84
164
|
constructor(attr, type, name, config = Parser.getConfig(), accum = []) {
|
|
@@ -87,8 +167,9 @@ class AttributeToken extends Token {
|
|
|
87
167
|
this.setAttribute('name', name).#parseAttr();
|
|
88
168
|
}
|
|
89
169
|
|
|
170
|
+
/** @override */
|
|
90
171
|
cloneNode() {
|
|
91
|
-
const cloned = this.
|
|
172
|
+
const cloned = this.cloneChildNodes();
|
|
92
173
|
return Parser.run(() => {
|
|
93
174
|
const token = new AttributeToken(undefined, this.type, this.name, this.getAttribute('config'));
|
|
94
175
|
token.append(...cloned);
|
|
@@ -97,28 +178,31 @@ class AttributeToken extends Token {
|
|
|
97
178
|
}
|
|
98
179
|
|
|
99
180
|
/**
|
|
181
|
+
* @override
|
|
100
182
|
* @template {string} T
|
|
101
|
-
* @param {T} key
|
|
183
|
+
* @param {T} key 属性键
|
|
102
184
|
* @returns {TokenAttribute<T>}
|
|
103
185
|
*/
|
|
104
186
|
getAttribute(key) {
|
|
105
|
-
if (key === '
|
|
106
|
-
return
|
|
187
|
+
if (key === 'matchesAttr') {
|
|
188
|
+
return this.#matchesAttr;
|
|
107
189
|
}
|
|
108
|
-
return super.getAttribute(key);
|
|
190
|
+
return key === 'attr' ? new Map(this.#attr) : super.getAttribute(key);
|
|
109
191
|
}
|
|
110
192
|
|
|
193
|
+
/** @override */
|
|
111
194
|
afterBuild() {
|
|
112
195
|
if (this.type !== 'ext-attr') {
|
|
196
|
+
const buildFromStr = this.getAttribute('buildFromStr');
|
|
113
197
|
for (let [key, text] of this.#attr) {
|
|
114
198
|
let built = false;
|
|
115
|
-
if (key.includes('\
|
|
199
|
+
if (key.includes('\0')) {
|
|
116
200
|
this.#attr.delete(key);
|
|
117
|
-
key =
|
|
201
|
+
key = buildFromStr(key).map(String).join('');
|
|
118
202
|
built = true;
|
|
119
203
|
}
|
|
120
|
-
if (typeof text === 'string' && text.includes('\
|
|
121
|
-
text =
|
|
204
|
+
if (typeof text === 'string' && text.includes('\0')) {
|
|
205
|
+
text = buildFromStr(text).map(String).join('');
|
|
122
206
|
built = true;
|
|
123
207
|
}
|
|
124
208
|
if (built) {
|
|
@@ -126,65 +210,74 @@ class AttributeToken extends Token {
|
|
|
126
210
|
}
|
|
127
211
|
}
|
|
128
212
|
}
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
};
|
|
213
|
+
const /** @type {AstListener} */ attributeListener = ({type, target}) => {
|
|
214
|
+
if (type === 'text' || target !== this) {
|
|
215
|
+
this.#parseAttr();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
135
218
|
this.addEventListener(['remove', 'insert', 'replace', 'text'], attributeListener);
|
|
136
219
|
return this;
|
|
137
220
|
}
|
|
138
221
|
|
|
139
|
-
/**
|
|
222
|
+
/**
|
|
223
|
+
* 标签是否具有某属性
|
|
224
|
+
* @param {string} key 属性键
|
|
225
|
+
*/
|
|
140
226
|
hasAttr(key) {
|
|
141
|
-
|
|
142
|
-
this.typeError('hasAttr', 'String');
|
|
143
|
-
}
|
|
144
|
-
return this.#attr.has(key.toLowerCase().trim());
|
|
227
|
+
return typeof key === 'string' ? this.#attr.has(key.toLowerCase().trim()) : this.typeError('hasAttr', 'String');
|
|
145
228
|
}
|
|
146
229
|
|
|
147
230
|
/**
|
|
231
|
+
* 获取标签属性
|
|
148
232
|
* @template {string|undefined} T
|
|
149
|
-
* @param {T} key
|
|
233
|
+
* @param {T} key 属性键
|
|
150
234
|
* @returns {T extends string ? string|true : Record<string, string|true>}
|
|
151
235
|
*/
|
|
152
236
|
getAttr(key) {
|
|
153
237
|
if (key === undefined) {
|
|
154
238
|
return Object.fromEntries(this.#attr);
|
|
155
|
-
} else if (typeof key !== 'string') {
|
|
156
|
-
this.typeError('getAttr', 'String');
|
|
157
239
|
}
|
|
158
|
-
return this.#attr.get(key.toLowerCase().trim());
|
|
240
|
+
return typeof key === 'string' ? this.#attr.get(key.toLowerCase().trim()) : this.typeError('getAttr', 'String');
|
|
159
241
|
}
|
|
160
242
|
|
|
243
|
+
/** 获取全部的标签属性名 */
|
|
161
244
|
getAttrNames() {
|
|
162
245
|
return [...this.#attr.keys()];
|
|
163
246
|
}
|
|
164
247
|
|
|
248
|
+
/** 标签是否具有任意属性 */
|
|
165
249
|
hasAttrs() {
|
|
166
250
|
return this.getAttrNames().length > 0;
|
|
167
251
|
}
|
|
168
252
|
|
|
169
253
|
/**
|
|
170
|
-
*
|
|
171
|
-
* @param {string
|
|
254
|
+
* 设置标签属性
|
|
255
|
+
* @param {string} key 属性键
|
|
256
|
+
* @param {string|boolean} value 属性值
|
|
257
|
+
* @param {boolean} init 是否是初次解析
|
|
172
258
|
* @complexity `n`
|
|
259
|
+
* @throws `RangeError` 扩展标签属性不能包含">"
|
|
260
|
+
* @throws `RangeError` 无效的属性名
|
|
173
261
|
*/
|
|
174
|
-
setAttr(key, value, init
|
|
262
|
+
setAttr(key, value, init) {
|
|
175
263
|
init &&= !externalUse('setAttr');
|
|
176
|
-
if (typeof key !== 'string' ||
|
|
177
|
-
this.typeError('
|
|
264
|
+
if (typeof key !== 'string' || typeof value !== 'string' && typeof value !== 'boolean') {
|
|
265
|
+
this.typeError('setAttr', 'String', 'Boolean');
|
|
178
266
|
} else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
|
|
179
267
|
throw new RangeError('扩展标签属性不能包含 ">"!');
|
|
180
268
|
}
|
|
181
269
|
key = key.toLowerCase().trim();
|
|
182
270
|
const config = this.getAttribute('config'),
|
|
183
271
|
include = this.getAttribute('include'),
|
|
184
|
-
parsedKey = this.type
|
|
185
|
-
?
|
|
186
|
-
:
|
|
187
|
-
|
|
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)) {
|
|
188
281
|
if (init) {
|
|
189
282
|
return false;
|
|
190
283
|
}
|
|
@@ -192,7 +285,7 @@ class AttributeToken extends Token {
|
|
|
192
285
|
} else if (value === false) {
|
|
193
286
|
this.#attr.delete(key);
|
|
194
287
|
} else {
|
|
195
|
-
this.#attr.set(key, value === true ? true : value.
|
|
288
|
+
this.#attr.set(key, value === true ? true : value.replaceAll(/\s/gu, ' ').trim());
|
|
196
289
|
}
|
|
197
290
|
if (!init) {
|
|
198
291
|
this.sanitize();
|
|
@@ -201,7 +294,8 @@ class AttributeToken extends Token {
|
|
|
201
294
|
}
|
|
202
295
|
|
|
203
296
|
/**
|
|
204
|
-
*
|
|
297
|
+
* 移除标签属性
|
|
298
|
+
* @param {string} key 属性键
|
|
205
299
|
* @complexity `n`
|
|
206
300
|
*/
|
|
207
301
|
removeAttr(key) {
|
|
@@ -215,9 +309,11 @@ class AttributeToken extends Token {
|
|
|
215
309
|
}
|
|
216
310
|
|
|
217
311
|
/**
|
|
218
|
-
*
|
|
219
|
-
* @param {
|
|
312
|
+
* 开关标签属性
|
|
313
|
+
* @param {string} key 属性键
|
|
314
|
+
* @param {boolean|undefined} force 强制开启或关闭
|
|
220
315
|
* @complexity `n`
|
|
316
|
+
* @throws `RangeError` 不为Boolean类型的属性值
|
|
221
317
|
*/
|
|
222
318
|
toggleAttr(key, force) {
|
|
223
319
|
if (typeof key !== 'string') {
|
|
@@ -233,31 +329,48 @@ class AttributeToken extends Token {
|
|
|
233
329
|
this.setAttr(key, force === true || force === undefined && value === false);
|
|
234
330
|
}
|
|
235
331
|
|
|
332
|
+
/**
|
|
333
|
+
* 生成引导空格
|
|
334
|
+
* @param {string} str 属性字符串
|
|
335
|
+
*/
|
|
236
336
|
#leadingSpace(str = super.toString()) {
|
|
237
|
-
return this.type !== 'table-attr' && str &&
|
|
337
|
+
return this.type !== 'table-attr' && str && str.trimStart() === str ? ' ' : '';
|
|
238
338
|
}
|
|
239
339
|
|
|
240
|
-
/**
|
|
241
|
-
|
|
242
|
-
|
|
340
|
+
/**
|
|
341
|
+
* @override
|
|
342
|
+
* @this {AttributeToken & Token}
|
|
343
|
+
* @param {string} selector
|
|
344
|
+
*/
|
|
345
|
+
toString(selector) {
|
|
346
|
+
if (this.type === 'table-attr') {
|
|
347
|
+
normalizeSpace(this);
|
|
348
|
+
}
|
|
349
|
+
const str = super.toString(selector);
|
|
243
350
|
return `${this.#leadingSpace(str)}${str}`;
|
|
244
351
|
}
|
|
245
352
|
|
|
353
|
+
/** @override */
|
|
246
354
|
getPadding() {
|
|
247
355
|
return this.#leadingSpace().length;
|
|
248
356
|
}
|
|
249
357
|
|
|
358
|
+
/** @override */
|
|
250
359
|
text() {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
360
|
+
if (this.type === 'table-attr') {
|
|
361
|
+
normalizeSpace(this);
|
|
362
|
+
}
|
|
363
|
+
const str = this.#updateFromAttr();
|
|
364
|
+
return `${this.#leadingSpace(str)}${str}`;
|
|
254
365
|
}
|
|
255
366
|
|
|
256
367
|
/**
|
|
257
|
-
* @
|
|
368
|
+
* @override
|
|
369
|
+
* @param {number} i 移除位置
|
|
370
|
+
* @param {boolean} done 是否已解析过改变后的标签属性
|
|
258
371
|
* @complexity `n`
|
|
259
372
|
*/
|
|
260
|
-
removeAt(i, done
|
|
373
|
+
removeAt(i, done) {
|
|
261
374
|
done &&= !externalUse('removeAt');
|
|
262
375
|
done ||= Parser.running;
|
|
263
376
|
const token = super.removeAt(i);
|
|
@@ -268,8 +381,11 @@ class AttributeToken extends Token {
|
|
|
268
381
|
}
|
|
269
382
|
|
|
270
383
|
/**
|
|
271
|
-
* @
|
|
272
|
-
* @
|
|
384
|
+
* @override
|
|
385
|
+
* @template {Token} T
|
|
386
|
+
* @param {T} token 待插入的节点
|
|
387
|
+
* @param {number} i 插入位置
|
|
388
|
+
* @param {boolean} done 是否已解析过改变后的标签属性
|
|
273
389
|
* @complexity `n`
|
|
274
390
|
*/
|
|
275
391
|
insertAt(token, i = this.childNodes.length, done = false) {
|
|
@@ -283,7 +399,8 @@ class AttributeToken extends Token {
|
|
|
283
399
|
}
|
|
284
400
|
|
|
285
401
|
/**
|
|
286
|
-
* @
|
|
402
|
+
* @override
|
|
403
|
+
* @param {...Token} elements 待替换的子节点
|
|
287
404
|
* @complexity `n²`
|
|
288
405
|
*/
|
|
289
406
|
replaceChildren(...elements) {
|
|
@@ -299,41 +416,6 @@ class AttributeToken extends Token {
|
|
|
299
416
|
this.insertAt(element, undefined, done);
|
|
300
417
|
}
|
|
301
418
|
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* @param {string} key
|
|
305
|
-
* @param {string|undefined} equal - `equal`存在时`val`和`i`也一定存在
|
|
306
|
-
* @param {string|undefined} val
|
|
307
|
-
* @param {string|undefined} i
|
|
308
|
-
*/
|
|
309
|
-
matchesAttr(key, equal, val, i) {
|
|
310
|
-
if (externalUse('matchesAttr')) {
|
|
311
|
-
throw new Error(`禁止外部调用 ${this.constructor.name}.matchesAttr 方法!`);
|
|
312
|
-
} else if (!equal) {
|
|
313
|
-
return this.hasAttr(key);
|
|
314
|
-
} else if (!this.hasAttr(key)) {
|
|
315
|
-
return equal === '!=';
|
|
316
|
-
}
|
|
317
|
-
val = toCase(val, i);
|
|
318
|
-
const attr = this.getAttr(key),
|
|
319
|
-
thisVal = toCase(attr === true ? '' : attr, i);
|
|
320
|
-
switch (equal) {
|
|
321
|
-
case '~=':
|
|
322
|
-
return attr !== true && thisVal.split(/\s/).some(v => v === val);
|
|
323
|
-
case '|=': // 允许`val === ''`
|
|
324
|
-
return thisVal === val || thisVal.startsWith(`${val}-`);
|
|
325
|
-
case '^=':
|
|
326
|
-
return attr !== true && thisVal.startsWith(val);
|
|
327
|
-
case '$=':
|
|
328
|
-
return attr !== true && thisVal.endsWith(val);
|
|
329
|
-
case '*=':
|
|
330
|
-
return attr !== true && thisVal.includes(val);
|
|
331
|
-
case '!=':
|
|
332
|
-
return thisVal !== val;
|
|
333
|
-
default: // `=`
|
|
334
|
-
return thisVal === val;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
419
|
}
|
|
338
420
|
|
|
339
421
|
Parser.classes.AttributeToken = __filename;
|
package/src/converter.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {text} = require('../util/string'),
|
|
4
|
-
|
|
4
|
+
Parser = require('..'),
|
|
5
5
|
Token = require('.'),
|
|
6
6
|
ConverterFlagsToken = require('./converterFlags'),
|
|
7
7
|
ConverterRuleToken = require('./converterRule');
|
|
@@ -14,116 +14,143 @@ class ConverterToken extends Token {
|
|
|
14
14
|
type = 'converter';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* @
|
|
17
|
+
* 是否无转换
|
|
18
|
+
* @this {ConverterToken & {lastChild: ConverterRuleToken}}
|
|
19
|
+
*/
|
|
20
|
+
get noConvert() {
|
|
21
|
+
return this.hasFlag('R') || this.childNodes.length === 2 && !this.lastChild.variant;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string[]} flags 转换类型标记
|
|
26
|
+
* @param {string[]} rules 转换规则
|
|
19
27
|
* @param {accum} accum
|
|
20
28
|
*/
|
|
21
29
|
constructor(flags, rules, config = Parser.getConfig(), accum = []) {
|
|
22
30
|
super(undefined, config, true, accum);
|
|
23
31
|
this.append(new ConverterFlagsToken(flags, config, accum));
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
35
|
-
}
|
|
32
|
+
const [firstRule] = rules,
|
|
33
|
+
hasColon = firstRule.includes(':'),
|
|
34
|
+
firstRuleToken = new ConverterRuleToken(firstRule, hasColon, config, accum);
|
|
35
|
+
if (hasColon && firstRuleToken.childNodes.length === 1) {
|
|
36
|
+
this.appendChild(new ConverterRuleToken(rules.join(';'), false, config, accum));
|
|
37
|
+
} else {
|
|
38
|
+
this.append(
|
|
39
|
+
firstRuleToken,
|
|
40
|
+
...rules.slice(1).map(rule => new ConverterRuleToken(rule, true, config, accum)),
|
|
41
|
+
);
|
|
36
42
|
}
|
|
37
|
-
this.protectChildren(0);
|
|
43
|
+
this.getAttribute('protectChildren')(0);
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
/** @override */
|
|
40
47
|
cloneNode() {
|
|
41
|
-
const [flags, ...rules] = this.
|
|
48
|
+
const [flags, ...rules] = this.cloneChildNodes(),
|
|
42
49
|
token = Parser.run(() => new ConverterToken([], [], this.getAttribute('config')));
|
|
43
50
|
token.firstElementChild.safeReplaceWith(flags);
|
|
44
51
|
token.append(...rules);
|
|
45
52
|
return token;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
/**
|
|
56
|
+
* @override
|
|
57
|
+
* @param {string} selector
|
|
58
|
+
*/
|
|
59
|
+
toString(selector) {
|
|
60
|
+
const {children: [flags, ...rules]} = this;
|
|
61
|
+
return selector && this.matches(selector)
|
|
62
|
+
? ''
|
|
63
|
+
: `-{${flags.toString(selector)}${flags.childNodes.length > 0 ? '|' : ''}${rules.map(String).join(';')}}-`;
|
|
51
64
|
}
|
|
52
65
|
|
|
66
|
+
/** @override */
|
|
53
67
|
getPadding() {
|
|
54
68
|
return 2;
|
|
55
69
|
}
|
|
56
70
|
|
|
57
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* /** @override
|
|
73
|
+
* @param {number} i 子节点位置
|
|
74
|
+
*/
|
|
58
75
|
getGaps(i = 0) {
|
|
59
76
|
i = i < 0 ? i + this.childNodes.length : i;
|
|
60
|
-
return i || this.firstElementChild.childNodes.length ? 1 : 0;
|
|
77
|
+
return i || this.firstElementChild.childNodes.length > 0 ? 1 : 0;
|
|
61
78
|
}
|
|
62
79
|
|
|
80
|
+
/** @override */
|
|
63
81
|
text() {
|
|
64
|
-
const [flags, ...rules] = this
|
|
82
|
+
const {children: [flags, ...rules]} = this;
|
|
65
83
|
return `-{${flags.text()}|${text(rules, ';')}}-`;
|
|
66
84
|
}
|
|
67
85
|
|
|
68
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* 获取所有转换类型标记
|
|
88
|
+
* @this {{firstChild: ConverterFlagsToken}}
|
|
89
|
+
*/
|
|
69
90
|
getAllFlags() {
|
|
70
91
|
return this.firstChild.getAllFlags();
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* 获取有效的转换类型标记
|
|
96
|
+
* @this {{firstChild: ConverterFlagsToken}}
|
|
97
|
+
*/
|
|
74
98
|
getEffectiveFlags() {
|
|
75
99
|
return this.firstChild.getEffectiveFlags();
|
|
76
100
|
}
|
|
77
101
|
|
|
78
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* 获取未知的转换类型标记
|
|
104
|
+
* @this {{firstChild: ConverterFlagsToken}}
|
|
105
|
+
*/
|
|
79
106
|
getUnknownFlags() {
|
|
80
107
|
return this.firstChild.getUnknownFlags();
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
/**
|
|
84
|
-
*
|
|
85
|
-
* @
|
|
111
|
+
* 是否具有某转换类型标记
|
|
112
|
+
* @this {{firstChild: ConverterFlagsToken}}
|
|
113
|
+
* @param {string} flag 转换类型标记
|
|
86
114
|
*/
|
|
87
115
|
hasFlag(flag) {
|
|
88
116
|
return this.firstChild.hasFlag(flag);
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
/**
|
|
92
|
-
*
|
|
93
|
-
* @
|
|
120
|
+
* 是否具有某有效的转换类型标记
|
|
121
|
+
* @this {{firstChild: ConverterFlagsToken}}
|
|
122
|
+
* @param {string} flag 转换类型标记
|
|
94
123
|
*/
|
|
95
124
|
hasEffectiveFlag(flag) {
|
|
96
125
|
return this.firstChild.hasEffectiveFlag(flag);
|
|
97
126
|
}
|
|
98
127
|
|
|
99
128
|
/**
|
|
100
|
-
*
|
|
101
|
-
* @
|
|
129
|
+
* 移除转换类型标记
|
|
130
|
+
* @this {{firstChild: ConverterFlagsToken}}
|
|
131
|
+
* @param {string} flag 转换类型标记
|
|
102
132
|
*/
|
|
103
133
|
removeFlag(flag) {
|
|
104
134
|
this.firstChild.removeFlag(flag);
|
|
105
135
|
}
|
|
106
136
|
|
|
107
137
|
/**
|
|
108
|
-
*
|
|
109
|
-
* @
|
|
138
|
+
* 设置转换类型标记
|
|
139
|
+
* @this {{firstChild: ConverterFlagsToken}}
|
|
140
|
+
* @param {string} flag 转换类型标记
|
|
110
141
|
*/
|
|
111
142
|
setFlag(flag) {
|
|
112
143
|
this.firstChild.setFlag(flag);
|
|
113
144
|
}
|
|
114
145
|
|
|
115
146
|
/**
|
|
116
|
-
*
|
|
117
|
-
* @
|
|
147
|
+
* 开关某转换类型标记
|
|
148
|
+
* @this {{firstChild: ConverterFlagsToken}}
|
|
149
|
+
* @param {string} flag 转换类型标记
|
|
118
150
|
*/
|
|
119
151
|
toggleFlag(flag) {
|
|
120
152
|
this.firstChild.toggleFlag(flag);
|
|
121
153
|
}
|
|
122
|
-
|
|
123
|
-
/** @this {ConverterToken & {children: [ConverterFlagsToken, ConverterRuleToken]}} */
|
|
124
|
-
get noConvert() {
|
|
125
|
-
return this.childNodes.length < 3 && !this.children[1]?.variant;
|
|
126
|
-
}
|
|
127
154
|
}
|
|
128
155
|
|
|
129
156
|
Parser.classes.ConverterToken = __filename;
|