wikiparser-node 0.9.2-b → 0.10.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 (90) hide show
  1. package/README.md +40 -0
  2. package/config/.schema.json +134 -0
  3. package/config/default.json +832 -0
  4. package/config/llwiki.json +630 -0
  5. package/config/moegirl.json +729 -0
  6. package/config/zhwiki.json +1269 -0
  7. package/i18n/zh-hans.json +1 -0
  8. package/i18n/zh-hant.json +1 -0
  9. package/index.js +333 -0
  10. package/lib/element.js +611 -0
  11. package/lib/node.js +770 -0
  12. package/lib/ranges.js +130 -0
  13. package/lib/text.js +263 -0
  14. package/lib/title.js +83 -0
  15. package/mixin/attributeParent.js +117 -0
  16. package/mixin/fixedToken.js +40 -0
  17. package/mixin/hidden.js +21 -0
  18. package/mixin/singleLine.js +31 -0
  19. package/mixin/sol.js +54 -0
  20. package/package.json +49 -47
  21. package/parser/brackets.js +126 -0
  22. package/parser/commentAndExt.js +59 -0
  23. package/parser/converter.js +46 -0
  24. package/parser/externalLinks.js +33 -0
  25. package/parser/hrAndDoubleUnderscore.js +49 -0
  26. package/parser/html.js +42 -0
  27. package/parser/links.js +94 -0
  28. package/parser/list.js +59 -0
  29. package/parser/magicLinks.js +41 -0
  30. package/parser/quotes.js +64 -0
  31. package/parser/selector.js +177 -0
  32. package/parser/table.js +114 -0
  33. package/src/arg.js +207 -0
  34. package/src/atom/hidden.js +13 -0
  35. package/src/atom/index.js +43 -0
  36. package/src/attribute.js +470 -0
  37. package/src/attributes.js +453 -0
  38. package/src/charinsert.js +97 -0
  39. package/src/converter.js +176 -0
  40. package/src/converterFlags.js +284 -0
  41. package/src/converterRule.js +256 -0
  42. package/src/extLink.js +180 -0
  43. package/src/gallery.js +149 -0
  44. package/src/hasNowiki/index.js +44 -0
  45. package/src/hasNowiki/pre.js +40 -0
  46. package/src/heading.js +134 -0
  47. package/src/html.js +254 -0
  48. package/src/imageParameter.js +303 -0
  49. package/src/imagemap.js +199 -0
  50. package/src/imagemapLink.js +41 -0
  51. package/src/index.js +932 -0
  52. package/src/link/category.js +44 -0
  53. package/src/link/file.js +287 -0
  54. package/src/link/galleryImage.js +120 -0
  55. package/src/link/index.js +388 -0
  56. package/src/magicLink.js +149 -0
  57. package/src/nested/choose.js +24 -0
  58. package/src/nested/combobox.js +23 -0
  59. package/src/nested/index.js +93 -0
  60. package/src/nested/references.js +23 -0
  61. package/src/nowiki/comment.js +71 -0
  62. package/src/nowiki/dd.js +59 -0
  63. package/src/nowiki/doubleUnderscore.js +56 -0
  64. package/src/nowiki/hr.js +41 -0
  65. package/src/nowiki/index.js +56 -0
  66. package/src/nowiki/list.js +16 -0
  67. package/src/nowiki/noinclude.js +28 -0
  68. package/src/nowiki/quote.js +69 -0
  69. package/src/onlyinclude.js +64 -0
  70. package/src/paramTag/index.js +89 -0
  71. package/src/paramTag/inputbox.js +35 -0
  72. package/src/parameter.js +239 -0
  73. package/src/syntax.js +91 -0
  74. package/src/table/index.js +983 -0
  75. package/src/table/td.js +338 -0
  76. package/src/table/tr.js +319 -0
  77. package/src/tagPair/ext.js +145 -0
  78. package/src/tagPair/include.js +50 -0
  79. package/src/tagPair/index.js +126 -0
  80. package/src/transclude.js +843 -0
  81. package/tool/index.js +1202 -0
  82. package/util/base.js +17 -0
  83. package/util/debug.js +73 -0
  84. package/util/diff.js +76 -0
  85. package/util/lint.js +55 -0
  86. package/util/string.js +126 -0
  87. package/bundle/bundle.min.js +0 -38
  88. package/extensions/editor.css +0 -62
  89. package/extensions/editor.js +0 -328
  90. package/extensions/ui.css +0 -119
package/src/html.js ADDED
@@ -0,0 +1,254 @@
1
+ 'use strict';
2
+
3
+ const {generateForSelf} = require('../util/lint'),
4
+ {noWrap} = require('../util/string'),
5
+ fixedToken = require('../mixin/fixedToken'),
6
+ attributeParent = require('../mixin/attributeParent'),
7
+ Parser = require('..'),
8
+ Token = require('.');
9
+
10
+ const magicWords = new Set(['if', 'ifeq', 'ifexpr', 'ifexist', 'iferror', 'switch']);
11
+
12
+ /**
13
+ * HTML标签
14
+ * @classdesc `{childNodes: [AttributesToken]}`
15
+ */
16
+ class HtmlToken extends attributeParent(fixedToken(Token)) {
17
+ type = 'html';
18
+ #closing;
19
+ #selfClosing;
20
+ #tag;
21
+
22
+ /** getter */
23
+ get closing() {
24
+ return this.#closing;
25
+ }
26
+
27
+ /** @throws `Error` 自闭合标签或空标签 */
28
+ set closing(value) {
29
+ if (!value) {
30
+ this.#closing = false;
31
+ return;
32
+ } else if (this.#selfClosing) {
33
+ throw new Error('这是一个自闭合标签!');
34
+ }
35
+ const {html: [,, tags]} = this.getAttribute('config');
36
+ if (tags.includes(this.name)) {
37
+ throw new Error('这是一个空标签!');
38
+ }
39
+ this.#closing = true;
40
+ }
41
+
42
+ /** getter */
43
+ get selfClosing() {
44
+ return this.#selfClosing;
45
+ }
46
+
47
+ /** @throws `Error` 闭合标签或无效自闭合标签 */
48
+ set selfClosing(value) {
49
+ if (!value) {
50
+ this.#selfClosing = false;
51
+ return;
52
+ } else if (this.#closing) {
53
+ throw new Error('这是一个闭合标签!');
54
+ }
55
+ const {html: [tags]} = this.getAttribute('config');
56
+ if (tags.includes(this.name)) {
57
+ throw new Error(`<${this.name}>标签自闭合无效!`);
58
+ }
59
+ this.#selfClosing = true;
60
+ }
61
+
62
+ /**
63
+ * @param {string} name 标签名
64
+ * @param {AttributesToken} attr 标签属性
65
+ * @param {boolean} closing 是否闭合
66
+ * @param {boolean} selfClosing 是否自封闭
67
+ * @param {accum} accum
68
+ */
69
+ constructor(name, attr, closing, selfClosing, config = Parser.getConfig(), accum = []) {
70
+ super(undefined, config, true, accum);
71
+ this.insertAt(attr);
72
+ this.setAttribute('name', name.toLowerCase());
73
+ this.#closing = closing;
74
+ this.#selfClosing = selfClosing;
75
+ this.#tag = name;
76
+ }
77
+
78
+ /**
79
+ * @override
80
+ * @param {string} selector
81
+ */
82
+ toString(selector) {
83
+ return selector && this.matches(selector)
84
+ ? ''
85
+ : `<${this.#closing ? '/' : ''}${this.#tag}${super.toString(selector)}${this.#selfClosing ? '/' : ''}>`;
86
+ }
87
+
88
+ /** @override */
89
+ text() {
90
+ return `<${this.#closing ? '/' : ''}${this.#tag}${
91
+ this.#closing ? '' : super.text()
92
+ }${this.#selfClosing ? '/' : ''}>`;
93
+ }
94
+
95
+ /** @override */
96
+ getPadding() {
97
+ return this.#tag.length + (this.#closing ? 2 : 1);
98
+ }
99
+
100
+ /** @override */
101
+ print() {
102
+ return super.print({
103
+ pre: `&lt;${this.#closing ? '/' : ''}${this.#tag}`,
104
+ post: `${this.#selfClosing ? '/' : ''}&gt;`,
105
+ });
106
+ }
107
+
108
+ /**
109
+ * @override
110
+ * @param {number} start 起始位置
111
+ */
112
+ lint(start = this.getAbsoluteIndex()) {
113
+ const errors = super.lint(start);
114
+ let wikitext, /** @type {LintError} */ refError;
115
+ if (this.name === 'h1' && !this.#closing) {
116
+ wikitext = String(this.getRootNode());
117
+ refError = generateForSelf(this, {start}, '<h1>');
118
+ errors.push({...refError, excerpt: wikitext.slice(start, start + 50)});
119
+ }
120
+ if (this.closest('table-attrs')) {
121
+ wikitext ||= String(this.getRootNode());
122
+ refError ||= generateForSelf(this, {start}, '');
123
+ const excerpt = wikitext.slice(Math.max(0, start - 25), start + 25);
124
+ errors.push({...refError, message: Parser.msg('HTML tag in table attributes'), excerpt});
125
+ }
126
+ try {
127
+ this.findMatchingTag();
128
+ } catch ({message: errorMsg}) {
129
+ wikitext ||= String(this.getRootNode());
130
+ refError ||= generateForSelf(this, {start}, '');
131
+ const [msg] = errorMsg.split(':'),
132
+ error = {...refError, message: Parser.msg(msg)};
133
+ if (msg === 'unclosed tag') {
134
+ error.severity = 'warning';
135
+ error.excerpt = wikitext.slice(start, start + 50);
136
+ } else if (msg === 'unmatched closing tag') {
137
+ const end = start + String(this).length;
138
+ error.excerpt = wikitext.slice(Math.max(0, end - 50), end);
139
+ if (magicWords.has(this.closest('magic-word')?.name)) {
140
+ error.severity = 'warning';
141
+ }
142
+ }
143
+ errors.push(error);
144
+ }
145
+ return errors;
146
+ }
147
+
148
+ /**
149
+ * 搜索匹配的标签
150
+ * @complexity `n`
151
+ * @throws `SyntaxError` 同时闭合和自封闭的标签
152
+ * @throws `SyntaxError` 无效自封闭标签
153
+ * @throws `SyntaxError` 未闭合的标签
154
+ */
155
+ findMatchingTag() {
156
+ const {html} = this.getAttribute('config'),
157
+ {name: tagName, parentNode} = this,
158
+ string = noWrap(String(this));
159
+ if (this.#closing && (this.#selfClosing || html[2].includes(tagName))) {
160
+ throw new SyntaxError(`tag that is both closing and self-closing: ${string}`);
161
+ } else if (html[2].includes(tagName) || this.#selfClosing && html[1].includes(tagName)) { // 自封闭标签
162
+ return this;
163
+ } else if (this.#selfClosing && html[0].includes(tagName)) {
164
+ throw new SyntaxError(`invalid self-closing tag: ${string}`);
165
+ } else if (!parentNode) {
166
+ return undefined;
167
+ }
168
+ const {childNodes} = parentNode,
169
+ i = childNodes.indexOf(this),
170
+ siblings = this.#closing
171
+ ? childNodes.slice(0, i).reverse().filter(({type, name}) => type === 'html' && name === tagName)
172
+ : childNodes.slice(i + 1).filter(({type, name}) => type === 'html' && name === tagName);
173
+ let imbalance = this.#closing ? -1 : 1;
174
+ for (const token of siblings) {
175
+ if (token.closing) {
176
+ imbalance--;
177
+ } else {
178
+ imbalance++;
179
+ }
180
+ if (imbalance === 0) {
181
+ return token;
182
+ }
183
+ }
184
+ throw new SyntaxError(`${this.#closing ? 'unmatched closing' : 'unclosed'} tag: ${string}`);
185
+ }
186
+
187
+ /** @override */
188
+ cloneNode() {
189
+ const [attr] = this.cloneChildNodes(),
190
+ config = this.getAttribute('config');
191
+ return Parser.run(() => new HtmlToken(this.#tag, attr, this.#closing, this.#selfClosing, config));
192
+ }
193
+
194
+ /**
195
+ * @override
196
+ * @template {string} T
197
+ * @param {T} key 属性键
198
+ * @returns {TokenAttribute<T>}
199
+ */
200
+ getAttribute(key) {
201
+ return key === 'tag' ? this.#tag : super.getAttribute(key);
202
+ }
203
+
204
+ /**
205
+ * 更换标签名
206
+ * @param {string} tag 标签名
207
+ * @throws `RangeError` 非法的HTML标签
208
+ */
209
+ replaceTag(tag) {
210
+ const name = tag.toLowerCase();
211
+ if (!this.getAttribute('config').html.flat().includes(name)) {
212
+ throw new RangeError(`非法的HTML标签:${tag}`);
213
+ }
214
+ this.setAttribute('name', name).#tag = tag;
215
+ }
216
+
217
+ /** 局部闭合 */
218
+ #localMatch() {
219
+ this.#selfClosing = false;
220
+ const root = Parser.parse(`</${this.name}>`, false, 3, this.getAttribute('config'));
221
+ this.after(root.firstChild);
222
+ }
223
+
224
+ /**
225
+ * 修复无效自封闭标签
226
+ * @complexity `n`
227
+ * @throws `Error` 无法修复无效自封闭标签
228
+ */
229
+ fix() {
230
+ const config = this.getAttribute('config'),
231
+ {parentNode, name: tagName, firstChild} = this;
232
+ if (!parentNode || !this.#selfClosing || !config.html[0].includes(tagName)) {
233
+ return;
234
+ } else if (firstChild.text().trim()) {
235
+ this.#localMatch();
236
+ return;
237
+ }
238
+ const {childNodes} = parentNode,
239
+ i = childNodes.indexOf(this),
240
+ /** @type {HtmlToken[]} */
241
+ prevSiblings = childNodes.slice(0, i).filter(({type, name}) => type === 'html' && name === tagName),
242
+ imbalance = prevSiblings.reduce((acc, {closing}) => acc + (closing ? 1 : -1), 0);
243
+ if (imbalance < 0) {
244
+ this.#selfClosing = false;
245
+ this.#closing = true;
246
+ } else {
247
+ Parser.warn('无法修复无效自封闭标签', noWrap(String(this)));
248
+ throw new Error(`无法修复无效自封闭标签:前文共有 ${imbalance} 个未匹配的闭合标签`);
249
+ }
250
+ }
251
+ }
252
+
253
+ Parser.classes.HtmlToken = __filename;
254
+ module.exports = HtmlToken;
@@ -0,0 +1,303 @@
1
+ 'use strict';
2
+
3
+ const {text, noWrap, print, extUrlChar, extUrlCharFirst} = require('../util/string'),
4
+ {generateForSelf} = require('../util/lint'),
5
+ Title = require('../lib/title'),
6
+ Parser = require('..'),
7
+ AstText = require('../lib/text'),
8
+ Token = require('.');
9
+
10
+ const params = new Set(['alt', 'link', 'lang', 'page', 'caption']);
11
+
12
+ /**
13
+ * 检查图片参数是否合法
14
+ * @template {string} T
15
+ * @param {T} key 参数名
16
+ * @param {string} val 参数值
17
+ * @returns {T extends 'link' ? string|Title : boolean}
18
+ */
19
+ const validate = (key, val, config = Parser.getConfig(), halfParsed = false) => {
20
+ val = val.trim();
21
+ let value = val.replace(/\0\d+t\x7F/gu, '').trim();
22
+ switch (key) {
23
+ case 'width':
24
+ return /^(?:\d+x?|\d*x\d+)$/u.test(value);
25
+ case 'link': {
26
+ if (!value) {
27
+ return val;
28
+ }
29
+ const regex = new RegExp(
30
+ `^(?:(?:${config.protocol}|//)${extUrlCharFirst}|\0\\d+m\x7F)${extUrlChar}$`,
31
+ 'iu',
32
+ );
33
+ if (regex.test(value)) {
34
+ return val;
35
+ } else if (value.startsWith('[[') && value.endsWith(']]')) {
36
+ value = value.slice(2, -2);
37
+ }
38
+ const title = Parser.normalizeTitle(value, 0, false, config, halfParsed, true, true);
39
+ return title.valid && title;
40
+ }
41
+ case 'lang':
42
+ return config.variants.includes(value);
43
+ case 'alt':
44
+ case 'class':
45
+ case 'manualthumb':
46
+ return true;
47
+ default:
48
+ return !isNaN(value);
49
+ }
50
+ };
51
+
52
+ /**
53
+ * 图片参数
54
+ * @classdesc `{childNodes: ...(AstText|Token)}`
55
+ */
56
+ class ImageParameterToken extends Token {
57
+ type = 'image-parameter';
58
+ #syntax = '';
59
+
60
+ /** 图片链接 */
61
+ get link() {
62
+ return this.name === 'link' ? validate('link', super.text(), this.getAttribute('config')) : undefined;
63
+ }
64
+
65
+ set link(value) {
66
+ if (this.name === 'link') {
67
+ this.setValue(value);
68
+ }
69
+ }
70
+
71
+ /** getValue()的getter */
72
+ get value() {
73
+ return this.getValue();
74
+ }
75
+
76
+ set value(value) {
77
+ this.setValue(value);
78
+ }
79
+
80
+ /** 图片大小 */
81
+ get size() {
82
+ if (this.name === 'width') {
83
+ const /** @type {string} */ size = this.getValue().trim();
84
+ if (!size.includes('{{')) {
85
+ const [width, height = ''] = size.split('x');
86
+ return {width, height};
87
+ }
88
+ const /** @type {{childNodes: AstText[]}} */ token = Parser.parse(size, false, 2, this.getAttribute('config')),
89
+ i = token.childNodes.findIndex(({type, data}) => type === 'text' && data.includes('x')),
90
+ str = token.childNodes[i];
91
+ if (i === -1) {
92
+ return {width: size, height: ''};
93
+ }
94
+ str.splitText(str.data.indexOf('x'));
95
+ str.nextSibling.splitText(1);
96
+ return {width: text(token.childNodes.slice(0, i + 1)), height: text(token.childNodes.slice(i + 2))};
97
+ }
98
+ return undefined;
99
+ }
100
+
101
+ /** 图片宽度 */
102
+ get width() {
103
+ return this.size?.width;
104
+ }
105
+
106
+ set width(width) {
107
+ if (this.name === 'width') {
108
+ const {height} = this;
109
+ this.setValue(`${String(width || '')}${height && 'x'}${height}`);
110
+ }
111
+ }
112
+
113
+ /** 图片高度 */
114
+ get height() {
115
+ return this.size?.height;
116
+ }
117
+
118
+ set height(height) {
119
+ height = String(height || '');
120
+ if (this.name === 'width') {
121
+ this.setValue(`${this.width}${height && 'x'}${height}`);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * @param {string} str 图片参数
127
+ * @param {accum} accum
128
+ */
129
+ constructor(str, config = Parser.getConfig(), accum = []) {
130
+ let mt;
131
+ const regexes = Object.entries(config.img).map(
132
+ /** @returns {[string, string, RegExp]} */
133
+ ([syntax, param]) => [syntax, param, new RegExp(`^(\\s*)${syntax.replace('$1', '(.*)')}(\\s*)$`, 'u')],
134
+ ),
135
+ param = regexes.find(([, key, regex]) => {
136
+ mt = regex.exec(str);
137
+ return mt && (mt.length !== 4 || validate(key, mt[2], config, true) !== false);
138
+ });
139
+ if (param) {
140
+ if (mt.length === 3) {
141
+ super(undefined, config, true, accum);
142
+ this.#syntax = str;
143
+ } else {
144
+ super(mt[2], config, true, accum, {
145
+ 'Stage-2': ':', '!HeadingToken': ':',
146
+ });
147
+ this.#syntax = `${mt[1]}${param[0]}${mt[3]}`;
148
+ }
149
+ this.setAttribute('name', param[1]);
150
+ return;
151
+ }
152
+ super(str, {...config, excludes: [...config.excludes, 'list']}, true, accum);
153
+ this.setAttribute('name', 'caption').setAttribute('stage', 7);
154
+ }
155
+
156
+ /** @override */
157
+ afterBuild() {
158
+ if (this.parentNode.type === 'gallery-image' && !params.has(this.name)) {
159
+ this.setAttribute('name', 'invalid');
160
+ }
161
+ }
162
+
163
+ /** @override */
164
+ isPlain() {
165
+ return this.name === 'caption';
166
+ }
167
+
168
+ /**
169
+ * @override
170
+ * @param {string} selector
171
+ */
172
+ toString(selector) {
173
+ return this.#syntax && !(selector && this.matches(selector))
174
+ ? this.#syntax.replace('$1', super.toString(selector))
175
+ : super.toString(selector);
176
+ }
177
+
178
+ /** @override */
179
+ text() {
180
+ return this.#syntax ? this.#syntax.replace('$1', super.text()).trim() : super.text().trim();
181
+ }
182
+
183
+ /** @override */
184
+ getPadding() {
185
+ return Math.max(0, this.#syntax.indexOf('$1'));
186
+ }
187
+
188
+ /**
189
+ * @override
190
+ * @this {ImageParameterToken & {link: Title}}
191
+ * @param {number} start 起始位置
192
+ */
193
+ lint(start = this.getAbsoluteIndex()) {
194
+ const errors = super.lint(start);
195
+ if (this.name === 'invalid') {
196
+ errors.push(generateForSelf(this, {start}, 'invalid gallery image parameter'));
197
+ } else if (this.link?.encoded) {
198
+ errors.push(generateForSelf(this, {start}, 'unnecessary URL encoding in an internal link'));
199
+ }
200
+ return errors;
201
+ }
202
+
203
+ /** @override */
204
+ print() {
205
+ return this.#syntax
206
+ ? `<span class="wpb-image-parameter">${
207
+ this.#syntax.replace('$1', `<span class="wpb-image-caption">${print(this.childNodes)}</span>`)
208
+ }</span>`
209
+ : super.print({class: 'image-caption'});
210
+ }
211
+
212
+ /** @override */
213
+ cloneNode() {
214
+ const cloned = this.cloneChildNodes(),
215
+ config = this.getAttribute('config');
216
+ return Parser.run(() => {
217
+ const token = new ImageParameterToken(this.#syntax.replace('$1', ''), config);
218
+ token.replaceChildren(...cloned);
219
+ return token;
220
+ });
221
+ }
222
+
223
+ /**
224
+ * @override
225
+ * @template {string} T
226
+ * @param {T} key 属性键
227
+ * @returns {TokenAttribute<T>}
228
+ */
229
+ getAttribute(key) {
230
+ return key === 'syntax' ? this.#syntax : super.getAttribute(key);
231
+ }
232
+
233
+ /**
234
+ * @override
235
+ * @param {PropertyKey} key 属性键
236
+ */
237
+ hasAttribute(key) {
238
+ return key === 'syntax' || super.hasAttribute(key);
239
+ }
240
+
241
+ /** 是否是不可变参数 */
242
+ #isVoid() {
243
+ return this.#syntax && !this.#syntax.includes('$1');
244
+ }
245
+
246
+ /**
247
+ * @override
248
+ * @template {Token} T
249
+ * @param {T} token 待插入的子节点
250
+ * @param {number} i 插入位置
251
+ * @complexity `n`
252
+ * @throws `Error` 不接受自定义输入的图片参数
253
+ */
254
+ insertAt(token, i = this.length) {
255
+ if (!Parser.running && this.#isVoid()) {
256
+ throw new Error(`图片参数 ${this.name} 不接受自定义输入!`);
257
+ }
258
+ return super.insertAt(token, i);
259
+ }
260
+
261
+ /**
262
+ * 获取参数值
263
+ * @complexity `n`
264
+ */
265
+ getValue() {
266
+ return this.name === 'invalid' ? this.text() : this.#isVoid() || super.text();
267
+ }
268
+
269
+ /**
270
+ * 设置参数值
271
+ * @param {string|boolean} value 参数值
272
+ * @complexity `n`
273
+ * @throws SyntaxError` 非法的参数值
274
+ */
275
+ setValue(value) {
276
+ if (this.name === 'invalid') {
277
+ throw new Error('无效的图片参数!');
278
+ } else if (this.#isVoid()) {
279
+ if (typeof value !== 'boolean') {
280
+ this.typeError('setValue', 'Boolean');
281
+ } else if (value === false) {
282
+ this.remove();
283
+ }
284
+ return;
285
+ } else if (typeof value !== 'string') {
286
+ this.typeError('setValue', 'String');
287
+ }
288
+ const root = Parser.parse(`[[File:F|${
289
+ this.#syntax ? this.#syntax.replace('$1', value) : value
290
+ }]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
291
+ {length, firstChild: file} = root,
292
+ {lastChild: imageParameter, type, name, length: fileLength} = file;
293
+ if (length !== 1 || type !== 'file' || name !== 'File:F' || fileLength !== 2
294
+ || imageParameter.name !== this.name
295
+ ) {
296
+ throw new SyntaxError(`非法的 ${this.name} 参数:${noWrap(value)}`);
297
+ }
298
+ this.replaceChildren(...imageParameter.childNodes);
299
+ }
300
+ }
301
+
302
+ Parser.classes.ImageParameterToken = __filename;
303
+ module.exports = ImageParameterToken;