wikiparser-node 0.4.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/config/default.json +129 -66
  2. package/config/zhwiki.json +4 -4
  3. package/index.js +97 -65
  4. package/lib/element.js +159 -302
  5. package/lib/node.js +384 -198
  6. package/lib/ranges.js +3 -4
  7. package/lib/text.js +65 -36
  8. package/lib/title.js +9 -8
  9. package/mixin/fixedToken.js +4 -4
  10. package/mixin/hidden.js +2 -0
  11. package/mixin/sol.js +16 -7
  12. package/package.json +14 -3
  13. package/parser/brackets.js +8 -2
  14. package/parser/commentAndExt.js +1 -1
  15. package/parser/converter.js +1 -1
  16. package/parser/externalLinks.js +2 -2
  17. package/parser/hrAndDoubleUnderscore.js +8 -7
  18. package/parser/links.js +8 -9
  19. package/parser/magicLinks.js +1 -1
  20. package/parser/selector.js +5 -5
  21. package/parser/table.js +18 -16
  22. package/src/arg.js +71 -42
  23. package/src/atom/index.js +7 -5
  24. package/src/attribute.js +102 -64
  25. package/src/charinsert.js +91 -0
  26. package/src/converter.js +34 -15
  27. package/src/converterFlags.js +87 -40
  28. package/src/converterRule.js +59 -53
  29. package/src/extLink.js +45 -37
  30. package/src/gallery.js +71 -16
  31. package/src/hasNowiki/index.js +42 -0
  32. package/src/hasNowiki/pre.js +40 -0
  33. package/src/heading.js +41 -18
  34. package/src/html.js +76 -48
  35. package/src/imageParameter.js +73 -51
  36. package/src/imagemap.js +205 -0
  37. package/src/imagemapLink.js +43 -0
  38. package/src/index.js +243 -138
  39. package/src/link/category.js +10 -14
  40. package/src/link/file.js +112 -56
  41. package/src/link/galleryImage.js +74 -10
  42. package/src/link/index.js +86 -61
  43. package/src/magicLink.js +48 -21
  44. package/src/nested/choose.js +24 -0
  45. package/src/nested/combobox.js +23 -0
  46. package/src/nested/index.js +88 -0
  47. package/src/nested/references.js +23 -0
  48. package/src/nowiki/comment.js +18 -4
  49. package/src/nowiki/dd.js +2 -2
  50. package/src/nowiki/doubleUnderscore.js +16 -11
  51. package/src/nowiki/index.js +12 -0
  52. package/src/nowiki/quote.js +28 -1
  53. package/src/onlyinclude.js +15 -8
  54. package/src/paramTag/index.js +83 -0
  55. package/src/paramTag/inputbox.js +42 -0
  56. package/src/parameter.js +73 -46
  57. package/src/syntax.js +9 -1
  58. package/src/table/index.js +58 -44
  59. package/src/table/td.js +63 -63
  60. package/src/table/tr.js +52 -35
  61. package/src/tagPair/ext.js +60 -43
  62. package/src/tagPair/include.js +11 -1
  63. package/src/tagPair/index.js +29 -20
  64. package/src/transclude.js +214 -166
  65. package/tool/index.js +720 -439
  66. package/util/base.js +17 -0
  67. package/util/debug.js +1 -1
  68. package/{test/util.js → util/diff.js} +15 -19
  69. package/util/lint.js +40 -0
  70. package/util/string.js +37 -20
  71. package/.eslintrc.json +0 -714
  72. package/errors/README +0 -1
  73. package/jsconfig.json +0 -7
  74. package/printed/README +0 -1
  75. package/printed/example.json +0 -120
  76. package/test/api.js +0 -83
  77. package/test/real.js +0 -133
  78. package/test/test.js +0 -28
  79. package/typings/api.d.ts +0 -13
  80. package/typings/array.d.ts +0 -28
  81. package/typings/event.d.ts +0 -24
  82. package/typings/index.d.ts +0 -94
  83. package/typings/node.d.ts +0 -29
  84. package/typings/parser.d.ts +0 -16
  85. package/typings/table.d.ts +0 -14
  86. package/typings/token.d.ts +0 -22
  87. package/typings/tool.d.ts +0 -11
package/src/parameter.js CHANGED
@@ -14,7 +14,7 @@ class ParameterToken extends fixedToken(Token) {
14
14
 
15
15
  /** 是否是匿名参数 */
16
16
  get anon() {
17
- return this.firstElementChild.childNodes.length === 0;
17
+ return this.firstChild.childNodes.length === 0;
18
18
  }
19
19
 
20
20
  /** getValue()的getter */
@@ -26,6 +26,19 @@ class ParameterToken extends fixedToken(Token) {
26
26
  this.setValue(value);
27
27
  }
28
28
 
29
+ /**
30
+ * 是否是重复参数
31
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
32
+ */
33
+ get duplicated() {
34
+ const TranscludeToken = require('./transclude');
35
+ try {
36
+ return Boolean(this.parentNode?.getDuplicatedArgs()?.some(([key]) => key === this.name));
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
29
42
  /**
30
43
  * @param {string|number} key 参数名
31
44
  * @param {string} value 参数值
@@ -42,34 +55,23 @@ class ParameterToken extends fixedToken(Token) {
42
55
  this.append(keyToken, token.setAttribute('stage', 2));
43
56
  }
44
57
 
45
- /** @override */
46
- cloneNode() {
47
- const [key, value] = this.cloneChildNodes(),
48
- config = this.getAttribute('config');
49
- return Parser.run(() => {
50
- const token = new ParameterToken(this.anon ? Number(this.name) : undefined, undefined, config);
51
- token.firstElementChild.safeReplaceWith(key);
52
- token.lastElementChild.safeReplaceWith(value);
53
- return token.afterBuild();
54
- });
55
- }
56
-
57
58
  /** @override */
58
59
  afterBuild() {
59
60
  if (!this.anon) {
60
- const name = this.firstElementChild.text().trim(),
61
+ const TranscludeToken = require('./transclude');
62
+ const name = this.firstChild.text().trim(),
61
63
  {parentNode} = this;
62
64
  this.setAttribute('name', name);
63
- if (parentNode && parentNode instanceof require('./transclude')) {
65
+ if (parentNode && parentNode instanceof TranscludeToken) {
64
66
  parentNode.getAttribute('keys').add(name);
65
67
  parentNode.getArgs(name, false, false).add(this);
66
68
  }
67
69
  }
68
70
  const /** @type {AstListener} */ parameterListener = ({prevTarget}, data) => {
69
71
  if (!this.anon) { // 匿名参数不管怎么变动还是匿名
70
- const {firstElementChild, name} = this;
71
- if (prevTarget === firstElementChild) {
72
- const newKey = firstElementChild.text().trim();
72
+ const {firstChild, name} = this;
73
+ if (prevTarget === firstChild) {
74
+ const newKey = firstChild.text().trim();
73
75
  data.oldKey = name;
74
76
  data.newKey = newKey;
75
77
  this.setAttribute('name', newKey);
@@ -87,7 +89,7 @@ class ParameterToken extends fixedToken(Token) {
87
89
  */
88
90
  toString(selector) {
89
91
  return this.anon && !(selector && this.matches(selector))
90
- ? this.lastElementChild.toString(selector)
92
+ ? this.lastChild.toString(selector)
91
93
  : super.toString(selector, '=');
92
94
  }
93
95
 
@@ -96,12 +98,29 @@ class ParameterToken extends fixedToken(Token) {
96
98
  return this.anon ? 0 : 1;
97
99
  }
98
100
 
101
+ /** @override */
102
+ print() {
103
+ return super.print({sep: this.anon ? '' : '='});
104
+ }
105
+
106
+ /** @override */
107
+ cloneNode() {
108
+ const [key, value] = this.cloneChildNodes(),
109
+ config = this.getAttribute('config');
110
+ return Parser.run(() => {
111
+ const token = new ParameterToken(this.anon ? Number(this.name) : undefined, undefined, config);
112
+ token.firstChild.safeReplaceWith(key);
113
+ token.lastChild.safeReplaceWith(value);
114
+ return token.afterBuild();
115
+ });
116
+ }
117
+
99
118
  /**
100
119
  * @override
101
120
  * @returns {string}
102
121
  */
103
122
  text() {
104
- return this.anon ? this.lastElementChild.text() : super.text('=');
123
+ return this.anon ? this.lastChild.text() : super.text('=');
105
124
  }
106
125
 
107
126
  /**
@@ -114,37 +133,46 @@ class ParameterToken extends fixedToken(Token) {
114
133
  return this.replaceWith(token);
115
134
  }
116
135
 
117
- /** 获取参数值 */
136
+ /**
137
+ * 获取参数值
138
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
139
+ */
118
140
  getValue() {
119
- const value = this.lastElementChild.text();
120
- return this.anon && this.parentNode?.matches('template, magic-word#invoke') ? value : value.trim();
141
+ const TranscludeToken = require('./transclude');
142
+ const value = this.lastChild.text();
143
+ return this.anon && this.parentNode?.isTemplate() ? value : value.trim();
121
144
  }
122
145
 
123
146
  /**
124
147
  * 设置参数值
148
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
125
149
  * @param {string} value 参数值
126
150
  * @throws `SyntaxError` 非法的模板参数
127
151
  */
128
152
  setValue(value) {
129
153
  value = String(value);
130
- const templateLike = this.parentNode?.matches('template, magic-word#invoke'),
154
+ const TranscludeToken = require('./transclude');
155
+ const templateLike = this.parentNode?.isTemplate(),
131
156
  wikitext = `{{${templateLike ? ':T|' : 'lc:'}${this.anon ? '' : '1='}${value}}}`,
132
157
  root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
133
- {childNodes: {length}, firstElementChild} = root,
134
- /** @type {ParameterToken} */ lastElementChild = firstElementChild?.lastElementChild;
135
- if (length !== 1 || !firstElementChild?.matches(templateLike ? 'template#T' : 'magic-word#lc')
136
- || firstElementChild.childNodes.length !== 2
137
- || lastElementChild.anon !== this.anon || lastElementChild.name !== '1'
158
+ {length, firstChild: transclude} = root,
159
+ /** @type {Token & {lastChild: ParameterToken}} */
160
+ {lastChild: parameter, type, name, length: transcludeLength} = transclude,
161
+ targetType = templateLike ? 'template' : 'magic-word',
162
+ targetName = templateLike ? 'T' : 'lc';
163
+ if (length !== 1 || type !== targetType || name !== targetName || transcludeLength !== 2
164
+ || parameter.anon !== this.anon || parameter.name !== '1'
138
165
  ) {
139
166
  throw new SyntaxError(`非法的模板参数:${noWrap(value)}`);
140
167
  }
141
- const {lastChild} = lastElementChild;
142
- lastElementChild.destroy(true);
143
- this.lastElementChild.safeReplaceWith(lastChild);
168
+ const {lastChild} = parameter;
169
+ parameter.destroy(true);
170
+ this.lastChild.safeReplaceWith(lastChild);
144
171
  }
145
172
 
146
173
  /**
147
174
  * 修改参数名
175
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
148
176
  * @param {string} key 新参数名
149
177
  * @param {boolean} force 是否无视冲突命名
150
178
  * @throws `Error` 仅用于模板参数
@@ -155,31 +183,30 @@ class ParameterToken extends fixedToken(Token) {
155
183
  if (typeof key !== 'string') {
156
184
  this.typeError('rename', 'String');
157
185
  }
186
+ const TranscludeToken = require('./transclude');
158
187
  const {parentNode} = this;
159
188
  // 必须检测是否是TranscludeToken
160
- if (!parentNode || !parentNode.matches('template, magic-word#invoke')
161
- || !(parentNode instanceof require('./transclude'))
162
- ) {
189
+ if (!parentNode?.isTemplate() || !(parentNode instanceof TranscludeToken)) {
163
190
  throw new Error(`${this.constructor.name}.rename 方法仅用于模板参数!`);
164
191
  }
165
192
  const root = Parser.parse(`{{:T|${key}=}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
166
- {childNodes: {length}, firstElementChild} = root;
167
- if (length !== 1 || !firstElementChild?.matches('template#T') || firstElementChild.childNodes.length !== 2) {
193
+ {length, firstChild: template} = root,
194
+ {type, name, lastChild: parameter, length: templateLength} = template;
195
+ if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2) {
168
196
  throw new SyntaxError(`非法的模板参数名:${key}`);
169
197
  }
170
- const {lastElementChild} = firstElementChild,
171
- {name, firstChild} = lastElementChild;
172
- if (this.name === name) {
173
- Parser.warn('未改变实际参数名', name);
174
- } else if (parentNode.hasArg(name)) {
198
+ const {name: parameterName, firstChild} = parameter;
199
+ if (this.name === parameterName) {
200
+ Parser.warn('未改变实际参数名', parameterName);
201
+ } else if (parentNode.hasArg(parameterName)) {
175
202
  if (force) {
176
- Parser.warn('参数更名造成重复参数', name);
203
+ Parser.warn('参数更名造成重复参数', parameterName);
177
204
  } else {
178
- throw new RangeError(`参数更名造成重复参数:${name}`);
205
+ throw new RangeError(`参数更名造成重复参数:${parameterName}`);
179
206
  }
180
207
  }
181
- lastElementChild.destroy(true);
182
- this.firstElementChild.safeReplaceWith(firstChild);
208
+ parameter.destroy(true);
209
+ this.firstChild.safeReplaceWith(firstChild);
183
210
  }
184
211
  }
185
212
 
package/src/syntax.js CHANGED
@@ -7,7 +7,7 @@ const {undo} = require('../util/debug'),
7
7
 
8
8
  /**
9
9
  * 满足特定语法格式的plain Token
10
- * @classdesc `{childNodes: (AstText|Token)[]}`
10
+ * @classdesc `{childNodes: ...AstText|Token}`
11
11
  */
12
12
  class SyntaxToken extends Token {
13
13
  #pattern;
@@ -65,6 +65,14 @@ class SyntaxToken extends Token {
65
65
  return key === 'pattern' ? this.#pattern : super.getAttribute(key);
66
66
  }
67
67
 
68
+ /**
69
+ * @override
70
+ * @param {PropertyKey} key 属性键
71
+ */
72
+ hasAttribute(key) {
73
+ return key === 'pattern' || super.hasAttribute(key);
74
+ }
75
+
68
76
  /**
69
77
  * @override
70
78
  * @param {...Token} elements 待替换的子节点
@@ -2,12 +2,13 @@
2
2
 
3
3
  const assert = require('assert/strict'),
4
4
  {noWrap} = require('../../util/string'),
5
+ {generateForChild} = require('../../util/lint'),
6
+ {isPlainObject} = require('../../util/base'),
5
7
  Parser = require('../..'),
6
8
  Token = require('..'),
7
9
  TrToken = require('./tr'),
8
10
  TdToken = require('./td'),
9
- SyntaxToken = require('../syntax'),
10
- AttributeToken = require('../attribute');
11
+ SyntaxToken = require('../syntax');
11
12
 
12
13
  const openingPattern = /^(?:\{\||\{\{\{\s*!\s*\}\}|\{\{\s*\(!\s*\}\})$/u,
13
14
  closingPattern = /^\n[^\S\n]*(?:\|\}|\{\{\s*!\s*\}\}\}|\{\{\s*!\)\s*\}\})$/u;
@@ -123,7 +124,7 @@ class TableToken extends TrToken {
123
124
 
124
125
  /** 表格是否闭合 */
125
126
  get closed() {
126
- return this.lastElementChild.type === 'table-syntax';
127
+ return this.lastChild.type === 'table-syntax';
127
128
  }
128
129
 
129
130
  set closed(closed) {
@@ -154,11 +155,11 @@ class TableToken extends TrToken {
154
155
  * @throws `SyntaxError` 表格的闭合部分非法
155
156
  */
156
157
  insertAt(token, i = this.childNodes.length) {
157
- const previous = this.children.at(i - 1);
158
+ const previous = this.childNodes.at(i - 1);
158
159
  if (token.type === 'td' && previous.type === 'tr') {
159
160
  Parser.warn('改为将单元格插入当前行。');
160
- return previous.appendChild(token);
161
- } else if (!Parser.running && i === this.childNodes.length && token instanceof SyntaxToken
161
+ return previous.insertAt(token);
162
+ } else if (i > 0 && i === this.childNodes.length && token instanceof SyntaxToken
162
163
  && (token.getAttribute('pattern') !== closingPattern || !closingPattern.test(token.text()))
163
164
  ) {
164
165
  throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(String(token))}`);
@@ -166,6 +167,18 @@ class TableToken extends TrToken {
166
167
  return super.insertAt(token, i);
167
168
  }
168
169
 
170
+ /**
171
+ * @override
172
+ * @param {number} start 起始位置
173
+ */
174
+ lint(start = 0) {
175
+ const errors = super.lint(start);
176
+ if (!this.closed) {
177
+ errors.push(generateForChild(this.firstChild, this.getRootNode().posFromIndex(start), '未闭合的表格'));
178
+ }
179
+ return errors;
180
+ }
181
+
169
182
  /**
170
183
  * 闭合表格语法
171
184
  * @complexity `n`
@@ -177,13 +190,13 @@ class TableToken extends TrToken {
177
190
  const config = this.getAttribute('config'),
178
191
  accum = this.getAttribute('accum'),
179
192
  inner = !halfParsed && Parser.parse(syntax, this.getAttribute('include'), 2, config),
180
- {lastElementChild} = this;
193
+ {lastChild} = this;
181
194
  if (!halfParsed && !closingPattern.test(inner.text())) {
182
195
  throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(syntax)}`);
183
- } else if (lastElementChild instanceof SyntaxToken) {
184
- lastElementChild.replaceChildren(...inner.childNodes);
196
+ } else if (lastChild instanceof SyntaxToken) {
197
+ lastChild.replaceChildren(...inner.childNodes);
185
198
  } else {
186
- this.appendChild(Parser.run(() => {
199
+ super.insertAt(Parser.run(() => {
187
200
  const token = new SyntaxToken(syntax, closingPattern, 'table-syntax', config, accum, {
188
201
  'Stage-1': ':', '!ExtToken': '', TranscludeToken: ':',
189
202
  });
@@ -202,7 +215,7 @@ class TableToken extends TrToken {
202
215
  */
203
216
  getRowCount() {
204
217
  return super.getRowCount()
205
- + this.children.filter(child => child.type === 'tr' && child.getRowCount()).length;
218
+ + this.childNodes.filter(child => child.type === 'tr' && child.getRowCount()).length;
206
219
  }
207
220
 
208
221
  /** @override */
@@ -228,7 +241,7 @@ class TableToken extends TrToken {
228
241
  * @throws `RangeError` 不存在该行
229
242
  */
230
243
  getNthRow(n, force, insert) {
231
- if (typeof n !== 'number') {
244
+ if (!Number.isInteger(n)) {
232
245
  this.typeError('getNthRow', 'Number');
233
246
  }
234
247
  const nRows = this.getRowCount(),
@@ -241,7 +254,7 @@ class TableToken extends TrToken {
241
254
  } else if (isRow) {
242
255
  n--;
243
256
  }
244
- for (const child of this.children.slice(2)) {
257
+ for (const child of this.childNodes.slice(2)) {
245
258
  if (child.type === 'tr' && child.getRowCount()) {
246
259
  n--;
247
260
  if (n < 0) {
@@ -262,7 +275,7 @@ class TableToken extends TrToken {
262
275
  getAllRows() {
263
276
  return [
264
277
  ...super.getRowCount() ? [this] : [],
265
- ...this.children.filter(child => child.type === 'tr' && child.getRowCount()),
278
+ ...this.childNodes.filter(child => child.type === 'tr' && child.getRowCount()),
266
279
  ];
267
280
  }
268
281
 
@@ -295,7 +308,7 @@ class TableToken extends TrToken {
295
308
  let j = 0,
296
309
  k = 0,
297
310
  last;
298
- for (const cell of rows[i].children.slice(2)) {
311
+ for (const cell of rows[i].childNodes.slice(2)) {
299
312
  if (cell instanceof TdToken) {
300
313
  if (cell.isIndependent()) {
301
314
  last = cell.subtype !== 'caption';
@@ -344,7 +357,7 @@ class TableToken extends TrToken {
344
357
  * @complexity `n`
345
358
  */
346
359
  toRenderedCoords({row, column}) {
347
- if (typeof row !== 'number' || typeof column !== 'number') {
360
+ if (!Number.isInteger(row) || !Number.isInteger(column)) {
348
361
  this.typeError('toRenderedCoords', 'Number');
349
362
  }
350
363
  const rowLayout = this.getLayout({row, column})[row],
@@ -358,19 +371,19 @@ class TableToken extends TrToken {
358
371
  * @complexity `n`
359
372
  */
360
373
  toRawCoords({x, y}) {
361
- if (typeof x !== 'number' || typeof y !== 'number') {
374
+ if (!Number.isInteger(x) || !Number.isInteger(y)) {
362
375
  this.typeError('toRawCoords', 'Number');
363
376
  }
364
377
  const rowLayout = this.getLayout({x, y})[y],
365
378
  coords = rowLayout?.[x];
366
379
  if (coords) {
367
380
  return {...coords, start: coords.row === y && rowLayout[x - 1] !== coords};
368
- } else if (!rowLayout && y === 0) {
369
- return {row: 0, column: 0, start: true};
381
+ } else if (rowLayout || y > 0) {
382
+ return x === rowLayout?.length
383
+ ? {row: y, column: (rowLayout.findLast(({row}) => row === y)?.column ?? -1) + 1, start: true}
384
+ : undefined;
370
385
  }
371
- return x === rowLayout?.length
372
- ? {row: y, column: (rowLayout.findLast(({row}) => row === y)?.column ?? -1) + 1, start: true}
373
- : undefined;
386
+ return {row: 0, column: 0, start: true};
374
387
  }
375
388
 
376
389
  /**
@@ -379,7 +392,7 @@ class TableToken extends TrToken {
379
392
  * @complexity `n²`
380
393
  */
381
394
  getFullRow(y) {
382
- if (typeof y !== 'number') {
395
+ if (!Number.isInteger(y)) {
383
396
  this.typeError('getFullRow', 'Number');
384
397
  }
385
398
  const rows = this.getAllRows();
@@ -394,7 +407,7 @@ class TableToken extends TrToken {
394
407
  * @complexity `n`
395
408
  */
396
409
  getFullCol(x) {
397
- if (typeof x !== 'number') {
410
+ if (!Number.isInteger(x)) {
398
411
  this.typeError('getFullCol', 'Number');
399
412
  }
400
413
  const layout = this.getLayout(),
@@ -474,7 +487,7 @@ class TableToken extends TrToken {
474
487
  if (coords.column === undefined) {
475
488
  const {x, y} = coords;
476
489
  coords = this.toRawCoords(coords);
477
- if (!coords?.start) { // eslint-disable-line unicorn/consistent-destructuring
490
+ if (!coords?.start) {
478
491
  throw new RangeError(`指定的坐标不是单元格起始点:(${x}, ${y})`);
479
492
  }
480
493
  }
@@ -490,16 +503,16 @@ class TableToken extends TrToken {
490
503
  */
491
504
  #prependTableRow() {
492
505
  const row = Parser.run(() => new TrToken('\n|-', undefined, this.getAttribute('config'))),
493
- {children} = this,
494
- [,, plain] = children,
495
- start = plain?.isPlain() ? 3 : 2,
496
- /** @type {TdToken[]} */ tdChildren = children.slice(start),
506
+ {childNodes} = this,
507
+ [,, plain] = childNodes,
508
+ start = plain?.constructor === Token ? 3 : 2,
509
+ /** @type {TdToken[]} */ tdChildren = childNodes.slice(start),
497
510
  index = tdChildren.findIndex(({type}) => type !== 'td');
498
511
  this.insertAt(row, index === -1 ? -1 : index + start);
499
512
  Parser.run(() => {
500
513
  for (const cell of tdChildren.slice(0, index === -1 ? undefined : index)) {
501
514
  if (cell.subtype !== 'caption') {
502
- row.appendChild(cell);
515
+ row.insertAt(cell);
503
516
  }
504
517
  }
505
518
  });
@@ -516,10 +529,11 @@ class TableToken extends TrToken {
516
529
  * @complexity `n`
517
530
  */
518
531
  insertTableRow(y, attr = {}, inner = undefined, subtype = 'td', innerAttr = {}) {
519
- if (typeof attr !== 'object') {
532
+ if (!isPlainObject(attr)) {
520
533
  this.typeError('insertTableRow', 'Object');
521
534
  }
522
535
  let reference = this.getNthRow(y, false, true);
536
+ const AttributeToken = require('../attribute');
523
537
  /** @type {TrToken & AttributeToken}} */
524
538
  const token = Parser.run(() => new TrToken('\n|-', undefined, this.getAttribute('config')));
525
539
  for (const [k, v] of Object.entries(attr)) {
@@ -539,7 +553,7 @@ class TableToken extends TrToken {
539
553
  for (let i = 0; i < maxCol; i++) {
540
554
  const coords = rowLayout[i];
541
555
  if (!coords) {
542
- token.appendChild(td.cloneNode());
556
+ token.insertAt(td.cloneNode());
543
557
  } else if (!set.has(coords)) {
544
558
  set.add(coords);
545
559
  if (coords.row < y) {
@@ -562,7 +576,7 @@ class TableToken extends TrToken {
562
576
  * @throws `RangeError` 列号过大
563
577
  */
564
578
  insertTableCol(x, inner, subtype = 'td', attr = {}) {
565
- if (typeof x !== 'number') {
579
+ if (!Number.isInteger(x)) {
566
580
  this.typeError('insertTableCol', 'Number');
567
581
  }
568
582
  const layout = this.getLayout(),
@@ -631,11 +645,11 @@ class TableToken extends TrToken {
631
645
  */
632
646
  removeTableCol(x) {
633
647
  for (const [token, start] of this.getFullCol(x)) {
634
- const {colspan, lastElementChild} = token;
648
+ const {colspan, lastChild} = token;
635
649
  if (colspan > 1) {
636
650
  token.colspan = colspan - 1;
637
651
  if (start) {
638
- lastElementChild.replaceChildren();
652
+ lastChild.replaceChildren();
639
653
  }
640
654
  } else {
641
655
  token.remove();
@@ -651,7 +665,7 @@ class TableToken extends TrToken {
651
665
  * @throws `RangeError` 待合并区域与外侧区域有重叠
652
666
  */
653
667
  mergeCells(xlim, ylim) {
654
- if ([...xlim, ...ylim].some(arg => typeof arg !== 'number')) {
668
+ if (![...xlim, ...ylim].every(Number.isInteger)) {
655
669
  this.typeError('mergeCells', 'Number');
656
670
  }
657
671
  const layout = this.getLayout(),
@@ -664,7 +678,7 @@ class TableToken extends TrToken {
664
678
  if ([...layout[ymin - 1] ?? [], ...layout[ymax] ?? []].some(coords => set.has(coords))
665
679
  || layout.some(rowLayout => set.has(rowLayout[xmin - 1]) || set.has(rowLayout[xmax]))
666
680
  ) {
667
- throw new RangeError(`待合并区域与外侧区域有重叠!`);
681
+ throw new RangeError('待合并区域与外侧区域有重叠!');
668
682
  }
669
683
  const corner = layout[ymin][xmin],
670
684
  rows = this.getAllRows(),
@@ -703,7 +717,7 @@ class TableToken extends TrToken {
703
717
  if (x !== undefined) {
704
718
  coords = this.toRawCoords(coords);
705
719
  }
706
- if (coords.start === false || x === undefined) { // eslint-disable-line unicorn/consistent-destructuring
720
+ if (coords.start === false || x === undefined) {
707
721
  ({x, y} = this.toRenderedCoords(coords));
708
722
  }
709
723
  const splitting = {rowspan: 1, colspan: 1};
@@ -804,7 +818,7 @@ class TableToken extends TrToken {
804
818
  * @throws `RangeError` 无法移动
805
819
  */
806
820
  moveTableRowBefore(y, before) {
807
- if (typeof y !== 'number' || typeof before !== 'number') {
821
+ if (!Number.isInteger(y) || !Number.isInteger(before)) {
808
822
  this.typeError('moveTableRowBefore', 'Number');
809
823
  }
810
824
  const layout = this.getLayout();
@@ -844,13 +858,13 @@ class TableToken extends TrToken {
844
858
  * @throws `RangeError` 无法移动
845
859
  */
846
860
  moveTableRowAfter(y, after) {
847
- if (typeof y !== 'number' || typeof after !== 'number') {
861
+ if (!Number.isInteger(y) || !Number.isInteger(after)) {
848
862
  this.typeError('moveTableRowAfter', 'Number');
849
863
  }
850
864
  const layout = this.getLayout(),
851
865
  afterToken = this.getNthRow(after),
852
866
  /** @type {TdToken[]} */
853
- cells = afterToken.children.filter(child => child instanceof TdToken && child.subtype !== 'caption');
867
+ cells = afterToken.childNodes.filter(child => child instanceof TdToken && child.subtype !== 'caption');
854
868
 
855
869
  /**
856
870
  * @type {(i: number, oneRow?: boolean) => number[]}
@@ -897,7 +911,7 @@ class TableToken extends TrToken {
897
911
  * @throws `RangeError` 无法移动
898
912
  */
899
913
  #moveCol(x, reference, after = false) {
900
- if (typeof x !== 'number' || typeof reference !== 'number') {
914
+ if (!Number.isInteger(x) || !Number.isInteger(reference)) {
901
915
  this.typeError(`moveTableCol${after ? 'After' : 'Before'}`, 'Number');
902
916
  }
903
917
  const layout = this.getLayout();
@@ -926,7 +940,7 @@ class TableToken extends TrToken {
926
940
  if (start) {
927
941
  const original = token;
928
942
  token = token.cloneNode();
929
- original.lastElementChild.replaceChildren();
943
+ original.lastChild.replaceChildren();
930
944
  token.colspan = 1;
931
945
  }
932
946
  }
@@ -934,7 +948,7 @@ class TableToken extends TrToken {
934
948
  const col = rowLayout.slice(reference + Number(after)).find(({row}) => row === i)?.column;
935
949
  rowToken.insertBefore(
936
950
  token, col === undefined && rowToken.type === 'table'
937
- ? rowToken.children.slice(2).find(isRowEnd)
951
+ ? rowToken.childNodes.slice(2).find(isRowEnd)
938
952
  : col !== undefined && rowToken.getNthCol(col),
939
953
  );
940
954
  }