wikiparser-node 0.11.0-m → 0.11.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 (141) hide show
  1. package/config/.schema.json +7 -0
  2. package/config/default.json +1 -0
  3. package/config/llwiki.json +35 -0
  4. package/config/moegirl.json +44 -0
  5. package/config/zhwiki.json +466 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/lib/element.d.ts +116 -2
  8. package/dist/lib/node.d.ts +193 -10
  9. package/dist/lib/ranges.d.ts +37 -0
  10. package/dist/lib/text.d.ts +35 -1
  11. package/dist/lib/title.d.ts +6 -0
  12. package/dist/mixin/attributeParent.d.ts +9 -0
  13. package/dist/mixin/fixedToken.d.ts +8 -0
  14. package/dist/mixin/singleLine.d.ts +8 -0
  15. package/dist/mixin/sol.d.ts +8 -0
  16. package/dist/parser/selector.d.ts +12 -0
  17. package/dist/src/arg.d.ts +30 -1
  18. package/dist/src/atom/index.d.ts +2 -1
  19. package/dist/src/attribute.d.ts +24 -1
  20. package/dist/src/attributes.d.ts +79 -0
  21. package/dist/src/charinsert.d.ts +32 -0
  22. package/dist/src/converter.d.ts +75 -1
  23. package/dist/src/converterFlags.d.ts +57 -1
  24. package/dist/src/converterRule.d.ts +47 -1
  25. package/dist/src/extLink.d.ts +41 -1
  26. package/dist/src/gallery.d.ts +15 -1
  27. package/dist/src/heading.d.ts +22 -2
  28. package/dist/src/html.d.ts +25 -2
  29. package/dist/src/imageParameter.d.ts +43 -1
  30. package/dist/src/imagemap.d.ts +12 -1
  31. package/dist/src/imagemapLink.d.ts +5 -0
  32. package/dist/src/index.d.ts +136 -3
  33. package/dist/src/link/category.d.ts +8 -0
  34. package/dist/src/link/file.d.ts +58 -0
  35. package/dist/src/link/index.d.ts +61 -2
  36. package/dist/src/magicLink.d.ts +22 -0
  37. package/dist/src/nested/index.d.ts +3 -5
  38. package/dist/src/nowiki/comment.d.ts +13 -1
  39. package/dist/src/nowiki/dd.d.ts +9 -0
  40. package/dist/src/nowiki/doubleUnderscore.d.ts +11 -1
  41. package/dist/src/nowiki/index.d.ts +11 -2
  42. package/dist/src/nowiki/quote.d.ts +0 -7
  43. package/dist/src/onlyinclude.d.ts +8 -1
  44. package/dist/src/paramTag/index.d.ts +8 -3
  45. package/dist/src/parameter.d.ts +48 -1
  46. package/dist/src/syntax.d.ts +6 -1
  47. package/dist/src/table/index.d.ts +257 -0
  48. package/dist/src/table/td.d.ts +67 -6
  49. package/dist/src/table/tr.d.ts +74 -0
  50. package/dist/src/tagPair/ext.d.ts +2 -1
  51. package/dist/src/tagPair/include.d.ts +9 -0
  52. package/dist/src/tagPair/index.d.ts +14 -1
  53. package/dist/src/transclude.d.ts +125 -1
  54. package/dist/tool/index.d.ts +420 -0
  55. package/dist/util/base.d.ts +10 -0
  56. package/dist/util/debug.d.ts +20 -0
  57. package/dist/util/diff.d.ts +8 -0
  58. package/dist/util/string.d.ts +24 -0
  59. package/index.js +256 -4
  60. package/lib/element.js +488 -6
  61. package/lib/node.js +495 -6
  62. package/lib/ranges.js +130 -0
  63. package/lib/text.js +96 -1
  64. package/lib/title.js +28 -1
  65. package/mixin/attributeParent.js +117 -0
  66. package/mixin/fixedToken.js +40 -0
  67. package/mixin/hidden.js +3 -0
  68. package/mixin/singleLine.js +31 -0
  69. package/mixin/sol.js +54 -0
  70. package/package.json +5 -2
  71. package/parser/brackets.js +1 -0
  72. package/parser/commentAndExt.js +1 -0
  73. package/parser/converter.js +1 -0
  74. package/parser/externalLinks.js +1 -0
  75. package/parser/hrAndDoubleUnderscore.js +1 -0
  76. package/parser/html.js +1 -0
  77. package/parser/links.js +5 -4
  78. package/parser/list.js +1 -0
  79. package/parser/magicLinks.js +1 -0
  80. package/parser/quotes.js +1 -0
  81. package/parser/selector.js +180 -0
  82. package/parser/table.js +1 -0
  83. package/src/arg.js +116 -2
  84. package/src/atom/hidden.js +2 -0
  85. package/src/atom/index.js +17 -0
  86. package/src/attribute.js +189 -3
  87. package/src/attributes.js +307 -4
  88. package/src/charinsert.js +97 -0
  89. package/src/converter.js +108 -2
  90. package/src/converterFlags.js +187 -0
  91. package/src/converterRule.js +183 -1
  92. package/src/extLink.js +121 -1
  93. package/src/gallery.js +54 -0
  94. package/src/hasNowiki/index.js +12 -0
  95. package/src/hasNowiki/pre.js +12 -0
  96. package/src/heading.js +54 -3
  97. package/src/html.js +118 -3
  98. package/src/imageParameter.js +164 -2
  99. package/src/imagemap.js +61 -2
  100. package/src/imagemapLink.js +13 -1
  101. package/src/index.js +530 -3
  102. package/src/link/category.js +32 -1
  103. package/src/link/file.js +157 -2
  104. package/src/link/galleryImage.js +60 -2
  105. package/src/link/index.js +270 -1
  106. package/src/magicLink.js +83 -0
  107. package/src/nested/choose.js +1 -0
  108. package/src/nested/combobox.js +1 -0
  109. package/src/nested/index.js +27 -0
  110. package/src/nested/references.js +1 -0
  111. package/src/nowiki/comment.js +25 -1
  112. package/src/nowiki/dd.js +47 -1
  113. package/src/nowiki/doubleUnderscore.js +31 -1
  114. package/src/nowiki/hr.js +20 -1
  115. package/src/nowiki/index.js +23 -1
  116. package/src/nowiki/list.js +5 -2
  117. package/src/nowiki/noinclude.js +14 -0
  118. package/src/nowiki/quote.js +14 -0
  119. package/src/onlyinclude.js +26 -1
  120. package/src/paramTag/index.js +24 -1
  121. package/src/paramTag/inputbox.js +4 -1
  122. package/src/parameter.js +147 -5
  123. package/src/syntax.js +68 -0
  124. package/src/table/index.js +941 -2
  125. package/src/table/td.js +230 -5
  126. package/src/table/tr.js +247 -2
  127. package/src/tagPair/ext.js +21 -1
  128. package/src/tagPair/include.js +24 -0
  129. package/src/tagPair/index.js +56 -2
  130. package/src/transclude.js +512 -5
  131. package/tool/index.js +1209 -0
  132. package/typings/array.d.ts +29 -0
  133. package/typings/event.d.ts +22 -0
  134. package/typings/index.d.ts +67 -0
  135. package/typings/node.d.ts +19 -0
  136. package/typings/parser.d.ts +7 -0
  137. package/typings/table.d.ts +10 -0
  138. package/typings/token.d.ts +3 -0
  139. package/typings/tool.d.ts +6 -0
  140. package/util/debug.js +73 -0
  141. package/util/string.js +51 -0
@@ -24,9 +24,21 @@ class HasNowikiToken extends Token {
24
24
  },
25
25
  );
26
26
  super(wikitext, config, true, accum, {
27
+ AstText: ':', NoincludeToken: ':',
27
28
  });
28
29
  this.type = type;
29
30
  }
31
+
32
+ /** @override */
33
+ cloneNode() {
34
+ const cloned = this.cloneChildNodes();
35
+ return Parser.run(() => {
36
+ const token = new HasNowikiToken(undefined, this.type, this.getAttribute('config'));
37
+ token.append(...cloned);
38
+ return token;
39
+ });
40
+ }
30
41
  }
31
42
 
43
+ Parser.classes.HasNowikiToken = __filename;
32
44
  module.exports = HasNowikiToken;
@@ -17,12 +17,24 @@ class PreToken extends HasNowikiToken {
17
17
  constructor(wikitext, config = Parser.getConfig(), accum = []) {
18
18
  super(wikitext, 'ext-inner', config, accum);
19
19
  this.setAttribute('stage', Parser.MAX_STAGE - 1);
20
+ this.setAttribute('acceptable', {AstText: ':', NoincludeToken: ':', ConverterToken: ':'});
20
21
  }
21
22
 
22
23
  /** @override */
23
24
  isPlain() {
24
25
  return true;
25
26
  }
27
+
28
+ /** @override */
29
+ cloneNode() {
30
+ const cloned = this.cloneChildNodes();
31
+ return Parser.run(() => {
32
+ const token = new PreToken(undefined, this.getAttribute('config'));
33
+ token.append(...cloned);
34
+ return token;
35
+ });
36
+ }
26
37
  }
27
38
 
39
+ Parser.classes.PreToken = __filename;
28
40
  module.exports = PreToken;
package/src/heading.js CHANGED
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const {generateForSelf} = require('../util/lint'),
4
+ fixedToken = require('../mixin/fixedToken'),
5
+ sol = require('../mixin/sol'),
4
6
  Parser = require('..'),
5
7
  Token = require('.'),
6
8
  SyntaxToken = require('./syntax');
@@ -9,9 +11,14 @@ const {generateForSelf} = require('../util/lint'),
9
11
  * 章节标题
10
12
  * @classdesc `{childNodes: [Token, SyntaxToken]}`
11
13
  */
12
- class HeadingToken extends Token {
14
+ class HeadingToken extends fixedToken(sol(Token)) {
13
15
  type = 'heading';
14
16
 
17
+ /** 内部wikitext */
18
+ get innerText() {
19
+ return this.firstChild.text();
20
+ }
21
+
15
22
  /**
16
23
  * @param {number} level 标题层级
17
24
  * @param {string[]} input 标题文字
@@ -22,28 +29,37 @@ class HeadingToken extends Token {
22
29
  this.setAttribute('name', String(level));
23
30
  const token = new Token(input[0], config, true, accum);
24
31
  token.type = 'heading-title';
32
+ token.setAttribute('name', this.name);
25
33
  token.setAttribute('stage', 2);
26
34
  const trail = new SyntaxToken(input[1], /^[^\S\n]*$/u, 'heading-trail', config, accum, {
35
+ 'Stage-1': ':', '!ExtToken': '',
27
36
  });
28
37
  this.append(token, trail);
29
38
  }
30
39
 
31
40
  /**
32
41
  * @override
42
+ * @this {{prependNewLine(): ''|'\n'} & HeadingToken}
43
+ * @param {string} selector
33
44
  * @returns {string}
34
45
  */
35
46
  toString(selector) {
36
47
  const equals = '='.repeat(Number(this.name));
37
- return `${equals}${this.firstChild.toString()}${equals}${this.lastChild.toString()}`;
48
+ return selector && this.matches(selector)
49
+ ? ''
50
+ : `${this.prependNewLine()}${equals}${
51
+ this.firstChild.toString(selector)
52
+ }${equals}${this.lastChild.toString(selector)}`;
38
53
  }
39
54
 
40
55
  /**
41
56
  * @override
57
+ * @this {HeadingToken & {prependNewLine(): ''|'\n'}}
42
58
  * @returns {string}
43
59
  */
44
60
  text() {
45
61
  const equals = '='.repeat(Number(this.name));
46
- return `${equals}${this.firstChild.text()}${equals}`;
62
+ return `${this.prependNewLine()}${equals}${this.firstChild.text()}${equals}`;
47
63
  }
48
64
 
49
65
  /** @override */
@@ -56,6 +72,12 @@ class HeadingToken extends Token {
56
72
  return Number(this.name);
57
73
  }
58
74
 
75
+ /** @override */
76
+ print() {
77
+ const equals = '='.repeat(Number(this.name));
78
+ return super.print({pre: equals, sep: equals});
79
+ }
80
+
59
81
  /**
60
82
  * @override
61
83
  * @param {number} start 起始位置
@@ -78,6 +100,35 @@ class HeadingToken extends Token {
78
100
  }
79
101
  return errors;
80
102
  }
103
+
104
+ /** @override */
105
+ cloneNode() {
106
+ const [title, trail] = this.cloneChildNodes();
107
+ return Parser.run(() => {
108
+ const token = new HeadingToken(Number(this.name), [], this.getAttribute('config'));
109
+ token.firsthild.safeReplaceWith(title);
110
+ token.lastChild.safeReplaceWith(trail);
111
+ return token;
112
+ });
113
+ }
114
+
115
+ /**
116
+ * 设置标题层级
117
+ * @param {number} n 标题层级
118
+ */
119
+ setLevel(n) {
120
+ if (!Number.isInteger(n)) {
121
+ this.typeError('setLevel', 'Number');
122
+ }
123
+ n = Math.min(Math.max(n, 1), 6);
124
+ this.setAttribute('name', String(n)).firstChild.setAttribute('name', this.name);
125
+ }
126
+
127
+ /** 移除标题后的不可见内容 */
128
+ removeTrail() {
129
+ this.lastChild.replaceChildren();
130
+ }
81
131
  }
82
132
 
133
+ Parser.classes.HeadingToken = __filename;
83
134
  module.exports = HeadingToken;
package/src/html.js CHANGED
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const {generateForSelf} = require('../util/lint'),
4
+ {noWrap} = require('../util/string'),
5
+ fixedToken = require('../mixin/fixedToken'),
6
+ attributeParent = require('../mixin/attributeParent'),
4
7
  Parser = require('..'),
5
8
  Token = require('.');
6
9
 
@@ -10,7 +13,7 @@ const magicWords = new Set(['if', 'ifeq', 'ifexpr', 'ifexist', 'iferror', 'switc
10
13
  * HTML标签
11
14
  * @classdesc `{childNodes: [AttributesToken]}`
12
15
  */
13
- class HtmlToken extends Token {
16
+ class HtmlToken extends attributeParent(fixedToken(Token)) {
14
17
  type = 'html';
15
18
  #closing;
16
19
  #selfClosing;
@@ -21,6 +24,41 @@ class HtmlToken extends Token {
21
24
  return this.#closing;
22
25
  }
23
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
+
24
62
  /**
25
63
  * @param {string} name 标签名
26
64
  * @param {AttributesToken} attr 标签属性
@@ -39,9 +77,12 @@ class HtmlToken extends Token {
39
77
 
40
78
  /**
41
79
  * @override
80
+ * @param {string} selector
42
81
  */
43
82
  toString(selector) {
44
- return `<${this.#closing ? '/' : ''}${this.#tag}${super.toString()}${this.#selfClosing ? '/' : ''}>`;
83
+ return selector && this.matches(selector)
84
+ ? ''
85
+ : `<${this.#closing ? '/' : ''}${this.#tag}${super.toString(selector)}${this.#selfClosing ? '/' : ''}>`;
45
86
  }
46
87
 
47
88
  /** @override */
@@ -56,6 +97,14 @@ class HtmlToken extends Token {
56
97
  return this.#tag.length + (this.#closing ? 2 : 1);
57
98
  }
58
99
 
100
+ /** @override */
101
+ print() {
102
+ return super.print({
103
+ pre: `&lt;${this.#closing ? '/' : ''}${this.#tag}`,
104
+ post: `${this.#selfClosing ? '/' : ''}&gt;`,
105
+ });
106
+ }
107
+
59
108
  /**
60
109
  * @override
61
110
  * @param {number} start 起始位置
@@ -106,7 +155,7 @@ class HtmlToken extends Token {
106
155
  findMatchingTag() {
107
156
  const {html} = this.getAttribute('config'),
108
157
  {name: tagName, parentNode} = this,
109
- string = String(this);
158
+ string = noWrap(String(this));
110
159
  if (this.#closing && (this.#selfClosing || html[2].includes(tagName))) {
111
160
  throw new SyntaxError(`tag that is both closing and self-closing: ${string}`);
112
161
  } else if (html[2].includes(tagName) || this.#selfClosing && html[1].includes(tagName)) { // 自封闭标签
@@ -134,6 +183,72 @@ class HtmlToken extends Token {
134
183
  }
135
184
  throw new SyntaxError(`${this.#closing ? 'unmatched closing' : 'unclosed'} tag: ${string}`);
136
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 {import('../typings/node').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
+ }
137
251
  }
138
252
 
253
+ Parser.classes.HtmlToken = __filename;
139
254
  module.exports = HtmlToken;
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const {extUrlChar, extUrlCharFirst} = require('../util/string'),
3
+ const {text, noWrap, print, extUrlChar, extUrlCharFirst} = require('../util/string'),
4
4
  {generateForSelf} = require('../util/lint'),
5
5
  Title = require('../lib/title'),
6
6
  Parser = require('..'),
7
+ AstText = require('../lib/text'),
7
8
  Token = require('.');
8
9
 
9
10
  const params = new Set(['alt', 'link', 'lang', 'page', 'caption']);
@@ -61,6 +62,66 @@ class ImageParameterToken extends Token {
61
62
  return this.name === 'link' ? validate('link', super.text(), this.getAttribute('config')) : undefined;
62
63
  }
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
+
64
125
  /**
65
126
  * @param {string} str 图片参数
66
127
  * @param {import('../typings/token').accum} accum
@@ -81,6 +142,7 @@ class ImageParameterToken extends Token {
81
142
  this.#syntax = str;
82
143
  } else {
83
144
  super(mt[2], config, true, accum, {
145
+ 'Stage-2': ':', '!HeadingToken': ':',
84
146
  });
85
147
  this.#syntax = `${mt[1]}${param[0]}${mt[3]}`;
86
148
  }
@@ -105,9 +167,10 @@ class ImageParameterToken extends Token {
105
167
 
106
168
  /**
107
169
  * @override
170
+ * @param {string} selector
108
171
  */
109
172
  toString(selector) {
110
- return this.#syntax
173
+ return this.#syntax && !(selector && this.matches(selector))
111
174
  ? this.#syntax.replace('$1', super.toString(selector))
112
175
  : super.toString(selector);
113
176
  }
@@ -136,6 +199,105 @@ class ImageParameterToken extends Token {
136
199
  }
137
200
  return errors;
138
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 {import('../typings/node').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
+ }
139
300
  }
140
301
 
302
+ Parser.classes.ImageParameterToken = __filename;
141
303
  module.exports = ImageParameterToken;
package/src/imagemap.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {generateForSelf, generateForChild} = require('../util/lint'),
4
+ singleLine = require('../mixin/singleLine'),
4
5
  Parser = require('..'),
5
6
  Token = require('.'),
6
7
  NoincludeToken = require('./nowiki/noinclude'),
@@ -23,6 +24,14 @@ class ImagemapToken extends Token {
23
24
  return this.childNodes.find(({type}) => type === 'imagemap-image');
24
25
  }
25
26
 
27
+ /**
28
+ * 链接
29
+ * @returns {ImagemapLinkToken[]}
30
+ */
31
+ get links() {
32
+ return this.childNodes.filter(({type}) => type === 'imagemap-link');
33
+ }
34
+
26
35
  /**
27
36
  * @param {string} inner 标签内部wikitext
28
37
  * @param {import('../typings/token').accum} accum
@@ -30,13 +39,14 @@ class ImagemapToken extends Token {
30
39
  */
31
40
  constructor(inner, config = Parser.getConfig(), accum = []) {
32
41
  super(undefined, config, true, accum, {
42
+ GalleryImageToken: ':', ImagemapLinkToken: ':', SingleLineNoincludeToken: ':', AstText: ':',
33
43
  });
34
44
  if (!inner) {
35
45
  return;
36
46
  }
37
47
  const lines = inner.split('\n'),
38
48
  protocols = new Set(config.protocol.split('|')),
39
- SingleLineNoincludeToken = NoincludeToken,
49
+ SingleLineNoincludeToken = singleLine(NoincludeToken),
40
50
  fallback = /** @param {string} line 一行文本 */ line => {
41
51
  super.insertAt(new SingleLineNoincludeToken(line, config, accum));
42
52
  };
@@ -49,7 +59,7 @@ class ImagemapToken extends Token {
49
59
  } else if (first) {
50
60
  const [file, ...options] = line.split('|'),
51
61
  title = this.normalizeTitle(file, 0, true);
52
- if (title.valid && title.ns === 6) {
62
+ if (title.valid && !title.interwiki && title.ns === 6) {
53
63
  const token = new GalleryImageToken(
54
64
  file, options.length > 0 ? options.join('|') : undefined, config, accum,
55
65
  );
@@ -58,6 +68,7 @@ class ImagemapToken extends Token {
58
68
  first = false;
59
69
  continue;
60
70
  } else {
71
+ Parser.error('<imagemap>标签内必须先包含一张合法图片!', line);
61
72
  error = true;
62
73
  }
63
74
  } else if (line.trim().split(/[\t ]/u)[0] === 'desc') {
@@ -101,6 +112,7 @@ class ImagemapToken extends Token {
101
112
 
102
113
  /**
103
114
  * @override
115
+ * @param {string} selector
104
116
  */
105
117
  toString(selector) {
106
118
  return super.toString(selector, '\n');
@@ -116,6 +128,11 @@ class ImagemapToken extends Token {
116
128
  return 1;
117
129
  }
118
130
 
131
+ /** @override */
132
+ print() {
133
+ return super.print({sep: '\n'});
134
+ }
135
+
119
136
  /**
120
137
  * @override
121
138
  * @param {number} start 起始位置
@@ -135,6 +152,48 @@ class ImagemapToken extends Token {
135
152
  }
136
153
  return errors;
137
154
  }
155
+
156
+ /**
157
+ * @override
158
+ * @template {string|Token} T
159
+ * @param {T} token 待插入的节点
160
+ * @param {number} i 插入位置
161
+ * @throws `Error` 当前缺少合法图片
162
+ * @throws `RangeError` 已有一张合法图片
163
+ */
164
+ insertAt(token, i = 0) {
165
+ const {image} = this;
166
+ if (!image && (token.type === 'imagemap-link' || token.type === 'text')) {
167
+ throw new Error('当前缺少一张合法图片!');
168
+ } else if (image && token.type === 'imagemap-image') {
169
+ throw new RangeError('已有一张合法图片!');
170
+ }
171
+ return super.insertAt(token, i);
172
+ }
173
+
174
+ /**
175
+ * @override
176
+ * @param {number} i 移除位置
177
+ * @throws `Error` 禁止移除图片
178
+ */
179
+ removeAt(i) {
180
+ const child = this.childNodes[i];
181
+ if (child.type === 'imagemap-image') {
182
+ throw new Error('禁止移除<imagemap>内的图片!');
183
+ }
184
+ return super.removeAt(i);
185
+ }
186
+
187
+ /** @override */
188
+ cloneNode() {
189
+ const cloned = this.cloneChildNodes();
190
+ return Parser.run(() => {
191
+ const token = new ImagemapToken(undefined, this.getAttribute('config'));
192
+ token.append(...cloned);
193
+ return token;
194
+ });
195
+ }
138
196
  }
139
197
 
198
+ Parser.classes.ImagemapToken = __filename;
140
199
  module.exports = ImagemapToken;
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const Title = require('../lib/title'),
4
+ fixedToken = require('../mixin/fixedToken'),
5
+ singleLine = require('../mixin/singleLine'),
6
+ Parser = require('..'),
4
7
  Token = require('.'),
5
8
  NoincludeToken = require('./nowiki/noinclude'),
6
9
  LinkToken = require('./link'),
@@ -10,9 +13,17 @@ const Title = require('../lib/title'),
10
13
  * `<imagemap>`内的链接
11
14
  * @classdesc `{childNodes: [AstText, LinkToken|ExtLinkToken, NoincludeToken]}`
12
15
  */
13
- class ImagemapLinkToken extends Token {
16
+ class ImagemapLinkToken extends fixedToken(singleLine(Token)) {
14
17
  type = 'imagemap-link';
15
18
 
19
+ /**
20
+ * 内外链接
21
+ * @this {{childNodes: (LinkToken|ExtLinkToken)[]}}
22
+ */
23
+ get link() {
24
+ return this.childNodes[1].link;
25
+ }
26
+
16
27
  /**
17
28
  * @param {string} pre 链接前的文本
18
29
  * @param {[string, string, string|Title]} linkStuff 内外链接
@@ -26,4 +37,5 @@ class ImagemapLinkToken extends Token {
26
37
  }
27
38
  }
28
39
 
40
+ Parser.classes.ImagemapLinkToken = __filename;
29
41
  module.exports = ImagemapLinkToken;