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.
Files changed (81) hide show
  1. package/.eslintrc.json +472 -34
  2. package/README.md +1 -1
  3. package/config/default.json +58 -30
  4. package/config/llwiki.json +22 -90
  5. package/config/moegirl.json +51 -13
  6. package/config/zhwiki.json +1269 -0
  7. package/index.js +114 -104
  8. package/lib/element.js +448 -440
  9. package/lib/node.js +335 -115
  10. package/lib/ranges.js +27 -18
  11. package/lib/text.js +146 -0
  12. package/lib/title.js +13 -5
  13. package/mixin/attributeParent.js +70 -24
  14. package/mixin/fixedToken.js +14 -6
  15. package/mixin/hidden.js +6 -4
  16. package/mixin/sol.js +27 -10
  17. package/package.json +9 -3
  18. package/parser/brackets.js +22 -17
  19. package/parser/commentAndExt.js +18 -16
  20. package/parser/converter.js +14 -13
  21. package/parser/externalLinks.js +12 -11
  22. package/parser/hrAndDoubleUnderscore.js +23 -14
  23. package/parser/html.js +10 -9
  24. package/parser/links.js +15 -14
  25. package/parser/list.js +12 -11
  26. package/parser/magicLinks.js +12 -11
  27. package/parser/quotes.js +6 -5
  28. package/parser/selector.js +175 -0
  29. package/parser/table.js +25 -18
  30. package/printed/example.json +120 -0
  31. package/src/arg.js +56 -32
  32. package/src/atom/hidden.js +5 -2
  33. package/src/atom/index.js +17 -9
  34. package/src/attribute.js +182 -100
  35. package/src/converter.js +68 -41
  36. package/src/converterFlags.js +67 -45
  37. package/src/converterRule.js +117 -65
  38. package/src/extLink.js +66 -18
  39. package/src/gallery.js +42 -15
  40. package/src/heading.js +34 -15
  41. package/src/html.js +97 -35
  42. package/src/imageParameter.js +83 -54
  43. package/src/index.js +299 -178
  44. package/src/link/category.js +20 -52
  45. package/src/link/file.js +59 -28
  46. package/src/link/galleryImage.js +21 -7
  47. package/src/link/index.js +146 -60
  48. package/src/magicLink.js +34 -12
  49. package/src/nowiki/comment.js +22 -10
  50. package/src/nowiki/dd.js +37 -22
  51. package/src/nowiki/doubleUnderscore.js +16 -7
  52. package/src/nowiki/hr.js +11 -7
  53. package/src/nowiki/index.js +16 -9
  54. package/src/nowiki/list.js +2 -2
  55. package/src/nowiki/noinclude.js +8 -4
  56. package/src/nowiki/quote.js +11 -7
  57. package/src/onlyinclude.js +19 -7
  58. package/src/parameter.js +65 -38
  59. package/src/syntax.js +26 -20
  60. package/src/table/index.js +260 -165
  61. package/src/table/td.js +98 -52
  62. package/src/table/tr.js +102 -58
  63. package/src/tagPair/ext.js +27 -19
  64. package/src/tagPair/include.js +16 -11
  65. package/src/tagPair/index.js +64 -29
  66. package/src/transclude.js +170 -93
  67. package/test/api.js +83 -0
  68. package/test/real.js +133 -0
  69. package/test/test.js +28 -0
  70. package/test/util.js +80 -0
  71. package/tool/index.js +41 -31
  72. package/typings/api.d.ts +13 -0
  73. package/typings/array.d.ts +28 -0
  74. package/typings/event.d.ts +24 -0
  75. package/typings/index.d.ts +46 -4
  76. package/typings/node.d.ts +15 -9
  77. package/typings/parser.d.ts +7 -0
  78. package/typings/tool.d.ts +3 -2
  79. package/util/debug.js +21 -18
  80. package/util/string.js +40 -27
  81. 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
- /** @type {Parser} */ Parser = require('..'),
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: [string]|(string|ArgToken|TranscludeToken)[]}`
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
- parent = this.closest('ext, parameter');
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
- /** @complexity `n` */
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
- /** @type {Token & {firstChild: string}} */ token;
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(() => new Token(string, config).parseOnce(0, include).parseOnce());
64
- string = token.firstChild;
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).replace(/\x00\d+~\x7f/g, '=');
67
- const build = /** @param {string|boolean} str */ str =>
68
- typeof str === 'boolean' || !token ? str : token.buildFromStr(str).map(String).join('');
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*)))?/sg)
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.cloneChildren();
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 === 'attr') {
106
- return new Map(this.#attr);
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('\x00')) {
199
+ if (key.includes('\0')) {
116
200
  this.#attr.delete(key);
117
- key = this.buildFromStr(key).map(String).join('');
201
+ key = buildFromStr(key).map(String).join('');
118
202
  built = true;
119
203
  }
120
- if (typeof text === 'string' && text.includes('\x00')) {
121
- text = this.buildFromStr(text).map(String).join('');
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 that = this,
130
- /** @type {AstListener} */ attributeListener = ({type, target}) => {
131
- if (type === 'text' || target !== that) {
132
- that.#parseAttr();
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
- /** @param {string} key */
222
+ /**
223
+ * 标签是否具有某属性
224
+ * @param {string} key 属性键
225
+ */
140
226
  hasAttr(key) {
141
- if (typeof key !== 'string') {
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
- * @param {string} key
171
- * @param {string|boolean} value
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 = false) {
262
+ setAttr(key, value, init) {
175
263
  init &&= !externalUse('setAttr');
176
- if (typeof key !== 'string' || !['string', 'boolean'].includes(typeof value)) {
177
- this.typeError('setValue', 'String', 'Boolean');
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 !== 'ext-attr' && !init
185
- ? Parser.run(() => new Token(key, config).parseOnce(0, include).parseOnce().firstChild)
186
- : key;
187
- if (!/^(?:[\w:]|\x00\d+[t!~{}+-]\x7f)(?:[\w:.-]|\x00\d+[t!~{}+-]\x7f)*$/.test(parsedKey)) {
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.replace(/\s/g, ' ').trim());
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
- * @param {string} key
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
- * @param {string} key
219
- * @param {boolean|undefined} force
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 && !/^\s/.test(str) ? ' ' : '';
337
+ return this.type !== 'table-attr' && str && str.trimStart() === str ? ' ' : '';
238
338
  }
239
339
 
240
- /** @this {AttributeToken & Token} */
241
- toString() {
242
- const str = this.type === 'table-attr' ? normalizeSpace(this) : super.toString();
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
- let str = this.#updateFromAttr();
252
- str = `${this.#leadingSpace(str)}${str}`;
253
- return this.type === 'table-attr' ? normalizeSpace(str) : str;
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
- * @param {number} i
368
+ * @override
369
+ * @param {number} i 移除位置
370
+ * @param {boolean} done 是否已解析过改变后的标签属性
258
371
  * @complexity `n`
259
372
  */
260
- removeAt(i, done = false) {
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
- * @template {string|Token} T
272
- * @param {T} token
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
- * @param {...string|Token} elements
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
- /** @type {Parser} */ Parser = require('..'),
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
- * @param {string[]} flags
18
- * @param {string[]} rules
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
- if (rules.length) {
25
- const [firstRule] = rules,
26
- hasColon = firstRule.includes(':'),
27
- firstRuleToken = new ConverterRuleToken(firstRule, hasColon, config, accum);
28
- if (hasColon && firstRuleToken.childNodes.length === 1) {
29
- this.appendChild(new ConverterRuleToken(rules.join(';'), false, config, accum));
30
- } else {
31
- this.append(
32
- firstRuleToken,
33
- ...rules.slice(1).map(rule => new ConverterRuleToken(rule, true, config, accum)),
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.cloneChildren(),
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
- toString() {
49
- const [flags, ...rules] = this.children;
50
- return `-{${flags.toString()}${flags.childNodes.length ? '|' : ''}${rules.map(String).join(';')}}-`;
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
- /** @param {number} i */
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.children;
82
+ const {children: [flags, ...rules]} = this;
65
83
  return `-{${flags.text()}|${text(rules, ';')}}-`;
66
84
  }
67
85
 
68
- /** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
86
+ /**
87
+ * 获取所有转换类型标记
88
+ * @this {{firstChild: ConverterFlagsToken}}
89
+ */
69
90
  getAllFlags() {
70
91
  return this.firstChild.getAllFlags();
71
92
  }
72
93
 
73
- /** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
94
+ /**
95
+ * 获取有效的转换类型标记
96
+ * @this {{firstChild: ConverterFlagsToken}}
97
+ */
74
98
  getEffectiveFlags() {
75
99
  return this.firstChild.getEffectiveFlags();
76
100
  }
77
101
 
78
- /** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
102
+ /**
103
+ * 获取未知的转换类型标记
104
+ * @this {{firstChild: ConverterFlagsToken}}
105
+ */
79
106
  getUnknownFlags() {
80
107
  return this.firstChild.getUnknownFlags();
81
108
  }
82
109
 
83
110
  /**
84
- * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
85
- * @param {string} flag
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
- * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
93
- * @param {string} flag
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
- * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
101
- * @param {string} flag
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
- * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
109
- * @param {string} flag
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
- * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
117
- * @param {string} flag
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;