wikiparser-node 0.1.0 → 0.2.2

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.
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('..'),
4
+ Token = require('.'),
5
+ AtomToken = require('./atom');
6
+
7
+ /**
8
+ * 转换flags
9
+ * @classdesc `{childNodes: ...AtomToken}`
10
+ */
11
+ class ConverterFlagsToken extends Token {
12
+ type = 'converter-flags';
13
+ /** @type {string[]} */ #flags;
14
+
15
+ /**
16
+ * @param {string[]} flags
17
+ * @param {accum} accum
18
+ */
19
+ constructor(flags, config = Parser.getConfig(), accum = []) {
20
+ super(undefined, config, false, accum, {AtomToken: ':'});
21
+ this.append(...flags.map(flag => new AtomToken(flag, 'converter-flag', config, accum)));
22
+ }
23
+
24
+ cloneNode() {
25
+ const cloned = this.cloneChildren(),
26
+ token = Parser.run(() => new ConverterFlagsToken([], this.getAttribute('config')));
27
+ token.append(...cloned);
28
+ token.afterBuild();
29
+ return token;
30
+ }
31
+
32
+ /** @complexity `n` */
33
+ afterBuild() {
34
+ this.#flags = this.children.map(child => child.text().trim());
35
+ const that = this,
36
+ /** @type {AstListener} */ converterFlagsListener = ({prevTarget}) => {
37
+ if (prevTarget) {
38
+ that.#flags[that.childNodes.indexOf(prevTarget)] = prevTarget.text().trim();
39
+ }
40
+ };
41
+ this.addEventListener(['remove', 'insert', 'text', 'replace'], converterFlagsListener);
42
+ return this;
43
+ }
44
+
45
+ /**
46
+ * @template {string} T
47
+ * @param {T} key
48
+ * @returns {TokenAttribute<T>}
49
+ */
50
+ getAttribute(key) {
51
+ if (key === 'flags') {
52
+ return Parser.debugging ? this.#flags : [...this.#flags];
53
+ }
54
+ return super.getAttribute(key);
55
+ }
56
+
57
+ /**
58
+ * @param {number} i
59
+ * @complexity `n`
60
+ */
61
+ removeAt(i) {
62
+ const /** @type {AtomToken} */ token = super.removeAt(i);
63
+ this.#flags?.splice(i, 1);
64
+ return token;
65
+ }
66
+
67
+ /**
68
+ * @param {AtomToken} token
69
+ * @complexity `n`
70
+ */
71
+ insertAt(token, i = this.childNodes.length) {
72
+ super.insertAt(token, i);
73
+ this.#flags?.splice(i, 0, token.text());
74
+ return token;
75
+ }
76
+
77
+ toString() {
78
+ return super.toString(';');
79
+ }
80
+
81
+ getGaps() {
82
+ return 1;
83
+ }
84
+
85
+ text() {
86
+ return super.text(';');
87
+ }
88
+
89
+ /** @returns {[number, string][]} */
90
+ plain() {
91
+ return [];
92
+ }
93
+
94
+ /**
95
+ * @param {string} flag
96
+ * @returns {AtomToken[]}
97
+ * @complexity `n`
98
+ */
99
+ getFlagToken(flag) {
100
+ return this.#flags.includes(flag) ? this.children.filter(child => child.text().trim() === flag) : [];
101
+ }
102
+
103
+ getAllFlags() {
104
+ return new Set(this.#flags);
105
+ }
106
+
107
+ /** @complexity `n` */
108
+ getEffectiveFlags() {
109
+ const {variants} = this.getAttribute('config'),
110
+ variantFlags = this.#flags.filter(flag => variants.includes(flag)),
111
+ unknownFlags = this.#flags.filter(flag => /{{.+}}/.test(flag));
112
+ if (variantFlags.length) {
113
+ return new Set([...variantFlags, ...unknownFlags]);
114
+ }
115
+ const validFlags = ['A', 'T', 'R', 'D', '-', 'H', 'N'],
116
+ flags = new Set([...this.#flags.filter(flag => validFlags.includes(flag)), ...unknownFlags]);
117
+ if (flags.size === 0) {
118
+ return new Set('S');
119
+ } else if (flags.has('R')) {
120
+ return new Set('R');
121
+ } else if (flags.has('N')) {
122
+ return new Set('N');
123
+ } else if (flags.has('-')) {
124
+ return new Set('-');
125
+ } else if (flags.has('H')) {
126
+ const hasT = flags.has('T'),
127
+ hasD = flags.has('D');
128
+ if (hasT && hasD) {
129
+ return new Set(['+', 'H', 'T', 'D']);
130
+ }
131
+ return new Set(['+', 'H', ...hasT ? ['T'] : [], ...hasD ? ['D'] : [], ...unknownFlags]);
132
+ }
133
+ if (flags.size === 1 && flags.has('T')) {
134
+ flags.add('H');
135
+ }
136
+ if (flags.has('A')) {
137
+ flags.add('+');
138
+ flags.add('S');
139
+ }
140
+ if (flags.has('D')) {
141
+ flags.delete('S');
142
+ }
143
+ return flags;
144
+ }
145
+
146
+ /** @complexity `n` */
147
+ getUnknownFlags() {
148
+ return [...this.getFlags()].filter(flag => /{{.+}}/.test(flag));
149
+ }
150
+
151
+ /** @param {string} flag */
152
+ hasFlag(flag) {
153
+ if (typeof flag !== 'string') {
154
+ this.typeError('hasFlag', 'String');
155
+ }
156
+ return this.#flags.includes(flag);
157
+ }
158
+
159
+ /**
160
+ * @param {string} flag
161
+ * @complexity `n`
162
+ */
163
+ hasEffectiveFlag(flag) {
164
+ if (typeof flag !== 'string') {
165
+ this.typeError('hasFlag', 'String');
166
+ }
167
+ return this.getEffectiveFlags().has(flag);
168
+ }
169
+
170
+ /**
171
+ * @param {string} flag
172
+ * @complexity `n²`
173
+ */
174
+ removeFlag(flag) {
175
+ for (const token of this.getFlagToken(flag)) {
176
+ token.remove();
177
+ }
178
+ }
179
+
180
+ /**
181
+ * @param {string} flag
182
+ * @complexity `n`
183
+ */
184
+ #newFlag(flag) {
185
+ const token = Parser.run(() => new AtomToken(flag, 'converter-flag', this.getAttribute('config')));
186
+ this.appendChild(token);
187
+ }
188
+
189
+ /**
190
+ * @param {string} flag
191
+ * @complexity `n`
192
+ */
193
+ setFlag(flag) {
194
+ if (!this.#flags.includes(flag)) {
195
+ this.#newFlag(flag);
196
+ } else if (!this.getEffectiveFlags().has(flag)) {
197
+ Parser.error('此 flag 不会生效', flag);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * @param {string} flag
203
+ * @complexity `n²`
204
+ */
205
+ toggleFlag(flag) {
206
+ if (!this.#flags.includes(flag)) {
207
+ this.#newFlag(flag);
208
+ } else {
209
+ this.removeFlag(flag);
210
+ }
211
+ }
212
+ }
213
+
214
+ Parser.classes.ConverterFlagsToken = __filename;
215
+ module.exports = ConverterFlagsToken;
@@ -0,0 +1,210 @@
1
+ 'use strict';
2
+
3
+ const {undo} = require('../util/debug'),
4
+ {noWrap} = require('../util/string'),
5
+ /** @type {Parser} */ Parser = require('..'),
6
+ Token = require('.'),
7
+ AtomToken = require('./atom');
8
+
9
+ /**
10
+ * 转换规则
11
+ * @classdesc `{childNodes: ...AtomToken)}`
12
+ */
13
+ class ConverterRuleToken extends Token {
14
+ type = 'converter-rule';
15
+ variant = '';
16
+ unidirectional = false;
17
+ bidirectional = false;
18
+
19
+ /**
20
+ * @param {string} rule
21
+ * @param {accum} accum
22
+ */
23
+ constructor(rule, hasColon = true, config = Parser.getConfig(), accum = []) {
24
+ super(undefined, config, true, accum, {AtomToken: ':'});
25
+ if (!hasColon) {
26
+ super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
27
+ } else {
28
+ const i = rule.indexOf(':'),
29
+ j = rule.slice(0, i).indexOf('=>'),
30
+ v = (j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i)).trim(),
31
+ {variants} = config;
32
+ if (!variants.includes(v)) {
33
+ super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
34
+ } else {
35
+ super.insertAt(new AtomToken(v, 'converter-rule-variant', config, accum));
36
+ super.insertAt(new AtomToken(rule.slice(i + 1), 'converter-rule-to', config, accum));
37
+ if (j === -1) {
38
+ this.bidirectional = true;
39
+ } else {
40
+ super.insertAt(new AtomToken(rule.slice(0, j), 'converter-rule-from', config, accum), 0);
41
+ this.unidirectional = true;
42
+ }
43
+ }
44
+ }
45
+ this.seal(['variant', 'unidirectional', 'bidirectional']);
46
+ }
47
+
48
+ cloneNode() {
49
+ const cloned = this.cloneChildren(),
50
+ placeholders = ['', ':', '=>:'],
51
+ placeholder = placeholders[cloned.length - 1],
52
+ token = Parser.run(() => new ConverterRuleToken(placeholder, placeholder, this.getAttribute('config')));
53
+ for (let i = 0; i < cloned.length; i++) {
54
+ token.children[i].safeReplaceWith(cloned[i]);
55
+ }
56
+ token.afterBuild();
57
+ return token;
58
+ }
59
+
60
+ afterBuild() {
61
+ if (this.childNodes.length > 1) {
62
+ this.setAttribute('variant', this.children.at(-2).text().trim());
63
+ }
64
+ const that = this,
65
+ /** @type {AstListener} */ converterRuleListener = (e, data) => {
66
+ const {childNodes} = that,
67
+ {prevTarget} = e;
68
+ if (childNodes.length > 1 && childNodes.at(-2) === prevTarget) {
69
+ const v = prevTarget.text().trim(),
70
+ {variants} = that.getAttribute('config');
71
+ if (variants.includes(v)) {
72
+ that.setAttribute('variant', v);
73
+ } else {
74
+ undo(e, data);
75
+ throw new Error(`无效的语言变体:${v}`);
76
+ }
77
+ }
78
+ };
79
+ this.addEventListener(['remove', 'insert', 'text', 'replace'], converterRuleListener);
80
+ return this;
81
+ }
82
+
83
+ /**
84
+ * @param {number} i
85
+ * @returns {AtomToken}
86
+ */
87
+ removeAt(i) {
88
+ if (i !== 0 && i !== -this.childNodes.length) {
89
+ throw new RangeError(`${this.constructor.name} 禁止移除第 ${i} 个子节点!`);
90
+ }
91
+ return super.removeAt(i);
92
+ }
93
+
94
+ insertAt() {
95
+ throw new Error('转换规则语法复杂,请勿尝试手动插入子节点!');
96
+ }
97
+
98
+ /** @returns {string} */
99
+ toString() {
100
+ if (this.childNodes.length === 3) {
101
+ const [from, variant, to] = this.children;
102
+ return `${from.toString()}=>${variant.toString()}:${to.toString()}`;
103
+ }
104
+ return super.toString(':');
105
+ }
106
+
107
+ /** @param {number} i */
108
+ getGaps(i = 0) {
109
+ const {length} = this.childNodes;
110
+ i = i < 0 ? i + length : i;
111
+ return i === 0 && length === 3 ? 2 : 1;
112
+ }
113
+
114
+ /** @returns {string} */
115
+ text() {
116
+ if (this.childNodes.length === 3) {
117
+ const [from, variant, to] = this.children;
118
+ return `${from.text()}=>${variant.text()}:${to.text()}`;
119
+ }
120
+ return super.text(':');
121
+ }
122
+
123
+ /** @returns {[number, string][]} */
124
+ plain() {
125
+ const {firstElementChild, lastElementChild, unidirectional} = this;
126
+ if (unidirectional) {
127
+ return [...firstElementChild.plain(), ...lastElementChild.plain()];
128
+ }
129
+ return lastElementChild.plain();
130
+ }
131
+
132
+ noConvert() {
133
+ for (let i = this.childNodes.length - 2; i >= 0; i--) {
134
+ super.removeAt(i);
135
+ }
136
+ this.setAttribute('unidirectional', false).setAttribute('bidirectional', false).setAttribute('variant', '');
137
+ }
138
+
139
+ /** @param {string} to */
140
+ setTo(to) {
141
+ to = String(to);
142
+ const config = this.getAttribute('config'),
143
+ root = Parser.parse(`-{|${config.variants[0]}:${to}}-`, this.getAttribute('include'), undefined, config),
144
+ {childNodes: {length}, firstElementChild} = root;
145
+ if (length !== 1 || firstElementChild.type !== 'converter' || firstElementChild.childNodes.length !== 2
146
+ || firstElementChild.lastElementChild.childNodes.length !== 2
147
+ ) {
148
+ throw new SyntaxError(`非法的转换目标:${noWrap(to)}`);
149
+ }
150
+ const {lastChild} = firstElementChild.lastElementChild;
151
+ firstElementChild.lastElementChild.removeAt(0);
152
+ this.lastElementChild.safeReplaceWith(lastChild);
153
+ }
154
+
155
+ /** @param {string} variant */
156
+ setVariant(variant) {
157
+ if (typeof variant !== 'string') {
158
+ this.typeError('setVariant', 'String');
159
+ }
160
+ const config = this.getAttribute('config'),
161
+ v = variant.trim();
162
+ if (!config.variants.includes(v)) {
163
+ throw new RangeError(`无效的语言变体:${v}`);
164
+ } else if (this.childNodes.length === 1) {
165
+ super.insertAt(Parser.run(() => new AtomToken(variant, 'converter-rule-variant', config)), 0);
166
+ this.setAttribute('bidirectional', true);
167
+ } else {
168
+ this.children.at(-2).setText(variant);
169
+ }
170
+ this.setAttribute('variant', v);
171
+ }
172
+
173
+ /** @param {string} from */
174
+ setFrom(from) {
175
+ const {variant} = this;
176
+ if (!variant) {
177
+ throw new Error('请先指定语言变体!');
178
+ }
179
+ from = String(from);
180
+ const config = this.getAttribute('config'),
181
+ root = Parser.parse(`-{|${from}=>${variant}:}-`, this.getAttribute('include'), undefined, config),
182
+ {childNodes: {length}, firstElementChild} = root;
183
+ if (length !== 1 || firstElementChild.type !== 'converter' || firstElementChild.childNodes.length !== 2
184
+ || firstElementChild.lastElementChild.childNodes.length !== 3
185
+ ) {
186
+ throw new SyntaxError(`非法的转换原文:${noWrap(from)}`);
187
+ }
188
+ if (this.unidirectional) {
189
+ this.firstElementChild.safeReplaceWith(firstElementChild.lastElementChild.firstChild);
190
+ } else {
191
+ super.insertAt(firstElementChild.lastElementChild.firstChild, 0);
192
+ this.setAttribute('unidirectional', true).setAttribute('bidirectional', false);
193
+ }
194
+ }
195
+
196
+ /** @param {string} from */
197
+ makeUnidirectional(from) {
198
+ this.setFrom(from);
199
+ }
200
+
201
+ makeBidirectional() {
202
+ if (this.unidirectional) {
203
+ super.removeAt(0);
204
+ this.setAttribute('unidirectional', false).setAttribute('bidirectional', true);
205
+ }
206
+ }
207
+ }
208
+
209
+ Parser.classes.ConverterRuleToken = __filename;
210
+ module.exports = ConverterRuleToken;
package/src/extLink.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {noWrap} = require('../util/string'),
3
+ const {noWrap, normalizeSpace} = require('../util/string'),
4
4
  /** @type {Parser} */ Parser = require('..'),
5
5
  Token = require('.'),
6
6
  MagicLinkToken = require('./magicLink');
@@ -33,9 +33,9 @@ class ExtLinkToken extends Token {
33
33
  this.appendChild(new MagicLinkToken(url, true, config, accum));
34
34
  this.#space = space;
35
35
  if (text) {
36
- const inner = new Token(text, config, true, accum);
36
+ const inner = new Token(text, config, true, accum, {'Stage-7': ':', ConverterToken: ':'});
37
37
  inner.type = 'ext-link-text';
38
- this.appendChild(inner.setAttribute('stage', 8));
38
+ this.appendChild(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
39
39
  }
40
40
  this.protectChildren(0);
41
41
  }
@@ -50,9 +50,9 @@ class ExtLinkToken extends Token {
50
50
  }
51
51
 
52
52
  #correct() {
53
- if (!this.#space
53
+ if (!this.#space && this.childNodes.length > 1
54
54
  // 都替换成`<`肯定不对,但无妨
55
- && /^[^[\]<>"\x00-\x20\x7f\p{Zs}\ufffd]/u.test(this.children[1]?.text().replace(/&[lg]t;/, '<'))
55
+ && /^[^[\]<>"{\x00-\x20\x7f\p{Zs}\ufffd]/u.test(this.lastElementChild.text().replace(/&[lg]t;/, '<'))
56
56
  ) {
57
57
  this.#space = ' ';
58
58
  }
@@ -60,7 +60,7 @@ class ExtLinkToken extends Token {
60
60
 
61
61
  toString() {
62
62
  this.#correct();
63
- return `[${this.firstElementChild.toString()}${this.#space}${this.children[1]?.toString() ?? ''}]`;
63
+ return `[${this.firstElementChild.toString()}${this.#space}${normalizeSpace(this.children[1])}]`;
64
64
  }
65
65
 
66
66
  getPadding() {
@@ -74,7 +74,7 @@ class ExtLinkToken extends Token {
74
74
  }
75
75
 
76
76
  text() {
77
- return `[${super.text(' ')}]`;
77
+ return `[${super.text(' ').replaceAll('\n', ' ')}]`;
78
78
  }
79
79
 
80
80
  /**
@@ -82,7 +82,7 @@ class ExtLinkToken extends Token {
82
82
  * @complexity `n`
83
83
  */
84
84
  plain() {
85
- return this.childElementCount === 1 ? [] : this.lastElementChild.plain();
85
+ return this.childNodes.length === 1 ? [] : this.lastElementChild.plain();
86
86
  }
87
87
 
88
88
  /** @this {ExtLinkToken & {firstElementChild: MagicLinkToken}} */
@@ -95,7 +95,7 @@ class ExtLinkToken extends Token {
95
95
  url = String(url);
96
96
  const root = Parser.parse(`[${url}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
97
97
  {childNodes: {length}, firstElementChild} = root;
98
- if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childElementCount !== 1) {
98
+ if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childNodes.length !== 1) {
99
99
  throw new SyntaxError(`非法的外链目标:${url}`);
100
100
  }
101
101
  const {firstChild} = firstElementChild;
@@ -109,14 +109,14 @@ class ExtLinkToken extends Token {
109
109
  text = String(text);
110
110
  const root = Parser.parse(`[//url ${text}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
111
111
  {childNodes: {length}, firstElementChild} = root;
112
- if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childElementCount !== 2) {
112
+ if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childNodes.length !== 2) {
113
113
  throw new SyntaxError(`非法的外链文字:${noWrap(text)}`);
114
114
  }
115
115
  const {lastChild} = firstElementChild;
116
- if (this.childElementCount === 1) {
116
+ if (this.childNodes.length === 1) {
117
117
  this.appendChild(lastChild);
118
118
  } else {
119
- this.lastElementChild.replaceWith(lastChild);
119
+ this.lastElementChild.safeReplaceWith(lastChild);
120
120
  }
121
121
  this.#space ||= ' ';
122
122
  }
@@ -213,7 +213,7 @@ class ImageParameterToken extends Token {
213
213
  {childNodes: {length}, firstElementChild} = root,
214
214
  param = firstElementChild?.lastElementChild;
215
215
  if (length !== 1 || !firstElementChild?.matches('file#File:F')
216
- || firstElementChild.childElementCount !== 2 || param.name !== this.name
216
+ || firstElementChild.childNodes.length !== 2 || param.name !== this.name
217
217
  ) {
218
218
  throw new SyntaxError(`非法的 ${this.name} 参数:${noWrap(value)}`);
219
219
  }
package/src/index.js CHANGED
@@ -38,6 +38,7 @@
38
38
  * q: QuoteToken
39
39
  * w: ExtLinkToken
40
40
  * d: ListToken
41
+ * v: ConverterToken
41
42
  */
42
43
 
43
44
  const {externalUse} = require('../util/debug'),
@@ -50,11 +51,8 @@ const {externalUse} = require('../util/debug'),
50
51
  class Token extends AstElement {
51
52
  type = 'root';
52
53
  /** 解析阶段,参见顶部注释。只对plain Token有意义。 */ #stage = 0;
53
- /** @type {ParserConfig} */ #config;
54
- /**
55
- * 这个数组起两个作用:1. 数组中的Token会在build时替换`/\x00\d+.\x7f/`标记;2. 数组中的Token会依次执行parseOnce和build方法。
56
- * @type {accum}
57
- */
54
+ #config;
55
+ /** 这个数组起两个作用:1. 数组中的Token会在build时替换`/\x00\d+.\x7f/`标记;2. 数组中的Token会依次执行parseOnce和build方法。 */
58
56
  #accum;
59
57
  /** @type {Record<string, Ranges>} */ #acceptable;
60
58
  #protectedChildren = new Ranges();
@@ -70,7 +68,9 @@ class Token extends AstElement {
70
68
  if (typeof wikitext === 'string') {
71
69
  this.appendChild(halfParsed ? wikitext : wikitext.replace(/[\x00\x7f]/g, ''));
72
70
  }
73
- this.setAttribute('config', config).setAttribute('accum', accum).setAttribute('acceptable', acceptable);
71
+ this.#config = config;
72
+ this.#accum = accum;
73
+ this.setAttribute('acceptable', acceptable);
74
74
  accum.push(this);
75
75
  }
76
76
 
@@ -202,9 +202,13 @@ class Token extends AstElement {
202
202
  * @complexity `n`
203
203
  */
204
204
  removeAt(i) {
205
+ if (typeof i !== 'number') {
206
+ this.typeError('removeAt', 'Number');
207
+ }
208
+ const iPos = i < 0 ? i + this.childNodes.length : i;
205
209
  if (!Parser.running) {
206
210
  const protectedIndices = this.#protectedChildren.applyTo(this.childNodes);
207
- if (protectedIndices.includes(i)) {
211
+ if (protectedIndices.includes(iPos)) {
208
212
  throw new Error(`${this.constructor.name} 的第 ${i} 个子节点不可移除!`);
209
213
  } else if (this.#acceptable) {
210
214
  const acceptableIndices = Object.fromEntries(
@@ -450,6 +454,7 @@ class Token extends AstElement {
450
454
  this.#parseList();
451
455
  break;
452
456
  case 10:
457
+ this.#parseConverter();
453
458
  // no default
454
459
  }
455
460
  if (this.type === 'root') {
@@ -528,25 +533,21 @@ class Token extends AstElement {
528
533
  return n ? this.build().afterBuild() : this;
529
534
  }
530
535
 
531
- /** @this {Token & {firstChild: string}} */
532
536
  #parseCommentAndExt(includeOnly = false) {
533
537
  const parseCommentAndExt = require('../parser/commentAndExt');
534
538
  this.setText(parseCommentAndExt(this.firstChild, this.#config, this.#accum, includeOnly));
535
539
  }
536
540
 
537
- /** @this {Token & {firstChild: string}} */
538
541
  #parseBrackets() {
539
542
  const parseBrackets = require('../parser/brackets');
540
543
  this.setText(parseBrackets(this.firstChild, this.#config, this.#accum));
541
544
  }
542
545
 
543
- /** @this {Token & {firstChild: string}} */
544
546
  #parseHtml() {
545
547
  const parseHtml = require('../parser/html');
546
548
  this.setText(parseHtml(this.firstChild, this.#config, this.#accum));
547
549
  }
548
550
 
549
- /** @this {Token & {firstChild: string}} */
550
551
  #parseTable() {
551
552
  const parseTable = require('../parser/table'),
552
553
  TableToken = require('./table');
@@ -565,13 +566,11 @@ class Token extends AstElement {
565
566
  }
566
567
  }
567
568
 
568
- /** @this {Token & {firstChild: string}} */
569
569
  #parseHrAndDoubleUndescore() {
570
570
  const parseHrAndDoubleUnderscore = require('../parser/hrAndDoubleUnderscore');
571
571
  this.setText(parseHrAndDoubleUnderscore(this.firstChild, this.#config, this.#accum));
572
572
  }
573
573
 
574
- /** @this {Token & {firstChild: string}} */
575
574
  #parseLinks() {
576
575
  const parseLinks = require('../parser/links');
577
576
  this.setText(parseLinks(this.firstChild, this.#config, this.#accum));
@@ -587,26 +586,30 @@ class Token extends AstElement {
587
586
  this.setText(lines.join('\n'));
588
587
  }
589
588
 
590
- /** @this {Token & {firstChild: string}} */
591
589
  #parseExternalLinks() {
592
590
  const parseExternalLinks = require('../parser/externalLinks');
593
591
  this.setText(parseExternalLinks(this.firstChild, this.#config, this.#accum));
594
592
  }
595
593
 
596
- /** @this {Token & {firstChild: string}} */
597
594
  #parseMagicLinks() {
598
595
  const parseMagicLinks = require('../parser/magicLinks');
599
596
  this.setText(parseMagicLinks(this.firstChild, this.#config, this.#accum));
600
597
  }
601
598
 
599
+ /** @this {Token & {firstChild: string}} */
602
600
  #parseList() {
603
601
  const parseList = require('../parser/list'),
604
602
  lines = this.firstChild.split('\n');
605
- for (let i = 0; i < lines.length; i++) {
603
+ for (let i = this.type === 'root' ? 0 : 1; i < lines.length; i++) {
606
604
  lines[i] = parseList(lines[i], this.#config, this.#accum);
607
605
  }
608
606
  this.setText(lines.join('\n'));
609
607
  }
608
+
609
+ #parseConverter() {
610
+ const parseConverter = require('../parser/converter');
611
+ this.setText(parseConverter(this.firstChild, this.#config, this.#accum));
612
+ }
610
613
  }
611
614
 
612
615
  Parser.classes.Token = __filename;
@@ -37,6 +37,7 @@ class CategoryToken extends LinkToken {
37
37
  }
38
38
  };
39
39
  this.addEventListener(['remove', 'insert', 'replace', 'text'], categoryListener);
40
+ return this;
40
41
  }
41
42
 
42
43
  #updateSortkey() {
package/src/link/file.js CHANGED
@@ -84,7 +84,7 @@ class FileToken extends LinkToken {
84
84
  * @param {ImageParameterToken} token
85
85
  * @complexity `n`
86
86
  */
87
- insertAt(token, i = this.childElementCount) {
87
+ insertAt(token, i = this.childNodes.length) {
88
88
  if (!Parser.running) {
89
89
  this.getArgs(token.name, false, false).add(token);
90
90
  this.#keys.add(token.name);
@@ -221,7 +221,7 @@ class FileToken extends LinkToken {
221
221
  root = Parser.parse(wikitext, this.getAttribute('include'), 6, config),
222
222
  {childNodes: {length}, firstElementChild} = root;
223
223
  if (length !== 1 || !firstElementChild?.matches('file#File:F')
224
- || firstElementChild.childElementCount !== 2 || firstElementChild.lastElementChild.name !== key
224
+ || firstElementChild.childNodes.length !== 2 || firstElementChild.lastElementChild.name !== key
225
225
  ) {
226
226
  throw new SyntaxError(`非法的 ${key} 参数:${noWrap(value)}`);
227
227
  }