wikiparser-node 0.0.2 → 0.2.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 (49) hide show
  1. package/README.md +226 -7
  2. package/config/default.json +12 -1
  3. package/config/llwiki.json +12 -1
  4. package/config/moegirl.json +9 -1
  5. package/errors/2022-07-04T22:30:41.785Z +1 -0
  6. package/errors/2022-07-04T22:30:41.785Z.err +11 -0
  7. package/errors/2022-07-04T22:30:41.785Z.json +5 -0
  8. package/errors/README +1 -0
  9. package/index.js +85 -9
  10. package/lib/element.js +72 -13
  11. package/lib/node.js +17 -9
  12. package/mixin/sol.js +42 -0
  13. package/package.json +1 -1
  14. package/parser/converter.js +44 -0
  15. package/parser/externalLinks.js +1 -1
  16. package/parser/list.js +58 -0
  17. package/parser/table.js +2 -2
  18. package/printed/README +1 -0
  19. package/src/arg.js +9 -9
  20. package/src/attribute.js +33 -23
  21. package/src/converter.js +135 -0
  22. package/src/converterFlags.js +214 -0
  23. package/src/converterRule.js +209 -0
  24. package/src/extLink.js +23 -10
  25. package/src/heading.js +15 -20
  26. package/src/html.js +4 -3
  27. package/src/imageParameter.js +6 -7
  28. package/src/index.js +38 -23
  29. package/src/link/file.js +9 -9
  30. package/src/link/index.js +9 -11
  31. package/src/magicLink.js +2 -3
  32. package/src/nowiki/comment.js +1 -1
  33. package/src/nowiki/dd.js +49 -0
  34. package/src/nowiki/hr.js +3 -2
  35. package/src/nowiki/list.js +16 -0
  36. package/src/parameter.js +5 -5
  37. package/src/syntax.js +3 -1
  38. package/src/table/index.js +35 -30
  39. package/src/table/td.js +3 -2
  40. package/src/table/tr.js +6 -12
  41. package/src/tagPair/index.js +1 -1
  42. package/src/transclude.js +28 -25
  43. package/tool/index.js +50 -40
  44. package/typings/index.d.ts +3 -0
  45. package/typings/node.d.ts +3 -3
  46. package/typings/token.d.ts +1 -0
  47. package/util/debug.js +3 -3
  48. package/util/string.js +16 -1
  49. package/src/listToken.js +0 -47
package/lib/node.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {typeError, externalUse} = require('../util/debug'),
4
- {text} = require('../util/string'),
4
+ {text, noWrap} = require('../util/string'),
5
5
  assert = require('assert/strict'),
6
6
  /** @type {Parser} */ Parser = require('..');
7
7
 
@@ -34,6 +34,14 @@ class AstNode {
34
34
  throw new Error(`${this.constructor.name}.${method} 方法仅用于代码调试!`);
35
35
  }
36
36
 
37
+ /**
38
+ * @param {string} method
39
+ * @param {...string} types
40
+ */
41
+ typeError(method, ...types) {
42
+ return typeError(this.constructor, method, ...types);
43
+ }
44
+
37
45
  /** @param {string|string[]} keys */
38
46
  seal(keys) {
39
47
  if (!Parser.running && !Parser.debugging) {
@@ -68,7 +76,7 @@ class AstNode {
68
76
  /** @param {PropertyKey} key */
69
77
  hasAttribute(key) {
70
78
  if (!['string', 'number', 'symbol'].includes(typeof key)) {
71
- typeError(this, 'hasAttribute', 'String', 'Number', 'Symbol');
79
+ this.typeError('hasAttribute', 'String', 'Number', 'Symbol');
72
80
  }
73
81
  return key in this;
74
82
  }
@@ -142,7 +150,7 @@ class AstNode {
142
150
  */
143
151
  toggleAttribute(key, force) {
144
152
  if (force !== undefined && typeof force !== 'boolean') {
145
- typeError(this, 'toggleAttribute', 'Boolean');
153
+ this.typeError('toggleAttribute', 'Boolean');
146
154
  } else if (this.hasAttribute(key) && typeof this[key] !== 'boolean') {
147
155
  throw new RangeError(`${key} 属性的值不为 Boolean!`);
148
156
  }
@@ -175,7 +183,7 @@ class AstNode {
175
183
  */
176
184
  contains(node) {
177
185
  if (!(node instanceof AstNode)) {
178
- typeError(this, 'contains', 'Token');
186
+ this.typeError('contains', 'Token');
179
187
  }
180
188
  return node === this || this.childNodes.some(child => child instanceof AstNode && child.contains(node));
181
189
  }
@@ -185,7 +193,7 @@ class AstNode {
185
193
  if (!Parser.debugging && externalUse('verifyChild')) {
186
194
  this.debugOnly('verifyChild');
187
195
  } else if (typeof i !== 'number') {
188
- typeError(this, 'verifyChild', 'Number');
196
+ this.typeError('verifyChild', 'Number');
189
197
  }
190
198
  const {length} = this.childNodes;
191
199
  if (i < -length || i >= length + addition || !Number.isInteger(i)) {
@@ -216,7 +224,7 @@ class AstNode {
216
224
  Parser.error('找不到子节点!', node);
217
225
  throw new RangeError('找不到子节点!');
218
226
  } else if (typeof node === 'string' && childNodes.lastIndexOf(node) > i) {
219
- throw new RangeError(`重复的纯文本节点 ${node.replaceAll('\n', '\\n')}!`);
227
+ throw new RangeError(`重复的纯文本节点 ${noWrap(node)}!`);
220
228
  }
221
229
  return i;
222
230
  }
@@ -238,7 +246,7 @@ class AstNode {
238
246
  */
239
247
  insertAt(node, i = this.childNodes.length) {
240
248
  if (typeof node !== 'string' && !(node instanceof AstNode)) {
241
- typeError(this, 'insertAt', 'String', 'Token');
249
+ this.typeError('insertAt', 'String', 'Token');
242
250
  } else if (node instanceof AstNode && node.contains(this)) {
243
251
  Parser.error('不能插入祖先节点!', node);
244
252
  throw new RangeError('不能插入祖先节点!');
@@ -297,7 +305,7 @@ class AstNode {
297
305
  /** @param {string} str */
298
306
  setText(str, i = 0) {
299
307
  if (typeof str !== 'string') {
300
- typeError(this, 'setText', 'String');
308
+ this.typeError('setText', 'String');
301
309
  }
302
310
  this.verifyChild(i);
303
311
  const oldText = this.childNodes.at(i);
@@ -316,7 +324,7 @@ class AstNode {
316
324
  */
317
325
  splitText(i, offset) {
318
326
  if (typeof offset !== 'number') {
319
- typeError(this, 'splitText', 'Number');
327
+ this.typeError('splitText', 'Number');
320
328
  }
321
329
  this.verifyChild(i);
322
330
  const oldText = this.childNodes.at(i);
package/mixin/sol.js ADDED
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('..'),
4
+ Token = require('../src'); // eslint-disable-line no-unused-vars
5
+
6
+ /**
7
+ * @template T
8
+ * @param {T} constructor
9
+ * @returns {T}
10
+ */
11
+ const sol = constructor => class extends constructor {
12
+ /** @this {Token} */
13
+ prependNewLine() {
14
+ const {previousVisibleSibling = '', parentNode} = this;
15
+ return (previousVisibleSibling || parentNode?.type !== 'root') && !String(previousVisibleSibling).endsWith('\n')
16
+ ? '\n'
17
+ : '';
18
+ }
19
+
20
+ /** @this {Token} */
21
+ appendNewLine() {
22
+ const {nextVisibleSibling = '', parentNode} = this;
23
+ return (nextVisibleSibling || parentNode?.type !== 'root') && !String(nextVisibleSibling ?? '').startsWith('\n')
24
+ ? '\n'
25
+ : '';
26
+ }
27
+
28
+ toString(ownLine = false) {
29
+ return `${this.prependNewLine()}${super.toString()}${ownLine ? this.appendNewLine() : ''}`;
30
+ }
31
+
32
+ getPadding() {
33
+ return this.prependNewLine().length;
34
+ }
35
+
36
+ text(ownLine = false) {
37
+ return `${this.prependNewLine()}${super.text()}${ownLine ? this.appendNewLine() : ''}`;
38
+ }
39
+ };
40
+
41
+ Parser.mixins.sol = __filename;
42
+ module.exports = sol;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikiparser-node",
3
- "version": "0.0.2",
3
+ "version": "0.2.0",
4
4
  "description": "A Node.js parser for MediaWiki markup with AST",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('..');
4
+
5
+ /**
6
+ * @param {string} firstChild
7
+ * @param {accum} accum
8
+ */
9
+ const parseConverter = (firstChild, config = Parser.getConfig(), accum = []) => {
10
+ const ConverterToken = require('../src/converter'),
11
+ regex1 = /-{/g,
12
+ regex2 = /-{|}-/g,
13
+ /** @type {RegExpExecArray[]} */ stack = [];
14
+ let regex = regex1,
15
+ mt = regex.exec(firstChild);
16
+ while (mt) {
17
+ const {0: syntax, index} = mt;
18
+ if (syntax === '}-') {
19
+ const top = stack.pop(),
20
+ {length} = accum,
21
+ str = firstChild.slice(top.index + 2, index),
22
+ i = str.indexOf('|'),
23
+ [flags, text] = i === -1 ? [[], str] : [str.slice(0, i).split(';'), str.slice(i + 1)],
24
+ temp = text.replace(/(?<=&[#a-z0-9]+);/i, '\x01'),
25
+ variants = `(?:${config.variants.join('|')})`,
26
+ rules = temp.split(new RegExp(`;(?=\\s*(?:${variants}|[^;]*?=>\\s*${variants})\\s*:)`))
27
+ .map(rule => rule.replaceAll('\x01', ';'));
28
+ new ConverterToken(flags, rules, config, accum);
29
+ firstChild = `${firstChild.slice(0, top.index)}\x00${length}v\x7f${firstChild.slice(index + 2)}`;
30
+ if (stack.length === 0) {
31
+ regex = regex1;
32
+ }
33
+ regex.lastIndex = top.index + 3 + String(length).length;
34
+ } else {
35
+ stack.push(mt);
36
+ regex = regex2;
37
+ }
38
+ mt = regex.exec(firstChild);
39
+ }
40
+ return firstChild;
41
+ };
42
+
43
+ Parser.parsers.parseConverter = __filename;
44
+ module.exports = parseConverter;
@@ -10,7 +10,7 @@ const {extUrlChar} = require('../util/string'),
10
10
  const parseExternalLinks = (firstChild, config = Parser.getConfig(), accum = []) => {
11
11
  const ExtLinkToken = require('../src/extLink'),
12
12
  regex = new RegExp(
13
- `\\[((?:${config.protocol}|//)${extUrlChar})(\\p{Zs}*)([^\\]\\x01-\\x08\\x0a-\\x1f\\ufffd]*)\\]`,
13
+ `\\[((?:${config.protocol}|//)${extUrlChar})(\\p{Zs}*)([^\\]\x01-\x08\x0a-\x1f\ufffd]*)\\]`,
14
14
  'gui',
15
15
  );
16
16
  return firstChild.replace(regex, /** @type {function(...string): string} */ (_, url, space, text) => {
package/parser/list.js ADDED
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('..');
4
+
5
+ /**
6
+ * @param {string} text
7
+ * @param {accum} accum
8
+ */
9
+ const parseList = (text, config = Parser.getConfig(), accum = []) => {
10
+ const mt = text.match(/^(?:[;:*#]|\x00\d+c\x7f)*[;:*#]/);
11
+ if (!mt) {
12
+ return text;
13
+ }
14
+ const ListToken = require('../src/nowiki/list'),
15
+ [prefix] = mt;
16
+ text = `\x00${accum.length}d\x7f${text.slice(prefix.length)}`;
17
+ new ListToken(prefix, config, accum);
18
+ let dt = prefix.split(';').length - 1;
19
+ if (!dt) {
20
+ return text;
21
+ }
22
+ const DdToken = require('../src/nowiki/dd');
23
+ let regex = /:+|-{/g,
24
+ ex = regex.exec(text),
25
+ lc = 0;
26
+ while (ex && dt) {
27
+ const {0: syntax, index} = ex;
28
+ if (syntax[0] === ':') {
29
+ if (syntax.length >= dt) {
30
+ new DdToken(':'.repeat(dt), config, accum);
31
+ return `${text.slice(0, index)}\x00${accum.length - 1}d\x7f${text.slice(index + dt)}`;
32
+ }
33
+ text = `${text.slice(0, index)}\x00${accum.length}d\x7f${text.slice(regex.lastIndex)}`;
34
+ dt -= syntax.length;
35
+ regex.lastIndex = index + 4 + String(accum.length).length;
36
+ new DdToken(syntax, config, accum);
37
+ } else if (syntax === '-{') {
38
+ if (!lc) {
39
+ const {lastIndex} = regex;
40
+ regex = /-{|}-/g;
41
+ regex.lastIndex = lastIndex;
42
+ }
43
+ lc++;
44
+ } else {
45
+ lc--;
46
+ if (!lc) {
47
+ const {lastIndex} = regex;
48
+ regex = /:+|-{/g;
49
+ regex.lastIndex = lastIndex;
50
+ }
51
+ }
52
+ ex = regex.exec(text);
53
+ }
54
+ return text;
55
+ };
56
+
57
+ Parser.parsers.parseList = __filename;
58
+ module.exports = parseList;
package/parser/table.js CHANGED
@@ -54,10 +54,10 @@ const parseTable = ({firstChild, type}, config = Parser.getConfig(), accum = [])
54
54
  }
55
55
  const [, closing, row, cell, attr] = matches;
56
56
  if (closing) {
57
- while (top.type !== 'table') {
57
+ while (!(top instanceof TableToken)) {
58
58
  top = stack.pop();
59
59
  }
60
- top.close(`\n${spaces}${closing}`);
60
+ top.close(`\n${spaces}${closing}`, true);
61
61
  push(attr, stack.at(-1));
62
62
  } else if (row) {
63
63
  if (top.type === 'td') {
package/printed/README ADDED
@@ -0,0 +1 @@
1
+ 这里存放以 JSON 格式打印的 AST。
package/src/arg.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {text} = require('../util/string'),
3
+ const {text, noWrap} = require('../util/string'),
4
4
  /** @type {Parser} */ Parser = require('..'),
5
5
  Token = require('.');
6
6
 
@@ -75,13 +75,13 @@ class ArgToken extends Token {
75
75
 
76
76
  /** @returns {[number, string][]} */
77
77
  plain() {
78
- return this.childElementCount > 1 ? this.children[1].plain() : [];
78
+ return this.childNodes.length > 1 ? this.children[1].plain() : [];
79
79
  }
80
80
 
81
81
  /** @complexity `n` */
82
82
  removeRedundant() {
83
83
  Parser.run(() => {
84
- for (let i = this.childElementCount - 1; i > 1; i--) {
84
+ for (let i = this.childNodes.length - 1; i > 1; i--) {
85
85
  super.removeAt(i);
86
86
  }
87
87
  });
@@ -100,8 +100,8 @@ class ArgToken extends Token {
100
100
  }
101
101
 
102
102
  /** @param {Token} token */
103
- insertAt(token, i = this.childElementCount) {
104
- const j = i < 0 ? i + this.childElementCount : i;
103
+ insertAt(token, i = this.childNodes.length) {
104
+ const j = i < 0 ? i + this.childNodes.length : i;
105
105
  if (j > 1 && !Parser.running) {
106
106
  throw new RangeError(`${this.constructor.name} 不可插入 arg-redundant 子节点!`);
107
107
  }
@@ -117,8 +117,8 @@ class ArgToken extends Token {
117
117
  name = String(name);
118
118
  const root = Parser.parse(`{{{${name}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
119
119
  {childNodes: {length}, firstElementChild} = root;
120
- if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childElementCount !== 1) {
121
- throw new SyntaxError(`非法的参数名称:${name.replaceAll('\n', '\\n')}`);
120
+ if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childNodes.length !== 1) {
121
+ throw new SyntaxError(`非法的参数名称:${noWrap(name)}`);
122
122
  }
123
123
  const newName = firstElementChild.firstElementChild;
124
124
  root.destroy();
@@ -131,8 +131,8 @@ class ArgToken extends Token {
131
131
  value = String(value);
132
132
  const root = Parser.parse(`{{{|${value}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
133
133
  {childNodes: {length}, firstElementChild} = root;
134
- if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childElementCount !== 2) {
135
- throw new SyntaxError(`非法的参数预设值:${value.replaceAll('\n', '\\n')}`);
134
+ if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childNodes.length !== 2) {
135
+ throw new SyntaxError(`非法的参数预设值:${noWrap(value)}`);
136
136
  }
137
137
  const [, oldDefault] = this.children,
138
138
  newDefault = firstElementChild.lastElementChild;
package/src/attribute.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const {typeError, externalUse} = require('../util/debug'),
4
- {toCase, removeComment} = require('../util/string'),
3
+ const {externalUse} = require('../util/debug'),
4
+ {toCase, removeComment, normalizeSpace} = require('../util/string'),
5
5
  /** @type {Parser} */ Parser = require('..'),
6
6
  Token = require('.');
7
7
 
@@ -28,14 +28,13 @@ class AttributeToken extends Token {
28
28
  ) {
29
29
  equal = '{{=}}';
30
30
  }
31
- const str = [...this.#attr].map(([k, v]) => {
31
+ return [...this.#attr].map(([k, v]) => {
32
32
  if (v === true) {
33
33
  return k;
34
34
  }
35
35
  const quote = v.includes('"') ? "'" : '"';
36
36
  return `${k}${equal}${quote}${v}${quote}`;
37
37
  }).join(' ');
38
- return str && ` ${str}`;
39
38
  }
40
39
 
41
40
  /** @complexity `n` */
@@ -56,17 +55,17 @@ class AttributeToken extends Token {
56
55
  */
57
56
  #parseAttr() {
58
57
  this.#attr.clear();
59
- const config = this.getAttribute('config'),
60
- include = this.getAttribute('include'),
61
- /** @type {Token & {firstChild: string}} */ token = this.type !== 'ext-attr' && !Parser.running
62
- ? Parser.run(() => new Token(string, config).parseOnce(0, include).parseOnce())
63
- : undefined,
64
- string = removeComment(token?.firstChild ?? this.toString()).replace(/\x00\d+~\x7f/g, '=');
65
- const build = /** @param {string|boolean} str */ str => {
66
- return typeof str === 'boolean' || !(token instanceof Token)
67
- ? str
68
- : token.buildFromStr(str).map(String).join('');
69
- };
58
+ let string = this.toString(),
59
+ /** @type {Token & {firstChild: string}} */ token;
60
+ if (this.type !== 'ext-attr' && !Parser.running) {
61
+ const config = this.getAttribute('config'),
62
+ include = this.getAttribute('include');
63
+ token = Parser.run(() => new Token(string, config).parseOnce(0, include).parseOnce());
64
+ string = token.firstChild;
65
+ }
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('');
70
69
  for (const [, key,, quoted, unquoted] of string
71
70
  .matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(?:\2|$)|(\S*)))?/sg)
72
71
  ) {
@@ -147,7 +146,7 @@ class AttributeToken extends Token {
147
146
  /** @param {string} key */
148
147
  hasAttr(key) {
149
148
  if (typeof key !== 'string') {
150
- typeError(this, 'hasAttr', 'String');
149
+ this.typeError('hasAttr', 'String');
151
150
  }
152
151
  return this.#attr.has(key.toLowerCase().trim());
153
152
  }
@@ -161,7 +160,7 @@ class AttributeToken extends Token {
161
160
  if (key === undefined) {
162
161
  return Object.fromEntries(this.#attr);
163
162
  } else if (typeof key !== 'string') {
164
- typeError(this, 'getAttr', 'String');
163
+ this.typeError('getAttr', 'String');
165
164
  }
166
165
  return this.#attr.get(key.toLowerCase().trim());
167
166
  }
@@ -182,7 +181,7 @@ class AttributeToken extends Token {
182
181
  setAttr(key, value, init = false) {
183
182
  init &&= !externalUse('setAttr');
184
183
  if (typeof key !== 'string' || !['string', 'boolean'].includes(typeof value)) {
185
- typeError(this, 'setValue', 'String', 'Boolean');
184
+ this.typeError('setValue', 'String', 'Boolean');
186
185
  } else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
187
186
  throw new RangeError('扩展标签属性不能包含 ">"!');
188
187
  }
@@ -214,7 +213,7 @@ class AttributeToken extends Token {
214
213
  */
215
214
  removeAttr(key) {
216
215
  if (typeof key !== 'string') {
217
- typeError(this, 'removeAttr', 'String');
216
+ this.typeError('removeAttr', 'String');
218
217
  }
219
218
  key = key.toLowerCase().trim();
220
219
  if (this.#attr.delete(key)) {
@@ -229,7 +228,7 @@ class AttributeToken extends Token {
229
228
  */
230
229
  toggleAttr(key, force) {
231
230
  if (typeof key !== 'string') {
232
- typeError(this, 'toggleAttr', 'String');
231
+ this.typeError('toggleAttr', 'String');
233
232
  } else if (force !== undefined) {
234
233
  force = Boolean(force);
235
234
  }
@@ -241,13 +240,24 @@ class AttributeToken extends Token {
241
240
  this.setAttr(key, force === true || force === undefined && value === false);
242
241
  }
243
242
 
243
+ #leadingSpace(str = super.toString()) {
244
+ return this.type !== 'table-attr' && str && !/^\s/.test(str) ? ' ' : '';
245
+ }
246
+
247
+ /** @this {AttributeToken & Token} */
244
248
  toString() {
245
- const str = super.toString();
246
- return this.type === 'table-attr' ? str.replaceAll('\n', ' ') : str;
249
+ const str = this.type === 'table-attr' ? normalizeSpace(this) : super.toString();
250
+ return `${this.#leadingSpace(str)}${str}`;
251
+ }
252
+
253
+ getPadding() {
254
+ return this.#leadingSpace().length;
247
255
  }
248
256
 
249
257
  text() {
250
- return this.#updateFromAttr();
258
+ let str = this.#updateFromAttr();
259
+ str = `${this.#leadingSpace(str)}${str}`;
260
+ return this.type === 'table-attr' ? normalizeSpace(str) : str;
251
261
  }
252
262
 
253
263
  /** @returns {[number, string][]} */
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ const {text} = require('../util/string'),
4
+ /** @type {Parser} */ Parser = require('..'),
5
+ Token = require('.'),
6
+ ConverterFlagsToken = require('./converterFlags'),
7
+ ConverterRuleToken = require('./converterRule');
8
+
9
+ /**
10
+ * 转换
11
+ * @classdesc `{childNodes: [ConverterFlagsToken, ...ConverterRuleToken]}`
12
+ */
13
+ class ConverterToken extends Token {
14
+ type = 'converter';
15
+
16
+ /**
17
+ * @param {string[]} flags
18
+ * @param {string[]} rules
19
+ * @param {accum} accum
20
+ */
21
+ constructor(flags, rules, config = Parser.getConfig(), accum = []) {
22
+ super(undefined, config, false, accum);
23
+ 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
+ }
36
+ }
37
+ this.protectChildren(0);
38
+ }
39
+
40
+ cloneNode() {
41
+ const [flags, ...rules] = this.cloneChildren(),
42
+ token = Parser.run(() => new ConverterToken([], [], this.getAttribute('config')));
43
+ token.firstElementChild.safeReplaceWith(flags);
44
+ token.append(...rules);
45
+ return token;
46
+ }
47
+
48
+ toString() {
49
+ const [flags, ...rules] = this.children;
50
+ return `-{${flags.toString()}${flags.childNodes.length ? '|' : ''}${rules.map(String).join(';')}}-`;
51
+ }
52
+
53
+ getPadding() {
54
+ return 2;
55
+ }
56
+
57
+ /** @param {number} i */
58
+ getGaps(i = 0) {
59
+ i = i < 0 ? i + this.childNodes.length : i;
60
+ return i || this.firstElementChild.childNodes.length ? 1 : 0;
61
+ }
62
+
63
+ text() {
64
+ const [flags, ...rules] = this.children;
65
+ return `-{${flags.text()}|${text(rules, ';')}}-`;
66
+ }
67
+
68
+ /** @returns {[number, string][]} */
69
+ plain() {
70
+ return this.children.slice(1).flatMap(child => child.plain());
71
+ }
72
+
73
+ /** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
74
+ getAllFlags() {
75
+ return this.firstChild.getAllFlags();
76
+ }
77
+
78
+ /** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
79
+ getEffectiveFlags() {
80
+ return this.firstChild.getEffectiveFlags();
81
+ }
82
+
83
+ /** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
84
+ getUnknownFlags() {
85
+ return this.firstChild.getUnknownFlags();
86
+ }
87
+
88
+ /**
89
+ * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
90
+ * @param {string} flag
91
+ */
92
+ hasFlag(flag) {
93
+ return this.firstChild.hasFlag(flag);
94
+ }
95
+
96
+ /**
97
+ * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
98
+ * @param {string} flag
99
+ */
100
+ hasEffectiveFlag(flag) {
101
+ return this.firstChild.hasEffectiveFlag(flag);
102
+ }
103
+
104
+ /**
105
+ * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
106
+ * @param {string} flag
107
+ */
108
+ removeFlag(flag) {
109
+ this.firstChild.removeFlag(flag);
110
+ }
111
+
112
+ /**
113
+ * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
114
+ * @param {string} flag
115
+ */
116
+ setFlag(flag) {
117
+ this.firstChild.setFlag(flag);
118
+ }
119
+
120
+ /**
121
+ * @this {ConverterToken & {firstChild: ConverterFlagsToken}}
122
+ * @param {string} flag
123
+ */
124
+ toggleFlag(flag) {
125
+ this.firstChild.toggleFlag(flag);
126
+ }
127
+
128
+ /** @this {ConverterToken & {children: [ConverterFlagsToken, ConverterRuleToken]}} */
129
+ get noConvert() {
130
+ return this.childNodes.length < 3 && !this.children[1]?.variant;
131
+ }
132
+ }
133
+
134
+ Parser.classes.ConverterToken = __filename;
135
+ module.exports = ConverterToken;