wikiparser-node 0.3.0 → 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 +472 -34
  2. package/README.md +1 -1
  3. package/config/default.json +58 -30
  4. package/config/llwiki.json +22 -90
  5. package/config/moegirl.json +51 -13
  6. package/config/zhwiki.json +1269 -0
  7. package/index.js +114 -104
  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 +9 -3
  18. package/parser/brackets.js +22 -17
  19. package/parser/commentAndExt.js +18 -16
  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 +10 -9
  24. package/parser/links.js +15 -14
  25. package/parser/list.js +12 -11
  26. package/parser/magicLinks.js +12 -11
  27. package/parser/quotes.js +6 -5
  28. package/parser/selector.js +175 -0
  29. package/parser/table.js +25 -18
  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 +182 -100
  35. package/src/converter.js +68 -41
  36. package/src/converterFlags.js +67 -45
  37. package/src/converterRule.js +117 -65
  38. package/src/extLink.js +66 -18
  39. package/src/gallery.js +42 -15
  40. package/src/heading.js +34 -15
  41. package/src/html.js +97 -35
  42. package/src/imageParameter.js +83 -54
  43. package/src/index.js +299 -178
  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 +146 -60
  48. package/src/magicLink.js +34 -12
  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 +26 -20
  60. package/src/table/index.js +260 -165
  61. package/src/table/td.js +98 -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 +170 -93
  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 +40 -27
  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,25 +91,23 @@ class TdToken extends fixedToken(TrToken) {
69
91
  return result;
70
92
  }
71
93
 
72
- static openingPattern = /^(?:\n[\S\n]*(?:[|!]|\|\+|{{\s*!\s*}}\+?)|(?:\||{{\s*!\s*}}){2}|!!|{{\s*!!\s*}})$/;
73
-
74
94
  getRowCount = undefined;
75
95
  getNthCol = undefined;
76
96
  insertTableCell = undefined;
77
97
 
78
98
  /**
79
- * @param {string} syntax
80
- * @param {string} inner
99
+ * @param {string} syntax 单元格语法
100
+ * @param {string} inner 内部wikitext
81
101
  * @param {accum} accum
82
102
  */
83
103
  constructor(syntax, inner, config = Parser.getConfig(), accum = []) {
84
- let innerSyntax = inner?.match(/\||\x00\d+!\x7f/),
104
+ let innerSyntax = inner?.match(/\||\0\d+!\x7F/u),
85
105
  attr = innerSyntax ? inner.slice(0, innerSyntax.index) : '';
86
- if (/\[\[|-{/.test(attr)) {
106
+ if (/\[\[|-\{/u.test(attr)) {
87
107
  innerSyntax = null;
88
108
  attr = '';
89
109
  }
90
- super(syntax, attr, config, accum, TdToken.openingPattern);
110
+ super(syntax, attr, config, accum, openingPattern);
91
111
  if (innerSyntax) {
92
112
  [this.#innerSyntax] = innerSyntax;
93
113
  }
@@ -95,9 +115,11 @@ class TdToken extends fixedToken(TrToken) {
95
115
  const innerToken = new Token(inner?.slice(innerSyntax?.index + this.#innerSyntax.length), config, true, accum);
96
116
  innerToken.type = 'td-inner';
97
117
  this.setAttribute('acceptable', {SyntaxToken: 0, AttributeToken: 1, Token: 2})
98
- .seal(['getRowCount', 'getNthCol', 'insertTableCell']).appendChild(innerToken.setAttribute('stage', 4));
118
+ .seal(['getRowCount', 'getNthCol', 'insertTableCell'], true)
119
+ .appendChild(innerToken.setAttribute('stage', 4));
99
120
  }
100
121
 
122
+ /** @override */
101
123
  cloneNode() {
102
124
  const /** @type {TdToken} */ token = super.cloneNode();
103
125
  token.setAttribute('innerSyntax', this.#innerSyntax);
@@ -105,14 +127,17 @@ class TdToken extends fixedToken(TrToken) {
105
127
  }
106
128
 
107
129
  /**
108
- * @param {string|Token} inner
109
- * @param {'td'|'th'|'caption'} subtype
110
- * @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` 非法的单元格类型
111
136
  */
112
137
  static create(inner, subtype = 'td', attr = {}, include = false, config = Parser.getConfig()) {
113
138
  if (typeof inner !== 'string' && (!(inner instanceof Token) || !inner.isPlain()) || typeof attr !== 'object') {
114
139
  typeError(this, 'create', 'String', 'Token', 'Object');
115
- } else if (!['td', 'th', 'caption'].includes(subtype)) {
140
+ } else if (subtype !== 'td' && subtype !== 'th' && subtype !== 'caption') {
116
141
  throw new RangeError('单元格的子类型只能为 "td"、"th" 或 "caption"!');
117
142
  } else if (typeof inner === 'string') {
118
143
  inner = Parser.parse(inner, include, undefined, config);
@@ -127,22 +152,22 @@ class TdToken extends fixedToken(TrToken) {
127
152
  }
128
153
 
129
154
  /**
155
+ * @override
130
156
  * @template {string} T
131
- * @param {T} key
157
+ * @param {T} key 属性键
132
158
  * @returns {TokenAttribute<T>}
133
159
  */
134
160
  getAttribute(key) {
135
- if (key === 'innerSyntax') {
136
- return this.#innerSyntax;
137
- }
138
- return super.getAttribute(key);
161
+ return key === 'innerSyntax' ? this.#innerSyntax : super.getAttribute(key);
139
162
  }
140
163
 
141
164
  /**
165
+ * @override
142
166
  * @template {string} T
143
- * @param {T} key
144
- * @param {TokenAttribute<T>} value
145
- * @return {this}
167
+ * @param {T} key 属性键
168
+ * @param {TokenAttribute<T>} value 属性值
169
+ * @returns {this}
170
+ * @throws `RangeError` 仅用于代码调试
146
171
  */
147
172
  setAttribute(key, value) {
148
173
  if (key !== 'innerSyntax') {
@@ -154,49 +179,66 @@ class TdToken extends fixedToken(TrToken) {
154
179
  return this;
155
180
  }
156
181
 
182
+ /** @override */
157
183
  afterBuild() {
158
- if (this.#innerSyntax.includes('\x00')) {
159
- this.#innerSyntax = this.buildFromStr(this.#innerSyntax).map(String).join('');
184
+ if (this.#innerSyntax.includes('\0')) {
185
+ this.#innerSyntax = this.getAttribute('buildFromStr')(this.#innerSyntax).map(String).join('');
160
186
  }
161
187
  return this;
162
188
  }
163
189
 
164
- static #aliases = {td: '\n|', th: '\n!', caption: '\n|+'};
165
-
166
- /** @param {string} syntax */
167
- setSyntax(syntax, esc = false) {
168
- 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);
169
197
  }
170
198
 
171
- /** @complexity `n` */
199
+ /**
200
+ * 修复\<td\>语法
201
+ * @complexity `n`
202
+ */
172
203
  #correct() {
173
- if (this.children[1].toString()) {
204
+ if (String(this.children[1])) {
174
205
  this.#innerSyntax ||= '|';
175
206
  }
176
- const {subtype, escape: esc, correction} = this.getSyntax();
207
+ const {subtype, escape, correction} = this.getSyntax();
177
208
  if (correction) {
178
- this.setSyntax(subtype, esc);
209
+ this.setSyntax(subtype, escape);
179
210
  }
180
211
  }
181
212
 
182
- /** @complexity `n` */
213
+ /**
214
+ * 改为独占一行
215
+ * @complexity `n`
216
+ */
183
217
  independence() {
184
218
  if (!this.isIndependent()) {
185
- const {subtype, escape: esc} = this.getSyntax();
186
- this.setSyntax(subtype, esc);
219
+ const {subtype, escape} = this.getSyntax();
220
+ this.setSyntax(subtype, escape);
187
221
  }
188
222
  }
189
223
 
190
224
  /**
225
+ * @override
226
+ * @param {string} selector
191
227
  * @returns {string}
192
228
  * @complexity `n`
193
229
  */
194
- toString() {
230
+ toString(selector) {
195
231
  this.#correct();
196
- const [syntax, attr, inner] = this.children;
197
- 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)}`;
198
236
  }
199
237
 
238
+ /**
239
+ * @override
240
+ * @param {number} i 子节点位置
241
+ */
200
242
  getGaps(i = 0) {
201
243
  i = i < 0 ? i + this.childNodes.length : i;
202
244
  if (i !== 1) {
@@ -207,49 +249,53 @@ class TdToken extends fixedToken(TrToken) {
207
249
  }
208
250
 
209
251
  /**
252
+ * @override
210
253
  * @returns {string}
211
254
  * @complexity `n`
212
255
  */
213
256
  text() {
214
257
  this.#correct();
215
- const [syntax, attr, inner] = this.children;
258
+ const {children: [syntax, attr, inner]} = this;
216
259
  return `${syntax.text()}${attr.text()}${this.#innerSyntax}${inner.text()}`;
217
260
  }
218
261
 
219
262
  /**
263
+ * 获取单元格属性
220
264
  * @template {string} T
221
- * @param {T} key
265
+ * @param {T} key 属性键
222
266
  * @returns {T extends 'rowspan'|'colspan' ? number : Record<string, string|true>}
223
267
  */
224
268
  getAttr(key) {
225
269
  const /** @type {string|true} */ value = super.getAttr(key);
226
270
  key = key?.toLowerCase()?.trim();
227
- return ['rowspan', 'colspan'].includes(key) ? Number(value) || 1 : value;
271
+ return key === 'rowspan' || key === 'colspan' ? Number(value) || 1 : value;
228
272
  }
229
273
 
230
274
  /**
275
+ * 设置单元格属性
231
276
  * @template {string} T
232
- * @param {T} key
233
- * @param {T extends 'rowspan'|'colspan' ? number : string|boolean} value
277
+ * @param {T} key 属性键
278
+ * @param {T extends 'rowspan'|'colspan' ? number : string|boolean} value 属性值
234
279
  */
235
280
  setAttr(key, value) {
236
281
  if (typeof key !== 'string') {
237
282
  this.typeError('setAttr', 'String');
238
283
  }
239
284
  key = key.toLowerCase().trim();
240
- if (typeof value === 'number' && ['rowspan', 'colspan'].includes(key)) {
285
+ if (typeof value === 'number' && (key === 'rowspan' || key === 'colspan')) {
241
286
  value = value === 1 ? false : String(value);
242
287
  }
243
288
  const /** @type {boolean} */ result = super.setAttr(key, value);
244
- if (!this.children[1].toString()) {
289
+ if (!String(this.children[1])) {
245
290
  this.#innerSyntax = '';
246
291
  }
247
292
  return result;
248
293
  }
249
294
 
295
+ /** @override */
250
296
  escape() {
251
297
  super.escape();
252
- if (this.children[1].toString()) {
298
+ if (String(this.children[1])) {
253
299
  this.#innerSyntax ||= '{{!}}';
254
300
  }
255
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
  }