wikiparser-node 0.4.0 → 0.6.1

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 (87) hide show
  1. package/config/default.json +129 -66
  2. package/config/zhwiki.json +4 -4
  3. package/index.js +97 -65
  4. package/lib/element.js +159 -302
  5. package/lib/node.js +384 -198
  6. package/lib/ranges.js +3 -4
  7. package/lib/text.js +65 -36
  8. package/lib/title.js +9 -8
  9. package/mixin/fixedToken.js +4 -4
  10. package/mixin/hidden.js +2 -0
  11. package/mixin/sol.js +16 -7
  12. package/package.json +14 -3
  13. package/parser/brackets.js +8 -2
  14. package/parser/commentAndExt.js +1 -1
  15. package/parser/converter.js +1 -1
  16. package/parser/externalLinks.js +2 -2
  17. package/parser/hrAndDoubleUnderscore.js +8 -7
  18. package/parser/links.js +8 -9
  19. package/parser/magicLinks.js +1 -1
  20. package/parser/selector.js +5 -5
  21. package/parser/table.js +18 -16
  22. package/src/arg.js +71 -42
  23. package/src/atom/index.js +7 -5
  24. package/src/attribute.js +102 -64
  25. package/src/charinsert.js +91 -0
  26. package/src/converter.js +34 -15
  27. package/src/converterFlags.js +87 -40
  28. package/src/converterRule.js +59 -53
  29. package/src/extLink.js +45 -37
  30. package/src/gallery.js +71 -16
  31. package/src/hasNowiki/index.js +42 -0
  32. package/src/hasNowiki/pre.js +40 -0
  33. package/src/heading.js +41 -18
  34. package/src/html.js +76 -48
  35. package/src/imageParameter.js +73 -51
  36. package/src/imagemap.js +205 -0
  37. package/src/imagemapLink.js +43 -0
  38. package/src/index.js +243 -138
  39. package/src/link/category.js +10 -14
  40. package/src/link/file.js +112 -56
  41. package/src/link/galleryImage.js +74 -10
  42. package/src/link/index.js +86 -61
  43. package/src/magicLink.js +48 -21
  44. package/src/nested/choose.js +24 -0
  45. package/src/nested/combobox.js +23 -0
  46. package/src/nested/index.js +88 -0
  47. package/src/nested/references.js +23 -0
  48. package/src/nowiki/comment.js +18 -4
  49. package/src/nowiki/dd.js +2 -2
  50. package/src/nowiki/doubleUnderscore.js +16 -11
  51. package/src/nowiki/index.js +12 -0
  52. package/src/nowiki/quote.js +28 -1
  53. package/src/onlyinclude.js +15 -8
  54. package/src/paramTag/index.js +83 -0
  55. package/src/paramTag/inputbox.js +42 -0
  56. package/src/parameter.js +73 -46
  57. package/src/syntax.js +9 -1
  58. package/src/table/index.js +58 -44
  59. package/src/table/td.js +63 -63
  60. package/src/table/tr.js +52 -35
  61. package/src/tagPair/ext.js +60 -43
  62. package/src/tagPair/include.js +11 -1
  63. package/src/tagPair/index.js +29 -20
  64. package/src/transclude.js +214 -166
  65. package/tool/index.js +720 -439
  66. package/util/base.js +17 -0
  67. package/util/debug.js +1 -1
  68. package/{test/util.js → util/diff.js} +15 -19
  69. package/util/lint.js +40 -0
  70. package/util/string.js +37 -20
  71. package/.eslintrc.json +0 -714
  72. package/errors/README +0 -1
  73. package/jsconfig.json +0 -7
  74. package/printed/README +0 -1
  75. package/printed/example.json +0 -120
  76. package/test/api.js +0 -83
  77. package/test/real.js +0 -133
  78. package/test/test.js +0 -28
  79. package/typings/api.d.ts +0 -13
  80. package/typings/array.d.ts +0 -28
  81. package/typings/event.d.ts +0 -24
  82. package/typings/index.d.ts +0 -94
  83. package/typings/node.d.ts +0 -29
  84. package/typings/parser.d.ts +0 -16
  85. package/typings/table.d.ts +0 -14
  86. package/typings/token.d.ts +0 -22
  87. package/typings/tool.d.ts +0 -11
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const Parser = require('..'),
3
+ const {generateForChild} = require('../util/lint'),
4
+ Parser = require('..'),
4
5
  Token = require('.'),
5
6
  AtomToken = require('./atom');
6
7
 
@@ -21,21 +22,12 @@ class ConverterFlagsToken extends Token {
21
22
  this.append(...flags.map(flag => new AtomToken(flag, 'converter-flag', config, accum)));
22
23
  }
23
24
 
24
- /** @override */
25
- cloneNode() {
26
- const cloned = this.cloneChildNodes(),
27
- token = Parser.run(() => new ConverterFlagsToken([], this.getAttribute('config')));
28
- token.append(...cloned);
29
- token.afterBuild();
30
- return token;
31
- }
32
-
33
25
  /**
34
26
  * @override
35
27
  * @complexity `n`
36
28
  */
37
29
  afterBuild() {
38
- this.#flags = this.children.map(child => child.text().trim());
30
+ this.#flags = this.childNodes.map(child => child.text().trim());
39
31
  const /** @type {AstListener} */ converterFlagsListener = ({prevTarget}) => {
40
32
  if (prevTarget) {
41
33
  this.#flags[this.childNodes.indexOf(prevTarget)] = prevTarget.text().trim();
@@ -45,6 +37,75 @@ class ConverterFlagsToken extends Token {
45
37
  return this;
46
38
  }
47
39
 
40
+ /**
41
+ * @override
42
+ * @param {string} selector
43
+ */
44
+ toString(selector) {
45
+ return super.toString(selector, ';');
46
+ }
47
+
48
+ /** @override */
49
+ getGaps() {
50
+ return 1;
51
+ }
52
+
53
+ /** @override */
54
+ print() {
55
+ return super.print({sep: ';'});
56
+ }
57
+
58
+ /**
59
+ * @override
60
+ * @param {number} start 起始位置
61
+ */
62
+ lint(start = 0) {
63
+ const variantFlags = this.getVariantFlags(),
64
+ unknownFlags = this.getUnknownFlags(),
65
+ emptyFlags = this.#flags.filter(flag => !flag),
66
+ validFlags = this.#flags.filter(flag => ['A', 'T', 'R', 'D', '-', 'H', 'N'].includes(flag)),
67
+ knownFlagCount = this.#flags.length - unknownFlags.length - emptyFlags,
68
+ errors = super.lint(start);
69
+ if (variantFlags.length === knownFlagCount || validFlags.length === knownFlagCount) {
70
+ return errors;
71
+ }
72
+ const rect = this.getRootNode().posFromIndex(start);
73
+ for (const child of this.childNodes) {
74
+ const flag = child.text().trim();
75
+ if (flag && !variantFlags.includes(flag) && !unknownFlags.includes(flag)
76
+ && (variantFlags.length > 0 || !validFlags.includes(flag))
77
+ ) {
78
+ errors.push(generateForChild(child, rect, '无效的转换标记'));
79
+ }
80
+ }
81
+ return errors;
82
+ }
83
+
84
+ /**
85
+ * 获取未知转换类型标记
86
+ * @complexity `n`
87
+ */
88
+ getUnknownFlags() {
89
+ return this.#flags.filter(flag => /\{\{[^{}]+\}\}/u.test(flag));
90
+ }
91
+
92
+ /** 获取指定语言变体的转换标记 */
93
+ getVariantFlags() {
94
+ const {variants} = this.getAttribute('config');
95
+ return this.#flags.filter(flag => variants.includes(flag));
96
+ }
97
+
98
+ /** @override */
99
+ cloneNode() {
100
+ const cloned = this.cloneChildNodes();
101
+ return Parser.run(() => {
102
+ const token = new ConverterFlagsToken([], this.getAttribute('config'));
103
+ token.append(...cloned);
104
+ token.afterBuild();
105
+ return token;
106
+ });
107
+ }
108
+
48
109
  /**
49
110
  * @override
50
111
  * @template {string} T
@@ -58,6 +119,14 @@ class ConverterFlagsToken extends Token {
58
119
  return super.getAttribute(key);
59
120
  }
60
121
 
122
+ /**
123
+ * @override
124
+ * @param {PropertyKey} key 属性键
125
+ */
126
+ hasAttribute(key) {
127
+ return key === 'flags' || super.hasAttribute(key);
128
+ }
129
+
61
130
  /**
62
131
  * @override
63
132
  * @param {number} i 移除位置
@@ -81,45 +150,24 @@ class ConverterFlagsToken extends Token {
81
150
  return token;
82
151
  }
83
152
 
84
- /**
85
- * @override
86
- * @param {string} selector
87
- */
88
- toString(selector) {
89
- return super.toString(selector, ';');
90
- }
91
-
92
- /** @override */
93
- getGaps() {
94
- return 1;
95
- }
96
-
97
153
  /** @override */
98
154
  text() {
99
155
  return super.text(';');
100
156
  }
101
157
 
102
- /**
103
- * 获取转换类型标记节点
104
- * @param {string} flag 转换类型标记
105
- * @returns {AtomToken[]}
106
- * @complexity `n`
107
- */
108
- getFlagToken(flag) {
109
- return this.#flags.includes(flag) ? this.children.filter(child => child.text().trim() === flag) : [];
110
- }
111
-
112
158
  /** 获取所有转换类型标记 */
113
159
  getAllFlags() {
114
160
  return new Set(this.#flags);
115
161
  }
116
162
 
117
163
  /**
118
- * 获取未知转换类型标记
164
+ * 获取转换类型标记节点
165
+ * @param {string} flag 转换类型标记
166
+ * @returns {AtomToken[]}
119
167
  * @complexity `n`
120
168
  */
121
- getUnknownFlags() {
122
- return this.#flags.filter(flag => /\{\{[^{}]+\}\}/u.test(flag));
169
+ getFlagToken(flag) {
170
+ return this.#flags.includes(flag) ? this.childNodes.filter(child => child.text().trim() === flag) : [];
123
171
  }
124
172
 
125
173
  /**
@@ -127,8 +175,7 @@ class ConverterFlagsToken extends Token {
127
175
  * @complexity `n`
128
176
  */
129
177
  getEffectiveFlags() {
130
- const {variants} = this.getAttribute('config'),
131
- variantFlags = this.#flags.filter(flag => variants.includes(flag)),
178
+ const variantFlags = this.getVariantFlags(),
132
179
  unknownFlags = this.getUnknownFlags();
133
180
  if (variantFlags.length > 0) {
134
181
  return new Set([...variantFlags, ...unknownFlags]);
@@ -198,7 +245,7 @@ class ConverterFlagsToken extends Token {
198
245
  */
199
246
  #newFlag(flag) {
200
247
  const token = Parser.run(() => new AtomToken(flag, 'converter-flag', this.getAttribute('config')));
201
- this.appendChild(token);
248
+ this.insertAt(token);
202
249
  }
203
250
 
204
251
  /**
@@ -15,7 +15,7 @@ class ConverterRuleToken extends Token {
15
15
 
16
16
  /** 语言变体 */
17
17
  get variant() {
18
- return this.children.at(-2)?.text()?.trim() ?? '';
18
+ return this.childNodes.at(-2)?.text()?.trim() ?? '';
19
19
  }
20
20
 
21
21
  set variant(variant) {
@@ -44,7 +44,7 @@ class ConverterRuleToken extends Token {
44
44
  * @param {accum} accum
45
45
  */
46
46
  constructor(rule, hasColon = true, config = Parser.getConfig(), accum = []) {
47
- super(undefined, config, true, accum, {AtomToken: ':'});
47
+ super(undefined, config, true, accum);
48
48
  if (hasColon) {
49
49
  const i = rule.indexOf(':'),
50
50
  j = rule.slice(0, i).indexOf('=>'),
@@ -65,17 +65,51 @@ class ConverterRuleToken extends Token {
65
65
  this.getAttribute('protectChildren')('1:');
66
66
  }
67
67
 
68
+ /**
69
+ * @override
70
+ * @param {string} selector
71
+ * @returns {string}
72
+ */
73
+ toString(selector) {
74
+ if (this.childNodes.length === 3 && !(selector && this.matches(selector))) {
75
+ const {childNodes: [from, variant, to]} = this;
76
+ return `${from.toString(selector)}=>${variant.toString(selector)}:${to.toString(selector)}`;
77
+ }
78
+ return super.toString(selector, ':');
79
+ }
80
+
81
+ /**
82
+ * @override
83
+ * @param {number} i 子节点序号
84
+ */
85
+ getGaps(i = 0) {
86
+ const {length} = this;
87
+ i = i < 0 ? i + length : i;
88
+ return i === 0 && length === 3 ? 2 : 1;
89
+ }
90
+
91
+ /** @override */
92
+ print() {
93
+ if (this.childNodes.length === 3) {
94
+ const {childNodes: [from, variant, to]} = this;
95
+ return `<span class="wpb-converter-rule">${from.print()}=>${variant.print()}:${to.print()}</span>`;
96
+ }
97
+ return super.print({sep: ':'});
98
+ }
99
+
68
100
  /** @override */
69
101
  cloneNode() {
70
102
  const cloned = this.cloneChildNodes(),
71
103
  placeholders = ['', 'zh:', '=>zh:'],
72
- placeholder = placeholders[cloned.length - 1],
73
- token = Parser.run(() => new ConverterRuleToken(placeholder, placeholder, this.getAttribute('config')));
74
- for (let i = 0; i < cloned.length; i++) {
75
- token.children[i].safeReplaceWith(cloned[i]);
76
- }
77
- token.afterBuild();
78
- return token;
104
+ placeholder = placeholders[cloned.length - 1];
105
+ return Parser.run(() => {
106
+ const token = new ConverterRuleToken(placeholder, placeholder, this.getAttribute('config'));
107
+ for (let i = 0; i < cloned.length; i++) {
108
+ token.childNodes[i].safeReplaceWith(cloned[i]);
109
+ }
110
+ token.afterBuild();
111
+ return token;
112
+ });
79
113
  }
80
114
 
81
115
  /** @override */
@@ -121,36 +155,13 @@ class ConverterRuleToken extends Token {
121
155
  throw new Error(`转换规则语法复杂,请勿尝试对 ${this.constructor.name} 手动插入子节点!`);
122
156
  }
123
157
 
124
- /**
125
- * @override
126
- * @param {string} selector
127
- * @returns {string}
128
- */
129
- toString(selector) {
130
- if (this.childNodes.length === 3 && !(selector && this.matches(selector))) {
131
- const {children: [from, variant, to]} = this;
132
- return `${from.toString(selector)}=>${variant.toString(selector)}:${to.toString(selector)}`;
133
- }
134
- return super.toString(selector, ':');
135
- }
136
-
137
- /**
138
- * @override
139
- * @param {number} i 子节点序号
140
- */
141
- getGaps(i = 0) {
142
- const {childNodes: {length}} = this;
143
- i = i < 0 ? i + length : i;
144
- return i === 0 && length === 3 ? 2 : 1;
145
- }
146
-
147
158
  /**
148
159
  * @override
149
160
  * @returns {string}
150
161
  */
151
162
  text() {
152
163
  if (this.childNodes.length === 3) {
153
- const {children: [from, variant, to]} = this;
164
+ const {childNodes: [from, variant, to]} = this;
154
165
  return `${from.text()}=>${variant.text()}:${to.text()}`;
155
166
  }
156
167
  return super.text(':');
@@ -158,11 +169,10 @@ class ConverterRuleToken extends Token {
158
169
 
159
170
  /** 修改为不转换 */
160
171
  noConvert() {
161
- const {childNodes: {length}, lastChild} = this;
172
+ const {length} = this;
162
173
  for (let i = 0; i < length - 1; i++) { // ConverterRuleToken只能从前往后删除子节点
163
174
  this.removeAt(0);
164
175
  }
165
- lastChild.type = 'converter-rule-noconvert';
166
176
  }
167
177
 
168
178
  /**
@@ -174,16 +184,14 @@ class ConverterRuleToken extends Token {
174
184
  to = String(to);
175
185
  const config = this.getAttribute('config'),
176
186
  root = Parser.parse(`-{|${config.variants[0]}:${to}}-`, this.getAttribute('include'), undefined, config),
177
- {childNodes: {length}, firstElementChild} = root;
178
- if (length !== 1 || firstElementChild.type !== 'converter' || firstElementChild.childNodes.length !== 2
179
- || firstElementChild.lastElementChild.childNodes.length !== 2
180
- ) {
187
+ {length, firstChild: converter} = root,
188
+ {lastChild: converterRule, type, length: converterLength} = converter;
189
+ if (length !== 1 || type !== 'converter' || converterLength !== 2 || converterRule.childNodes.length !== 2) {
181
190
  throw new SyntaxError(`非法的转换目标:${noWrap(to)}`);
182
191
  }
183
- const {lastElementChild} = firstElementChild,
184
- {lastChild} = lastElementChild;
185
- lastElementChild.destroy(true);
186
- this.lastElementChild.safeReplaceWith(lastChild);
192
+ const {lastChild} = converterRule;
193
+ converterRule.destroy(true);
194
+ this.lastChild.safeReplaceWith(lastChild);
187
195
  }
188
196
 
189
197
  /**
@@ -202,7 +210,7 @@ class ConverterRuleToken extends Token {
202
210
  } else if (this.childNodes.length === 1) {
203
211
  super.insertAt(Parser.run(() => new AtomToken(variant, 'converter-rule-variant', config)), 0);
204
212
  } else {
205
- this.children.at(-2).setText(variant);
213
+ this.childNodes.at(-2).setText(variant);
206
214
  }
207
215
  }
208
216
 
@@ -220,16 +228,14 @@ class ConverterRuleToken extends Token {
220
228
  from = String(from);
221
229
  const config = this.getAttribute('config'),
222
230
  root = Parser.parse(`-{|${from}=>${variant}:}-`, this.getAttribute('include'), undefined, config),
223
- {childNodes: {length}, firstElementChild} = root;
224
- if (length !== 1 || firstElementChild.type !== 'converter' || firstElementChild.childNodes.length !== 2
225
- || firstElementChild.lastElementChild.childNodes.length !== 3
226
- ) {
231
+ {length, firstChild: converter} = root,
232
+ {type, length: converterLength, lastChild: converterRule} = converter;
233
+ if (length !== 1 || type !== 'converter' || converterLength !== 2 || converterRule.childNodes.length !== 3) {
227
234
  throw new SyntaxError(`非法的转换原文:${noWrap(from)}`);
228
- }
229
- if (unidirectional) {
230
- this.firstElementChild.safeReplaceWith(firstElementChild.lastElementChild.firstChild);
235
+ } else if (unidirectional) {
236
+ this.firstChild.safeReplaceWith(converterRule.firstChild);
231
237
  } else {
232
- super.insertAt(firstElementChild.lastElementChild.firstChild, 0);
238
+ super.insertAt(converterRule.firstChild, 0);
233
239
  }
234
240
  }
235
241
 
@@ -244,7 +250,7 @@ class ConverterRuleToken extends Token {
244
250
  /** 修改为双向转换 */
245
251
  makeBidirectional() {
246
252
  if (this.unidirectional) {
247
- super.removeAt(0);
253
+ this.removeAt(0);
248
254
  }
249
255
  }
250
256
  }
package/src/extLink.js CHANGED
@@ -41,7 +41,7 @@ class ExtLinkToken extends Token {
41
41
  /** 链接显示文字 */
42
42
  get innerText() {
43
43
  return this.childNodes.length > 1
44
- ? this.lastElementChild.text()
44
+ ? this.lastChild.text()
45
45
  : `[${this.getRootNode().querySelectorAll('ext-link[childElementCount=1]').indexOf(this) + 1}]`;
46
46
  }
47
47
 
@@ -52,38 +52,17 @@ class ExtLinkToken extends Token {
52
52
  * @param {accum} accum
53
53
  */
54
54
  constructor(url, space, text, config = Parser.getConfig(), accum = []) {
55
- super(undefined, config, true, accum, {AtomToken: 0, Token: 1});
56
- this.appendChild(new MagicLinkToken(url, true, config, accum));
55
+ super(undefined, config, true, accum, {MagicLinkToken: 0, Token: 1});
56
+ this.insertAt(new MagicLinkToken(url, true, config, accum));
57
57
  this.#space = space;
58
58
  if (text) {
59
59
  const inner = new Token(text, config, true, accum, {'Stage-7': ':', ConverterToken: ':'});
60
60
  inner.type = 'ext-link-text';
61
- this.appendChild(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
61
+ this.insertAt(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
62
62
  }
63
63
  this.getAttribute('protectChildren')(0);
64
64
  }
65
65
 
66
- /** @override */
67
- cloneNode() {
68
- const [url, text] = this.cloneChildNodes(),
69
- token = Parser.run(() => new ExtLinkToken(undefined, '', '', this.getAttribute('config')));
70
- token.firstElementChild.safeReplaceWith(url);
71
- if (text) {
72
- token.appendChild(text);
73
- }
74
- return token;
75
- }
76
-
77
- /** 修正空白字符 */
78
- #correct() {
79
- if (!this.#space && this.childNodes.length > 1
80
- // 都替换成`<`肯定不对,但无妨
81
- && /^[^[\]<>"{\0-\x1F\x7F\p{Zs}\uFFFD]/u.test(this.lastElementChild.text().replace(/&[lg]t;/u, '<'))
82
- ) {
83
- this.#space = ' ';
84
- }
85
- }
86
-
87
66
  /**
88
67
  * @override
89
68
  * @param {string} selector
@@ -95,7 +74,7 @@ class ExtLinkToken extends Token {
95
74
  return `[${super.toString(selector)}${this.#space}]`;
96
75
  }
97
76
  this.#correct();
98
- normalizeSpace(this.lastElementChild);
77
+ normalizeSpace(this.lastChild);
99
78
  return `[${super.toString(selector, this.#space)}]`;
100
79
  }
101
80
 
@@ -110,9 +89,38 @@ class ExtLinkToken extends Token {
110
89
  return this.#space.length;
111
90
  }
112
91
 
92
+ /** @override */
93
+ print() {
94
+ const {length} = this;
95
+ return super.print(length > 1 ? {pre: '[', sep: this.#space, post: ']'} : {pre: '[', post: `${this.#space}]`});
96
+ }
97
+
98
+ /** @override */
99
+ cloneNode() {
100
+ const [url, text] = this.cloneChildNodes();
101
+ return Parser.run(() => {
102
+ const token = new ExtLinkToken(undefined, '', '', this.getAttribute('config'));
103
+ token.firstChild.safeReplaceWith(url);
104
+ if (text) {
105
+ token.insertAt(text);
106
+ }
107
+ return token;
108
+ });
109
+ }
110
+
111
+ /** 修正空白字符 */
112
+ #correct() {
113
+ if (!this.#space && this.childNodes.length > 1
114
+ // 都替换成`<`肯定不对,但无妨
115
+ && /^[^[\]<>"{\0-\x1F\x7F\p{Zs}\uFFFD]/u.test(this.lastChild.text().replace(/&[lg]t;/u, '<'))
116
+ ) {
117
+ this.#space = ' ';
118
+ }
119
+ }
120
+
113
121
  /** @override */
114
122
  text() {
115
- normalizeSpace(this.children[1]);
123
+ normalizeSpace(this.childNodes[1]);
116
124
  return `[${super.text(' ')}]`;
117
125
  }
118
126
 
@@ -132,13 +140,13 @@ class ExtLinkToken extends Token {
132
140
  setTarget(url) {
133
141
  url = String(url);
134
142
  const root = Parser.parse(`[${url}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
135
- {childNodes: {length}, firstElementChild} = root;
136
- if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childNodes.length !== 1) {
143
+ {length, firstChild: extLink} = root;
144
+ if (length !== 1 || extLink.type !== 'ext-link' || extLink.childNodes.length !== 1) {
137
145
  throw new SyntaxError(`非法的外链目标:${url}`);
138
146
  }
139
- const {firstChild} = firstElementChild;
140
- firstElementChild.destroy(true);
141
- this.firstElementChild.safeReplaceWith(firstChild);
147
+ const {firstChild} = extLink;
148
+ extLink.destroy(true);
149
+ this.firstChild.safeReplaceWith(firstChild);
142
150
  }
143
151
 
144
152
  /**
@@ -149,15 +157,15 @@ class ExtLinkToken extends Token {
149
157
  setLinkText(text) {
150
158
  text = String(text);
151
159
  const root = Parser.parse(`[//url ${text}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
152
- {childNodes: {length}, firstElementChild} = root;
153
- if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childNodes.length !== 2) {
160
+ {length, firstChild: extLink} = root;
161
+ if (length !== 1 || extLink.type !== 'ext-link' || extLink.childNodes.length !== 2) {
154
162
  throw new SyntaxError(`非法的外链文字:${noWrap(text)}`);
155
163
  }
156
- const {lastChild} = firstElementChild;
164
+ const {lastChild} = extLink;
157
165
  if (this.childNodes.length === 1) {
158
- this.appendChild(lastChild);
166
+ this.insertAt(lastChild);
159
167
  } else {
160
- this.lastElementChild.safeReplaceWith(lastChild);
168
+ this.lastChild.safeReplaceWith(lastChild);
161
169
  }
162
170
  this.#space ||= ' ';
163
171
  }
package/src/gallery.js CHANGED
@@ -13,16 +13,23 @@ class GalleryToken extends Token {
13
13
  type = 'ext-inner';
14
14
  name = 'gallery';
15
15
 
16
+ /** 所有图片 */
17
+ get images() {
18
+ return this.childNodes.filter(({type}) => type === 'gallery-image');
19
+ }
20
+
16
21
  /**
17
22
  * @param {string} inner 标签内部wikitext
18
23
  * @param {accum} accum
19
24
  */
20
25
  constructor(inner, config = Parser.getConfig(), accum = []) {
21
- super(undefined, config, true, accum, {AstText: ':', GalleryImageToken: ':'});
26
+ super(undefined, config, true, accum, {AstText: ':', GalleryImageToken: ':', HiddenToken: ':'});
27
+ const newConfig = structuredClone(config);
28
+ newConfig.img = Object.fromEntries(Object.entries(config.img).filter(([, param]) => param !== 'width'));
22
29
  for (const line of inner?.split('\n') ?? []) {
23
30
  const matches = /^([^|]+)(?:\|(.*))?/u.exec(line);
24
31
  if (!matches) {
25
- this.appendChild(line.trim() ? new HiddenToken(line, undefined, config, [], {AstText: ':'}) : line);
32
+ super.insertAt(line.trim() ? new HiddenToken(line, undefined, config, [], {AstText: ':'}) : line);
26
33
  continue;
27
34
  }
28
35
  const [, file, alt] = matches;
@@ -33,21 +40,13 @@ class GalleryToken extends Token {
33
40
  title = this.normalizeTitle(file, 6, true);
34
41
  }
35
42
  if (title.valid) {
36
- this.appendChild(new GalleryImageToken(file, alt, title, config, accum));
43
+ super.insertAt(new GalleryImageToken(file, alt, title, newConfig, accum));
37
44
  } else {
38
- this.appendChild(new HiddenToken(line, undefined, config, [], {AstText: ':'}));
45
+ super.insertAt(new HiddenToken(line, undefined, config, [], {AstText: ':'}));
39
46
  }
40
47
  }
41
48
  }
42
49
 
43
- /** @override */
44
- cloneNode() {
45
- const cloned = this.cloneChildNodes(),
46
- token = Parser.run(() => new GalleryToken(undefined, this.getAttribute('config')));
47
- token.append(...cloned);
48
- return token;
49
- }
50
-
51
50
  /**
52
51
  * @override
53
52
  * @param {string} selector
@@ -61,6 +60,48 @@ class GalleryToken extends Token {
61
60
  return 1;
62
61
  }
63
62
 
63
+ /** @override */
64
+ print() {
65
+ return super.print({sep: '\n'});
66
+ }
67
+
68
+ /**
69
+ * @override
70
+ * @param {number} start 起始位置
71
+ */
72
+ lint(start = 0) {
73
+ const {top, left} = this.getRootNode().posFromIndex(start),
74
+ /** @type {LintError[]} */ errors = [];
75
+ for (let i = 0, cur = start; i < this.childNodes.length; i++) {
76
+ const child = this.childNodes[i],
77
+ str = String(child),
78
+ trimmed = str.trim();
79
+ if (child.type === 'hidden' && trimmed && !/^<!--.*-->$/u.test(trimmed)) {
80
+ errors.push({
81
+ message: '图库中的无效内容',
82
+ startLine: top + i,
83
+ endLine: top + i,
84
+ startCol: i ? 0 : left,
85
+ endCol: i ? str.length : left + str.length,
86
+ });
87
+ } else if (child.type !== 'hidden' && child.type !== 'text') {
88
+ errors.push(...child.lint(cur));
89
+ }
90
+ cur += str.length + 1;
91
+ }
92
+ return errors;
93
+ }
94
+
95
+ /** @override */
96
+ cloneNode() {
97
+ const cloned = this.cloneChildNodes();
98
+ return Parser.run(() => {
99
+ const token = new GalleryToken(undefined, this.getAttribute('config'));
100
+ token.append(...cloned);
101
+ return token;
102
+ });
103
+ }
104
+
64
105
  /** @override */
65
106
  text() {
66
107
  return super.text('\n').replaceAll(/\n\s*\n/gu, '\n');
@@ -79,11 +120,25 @@ class GalleryToken extends Token {
79
120
  } catch {
80
121
  title = this.normalizeTitle(file, 6, true);
81
122
  }
82
- if (!title.valid) {
83
- throw new SyntaxError(`非法的文件名:${file}`);
123
+ if (title.valid) {
124
+ const token = Parser.run(() => new GalleryImageToken(file, undefined, title, this.getAttribute('config')));
125
+ return this.insertAt(token, i);
126
+ }
127
+ throw new SyntaxError(`非法的文件名:${file}`);
128
+ }
129
+
130
+ /**
131
+ * @override
132
+ * @template {string|Token} T
133
+ * @param {T} token 待插入的节点
134
+ * @param {number} i 插入位置
135
+ * @throws `RangeError` 插入不可见内容
136
+ */
137
+ insertAt(token, i = 0) {
138
+ if (typeof token === 'string' && token.trim() || token instanceof HiddenToken) {
139
+ throw new RangeError('请勿向图库中插入不可见内容!');
84
140
  }
85
- const token = Parser.run(() => new GalleryImageToken(file, undefined, title, this.getAttribute('config')));
86
- return this.insertAt(token, i);
141
+ return super.insertAt(token, i);
87
142
  }
88
143
  }
89
144
 
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('../..'),
4
+ Token = require('..');
5
+
6
+ /**
7
+ * `<pre>`
8
+ * @classdesc `{childNodes: [...AstText|NoincludeToken]}`
9
+ */
10
+ class HasNowikiToken extends Token {
11
+ /**
12
+ * @param {string} wikitext wikitext
13
+ * @param {string} type type
14
+ * @param {accum} accum
15
+ */
16
+ constructor(wikitext, type, config = Parser.getConfig(), accum = []) {
17
+ const NoincludeToken = require('../nowiki/noinclude');
18
+ wikitext = wikitext.replaceAll(
19
+ /(<nowiki>)(.*?)(<\/nowiki>)/giu,
20
+ /** @type {function(...string): string} */ (_, opening, inner, closing) => {
21
+ new NoincludeToken(opening, config, accum);
22
+ new NoincludeToken(closing, config, accum);
23
+ return `\0${accum.length - 1}c\x7F${inner}\0${accum.length}c\x7F`;
24
+ },
25
+ );
26
+ super(wikitext, config, true, accum, {AstText: ':', NoincludeToken: ':'});
27
+ this.type = type;
28
+ }
29
+
30
+ /** @override */
31
+ cloneNode() {
32
+ const cloned = this.cloneChildNodes();
33
+ return Parser.run(() => {
34
+ const token = new HasNowikiToken(undefined, this.type, this.getAttribute('config'));
35
+ token.append(...cloned);
36
+ return token;
37
+ });
38
+ }
39
+ }
40
+
41
+ Parser.classes.HasNowikiToken = __filename;
42
+ module.exports = HasNowikiToken;