wikiparser-node 0.3.1 → 0.4.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 (81) hide show
  1. package/.eslintrc.json +446 -51
  2. package/README.md +1 -1
  3. package/config/default.json +13 -17
  4. package/config/llwiki.json +11 -79
  5. package/config/moegirl.json +7 -1
  6. package/config/zhwiki.json +1269 -0
  7. package/index.js +106 -96
  8. package/lib/element.js +448 -440
  9. package/lib/node.js +335 -115
  10. package/lib/ranges.js +27 -18
  11. package/lib/text.js +146 -0
  12. package/lib/title.js +13 -5
  13. package/mixin/attributeParent.js +70 -24
  14. package/mixin/fixedToken.js +14 -6
  15. package/mixin/hidden.js +6 -4
  16. package/mixin/sol.js +27 -10
  17. package/package.json +7 -4
  18. package/parser/brackets.js +18 -18
  19. package/parser/commentAndExt.js +15 -13
  20. package/parser/converter.js +14 -13
  21. package/parser/externalLinks.js +12 -11
  22. package/parser/hrAndDoubleUnderscore.js +23 -14
  23. package/parser/html.js +8 -7
  24. package/parser/links.js +13 -12
  25. package/parser/list.js +12 -11
  26. package/parser/magicLinks.js +11 -10
  27. package/parser/quotes.js +6 -5
  28. package/parser/selector.js +175 -0
  29. package/parser/table.js +24 -17
  30. package/printed/example.json +120 -0
  31. package/src/arg.js +56 -32
  32. package/src/atom/hidden.js +5 -2
  33. package/src/atom/index.js +17 -9
  34. package/src/attribute.js +180 -98
  35. package/src/converter.js +68 -41
  36. package/src/converterFlags.js +63 -41
  37. package/src/converterRule.js +117 -65
  38. package/src/extLink.js +66 -18
  39. package/src/gallery.js +28 -16
  40. package/src/heading.js +33 -14
  41. package/src/html.js +97 -35
  42. package/src/imageParameter.js +82 -53
  43. package/src/index.js +296 -175
  44. package/src/link/category.js +20 -52
  45. package/src/link/file.js +59 -28
  46. package/src/link/galleryImage.js +21 -7
  47. package/src/link/index.js +143 -57
  48. package/src/magicLink.js +33 -11
  49. package/src/nowiki/comment.js +22 -10
  50. package/src/nowiki/dd.js +37 -22
  51. package/src/nowiki/doubleUnderscore.js +16 -7
  52. package/src/nowiki/hr.js +11 -7
  53. package/src/nowiki/index.js +16 -9
  54. package/src/nowiki/list.js +2 -2
  55. package/src/nowiki/noinclude.js +8 -4
  56. package/src/nowiki/quote.js +11 -7
  57. package/src/onlyinclude.js +19 -7
  58. package/src/parameter.js +65 -38
  59. package/src/syntax.js +23 -20
  60. package/src/table/index.js +260 -165
  61. package/src/table/td.js +97 -52
  62. package/src/table/tr.js +102 -58
  63. package/src/tagPair/ext.js +27 -19
  64. package/src/tagPair/include.js +16 -11
  65. package/src/tagPair/index.js +64 -29
  66. package/src/transclude.js +167 -92
  67. package/test/api.js +83 -0
  68. package/test/real.js +133 -0
  69. package/test/test.js +28 -0
  70. package/test/util.js +80 -0
  71. package/tool/index.js +41 -31
  72. package/typings/api.d.ts +13 -0
  73. package/typings/array.d.ts +28 -0
  74. package/typings/event.d.ts +24 -0
  75. package/typings/index.d.ts +46 -4
  76. package/typings/node.d.ts +15 -9
  77. package/typings/parser.d.ts +7 -0
  78. package/typings/tool.d.ts +3 -2
  79. package/util/debug.js +21 -18
  80. package/util/string.js +38 -25
  81. package/typings/element.d.ts +0 -28
package/src/table/td.js CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  const fixedToken = require('../../mixin/fixedToken'),
4
4
  {externalUse, typeError} = require('../../util/debug'),
5
- /** @type {Parser} */ Parser = require('../..'),
5
+ Parser = require('../..'),
6
6
  Token = require('..'),
7
7
  TrToken = require('./tr');
8
8
 
9
+ const aliases = {td: '\n|', th: '\n!', caption: '\n|+'},
10
+ openingPattern = /^(?:\n[\S\n]*(?:[|!]|\|\+|\{\{\s*!\s*\}\}\+?)|(?:\||\{\{\s*!\s*\}\}){2}|!!|\{\{\s*!!\s*\}\})$/u;
11
+
9
12
  /**
10
13
  * `<td>`、`<th>`和`<caption>`
11
14
  * @classdesc `{childNodes: [SyntaxToken, AttributeToken, Token]}`
@@ -14,42 +17,59 @@ class TdToken extends fixedToken(TrToken) {
14
17
  type = 'td';
15
18
  #innerSyntax = '';
16
19
 
17
- /** @complexity `n` */
20
+ /**
21
+ * 单元格类型
22
+ * @complexity `n`
23
+ */
18
24
  get subtype() {
19
25
  return this.getSyntax().subtype;
20
26
  }
27
+
21
28
  set subtype(subtype) {
22
29
  this.setSyntax(subtype);
23
30
  }
24
31
 
32
+ /** rowspan */
25
33
  get rowspan() {
26
34
  return this.getAttr('rowspan');
27
35
  }
36
+
28
37
  set rowspan(rowspan) {
29
38
  this.setAttr('rowspan', rowspan);
30
39
  }
40
+
41
+ /** colspan */
31
42
  get colspan() {
32
43
  return this.getAttr('colspan');
33
44
  }
45
+
34
46
  set colspan(colspan) {
35
47
  this.setAttr('colspan', colspan);
36
48
  }
37
49
 
50
+ /** 内部wikitext */
51
+ get innerText() {
52
+ return this.lastElementChild.text();
53
+ }
54
+
55
+ /** 是否位于行首 */
38
56
  isIndependent() {
39
- return this.firstElementChild.text().startsWith('\n');
57
+ return this.firstElementChild.text()[0] === '\n';
40
58
  }
41
59
 
42
60
  /**
61
+ * 获取单元格语法信息
43
62
  * @returns {{subtype: 'td'|'th'|'caption', escape: boolean, correction: boolean}}
44
63
  * @complexity `n`
45
64
  */
46
65
  getSyntax() {
47
66
  const syntax = this.firstElementChild.text(),
48
- esc = syntax.includes('{{');
67
+ esc = syntax.includes('{{'),
68
+ char = syntax.at(-1);
49
69
  let subtype = 'td';
50
- if (syntax.endsWith('!')) {
70
+ if (char === '!') {
51
71
  subtype = 'th';
52
- } else if (syntax.endsWith('+')) {
72
+ } else if (char === '+') {
53
73
  subtype = 'caption';
54
74
  }
55
75
  if (this.isIndependent()) {
@@ -61,7 +81,9 @@ class TdToken extends fixedToken(TrToken) {
61
81
  }
62
82
  const result = previousElementSibling.getSyntax();
63
83
  result.escape ||= esc;
64
- result.correction = previousElementSibling.lastElementChild.offsetHeight > 1;
84
+ result.correction = previousElementSibling.lastElementChild
85
+ .toString('comment, ext, include, noinclude, arg, template, magic-word, html')
86
+ .includes('\n');
65
87
  if (subtype === 'th' && result.subtype !== 'th') {
66
88
  result.subtype = 'th';
67
89
  result.correction = true;
@@ -69,26 +91,23 @@ class TdToken extends fixedToken(TrToken) {
69
91
  return result;
70
92
  }
71
93
 
72
- static openingPattern
73
- = /^(?:\n[\S\n]*(?:[|!]|\|\+|\{\{\s*!\s*\}\}\+?)|(?:\||\{\{\s*!\s*\}\}){2}|!!|\{\{\s*!!\s*\}\})$/;
74
-
75
94
  getRowCount = undefined;
76
95
  getNthCol = undefined;
77
96
  insertTableCell = undefined;
78
97
 
79
98
  /**
80
- * @param {string} syntax
81
- * @param {string} inner
99
+ * @param {string} syntax 单元格语法
100
+ * @param {string} inner 内部wikitext
82
101
  * @param {accum} accum
83
102
  */
84
103
  constructor(syntax, inner, config = Parser.getConfig(), accum = []) {
85
- let innerSyntax = /\||\0\d+!\x7f/.exec(inner),
104
+ let innerSyntax = inner?.match(/\||\0\d+!\x7F/u),
86
105
  attr = innerSyntax ? inner.slice(0, innerSyntax.index) : '';
87
- if (/\[\[|-\{/.test(attr)) {
106
+ if (/\[\[|-\{/u.test(attr)) {
88
107
  innerSyntax = null;
89
108
  attr = '';
90
109
  }
91
- super(syntax, attr, config, accum, TdToken.openingPattern);
110
+ super(syntax, attr, config, accum, openingPattern);
92
111
  if (innerSyntax) {
93
112
  [this.#innerSyntax] = innerSyntax;
94
113
  }
@@ -96,9 +115,11 @@ class TdToken extends fixedToken(TrToken) {
96
115
  const innerToken = new Token(inner?.slice(innerSyntax?.index + this.#innerSyntax.length), config, true, accum);
97
116
  innerToken.type = 'td-inner';
98
117
  this.setAttribute('acceptable', {SyntaxToken: 0, AttributeToken: 1, Token: 2})
99
- .seal(['getRowCount', 'getNthCol', 'insertTableCell']).appendChild(innerToken.setAttribute('stage', 4));
118
+ .seal(['getRowCount', 'getNthCol', 'insertTableCell'], true)
119
+ .appendChild(innerToken.setAttribute('stage', 4));
100
120
  }
101
121
 
122
+ /** @override */
102
123
  cloneNode() {
103
124
  const /** @type {TdToken} */ token = super.cloneNode();
104
125
  token.setAttribute('innerSyntax', this.#innerSyntax);
@@ -106,14 +127,17 @@ class TdToken extends fixedToken(TrToken) {
106
127
  }
107
128
 
108
129
  /**
109
- * @param {string|Token} inner
110
- * @param {'td'|'th'|'caption'} subtype
111
- * @param {Record<string, string>} attr
130
+ * 创建新的单元格
131
+ * @param {string|Token} inner 内部wikitext
132
+ * @param {'td'|'th'|'caption'} subtype 单元格类型
133
+ * @param {Record<string, string>} attr 单元格属性
134
+ * @param {boolean} include 是否嵌入
135
+ * @throws `RangeError` 非法的单元格类型
112
136
  */
113
137
  static create(inner, subtype = 'td', attr = {}, include = false, config = Parser.getConfig()) {
114
138
  if (typeof inner !== 'string' && (!(inner instanceof Token) || !inner.isPlain()) || typeof attr !== 'object') {
115
139
  typeError(this, 'create', 'String', 'Token', 'Object');
116
- } else if (!['td', 'th', 'caption'].includes(subtype)) {
140
+ } else if (subtype !== 'td' && subtype !== 'th' && subtype !== 'caption') {
117
141
  throw new RangeError('单元格的子类型只能为 "td"、"th" 或 "caption"!');
118
142
  } else if (typeof inner === 'string') {
119
143
  inner = Parser.parse(inner, include, undefined, config);
@@ -128,22 +152,22 @@ class TdToken extends fixedToken(TrToken) {
128
152
  }
129
153
 
130
154
  /**
155
+ * @override
131
156
  * @template {string} T
132
- * @param {T} key
157
+ * @param {T} key 属性键
133
158
  * @returns {TokenAttribute<T>}
134
159
  */
135
160
  getAttribute(key) {
136
- if (key === 'innerSyntax') {
137
- return this.#innerSyntax;
138
- }
139
- return super.getAttribute(key);
161
+ return key === 'innerSyntax' ? this.#innerSyntax : super.getAttribute(key);
140
162
  }
141
163
 
142
164
  /**
165
+ * @override
143
166
  * @template {string} T
144
- * @param {T} key
145
- * @param {TokenAttribute<T>} value
146
- * @return {this}
167
+ * @param {T} key 属性键
168
+ * @param {TokenAttribute<T>} value 属性值
169
+ * @returns {this}
170
+ * @throws `RangeError` 仅用于代码调试
147
171
  */
148
172
  setAttribute(key, value) {
149
173
  if (key !== 'innerSyntax') {
@@ -155,49 +179,66 @@ class TdToken extends fixedToken(TrToken) {
155
179
  return this;
156
180
  }
157
181
 
182
+ /** @override */
158
183
  afterBuild() {
159
184
  if (this.#innerSyntax.includes('\0')) {
160
- this.#innerSyntax = this.buildFromStr(this.#innerSyntax).map(String).join('');
185
+ this.#innerSyntax = this.getAttribute('buildFromStr')(this.#innerSyntax).map(String).join('');
161
186
  }
162
187
  return this;
163
188
  }
164
189
 
165
- static #aliases = {td: '\n|', th: '\n!', caption: '\n|+'};
166
-
167
- /** @param {string} syntax */
168
- setSyntax(syntax, esc = false) {
169
- super.setSyntax(TdToken.#aliases[syntax] ?? syntax, esc);
190
+ /**
191
+ * @override
192
+ * @param {string} syntax 表格语法
193
+ * @param {boolean} esc 是否需要转义
194
+ */
195
+ setSyntax(syntax, esc) {
196
+ super.setSyntax(aliases[syntax] ?? syntax, esc);
170
197
  }
171
198
 
172
- /** @complexity `n` */
199
+ /**
200
+ * 修复\<td\>语法
201
+ * @complexity `n`
202
+ */
173
203
  #correct() {
174
- if (this.children[1].toString()) {
204
+ if (String(this.children[1])) {
175
205
  this.#innerSyntax ||= '|';
176
206
  }
177
- const {subtype, escape: esc, correction} = this.getSyntax();
207
+ const {subtype, escape, correction} = this.getSyntax();
178
208
  if (correction) {
179
- this.setSyntax(subtype, esc);
209
+ this.setSyntax(subtype, escape);
180
210
  }
181
211
  }
182
212
 
183
- /** @complexity `n` */
213
+ /**
214
+ * 改为独占一行
215
+ * @complexity `n`
216
+ */
184
217
  independence() {
185
218
  if (!this.isIndependent()) {
186
- const {subtype, escape: esc} = this.getSyntax();
187
- this.setSyntax(subtype, esc);
219
+ const {subtype, escape} = this.getSyntax();
220
+ this.setSyntax(subtype, escape);
188
221
  }
189
222
  }
190
223
 
191
224
  /**
225
+ * @override
226
+ * @param {string} selector
192
227
  * @returns {string}
193
228
  * @complexity `n`
194
229
  */
195
- toString() {
230
+ toString(selector) {
196
231
  this.#correct();
197
- const [syntax, attr, inner] = this.children;
198
- return `${syntax.toString()}${attr.toString()}${this.#innerSyntax}${inner.toString()}`;
232
+ const {children: [syntax, attr, inner]} = this;
233
+ return selector && this.matches(selector)
234
+ ? ''
235
+ : `${syntax.toString(selector)}${attr.toString(selector)}${this.#innerSyntax}${inner.toString(selector)}`;
199
236
  }
200
237
 
238
+ /**
239
+ * @override
240
+ * @param {number} i 子节点位置
241
+ */
201
242
  getGaps(i = 0) {
202
243
  i = i < 0 ? i + this.childNodes.length : i;
203
244
  if (i !== 1) {
@@ -208,49 +249,53 @@ class TdToken extends fixedToken(TrToken) {
208
249
  }
209
250
 
210
251
  /**
252
+ * @override
211
253
  * @returns {string}
212
254
  * @complexity `n`
213
255
  */
214
256
  text() {
215
257
  this.#correct();
216
- const [syntax, attr, inner] = this.children;
258
+ const {children: [syntax, attr, inner]} = this;
217
259
  return `${syntax.text()}${attr.text()}${this.#innerSyntax}${inner.text()}`;
218
260
  }
219
261
 
220
262
  /**
263
+ * 获取单元格属性
221
264
  * @template {string} T
222
- * @param {T} key
265
+ * @param {T} key 属性键
223
266
  * @returns {T extends 'rowspan'|'colspan' ? number : Record<string, string|true>}
224
267
  */
225
268
  getAttr(key) {
226
269
  const /** @type {string|true} */ value = super.getAttr(key);
227
270
  key = key?.toLowerCase()?.trim();
228
- return ['rowspan', 'colspan'].includes(key) ? Number(value) || 1 : value;
271
+ return key === 'rowspan' || key === 'colspan' ? Number(value) || 1 : value;
229
272
  }
230
273
 
231
274
  /**
275
+ * 设置单元格属性
232
276
  * @template {string} T
233
- * @param {T} key
234
- * @param {T extends 'rowspan'|'colspan' ? number : string|boolean} value
277
+ * @param {T} key 属性键
278
+ * @param {T extends 'rowspan'|'colspan' ? number : string|boolean} value 属性值
235
279
  */
236
280
  setAttr(key, value) {
237
281
  if (typeof key !== 'string') {
238
282
  this.typeError('setAttr', 'String');
239
283
  }
240
284
  key = key.toLowerCase().trim();
241
- if (typeof value === 'number' && ['rowspan', 'colspan'].includes(key)) {
285
+ if (typeof value === 'number' && (key === 'rowspan' || key === 'colspan')) {
242
286
  value = value === 1 ? false : String(value);
243
287
  }
244
288
  const /** @type {boolean} */ result = super.setAttr(key, value);
245
- if (!this.children[1].toString()) {
289
+ if (!String(this.children[1])) {
246
290
  this.#innerSyntax = '';
247
291
  }
248
292
  return result;
249
293
  }
250
294
 
295
+ /** @override */
251
296
  escape() {
252
297
  super.escape();
253
- if (this.children[1].toString()) {
298
+ if (String(this.children[1])) {
254
299
  this.#innerSyntax ||= '{{!}}';
255
300
  }
256
301
  if (this.#innerSyntax === '|') {
package/src/table/tr.js CHANGED
@@ -1,11 +1,30 @@
1
1
  'use strict';
2
2
 
3
3
  const attributeParent = require('../../mixin/attributeParent'),
4
- /** @type {Parser} */ Parser = require('../..'),
4
+ Parser = require('../..'),
5
+ AstText = require('../../lib/text'),
5
6
  Token = require('..'),
6
7
  SyntaxToken = require('../syntax'),
7
8
  AttributeToken = require('../attribute');
8
9
 
10
+ const openingPattern = /^\n[^\S\n]*(?:\|-+|\{\{\s*!\s*\}\}-+|\{\{\s*!-\s*\}\}-*)$/u;
11
+
12
+ /**
13
+ * 转义表格语法
14
+ * @param {SyntaxToken} syntax 表格语法节点
15
+ */
16
+ const escapeTable = syntax => {
17
+ const wikitext = syntax.childNodes.map(
18
+ child => child.type === 'text'
19
+ ? String(child).replaceAll('{|', '{{(!}}').replaceAll('|}', '{{!)}}')
20
+ .replaceAll('||', '{{!!}}')
21
+ .replaceAll('|', '{{!}}')
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, AttributeToken, ?Token, ...TdToken]}`
@@ -13,13 +32,13 @@ const attributeParent = require('../../mixin/attributeParent'),
13
32
  class TrToken extends attributeParent(Token, 1) {
14
33
  type = 'tr';
15
34
 
16
- static openingPattern = /^\n[^\S\n]*(?:\|-+|\{\{\s*!\s*\}\}-+|\{\{\s*!-\s*\}\}-*)$/;
17
-
18
35
  /**
19
- * @param {string} syntax
36
+ * @param {string} syntax 表格语法
37
+ * @param {string} attr 表格属性
20
38
  * @param {accum} accum
39
+ * @param {RegExp} pattern 表格语法正则
21
40
  */
22
- constructor(syntax, attr = '', config = Parser.getConfig(), accum = [], pattern = TrToken.openingPattern) {
41
+ constructor(syntax, attr = '', config = Parser.getConfig(), accum = [], pattern = openingPattern) {
23
42
  super(undefined, config, true, accum, {Token: 2, SyntaxToken: 0, AttributeToken: 1, TdToken: '2:'});
24
43
  this.append(
25
44
  new SyntaxToken(syntax, pattern, 'table-syntax', config, accum, {
@@ -27,14 +46,17 @@ class TrToken extends attributeParent(Token, 1) {
27
46
  }),
28
47
  new AttributeToken(attr, 'table-attr', 'tr', config, accum),
29
48
  );
30
- this.protectChildren(0, 1);
49
+ this.getAttribute('protectChildren')(0, 1);
31
50
  }
32
51
 
52
+ /**
53
+ * @override
54
+ * @this {TrToken & {constructor: typeof TrToken}}
55
+ */
33
56
  cloneNode() {
34
- const [syntax, attr, inner, ...cloned] = this.cloneChildren(),
35
- /** @type {typeof TrToken} */ Constructor = this.constructor;
57
+ const [syntax, attr, inner, ...cloned] = this.cloneChildNodes();
36
58
  return Parser.run(() => {
37
- const token = new Constructor(undefined, undefined, this.getAttribute('config'));
59
+ const token = new this.constructor(undefined, undefined, this.getAttribute('config'));
38
60
  token.firstElementChild.safeReplaceWith(syntax);
39
61
  token.children[1].safeReplaceWith(attr);
40
62
  if (token.type === 'td') { // TdToken
@@ -47,67 +69,70 @@ class TrToken extends attributeParent(Token, 1) {
47
69
  });
48
70
  }
49
71
 
72
+ /** 修复简单的表格语法错误 */
50
73
  #correct() {
51
- const [,, child] = this.children;
74
+ const {children: [,, child]} = this;
52
75
  if (child?.isPlain()) {
53
- const {firstChild} = child;
54
- if (typeof firstChild !== 'string') {
76
+ const /** @type {{firstChild: AstText}} */ {firstChild: {type, data}} = child;
77
+ if (type !== 'text') {
55
78
  child.prepend('\n');
56
- } else if (!firstChild.startsWith('\n')) {
57
- child.setText(`\n${firstChild}`);
79
+ } else if (data[0] !== '\n') {
80
+ child.setText(`\n${data}`);
58
81
  }
59
82
  }
60
83
  }
61
84
 
62
- toString() {
85
+ /**
86
+ * @override
87
+ * @param {string} selector
88
+ */
89
+ toString(selector) {
63
90
  this.#correct();
64
- return super.toString();
91
+ return super.toString(selector);
65
92
  }
66
93
 
94
+ /** @override */
67
95
  text() {
68
96
  this.#correct();
69
97
  const str = super.text();
70
98
  return this.type === 'tr' && !str.trim().includes('\n') ? '' : str;
71
99
  }
72
100
 
73
- /** @param {SyntaxToken} syntax */
74
- static escape(syntax) {
75
- const wikitext = syntax.childNodes.map(child => typeof child === 'string'
76
- ? child.replaceAll('{|', '{{(!}}').replaceAll('|}', '{{!)}}').replaceAll('||', '{{!!}}')
77
- .replaceAll('|', '{{!}}')
78
- : child.toString(),
79
- ).join(''),
80
- token = Parser.parse(wikitext, syntax.getAttribute('include'), 2, syntax.getAttribute('config'));
81
- syntax.replaceChildren(...token.childNodes);
82
- }
83
-
84
- /** @complexity `n` */
101
+ /**
102
+ * 转义表格语法
103
+ * @complexity `n`
104
+ */
85
105
  escape() {
86
106
  for (const child of this.children) {
87
107
  if (child instanceof SyntaxToken) {
88
- TrToken.escape(child);
108
+ escapeTable(child);
89
109
  } else if (child instanceof TrToken) {
90
110
  child.escape();
91
111
  }
92
112
  }
93
113
  }
94
114
 
95
- /** @param {string} syntax */
96
- setSyntax(syntax, esc = false) {
115
+ /**
116
+ * 设置表格语法
117
+ * @param {string} syntax 表格语法
118
+ * @param {boolean} esc 是否需要转义
119
+ */
120
+ setSyntax(syntax, esc) {
97
121
  const {firstElementChild} = this;
98
122
  firstElementChild.replaceChildren(syntax);
99
123
  if (esc) {
100
- TrToken.escape(firstElementChild);
124
+ escapeTable(firstElementChild);
101
125
  }
102
126
  }
103
127
 
104
128
  /**
105
- * @param {number} i
129
+ * @override
130
+ * @param {number} i 移除位置
106
131
  * @complexity `n`
107
132
  */
108
133
  removeAt(i) {
109
- const TdToken = require('./td'),
110
- child = this.childNodes.at(i);
134
+ const TdToken = require('./td');
135
+ const child = this.childNodes.at(i);
111
136
  if (child instanceof TdToken && child.isIndependent()) {
112
137
  const {nextElementSibling} = child;
113
138
  if (nextElementSibling?.type === 'td') {
@@ -118,8 +143,10 @@ class TrToken extends attributeParent(Token, 1) {
118
143
  }
119
144
 
120
145
  /**
121
- * @template {string|Token} T
122
- * @param {T} token
146
+ * @override
147
+ * @template {AstText|Token} T
148
+ * @param {T} token 待插入的子节点
149
+ * @param {number} i 插入位置
123
150
  * @returns {T}
124
151
  * @complexity `n`
125
152
  */
@@ -127,8 +154,8 @@ class TrToken extends attributeParent(Token, 1) {
127
154
  if (!Parser.running && !(token instanceof TrToken)) {
128
155
  this.typeError('insertAt', 'TrToken');
129
156
  }
130
- const TdToken = require('./td'),
131
- child = this.childNodes.at(i);
157
+ const TdToken = require('./td');
158
+ const child = this.childNodes.at(i);
132
159
  if (token instanceof TdToken && token.isIndependent() && child instanceof TdToken) {
133
160
  child.independence();
134
161
  }
@@ -136,45 +163,57 @@ class TrToken extends attributeParent(Token, 1) {
136
163
  }
137
164
 
138
165
  /**
166
+ * 获取行数
139
167
  * @returns {0|1}
140
168
  * @complexity `n`
141
169
  */
142
170
  getRowCount() {
143
171
  const TdToken = require('./td');
144
- return Number(this.children.some(child =>
145
- child instanceof TdToken && child.isIndependent() && !child.firstElementChild.text().endsWith('+'),
172
+ return Number(this.children.some(
173
+ child => child instanceof TdToken && child.isIndependent() && child.firstElementChild.text().at(-1) !== '+',
146
174
  ));
147
175
  }
148
176
 
149
177
  /**
150
- * @param {(children: Token[], index: number) => Token[]} subset
178
+ * 获取相邻行
179
+ * @param {(children: Token[], index: number) => Token[]} subset 筛选兄弟节点的方法
151
180
  * @complexity `n`
152
181
  */
153
182
  #getSiblingRow(subset) {
154
- const {parentElement} = this;
155
- if (!parentElement) {
156
- return;
183
+ const {parentNode} = this;
184
+ if (!parentNode) {
185
+ return undefined;
157
186
  }
158
- const {children} = parentElement,
187
+ const {children} = parentNode,
159
188
  index = children.indexOf(this);
160
189
  for (const child of subset(children, index)) {
161
190
  if (child instanceof TrToken && child.getRowCount()) {
162
191
  return child;
163
192
  }
164
193
  }
194
+ return undefined;
165
195
  }
166
196
 
167
- /** @complexity `n` */
197
+ /**
198
+ * 获取下一行
199
+ * @complexity `n`
200
+ */
168
201
  getNextRow() {
169
202
  return this.#getSiblingRow((children, index) => children.slice(index + 1));
170
203
  }
171
204
 
172
- /** @complexity `n` */
205
+ /**
206
+ * 获取前一行
207
+ * @complexity `n`
208
+ */
173
209
  getPreviousRow() {
174
210
  return this.#getSiblingRow((children, index) => children.slice(0, index).reverse());
175
211
  }
176
212
 
177
- /** @complexity `n` */
213
+ /**
214
+ * 获取列数
215
+ * @complexity `n`
216
+ */
178
217
  getColCount() {
179
218
  const TdToken = require('./td');
180
219
  let count = 0,
@@ -189,11 +228,14 @@ class TrToken extends attributeParent(Token, 1) {
189
228
  }
190
229
 
191
230
  /**
192
- * @param {number} n
231
+ * 获取第n
232
+ * @param {number} n 列号
233
+ * @param {boolean} insert 是否用于判断插入新列的位置
193
234
  * @returns {TdToken}
194
235
  * @complexity `n`
236
+ * @throws `RangeError` 不存在对应单元格
195
237
  */
196
- getNthCol(n, insert = false) {
238
+ getNthCol(n, insert) {
197
239
  if (typeof n !== 'number') {
198
240
  this.typeError('getNthCol', 'Number');
199
241
  }
@@ -213,23 +255,25 @@ class TrToken extends attributeParent(Token, 1) {
213
255
  if (n < 0) {
214
256
  return child;
215
257
  }
216
- } else if (['tr', 'table-syntax'].includes(child.type)) {
258
+ } else if (child.type === 'tr' || child.type === 'table-syntax') {
217
259
  return child;
218
260
  }
219
261
  }
262
+ return undefined;
220
263
  }
221
264
 
222
265
  /**
223
- * @param {string|Token} inner
224
- * @param {TableCoords}
225
- * @param {'td'|'th'|'caption'} subtype
226
- * @param {Record<string, string|boolean>} attr
266
+ * 插入新的单元格
267
+ * @param {string|Token} inner 单元格内部wikitext
268
+ * @param {TableCoords} coord 单元格坐标
269
+ * @param {'td'|'th'|'caption'} subtype 单元格类型
270
+ * @param {Record<string, string|boolean>} attr 单元格属性
227
271
  * @returns {TdToken}
228
272
  * @complexity `n`
229
273
  */
230
274
  insertTableCell(inner, {column}, subtype = 'td', attr = {}) {
231
- const TdToken = require('./td'),
232
- token = TdToken.create(inner, subtype, attr, this.getAttribute('include'), this.getAttribute('config'));
275
+ const TdToken = require('./td');
276
+ const token = TdToken.create(inner, subtype, attr, this.getAttribute('include'), this.getAttribute('config'));
233
277
  return this.insertBefore(token, this.getNthCol(column, true));
234
278
  }
235
279
  }