wikiparser-node 0.7.0-m → 0.7.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 +39 -0
  2. package/config/llwiki.json +35 -0
  3. package/config/moegirl.json +44 -0
  4. package/config/zhwiki.json +466 -0
  5. package/index.js +259 -11
  6. package/lib/element.js +481 -7
  7. package/lib/node.js +552 -6
  8. package/lib/ranges.js +130 -0
  9. package/lib/text.js +112 -21
  10. package/lib/title.js +27 -0
  11. package/mixin/attributeParent.js +117 -0
  12. package/mixin/fixedToken.js +40 -0
  13. package/mixin/hidden.js +3 -0
  14. package/mixin/singleLine.js +31 -0
  15. package/mixin/sol.js +65 -0
  16. package/package.json +5 -4
  17. package/parser/brackets.js +1 -0
  18. package/parser/commentAndExt.js +4 -3
  19. package/parser/converter.js +1 -0
  20. package/parser/externalLinks.js +1 -0
  21. package/parser/hrAndDoubleUnderscore.js +1 -0
  22. package/parser/html.js +1 -0
  23. package/parser/links.js +5 -4
  24. package/parser/list.js +1 -0
  25. package/parser/magicLinks.js +5 -4
  26. package/parser/quotes.js +2 -1
  27. package/parser/selector.js +177 -0
  28. package/parser/table.js +1 -0
  29. package/src/arg.js +116 -2
  30. package/src/atom/hidden.js +2 -0
  31. package/src/atom/index.js +17 -0
  32. package/src/attribute.js +171 -4
  33. package/src/attributes.js +306 -4
  34. package/src/charinsert.js +57 -1
  35. package/src/converter.js +108 -2
  36. package/src/converterFlags.js +187 -0
  37. package/src/converterRule.js +184 -1
  38. package/src/extLink.js +120 -1
  39. package/src/gallery.js +55 -5
  40. package/src/hasNowiki/index.js +12 -0
  41. package/src/hasNowiki/pre.js +12 -0
  42. package/src/heading.js +55 -4
  43. package/src/html.js +118 -3
  44. package/src/imageParameter.js +176 -5
  45. package/src/imagemap.js +60 -1
  46. package/src/imagemapLink.js +13 -1
  47. package/src/index.js +527 -3
  48. package/src/link/category.js +37 -1
  49. package/src/link/file.js +159 -2
  50. package/src/link/galleryImage.js +59 -1
  51. package/src/link/index.js +269 -1
  52. package/src/magicLink.js +90 -9
  53. package/src/nested/choose.js +1 -0
  54. package/src/nested/combobox.js +1 -0
  55. package/src/nested/index.js +30 -3
  56. package/src/nested/references.js +1 -0
  57. package/src/nowiki/comment.js +25 -1
  58. package/src/nowiki/dd.js +47 -1
  59. package/src/nowiki/doubleUnderscore.js +31 -1
  60. package/src/nowiki/hr.js +20 -1
  61. package/src/nowiki/index.js +23 -1
  62. package/src/nowiki/list.js +5 -2
  63. package/src/nowiki/noinclude.js +14 -0
  64. package/src/nowiki/quote.js +16 -2
  65. package/src/onlyinclude.js +26 -1
  66. package/src/paramTag/index.js +24 -1
  67. package/src/paramTag/inputbox.js +44 -0
  68. package/src/parameter.js +148 -6
  69. package/src/syntax.js +68 -0
  70. package/src/table/index.js +940 -2
  71. package/src/table/td.js +225 -5
  72. package/src/table/tr.js +247 -2
  73. package/src/tagPair/ext.js +24 -4
  74. package/src/tagPair/include.js +24 -0
  75. package/src/tagPair/index.js +52 -2
  76. package/src/transclude.js +512 -11
  77. package/tool/index.js +1202 -0
  78. package/util/debug.js +73 -0
  79. package/util/string.js +48 -1
  80. package/config/minimum.json +0 -142
package/src/table/td.js CHANGED
@@ -1,15 +1,21 @@
1
1
  'use strict';
2
2
 
3
3
  const {generateForChild} = require('../../util/lint'),
4
+ fixedToken = require('../../mixin/fixedToken'),
5
+ {typeError} = require('../../util/debug'),
6
+ {isPlainObject} = require('../../util/base'),
4
7
  Parser = require('../..'),
5
8
  Token = require('..'),
6
9
  TrToken = require('./tr');
7
10
 
11
+ const aliases = {td: '\n|', th: '\n!', caption: '\n|+'},
12
+ openingPattern = /^(?:\n[\S\n]*(?:[|!]|\|\+|\{\{\s*!\s*\}\}\+?)|(?:\||\{\{\s*!\s*\}\}){2}|!!|\{\{\s*!!\s*\}\})$/u;
13
+
8
14
  /**
9
15
  * `<td>`、`<th>`和`<caption>`
10
16
  * @classdesc `{childNodes: [SyntaxToken, AttributesToken, Token]}`
11
17
  */
12
- class TdToken extends TrToken {
18
+ class TdToken extends fixedToken(TrToken) {
13
19
  type = 'td';
14
20
  #innerSyntax = '';
15
21
 
@@ -21,13 +27,46 @@ class TdToken extends TrToken {
21
27
  return this.getSyntax().subtype;
22
28
  }
23
29
 
30
+ set subtype(subtype) {
31
+ this.setSyntax(subtype);
32
+ }
33
+
34
+ /** rowspan */
35
+ get rowspan() {
36
+ return this.getAttr('rowspan');
37
+ }
38
+
39
+ set rowspan(rowspan) {
40
+ this.setAttr('rowspan', rowspan);
41
+ }
42
+
43
+ /** colspan */
44
+ get colspan() {
45
+ return this.getAttr('colspan');
46
+ }
47
+
48
+ set colspan(colspan) {
49
+ this.setAttr('colspan', colspan);
50
+ }
51
+
52
+ /** 内部wikitext */
53
+ get innerText() {
54
+ return this.lastChild.text();
55
+ }
56
+
57
+ /** 是否位于行首 */
58
+ isIndependent() {
59
+ return this.firstChild.text()[0] === '\n';
60
+ }
61
+
24
62
  /**
25
63
  * 获取单元格语法信息
26
- * @returns {{subtype: 'td'|'th'|'caption'}}
64
+ * @returns {{subtype: 'td'|'th'|'caption', escape: boolean, correction: boolean}}
27
65
  * @complexity `n`
28
66
  */
29
67
  getSyntax() {
30
68
  const syntax = this.firstChild.text(),
69
+ esc = syntax.includes('{{'),
31
70
  char = syntax.at(-1);
32
71
  let subtype = 'td';
33
72
  if (char === '!') {
@@ -35,7 +74,23 @@ class TdToken extends TrToken {
35
74
  } else if (char === '+') {
36
75
  subtype = 'caption';
37
76
  }
38
- return {subtype};
77
+ if (this.isIndependent()) {
78
+ return {subtype, escape: esc, correction: false};
79
+ }
80
+ const {previousSibling} = this;
81
+ if (previousSibling?.type !== 'td') {
82
+ return {subtype, escape: esc, correction: true};
83
+ }
84
+ const result = previousSibling.getSyntax();
85
+ result.escape ||= esc;
86
+ result.correction = previousSibling.lastChild
87
+ .toString('comment, ext, include, noinclude, arg, template, magic-word')
88
+ .includes('\n');
89
+ if (subtype === 'th' && result.subtype !== 'th') {
90
+ result.subtype = 'th';
91
+ result.correction = true;
92
+ }
93
+ return result;
39
94
  }
40
95
 
41
96
  /**
@@ -50,7 +105,7 @@ class TdToken extends TrToken {
50
105
  innerSyntax = undefined;
51
106
  attr = '';
52
107
  }
53
- super(syntax, attr, config, accum);
108
+ super(syntax, attr, config, accum, openingPattern);
54
109
  if (innerSyntax) {
55
110
  [this.#innerSyntax] = innerSyntax;
56
111
  }
@@ -58,6 +113,8 @@ class TdToken extends TrToken {
58
113
  const innerToken = new Token(inner?.slice(innerSyntax?.index + this.#innerSyntax.length), config, true, accum);
59
114
  innerToken.type = 'td-inner';
60
115
  this.insertAt(innerToken.setAttribute('stage', 4));
116
+ this.setAttribute('acceptable', {SyntaxToken: 0, AttributesToken: 1, Token: 2})
117
+ .seal(['getRowCount', 'getNthCol', 'insertTableCell'], true);
61
118
  }
62
119
 
63
120
  /** @override */
@@ -69,12 +126,16 @@ class TdToken extends TrToken {
69
126
 
70
127
  /**
71
128
  * @override
129
+ * @param {string} selector
72
130
  * @returns {string}
73
131
  * @complexity `n`
74
132
  */
75
133
  toString(selector) {
134
+ this.#correct();
76
135
  const {childNodes: [syntax, attr, inner]} = this;
77
- return `${syntax.toString()}${attr.toString()}${this.#innerSyntax}${inner.toString()}`;
136
+ return selector && this.matches(selector)
137
+ ? ''
138
+ : `${syntax.toString(selector)}${attr.toString(selector)}${this.#innerSyntax}${inner.toString(selector)}`;
78
139
  }
79
140
 
80
141
  /**
@@ -83,6 +144,7 @@ class TdToken extends TrToken {
83
144
  * @complexity `n`
84
145
  */
85
146
  text() {
147
+ this.#correct();
86
148
  const {childNodes: [syntax, attr, inner]} = this;
87
149
  return `${syntax.text()}${attr.text()}${this.#innerSyntax}${inner.text()}`;
88
150
  }
@@ -94,6 +156,7 @@ class TdToken extends TrToken {
94
156
  getGaps(i = 0) {
95
157
  i = i < 0 ? i + this.length : i;
96
158
  if (i === 1) {
159
+ this.#correct();
97
160
  return this.#innerSyntax.length;
98
161
  }
99
162
  return 0;
@@ -114,6 +177,163 @@ class TdToken extends TrToken {
114
177
  }
115
178
  return errors;
116
179
  }
180
+
181
+ /** @override */
182
+ print() {
183
+ const {childNodes: [syntax, attr, inner]} = this;
184
+ return `<span class="wpb-td">${syntax.print()}${attr.print()}${this.#innerSyntax}${inner.print()}</span>`;
185
+ }
186
+
187
+ /** @override */
188
+ cloneNode() {
189
+ const /** @type {TdToken} */ token = super.cloneNode();
190
+ token.setAttribute('innerSyntax', this.#innerSyntax);
191
+ return token;
192
+ }
193
+
194
+ /**
195
+ * 创建新的单元格
196
+ * @param {string|Token} inner 内部wikitext
197
+ * @param {'td'|'th'|'caption'} subtype 单元格类型
198
+ * @param {Record<string, string>} attr 单元格属性
199
+ * @param {boolean} include 是否嵌入
200
+ * @throws `RangeError` 非法的单元格类型
201
+ */
202
+ static create(inner, subtype = 'td', attr = {}, include = false, config = Parser.getConfig()) {
203
+ if (typeof inner !== 'string' && inner?.constructor !== Token || !isPlainObject(attr)) {
204
+ typeError(this, 'create', 'String', 'Token', 'Object');
205
+ } else if (subtype !== 'td' && subtype !== 'th' && subtype !== 'caption') {
206
+ throw new RangeError('单元格的子类型只能为 "td"、"th" 或 "caption"!');
207
+ } else if (typeof inner === 'string') {
208
+ inner = Parser.parse(inner, include, undefined, config);
209
+ }
210
+ const token = Parser.run(() => new TdToken('\n|', undefined, config));
211
+ token.setSyntax(subtype);
212
+ token.lastChild.safeReplaceWith(inner);
213
+ for (const [k, v] of Object.entries(attr)) {
214
+ token.setAttr(k, v);
215
+ }
216
+ return token;
217
+ }
218
+
219
+ /**
220
+ * @override
221
+ * @template {string} T
222
+ * @param {T} key 属性键
223
+ * @returns {TokenAttribute<T>}
224
+ */
225
+ getAttribute(key) {
226
+ return key === 'innerSyntax' ? this.#innerSyntax : super.getAttribute(key);
227
+ }
228
+
229
+ /**
230
+ * @override
231
+ * @template {string} T
232
+ * @param {T} key 属性键
233
+ * @param {TokenAttribute<T>} value 属性值
234
+ * @returns {this}
235
+ */
236
+ setAttribute(key, value) {
237
+ if (key === 'innerSyntax') {
238
+ this.#innerSyntax = String(value);
239
+ return this;
240
+ }
241
+ return super.setAttribute(key, value);
242
+ }
243
+
244
+ /**
245
+ * @override
246
+ * @param {string} syntax 表格语法
247
+ * @param {boolean} esc 是否需要转义
248
+ */
249
+ setSyntax(syntax, esc) {
250
+ super.setSyntax(aliases[syntax] ?? syntax, esc);
251
+ }
252
+
253
+ /**
254
+ * 修复\<td\>语法
255
+ * @complexity `n`
256
+ */
257
+ #correct() {
258
+ if (String(this.childNodes[1])) {
259
+ this.#innerSyntax ||= '|';
260
+ }
261
+ const {subtype, escape, correction} = this.getSyntax();
262
+ if (correction) {
263
+ this.setSyntax(subtype, escape);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * 改为独占一行
269
+ * @complexity `n`
270
+ */
271
+ independence() {
272
+ if (!this.isIndependent()) {
273
+ const {subtype, escape} = this.getSyntax();
274
+ this.setSyntax(subtype, escape);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * 获取单元格属性
280
+ * @template {string} T
281
+ * @param {T} key 属性键
282
+ * @returns {T extends 'rowspan'|'colspan' ? number : string|true}
283
+ */
284
+ getAttr(key) {
285
+ const /** @type {string|true} */ value = super.getAttr(key);
286
+ key = key?.toLowerCase()?.trim();
287
+ return key === 'rowspan' || key === 'colspan' ? Number(value) || 1 : value;
288
+ }
289
+
290
+ /**
291
+ * 获取全部单元格属性
292
+ * @returns {{rowspan: number, colspan: number, [key: string]: string|true}}
293
+ */
294
+ getAttrs() {
295
+ const /** @type {record<string, string|true>} */ attr = super.getAttrs();
296
+ if ('rowspan' in attr) {
297
+ attr.rowspan = Number(attr.rowspan);
298
+ }
299
+ if ('colspan' in attr) {
300
+ attr.colspan = Number(attr.colspan);
301
+ }
302
+ return attr;
303
+ }
304
+
305
+ /**
306
+ * 设置单元格属性
307
+ * @template {string} T
308
+ * @param {T} key 属性键
309
+ * @param {T extends 'rowspan'|'colspan' ? number : string|boolean} value 属性值
310
+ */
311
+ setAttr(key, value) {
312
+ if (typeof key !== 'string') {
313
+ this.typeError('setAttr', 'String');
314
+ }
315
+ key = key.toLowerCase().trim();
316
+ if (typeof value === 'number' && (key === 'rowspan' || key === 'colspan')) {
317
+ value = value === 1 ? false : String(value);
318
+ }
319
+ const /** @type {boolean} */ result = super.setAttr(key, value);
320
+ if (!String(this.childNodes[1])) {
321
+ this.#innerSyntax = '';
322
+ }
323
+ return result;
324
+ }
325
+
326
+ /** @override */
327
+ escape() {
328
+ super.escape();
329
+ if (String(this.childNodes[1])) {
330
+ this.#innerSyntax ||= '{{!}}';
331
+ }
332
+ if (this.#innerSyntax === '|') {
333
+ this.#innerSyntax = '{{!}}';
334
+ }
335
+ }
117
336
  }
118
337
 
338
+ Parser.classes.TdToken = __filename;
119
339
  module.exports = TdToken;
package/src/table/tr.js CHANGED
@@ -1,16 +1,35 @@
1
1
  'use strict';
2
2
 
3
3
  const {generateForChild} = require('../../util/lint'),
4
+ attributeParent = require('../../mixin/attributeParent'),
4
5
  Parser = require('../..'),
6
+ AstText = require('../../lib/text'),
5
7
  Token = require('..'),
6
8
  SyntaxToken = require('../syntax'),
7
9
  AttributesToken = require('../attributes');
8
10
 
11
+ const openingPattern = /^\n[^\S\n]*(?:\|-+|\{\{\s*!\s*\}\}-+|\{\{\s*!-\s*\}\}-*)$/u;
12
+
13
+ /**
14
+ * 转义表格语法
15
+ * @param {SyntaxToken} syntax 表格语法节点
16
+ */
17
+ const escapeTable = syntax => {
18
+ const templates = {'{|': '(!', '|}': '!)', '||': '!!', '|': '!'},
19
+ wikitext = syntax.childNodes.map(
20
+ child => child.type === 'text'
21
+ ? String(child).replace(/\{\||\|\}|\|{2}|\|/gu, p => `{{${templates[p]}}}`)
22
+ : String(child),
23
+ ).join(''),
24
+ token = Parser.parse(wikitext, syntax.getAttribute('include'), 2, syntax.getAttribute('config'));
25
+ syntax.replaceChildren(...token.childNodes);
26
+ };
27
+
9
28
  /**
10
29
  * 表格行,含开头的换行,不含结尾的换行
11
30
  * @classdesc `{childNodes: [SyntaxToken, AttributesToken, ?Token, ...TdToken]}`
12
31
  */
13
- class TrToken extends Token {
32
+ class TrToken extends attributeParent(Token, 1) {
14
33
  type = 'tr';
15
34
 
16
35
  /**
@@ -19,14 +38,17 @@ class TrToken extends Token {
19
38
  * @param {accum} accum
20
39
  * @param {RegExp} pattern 表格语法正则
21
40
  */
22
- constructor(syntax, attr = '', config = Parser.getConfig(), accum = [], pattern = undefined) {
41
+ constructor(syntax, attr = '', config = Parser.getConfig(), accum = [], pattern = openingPattern) {
23
42
  super(undefined, config, true, accum, {
43
+ Token: 2, SyntaxToken: 0, AttributesToken: 1, TdToken: '2:',
24
44
  });
25
45
  this.append(
26
46
  new SyntaxToken(syntax, pattern, 'table-syntax', config, accum, {
47
+ 'Stage-1': ':', '!ExtToken': '', TranscludeToken: ':',
27
48
  }),
28
49
  new AttributesToken(attr, 'table-attrs', this.type, config, accum),
29
50
  );
51
+ this.getAttribute('protectChildren')(0, 1);
30
52
  }
31
53
 
32
54
  /**
@@ -66,9 +88,232 @@ class TrToken extends Token {
66
88
 
67
89
  /** @override */
68
90
  text() {
91
+ this.#correct();
69
92
  const str = super.text();
70
93
  return this.type === 'tr' && !str.trim().includes('\n') ? '' : str;
71
94
  }
95
+
96
+ /**
97
+ * @override
98
+ * @this {TrToken & {constructor: typeof TrToken}}
99
+ */
100
+ cloneNode() {
101
+ const [syntax, attr, inner, ...cloned] = this.cloneChildNodes();
102
+ return Parser.run(() => {
103
+ const token = new this.constructor(undefined, undefined, this.getAttribute('config'));
104
+ token.firstChild.safeReplaceWith(syntax);
105
+ token.childNodes[1].safeReplaceWith(attr);
106
+ if (token.type === 'td') { // TdToken
107
+ token.childNodes[2].safeReplaceWith(inner);
108
+ } else if (inner !== undefined) {
109
+ token.insertAt(inner);
110
+ }
111
+ token.append(...cloned);
112
+ return token;
113
+ });
114
+ }
115
+
116
+ /** 修复简单的表格语法错误 */
117
+ #correct() {
118
+ const {childNodes: [,, child]} = this;
119
+ if (child?.constructor === Token) {
120
+ const /** @type {{firstChild: AstText}} */ {firstChild} = child;
121
+ if (firstChild.type !== 'text') {
122
+ child.prepend('\n');
123
+ } else if (firstChild.data[0] !== '\n') {
124
+ firstChild.insertData(0, '\n');
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * @override
131
+ * @param {string} selector
132
+ */
133
+ toString(selector) {
134
+ this.#correct();
135
+ return super.toString(selector);
136
+ }
137
+
138
+ /**
139
+ * 转义表格语法
140
+ * @complexity `n`
141
+ */
142
+ escape() {
143
+ for (const child of this.childNodes) {
144
+ if (child instanceof SyntaxToken) {
145
+ escapeTable(child);
146
+ } else if (child instanceof TrToken) {
147
+ child.escape();
148
+ }
149
+ }
150
+ }
151
+
152
+ /**
153
+ * 设置表格语法
154
+ * @param {string} syntax 表格语法
155
+ * @param {boolean} esc 是否需要转义
156
+ */
157
+ setSyntax(syntax, esc) {
158
+ const {firstChild} = this;
159
+ firstChild.replaceChildren(syntax);
160
+ if (esc) {
161
+ escapeTable(firstChild);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @override
167
+ * @param {number} i 移除位置
168
+ * @complexity `n`
169
+ */
170
+ removeAt(i) {
171
+ const TdToken = require('./td');
172
+ const child = this.childNodes.at(i);
173
+ if (child instanceof TdToken && child.isIndependent()) {
174
+ const {nextSibling} = child;
175
+ if (nextSibling?.type === 'td') {
176
+ nextSibling.independence();
177
+ }
178
+ }
179
+ return super.removeAt(i);
180
+ }
181
+
182
+ /**
183
+ * @override
184
+ * @template {AstText|Token} T
185
+ * @param {T} token 待插入的子节点
186
+ * @param {number} i 插入位置
187
+ * @returns {T}
188
+ * @complexity `n`
189
+ */
190
+ insertAt(token, i = this.length) {
191
+ if (!Parser.running && !(token instanceof TrToken)) {
192
+ this.typeError('insertAt', 'TrToken');
193
+ }
194
+ const TdToken = require('./td');
195
+ const child = this.childNodes.at(i);
196
+ if (token instanceof TdToken && token.isIndependent() && child instanceof TdToken) {
197
+ child.independence();
198
+ }
199
+ return super.insertAt(token, i);
200
+ }
201
+
202
+ /**
203
+ * 获取行数
204
+ * @returns {0|1}
205
+ * @complexity `n`
206
+ */
207
+ getRowCount() {
208
+ const TdToken = require('./td');
209
+ return Number(this.childNodes.some(
210
+ child => child instanceof TdToken && child.isIndependent() && child.firstChild.text().at(-1) !== '+',
211
+ ));
212
+ }
213
+
214
+ /**
215
+ * 获取相邻行
216
+ * @param {(childNodes: Token[], index: number) => Token[]} subset 筛选兄弟节点的方法
217
+ * @complexity `n`
218
+ */
219
+ #getSiblingRow(subset) {
220
+ const {parentNode} = this;
221
+ if (!parentNode) {
222
+ return undefined;
223
+ }
224
+ const {childNodes} = parentNode,
225
+ index = childNodes.indexOf(this);
226
+ for (const child of subset(childNodes, index)) {
227
+ if (child instanceof TrToken && child.getRowCount()) {
228
+ return child;
229
+ }
230
+ }
231
+ return undefined;
232
+ }
233
+
234
+ /**
235
+ * 获取下一行
236
+ * @complexity `n`
237
+ */
238
+ getNextRow() {
239
+ return this.#getSiblingRow((childNodes, index) => childNodes.slice(index + 1));
240
+ }
241
+
242
+ /**
243
+ * 获取前一行
244
+ * @complexity `n`
245
+ */
246
+ getPreviousRow() {
247
+ return this.#getSiblingRow((childNodes, index) => childNodes.slice(0, index).reverse());
248
+ }
249
+
250
+ /**
251
+ * 获取列数
252
+ * @complexity `n`
253
+ */
254
+ getColCount() {
255
+ const TdToken = require('./td');
256
+ let count = 0,
257
+ last = 0;
258
+ for (const child of this.childNodes) {
259
+ if (child instanceof TdToken) {
260
+ last = child.isIndependent() ? Number(child.subtype !== 'caption') : last;
261
+ count += last;
262
+ }
263
+ }
264
+ return count;
265
+ }
266
+
267
+ /**
268
+ * 获取第n列
269
+ * @param {number} n 列号
270
+ * @param {boolean} insert 是否用于判断插入新列的位置
271
+ * @returns {TdToken}
272
+ * @complexity `n`
273
+ * @throws `RangeError` 不存在对应单元格
274
+ */
275
+ getNthCol(n, insert) {
276
+ if (!Number.isInteger(n)) {
277
+ this.typeError('getNthCol', 'Number');
278
+ }
279
+ const nCols = this.getColCount();
280
+ n = n < 0 ? n + nCols : n;
281
+ if (n < 0 || n > nCols || n === nCols && !insert) {
282
+ throw new RangeError(`不存在第 ${n} 个单元格!`);
283
+ }
284
+ const TdToken = require('./td');
285
+ let last = 0;
286
+ for (const child of this.childNodes.slice(2)) {
287
+ if (child instanceof TdToken) {
288
+ if (child.isIndependent()) {
289
+ last = Number(child.subtype !== 'caption');
290
+ }
291
+ n -= last;
292
+ if (n < 0) {
293
+ return child;
294
+ }
295
+ } else if (child.type === 'tr' || child.type === 'table-syntax') {
296
+ return child;
297
+ }
298
+ }
299
+ return undefined;
300
+ }
301
+
302
+ /**
303
+ * 插入新的单元格
304
+ * @param {string|Token} inner 单元格内部wikitext
305
+ * @param {TableCoords} coord 单元格坐标
306
+ * @param {'td'|'th'|'caption'} subtype 单元格类型
307
+ * @param {Record<string, string|boolean>} attr 单元格属性
308
+ * @returns {TdToken}
309
+ * @complexity `n`
310
+ */
311
+ insertTableCell(inner, {column}, subtype = 'td', attr = {}) {
312
+ const TdToken = require('./td');
313
+ const token = TdToken.create(inner, subtype, attr, this.getAttribute('include'), this.getAttribute('config'));
314
+ return this.insertBefore(token, this.getNthCol(column, true));
315
+ }
72
316
  }
73
317
 
318
+ Parser.classes.TrToken = __filename;
74
319
  module.exports = TrToken;
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const {generateForSelf} = require('../../util/lint'),
4
+ attributeParent = require('../../mixin/attributeParent'),
5
+ path = require('path'),
4
6
  Parser = require('../..'),
5
7
  Token = require('..'),
6
8
  TagPairToken = require('.'),
@@ -10,7 +12,7 @@ const {generateForSelf} = require('../../util/lint'),
10
12
  * 扩展标签
11
13
  * @classdesc `{childNodes: [AttributesToken, NowikiToken|Token]}`
12
14
  */
13
- class ExtToken extends TagPairToken {
15
+ class ExtToken extends attributeParent(TagPairToken) {
14
16
  type = 'ext';
15
17
  closed = true;
16
18
 
@@ -22,7 +24,7 @@ class ExtToken extends TagPairToken {
22
24
  * @param {accum} accum
23
25
  */
24
26
  constructor(name, attr = '', inner = '', closed = undefined, config = Parser.getConfig(), accum = []) {
25
- attr = !attr || /^\s/u.test(attr) ? attr : ` ${attr}`;
27
+ attr = !attr || attr.trimStart() !== attr ? attr : ` ${attr}`;
26
28
  const lcName = name.toLowerCase(),
27
29
  attrToken = new AttributesToken(attr, 'ext-attrs', lcName, config, accum),
28
30
  newConfig = structuredClone(config),
@@ -43,7 +45,6 @@ class ExtToken extends TagPairToken {
43
45
  case 'tabs':
44
46
  case 'poll':
45
47
  case 'seo':
46
- case 'inputbox':
47
48
  innerToken = new Token(inner, newConfig, true, accum);
48
49
  break;
49
50
  case 'gallery': {
@@ -65,7 +66,7 @@ class ExtToken extends TagPairToken {
65
66
  case 'choose':
66
67
  case 'combobox': {
67
68
  const NestedToken = require('../nested'),
68
- /** @type {typeof NestedToken} */ NestedExtToken = require(`../nested/${lcName}`);
69
+ /** @type {typeof NestedToken} */ NestedExtToken = require(path.join('..', 'nested', lcName));
69
70
  innerToken = new NestedExtToken(inner, newConfig, accum);
70
71
  break;
71
72
  }
@@ -79,6 +80,11 @@ class ExtToken extends TagPairToken {
79
80
  innerToken = new ParamTagToken(inner, newConfig, accum);
80
81
  break;
81
82
  }
83
+ case 'inputbox': {
84
+ const InputboxToken = require('../paramTag/inputbox');
85
+ innerToken = new InputboxToken(inner, newConfig, accum);
86
+ break;
87
+ }
82
88
 
83
89
  /*
84
90
  * 更多定制扩展的代码示例:
@@ -113,6 +119,20 @@ class ExtToken extends TagPairToken {
113
119
  }
114
120
  return errors;
115
121
  }
122
+
123
+ /** @override */
124
+ cloneNode() {
125
+ const inner = this.lastChild.cloneNode(),
126
+ tags = this.getAttribute('tags'),
127
+ config = this.getAttribute('config'),
128
+ attr = String(this.firstChild);
129
+ return Parser.run(() => {
130
+ const token = new ExtToken(tags[0], attr, '', this.selfClosing ? undefined : tags[1], config);
131
+ token.lastChild.safeReplaceWith(inner);
132
+ return token;
133
+ });
134
+ }
116
135
  }
117
136
 
137
+ Parser.classes.ExtToken = __filename;
118
138
  module.exports = ExtToken;
@@ -31,6 +31,30 @@ class IncludeToken extends hidden(TagPairToken) {
31
31
  lint(start = 0) {
32
32
  return this.closed ? [] : [generateForSelf(this, {start}, '未闭合的标签')];
33
33
  }
34
+
35
+ /** @override */
36
+ cloneNode() {
37
+ const tags = this.getAttribute('tags'),
38
+ config = this.getAttribute('config'),
39
+ inner = this.selfClosing ? undefined : String(this.lastChild),
40
+ closing = this.selfClosing || !this.closed ? undefined : tags[1],
41
+ token = Parser.run(() => new IncludeToken(tags[0], String(this.firstChild), inner, closing, config));
42
+ return token;
43
+ }
44
+
45
+ /**
46
+ * @override
47
+ * @param {string} str 新文本
48
+ */
49
+ setText(str) {
50
+ return super.setText(str, 1);
51
+ }
52
+
53
+ /** 清除标签属性 */
54
+ removeAttr() {
55
+ super.setText('', 0);
56
+ }
34
57
  }
35
58
 
59
+ Parser.classes.IncludeToken = __filename;
36
60
  module.exports = IncludeToken;