wikiparser-node 0.3.1 → 0.5.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 (80) hide show
  1. package/README.md +1 -1
  2. package/config/default.json +13 -17
  3. package/config/llwiki.json +11 -79
  4. package/config/moegirl.json +7 -1
  5. package/config/zhwiki.json +1269 -0
  6. package/index.js +130 -97
  7. package/lib/element.js +410 -518
  8. package/lib/node.js +493 -115
  9. package/lib/ranges.js +27 -19
  10. package/lib/text.js +175 -0
  11. package/lib/title.js +14 -6
  12. package/mixin/attributeParent.js +70 -24
  13. package/mixin/fixedToken.js +18 -10
  14. package/mixin/hidden.js +6 -4
  15. package/mixin/sol.js +39 -12
  16. package/package.json +17 -4
  17. package/parser/brackets.js +18 -18
  18. package/parser/commentAndExt.js +16 -14
  19. package/parser/converter.js +14 -13
  20. package/parser/externalLinks.js +12 -11
  21. package/parser/hrAndDoubleUnderscore.js +24 -14
  22. package/parser/html.js +8 -7
  23. package/parser/links.js +13 -13
  24. package/parser/list.js +12 -11
  25. package/parser/magicLinks.js +11 -10
  26. package/parser/quotes.js +6 -5
  27. package/parser/selector.js +175 -0
  28. package/parser/table.js +31 -24
  29. package/src/arg.js +91 -43
  30. package/src/atom/hidden.js +5 -2
  31. package/src/atom/index.js +17 -9
  32. package/src/attribute.js +210 -101
  33. package/src/converter.js +78 -43
  34. package/src/converterFlags.js +104 -45
  35. package/src/converterRule.js +136 -78
  36. package/src/extLink.js +81 -27
  37. package/src/gallery.js +63 -20
  38. package/src/heading.js +58 -20
  39. package/src/html.js +138 -48
  40. package/src/imageParameter.js +93 -58
  41. package/src/index.js +314 -186
  42. package/src/link/category.js +22 -54
  43. package/src/link/file.js +83 -32
  44. package/src/link/galleryImage.js +21 -7
  45. package/src/link/index.js +170 -81
  46. package/src/magicLink.js +64 -14
  47. package/src/nowiki/comment.js +36 -10
  48. package/src/nowiki/dd.js +37 -22
  49. package/src/nowiki/doubleUnderscore.js +21 -7
  50. package/src/nowiki/hr.js +11 -7
  51. package/src/nowiki/index.js +16 -9
  52. package/src/nowiki/list.js +2 -2
  53. package/src/nowiki/noinclude.js +8 -4
  54. package/src/nowiki/quote.js +38 -7
  55. package/src/onlyinclude.js +24 -7
  56. package/src/parameter.js +102 -62
  57. package/src/syntax.js +23 -20
  58. package/src/table/index.js +282 -174
  59. package/src/table/td.js +112 -61
  60. package/src/table/tr.js +135 -74
  61. package/src/tagPair/ext.js +30 -23
  62. package/src/tagPair/include.js +26 -11
  63. package/src/tagPair/index.js +72 -29
  64. package/src/transclude.js +235 -127
  65. package/tool/index.js +42 -32
  66. package/util/debug.js +21 -18
  67. package/util/diff.js +76 -0
  68. package/util/lint.js +40 -0
  69. package/util/string.js +56 -26
  70. package/.eslintrc.json +0 -319
  71. package/errors/README +0 -1
  72. package/jsconfig.json +0 -7
  73. package/printed/README +0 -1
  74. package/typings/element.d.ts +0 -28
  75. package/typings/index.d.ts +0 -52
  76. package/typings/node.d.ts +0 -23
  77. package/typings/parser.d.ts +0 -9
  78. package/typings/table.d.ts +0 -14
  79. package/typings/token.d.ts +0 -22
  80. package/typings/tool.d.ts +0 -10
@@ -2,7 +2,7 @@
2
2
 
3
3
  const {undo} = require('../util/debug'),
4
4
  {noWrap} = require('../util/string'),
5
- /** @type {Parser} */ Parser = require('..'),
5
+ Parser = require('..'),
6
6
  Token = require('.'),
7
7
  AtomToken = require('./atom');
8
8
 
@@ -12,138 +12,192 @@ const {undo} = require('../util/debug'),
12
12
  */
13
13
  class ConverterRuleToken extends Token {
14
14
  type = 'converter-rule';
15
- variant = '';
16
- unidirectional = false;
17
- bidirectional = false;
15
+
16
+ /** 语言变体 */
17
+ get variant() {
18
+ return this.childNodes.at(-2)?.text()?.trim() ?? '';
19
+ }
20
+
21
+ set variant(variant) {
22
+ this.setVariant(variant);
23
+ }
24
+
25
+ /** 是否是单向转换 */
26
+ get unidirectional() {
27
+ return this.childNodes.length === 3;
28
+ }
29
+
30
+ set unidirectional(unidirectional) {
31
+ if (unidirectional === false) {
32
+ this.makeBidirectional();
33
+ }
34
+ }
35
+
36
+ /** 是否是双向转换 */
37
+ get bidirectional() {
38
+ return this.childNodes.length === 2;
39
+ }
18
40
 
19
41
  /**
20
- * @param {string} rule
42
+ * @param {string} rule 转换规则
43
+ * @param {boolean} hasColon 是否带有":"
21
44
  * @param {accum} accum
22
45
  */
23
46
  constructor(rule, hasColon = true, config = Parser.getConfig(), accum = []) {
24
47
  super(undefined, config, true, accum, {AtomToken: ':'});
25
- if (!hasColon) {
26
- super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
27
- } else {
48
+ if (hasColon) {
28
49
  const i = rule.indexOf(':'),
29
50
  j = rule.slice(0, i).indexOf('=>'),
30
- v = (j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i)).trim(),
51
+ v = j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i),
31
52
  {variants} = config;
32
- if (!variants.includes(v)) {
33
- super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
34
- } else {
53
+ if (variants.includes(v.trim())) {
35
54
  super.insertAt(new AtomToken(v, 'converter-rule-variant', config, accum));
36
55
  super.insertAt(new AtomToken(rule.slice(i + 1), 'converter-rule-to', config, accum));
37
- if (j === -1) {
38
- this.bidirectional = true;
39
- } else {
56
+ if (j !== -1) {
40
57
  super.insertAt(new AtomToken(rule.slice(0, j), 'converter-rule-from', config, accum), 0);
41
- this.unidirectional = true;
42
58
  }
59
+ } else {
60
+ super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
43
61
  }
62
+ } else {
63
+ super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
44
64
  }
45
- this.seal(['variant', 'unidirectional', 'bidirectional']);
65
+ this.getAttribute('protectChildren')('1:');
46
66
  }
47
67
 
68
+ /** @override */
48
69
  cloneNode() {
49
- const cloned = this.cloneChildren(),
70
+ const cloned = this.cloneChildNodes(),
50
71
  placeholders = ['', 'zh:', '=>zh:'],
51
72
  placeholder = placeholders[cloned.length - 1],
52
73
  token = Parser.run(() => new ConverterRuleToken(placeholder, placeholder, this.getAttribute('config')));
53
74
  for (let i = 0; i < cloned.length; i++) {
54
- token.children[i].safeReplaceWith(cloned[i]);
75
+ token.childNodes[i].safeReplaceWith(cloned[i]);
55
76
  }
56
77
  token.afterBuild();
57
78
  return token;
58
79
  }
59
80
 
81
+ /** @override */
60
82
  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
- }
83
+ const /** @type {AstListener} */ converterRuleListener = (e, data) => {
84
+ const {childNodes} = this,
85
+ {prevTarget} = e;
86
+ if (childNodes.length > 1 && childNodes.at(-2) === prevTarget) {
87
+ const v = prevTarget.text().trim(),
88
+ {variants} = this.getAttribute('config');
89
+ if (!variants.includes(v)) {
90
+ undo(e, data);
91
+ throw new Error(`无效的语言变体:${v}`);
77
92
  }
78
- };
93
+ }
94
+ };
79
95
  this.addEventListener(['remove', 'insert', 'text', 'replace'], converterRuleListener);
80
96
  return this;
81
97
  }
82
98
 
83
99
  /**
84
- * @param {number} i
100
+ * @override
101
+ * @param {number} i 移除位置
85
102
  * @returns {AtomToken}
103
+ * @throws `Error` 至少保留1个子节点
86
104
  */
87
105
  removeAt(i) {
88
- if (i !== 0 && i !== -this.childNodes.length) {
89
- throw new RangeError(`${this.constructor.name} 禁止移除第 ${i} 个子节点!`);
106
+ if (this.childNodes.length === 1) {
107
+ throw new Error(`${this.constructor.name} 需至少保留 1 个子节点!`);
90
108
  }
91
- return super.removeAt(i);
109
+ const removed = super.removeAt(i);
110
+ if (this.childNodes.length === 1) {
111
+ this.firstChild.type = 'converter-rule-noconvert';
112
+ }
113
+ return removed;
92
114
  }
93
115
 
116
+ /**
117
+ * @override
118
+ * @throws `Error` 请勿手动插入子节点
119
+ */
94
120
  insertAt() {
95
- throw new Error('转换规则语法复杂,请勿尝试手动插入子节点!');
121
+ throw new Error(`转换规则语法复杂,请勿尝试对 ${this.constructor.name} 手动插入子节点!`);
96
122
  }
97
123
 
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()}`;
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 {childNodes: [from, variant, to]} = this;
132
+ return `${from.toString(selector)}=>${variant.toString(selector)}:${to.toString(selector)}`;
103
133
  }
104
- return super.toString(':');
134
+ return super.toString(selector, ':');
105
135
  }
106
136
 
107
- /** @param {number} i */
137
+ /**
138
+ * @override
139
+ * @param {number} i 子节点序号
140
+ */
108
141
  getGaps(i = 0) {
109
- const {length} = this.childNodes;
142
+ const {childNodes: {length}} = this;
110
143
  i = i < 0 ? i + length : i;
111
144
  return i === 0 && length === 3 ? 2 : 1;
112
145
  }
113
146
 
114
- /** @returns {string} */
147
+ /** @override */
148
+ print() {
149
+ if (this.childNodes.length === 3) {
150
+ const {childNodes: [from, variant, to]} = this;
151
+ return `<span class="wpb-converter-rule">${from.print()}=>${variant.print()}:${to.print()}</span>`;
152
+ }
153
+ return super.print({sep: ':'});
154
+ }
155
+
156
+ /**
157
+ * @override
158
+ * @returns {string}
159
+ */
115
160
  text() {
116
161
  if (this.childNodes.length === 3) {
117
- const [from, variant, to] = this.children;
162
+ const {childNodes: [from, variant, to]} = this;
118
163
  return `${from.text()}=>${variant.text()}:${to.text()}`;
119
164
  }
120
165
  return super.text(':');
121
166
  }
122
167
 
168
+ /** 修改为不转换 */
123
169
  noConvert() {
124
- for (let i = this.childNodes.length - 2; i >= 0; i--) {
125
- super.removeAt(i);
170
+ const {childNodes: {length}, lastChild} = this;
171
+ for (let i = 0; i < length - 1; i++) { // ConverterRuleToken只能从前往后删除子节点
172
+ this.removeAt(0);
126
173
  }
127
- this.setAttribute('unidirectional', false).setAttribute('bidirectional', false).setAttribute('variant', '');
174
+ lastChild.type = 'converter-rule-noconvert';
128
175
  }
129
176
 
130
- /** @param {string} to */
177
+ /**
178
+ * 设置转换目标
179
+ * @param {string} to 转换目标
180
+ * @throws `SyntaxError` 非法的转换目标
181
+ */
131
182
  setTo(to) {
132
183
  to = String(to);
133
184
  const config = this.getAttribute('config'),
134
185
  root = Parser.parse(`-{|${config.variants[0]}:${to}}-`, this.getAttribute('include'), undefined, config),
135
- {childNodes: {length}, firstElementChild} = root;
136
- if (length !== 1 || firstElementChild.type !== 'converter' || firstElementChild.childNodes.length !== 2
137
- || firstElementChild.lastElementChild.childNodes.length !== 2
138
- ) {
186
+ {childNodes: {length}, firstChild: converter} = root,
187
+ {lastChild: converterRule, type, childNodes: {length: converterLength}} = converter;
188
+ if (length !== 1 || type !== 'converter' || converterLength !== 2 || converterRule.childNodes.length !== 2) {
139
189
  throw new SyntaxError(`非法的转换目标:${noWrap(to)}`);
140
190
  }
141
- const {lastChild} = firstElementChild.lastElementChild;
142
- firstElementChild.lastElementChild.removeAt(0);
143
- this.lastElementChild.safeReplaceWith(lastChild);
191
+ const {lastChild} = converterRule;
192
+ converterRule.destroy(true);
193
+ this.lastChild.safeReplaceWith(lastChild);
144
194
  }
145
195
 
146
- /** @param {string} variant */
196
+ /**
197
+ * 设置语言变体
198
+ * @param {string} variant 语言变体
199
+ * @throws `RangeError` 无效的语言变体
200
+ */
147
201
  setVariant(variant) {
148
202
  if (typeof variant !== 'string') {
149
203
  this.typeError('setVariant', 'String');
@@ -154,45 +208,49 @@ class ConverterRuleToken extends Token {
154
208
  throw new RangeError(`无效的语言变体:${v}`);
155
209
  } else if (this.childNodes.length === 1) {
156
210
  super.insertAt(Parser.run(() => new AtomToken(variant, 'converter-rule-variant', config)), 0);
157
- this.setAttribute('bidirectional', true);
158
211
  } else {
159
- this.children.at(-2).setText(variant);
212
+ this.childNodes.at(-2).setText(variant);
160
213
  }
161
- this.setAttribute('variant', v);
162
214
  }
163
215
 
164
- /** @param {string} from */
216
+ /**
217
+ * 设置转换原文
218
+ * @param {string} from 转换原文
219
+ * @throws `Error` 尚未指定语言变体
220
+ * @throws `SyntaxError` 非法的转换原文
221
+ */
165
222
  setFrom(from) {
166
- const {variant} = this;
223
+ const {variant, unidirectional} = this;
167
224
  if (!variant) {
168
225
  throw new Error('请先指定语言变体!');
169
226
  }
170
227
  from = String(from);
171
228
  const config = this.getAttribute('config'),
172
229
  root = Parser.parse(`-{|${from}=>${variant}:}-`, this.getAttribute('include'), undefined, config),
173
- {childNodes: {length}, firstElementChild} = root;
174
- if (length !== 1 || firstElementChild.type !== 'converter' || firstElementChild.childNodes.length !== 2
175
- || firstElementChild.lastElementChild.childNodes.length !== 3
176
- ) {
230
+ {childNodes: {length}, firstChild: converter} = root,
231
+ {type, childNodes: {length: converterLength}, lastChild: converterRule} = converter;
232
+ if (length !== 1 || type !== 'converter' || converterLength !== 2 || converterRule.childNodes.length !== 3) {
177
233
  throw new SyntaxError(`非法的转换原文:${noWrap(from)}`);
178
234
  }
179
- if (this.unidirectional) {
180
- this.firstElementChild.safeReplaceWith(firstElementChild.lastElementChild.firstChild);
235
+ if (unidirectional) {
236
+ this.firstChild.safeReplaceWith(converterRule.firstChild);
181
237
  } else {
182
- super.insertAt(firstElementChild.lastElementChild.firstChild, 0);
183
- this.setAttribute('unidirectional', true).setAttribute('bidirectional', false);
238
+ super.insertAt(converterRule.firstChild, 0);
184
239
  }
185
240
  }
186
241
 
187
- /** @param {string} from */
242
+ /**
243
+ * 修改为单向转换
244
+ * @param {string} from 转换来源
245
+ */
188
246
  makeUnidirectional(from) {
189
247
  this.setFrom(from);
190
248
  }
191
249
 
250
+ /** 修改为双向转换 */
192
251
  makeBidirectional() {
193
252
  if (this.unidirectional) {
194
253
  super.removeAt(0);
195
- this.setAttribute('unidirectional', false).setAttribute('bidirectional', true);
196
254
  }
197
255
  }
198
256
  }
package/src/extLink.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {noWrap, normalizeSpace} = require('../util/string'),
4
- /** @type {Parser} */ Parser = require('..'),
4
+ Parser = require('..'),
5
5
  Token = require('.'),
6
6
  MagicLinkToken = require('./magicLink');
7
7
 
@@ -13,19 +13,42 @@ class ExtLinkToken extends Token {
13
13
  type = 'ext-link';
14
14
  #space;
15
15
 
16
- /** @this {{firstChild: MagicLinkToken}} */
16
+ /**
17
+ * 协议
18
+ * @this {{firstChild: MagicLinkToken}}
19
+ */
17
20
  get protocol() {
18
21
  return this.firstChild.protocol;
19
22
  }
23
+
20
24
  /** @this {{firstChild: MagicLinkToken}} */
21
25
  set protocol(value) {
22
26
  this.firstChild.protocol = value;
23
27
  }
24
28
 
25
29
  /**
26
- * @param {string} url
27
- * @param {string} space
28
- * @param {string} text
30
+ * 和内链保持一致
31
+ * @this {{firstChild: MagicLinkToken}}
32
+ */
33
+ get link() {
34
+ return this.firstChild.link;
35
+ }
36
+
37
+ set link(url) {
38
+ this.setTarget(url);
39
+ }
40
+
41
+ /** 链接显示文字 */
42
+ get innerText() {
43
+ return this.childNodes.length > 1
44
+ ? this.lastChild.text()
45
+ : `[${this.getRootNode().querySelectorAll('ext-link[childElementCount=1]').indexOf(this) + 1}]`;
46
+ }
47
+
48
+ /**
49
+ * @param {string} url 网址
50
+ * @param {string} space 空白字符
51
+ * @param {string} text 链接文字
29
52
  * @param {accum} accum
30
53
  */
31
54
  constructor(url, space, text, config = Parser.getConfig(), accum = []) {
@@ -37,79 +60,110 @@ class ExtLinkToken extends Token {
37
60
  inner.type = 'ext-link-text';
38
61
  this.appendChild(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
39
62
  }
40
- this.protectChildren(0);
63
+ this.getAttribute('protectChildren')(0);
41
64
  }
42
65
 
66
+ /** @override */
43
67
  cloneNode() {
44
- const [url, text] = this.cloneChildren(),
68
+ const [url, text] = this.cloneChildNodes(),
45
69
  token = Parser.run(() => new ExtLinkToken(undefined, '', '', this.getAttribute('config')));
46
- token.firstElementChild.safeReplaceWith(url);
70
+ token.firstChild.safeReplaceWith(url);
47
71
  if (text) {
48
72
  token.appendChild(text);
49
73
  }
50
74
  return token;
51
75
  }
52
76
 
77
+ /** 修正空白字符 */
53
78
  #correct() {
54
79
  if (!this.#space && this.childNodes.length > 1
55
80
  // 都替换成`<`肯定不对,但无妨
56
- && /^[^[\]<>"{\0-\x1f\x7f\p{Zs}\ufffd]/u.test(this.lastElementChild.text().replace(/&[lg]t;/, '<'))
81
+ && /^[^[\]<>"{\0-\x1F\x7F\p{Zs}\uFFFD]/u.test(this.lastChild.text().replace(/&[lg]t;/u, '<'))
57
82
  ) {
58
83
  this.#space = ' ';
59
84
  }
60
85
  }
61
86
 
62
- toString() {
87
+ /**
88
+ * @override
89
+ * @param {string} selector
90
+ */
91
+ toString(selector) {
92
+ if (selector && this.matches(selector)) {
93
+ return '';
94
+ } else if (this.childNodes.length === 1) {
95
+ return `[${super.toString(selector)}${this.#space}]`;
96
+ }
63
97
  this.#correct();
64
- return `[${this.firstElementChild.toString()}${this.#space}${normalizeSpace(this.children[1])}]`;
98
+ normalizeSpace(this.lastChild);
99
+ return `[${super.toString(selector, this.#space)}]`;
65
100
  }
66
101
 
102
+ /** @override */
67
103
  getPadding() {
68
- this.#correct();
69
104
  return 1;
70
105
  }
71
106
 
107
+ /** @override */
72
108
  getGaps() {
73
109
  this.#correct();
74
110
  return this.#space.length;
75
111
  }
76
112
 
113
+ /** @override */
114
+ print() {
115
+ const {childNodes: {length}} = this;
116
+ return super.print(length > 1 ? {pre: '[', sep: this.#space, post: ']'} : {pre: '[', post: `${this.#space}]`});
117
+ }
118
+
119
+ /** @override */
77
120
  text() {
78
- return `[${super.text(' ').replaceAll('\n', ' ')}]`;
121
+ normalizeSpace(this.childNodes[1]);
122
+ return `[${super.text(' ')}]`;
79
123
  }
80
124
 
81
- /** @this {ExtLinkToken & {firstElementChild: MagicLinkToken}} */
125
+ /**
126
+ * 获取网址
127
+ * @this {{firstChild: MagicLinkToken}}
128
+ */
82
129
  getUrl() {
83
- return this.firstElementChild.getUrl();
130
+ return this.firstChild.getUrl();
84
131
  }
85
132
 
86
- /** @param {string|URL} url */
133
+ /**
134
+ * 设置链接目标
135
+ * @param {string|URL} url 网址
136
+ * @throws `SyntaxError` 非法的外链目标
137
+ */
87
138
  setTarget(url) {
88
139
  url = String(url);
89
140
  const root = Parser.parse(`[${url}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
90
- {childNodes: {length}, firstElementChild} = root;
91
- if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childNodes.length !== 1) {
141
+ {childNodes: {length}, firstChild: extLink} = root;
142
+ if (length !== 1 || extLink.type !== 'ext-link' || extLink.childNodes.length !== 1) {
92
143
  throw new SyntaxError(`非法的外链目标:${url}`);
93
144
  }
94
- const {firstChild} = firstElementChild;
95
- root.destroy();
96
- firstElementChild.destroy();
97
- this.firstElementChild.safeReplaceWith(firstChild);
145
+ const {firstChild} = extLink;
146
+ extLink.destroy(true);
147
+ this.firstChild.safeReplaceWith(firstChild);
98
148
  }
99
149
 
100
- /** @param {string} text */
150
+ /**
151
+ * 设置链接显示文字
152
+ * @param {string} text 链接显示文字
153
+ * @throws `SyntaxError` 非法的链接显示文字
154
+ */
101
155
  setLinkText(text) {
102
156
  text = String(text);
103
157
  const root = Parser.parse(`[//url ${text}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
104
- {childNodes: {length}, firstElementChild} = root;
105
- if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childNodes.length !== 2) {
158
+ {childNodes: {length}, firstChild: extLink} = root;
159
+ if (length !== 1 || extLink.type !== 'ext-link' || extLink.childNodes.length !== 2) {
106
160
  throw new SyntaxError(`非法的外链文字:${noWrap(text)}`);
107
161
  }
108
- const {lastChild} = firstElementChild;
162
+ const {lastChild} = extLink;
109
163
  if (this.childNodes.length === 1) {
110
164
  this.appendChild(lastChild);
111
165
  } else {
112
- this.lastElementChild.safeReplaceWith(lastChild);
166
+ this.lastChild.safeReplaceWith(lastChild);
113
167
  }
114
168
  this.#space ||= ' ';
115
169
  }
package/src/gallery.js CHANGED
@@ -1,28 +1,28 @@
1
1
  'use strict';
2
2
 
3
- const {text} = require('../util/string'),
4
- /** @type {Parser} */ Parser = require('..'),
3
+ const Parser = require('..'),
5
4
  Token = require('.'),
6
- GalleryImageToken = require('./link/galleryImage');
5
+ GalleryImageToken = require('./link/galleryImage'),
6
+ HiddenToken = require('./atom/hidden');
7
7
 
8
8
  /**
9
9
  * gallery标签
10
- * @classdesc `{childNodes: (string|FileToken)[]]}`
10
+ * @classdesc `{childNodes: ...(GalleryImageToken|HiddenToken|AstText)}`
11
11
  */
12
12
  class GalleryToken extends Token {
13
13
  type = 'ext-inner';
14
14
  name = 'gallery';
15
15
 
16
16
  /**
17
- * @param {string} inner
17
+ * @param {string} inner 标签内部wikitext
18
18
  * @param {accum} accum
19
19
  */
20
20
  constructor(inner, config = Parser.getConfig(), accum = []) {
21
- super(undefined, config, true, accum, {String: ':', GalleryImageToken: ':'});
21
+ super(undefined, config, true, accum, {AstText: ':', GalleryImageToken: ':'});
22
22
  for (const line of inner?.split('\n') ?? []) {
23
- const matches = /^([^|]+)(?:\|(.*))?/.exec(line);
23
+ const matches = /^([^|]+)(?:\|(.*))?/u.exec(line);
24
24
  if (!matches) {
25
- this.appendChild(line);
25
+ this.appendChild(line.trim() ? new HiddenToken(line, undefined, config, [], {AstText: ':'}) : line);
26
26
  continue;
27
27
  }
28
28
  const [, file, alt] = matches;
@@ -32,34 +32,51 @@ class GalleryToken extends Token {
32
32
  } catch {
33
33
  title = this.normalizeTitle(file, 6, true);
34
34
  }
35
- if (!title.valid) {
36
- this.appendChild(line);
37
- } else {
35
+ if (title.valid) {
38
36
  this.appendChild(new GalleryImageToken(file, alt, title, config, accum));
37
+ } else {
38
+ this.appendChild(new HiddenToken(line, undefined, config, [], {AstText: ':'}));
39
39
  }
40
40
  }
41
41
  }
42
42
 
43
+ /** @override */
43
44
  cloneNode() {
44
- const cloned = this.cloneChildren(),
45
+ const cloned = this.cloneChildNodes(),
45
46
  token = Parser.run(() => new GalleryToken(undefined, this.getAttribute('config')));
46
47
  token.append(...cloned);
47
48
  return token;
48
49
  }
49
50
 
50
- toString() {
51
- return super.toString('\n');
51
+ /**
52
+ * @override
53
+ * @param {string} selector
54
+ */
55
+ toString(selector) {
56
+ return super.toString(selector, '\n');
52
57
  }
53
58
 
59
+ /** @override */
54
60
  getGaps() {
55
61
  return 1;
56
62
  }
57
63
 
64
+ /** @override */
65
+ print() {
66
+ return super.print({sep: '\n'});
67
+ }
68
+
69
+ /** @override */
58
70
  text() {
59
- return text(this.children, '\n');
71
+ return super.text('\n').replaceAll(/\n\s*\n/gu, '\n');
60
72
  }
61
73
 
62
- /** @param {string} file */
74
+ /**
75
+ * 插入图片
76
+ * @param {string} file 图片文件名
77
+ * @param {number} i 插入位置
78
+ * @throws `SyntaxError` 非法的文件名
79
+ */
63
80
  insertImage(file, i = this.childNodes.length) {
64
81
  let title;
65
82
  try {
@@ -67,11 +84,37 @@ class GalleryToken extends Token {
67
84
  } catch {
68
85
  title = this.normalizeTitle(file, 6, true);
69
86
  }
70
- if (!title.valid) {
71
- throw new SyntaxError(`非法的文件名:${file}`);
87
+ if (title.valid) {
88
+ const token = Parser.run(() => new GalleryImageToken(file, undefined, title, this.getAttribute('config')));
89
+ return this.insertAt(token, i);
90
+ }
91
+ throw new SyntaxError(`非法的文件名:${file}`);
92
+ }
93
+
94
+ /**
95
+ * @override
96
+ * @param {number} start 起始位置
97
+ */
98
+ lint(start = 0) {
99
+ const {top, left} = this.getRootNode().posFromIndex(start),
100
+ /** @type {LintError[]} */ errors = [];
101
+ for (let i = 0, cur = start; i < this.childNodes.length; i++) {
102
+ const child = this.childNodes[i],
103
+ str = String(child);
104
+ if (child.type === 'hidden' && str.trim() && !/^<!--.*-->$/u.test(str)) {
105
+ errors.push({
106
+ message: '图库中的无效内容',
107
+ startLine: top + i,
108
+ endLine: top + i,
109
+ startCol: i ? 0 : left,
110
+ endCol: i ? str.length : left + str.length,
111
+ });
112
+ } else if (child.type !== 'hidden' && child.type !== 'text') {
113
+ errors.push(...child.lint(cur));
114
+ }
115
+ cur += str.length + 1;
72
116
  }
73
- const token = Parser.run(() => new GalleryImageToken(file, undefined, title, this.getAttribute('config')));
74
- return this.insertAt(token, i);
117
+ return errors;
75
118
  }
76
119
  }
77
120