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.
- package/.eslintrc.json +472 -34
- package/README.md +1 -1
- package/config/default.json +58 -30
- package/config/llwiki.json +22 -90
- package/config/moegirl.json +51 -13
- package/config/zhwiki.json +1269 -0
- package/index.js +114 -104
- package/lib/element.js +448 -440
- package/lib/node.js +335 -115
- package/lib/ranges.js +27 -18
- package/lib/text.js +146 -0
- package/lib/title.js +13 -5
- package/mixin/attributeParent.js +70 -24
- package/mixin/fixedToken.js +14 -6
- package/mixin/hidden.js +6 -4
- package/mixin/sol.js +27 -10
- package/package.json +9 -3
- package/parser/brackets.js +22 -17
- package/parser/commentAndExt.js +18 -16
- package/parser/converter.js +14 -13
- package/parser/externalLinks.js +12 -11
- package/parser/hrAndDoubleUnderscore.js +23 -14
- package/parser/html.js +10 -9
- package/parser/links.js +15 -14
- package/parser/list.js +12 -11
- package/parser/magicLinks.js +12 -11
- package/parser/quotes.js +6 -5
- package/parser/selector.js +175 -0
- package/parser/table.js +25 -18
- package/printed/example.json +120 -0
- package/src/arg.js +56 -32
- package/src/atom/hidden.js +5 -2
- package/src/atom/index.js +17 -9
- package/src/attribute.js +182 -100
- package/src/converter.js +68 -41
- package/src/converterFlags.js +67 -45
- package/src/converterRule.js +117 -65
- package/src/extLink.js +66 -18
- package/src/gallery.js +42 -15
- package/src/heading.js +34 -15
- package/src/html.js +97 -35
- package/src/imageParameter.js +83 -54
- package/src/index.js +299 -178
- package/src/link/category.js +20 -52
- package/src/link/file.js +59 -28
- package/src/link/galleryImage.js +21 -7
- package/src/link/index.js +146 -60
- package/src/magicLink.js +34 -12
- package/src/nowiki/comment.js +22 -10
- package/src/nowiki/dd.js +37 -22
- package/src/nowiki/doubleUnderscore.js +16 -7
- package/src/nowiki/hr.js +11 -7
- package/src/nowiki/index.js +16 -9
- package/src/nowiki/list.js +2 -2
- package/src/nowiki/noinclude.js +8 -4
- package/src/nowiki/quote.js +11 -7
- package/src/onlyinclude.js +19 -7
- package/src/parameter.js +65 -38
- package/src/syntax.js +26 -20
- package/src/table/index.js +260 -165
- package/src/table/td.js +98 -52
- package/src/table/tr.js +102 -58
- package/src/tagPair/ext.js +27 -19
- package/src/tagPair/include.js +16 -11
- package/src/tagPair/index.js +64 -29
- package/src/transclude.js +170 -93
- package/test/api.js +83 -0
- package/test/real.js +133 -0
- package/test/test.js +28 -0
- package/test/util.js +80 -0
- package/tool/index.js +41 -31
- package/typings/api.d.ts +13 -0
- package/typings/array.d.ts +28 -0
- package/typings/event.d.ts +24 -0
- package/typings/index.d.ts +46 -4
- package/typings/node.d.ts +15 -9
- package/typings/parser.d.ts +7 -0
- package/typings/tool.d.ts +3 -2
- package/util/debug.js +21 -18
- package/util/string.js +40 -27
- package/typings/element.d.ts +0 -28
package/src/table/index.js
CHANGED
|
@@ -2,26 +2,93 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('assert/strict'),
|
|
4
4
|
{noWrap} = require('../../util/string'),
|
|
5
|
-
|
|
5
|
+
Parser = require('../..'),
|
|
6
6
|
Token = require('..'),
|
|
7
7
|
TrToken = require('./tr'),
|
|
8
8
|
TdToken = require('./td'),
|
|
9
9
|
SyntaxToken = require('../syntax'),
|
|
10
10
|
AttributeToken = require('../attribute');
|
|
11
11
|
|
|
12
|
+
const openingPattern = /^(?:\{\||\{\{\{\s*!\s*\}\}|\{\{\s*\(!\s*\}\})$/u,
|
|
13
|
+
closingPattern = /^\n[^\S\n]*(?:\|\}|\{\{\s*!\s*\}\}\}|\{\{\s*!\)\s*\}\})$/u;
|
|
14
|
+
|
|
12
15
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param {TableCoords}
|
|
16
|
+
* 比较两个表格坐标
|
|
17
|
+
* @param {TableCoords} coords1 坐标1
|
|
18
|
+
* @param {TableCoords} coords2 坐标2
|
|
15
19
|
*/
|
|
16
20
|
const cmpCoords = (coords1, coords2) => {
|
|
17
21
|
const diff = coords1.row - coords2.row;
|
|
18
22
|
return diff === 0 ? coords1.column - coords2.column : diff;
|
|
19
23
|
};
|
|
20
|
-
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 是否是行尾
|
|
27
|
+
* @param {Token} cell 表格单元格
|
|
28
|
+
*/
|
|
29
|
+
const isRowEnd = ({type}) => type === 'tr' || type === 'table-syntax';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 是否是合并单元格的第一列
|
|
33
|
+
* @param {TableCoords[]} rowLayout 行布局
|
|
34
|
+
* @param {number} i 单元格序号
|
|
35
|
+
* @param {boolean} oneCol 是否仅有一列
|
|
36
|
+
*/
|
|
37
|
+
const isStartCol = (rowLayout, i, oneCol = false) => {
|
|
38
|
+
const coords = rowLayout[i];
|
|
39
|
+
return rowLayout[i - 1] !== coords && (!oneCol || rowLayout[i + 1] !== coords);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 设置表格格式
|
|
44
|
+
* @param {Map<TdToken, boolean>} cells 单元格
|
|
45
|
+
* @param {string|Record<string, string|boolean>} attr 属性
|
|
46
|
+
* @param {boolean} multi 是否对所有单元格设置,或是仅对行首单元格设置
|
|
47
|
+
* @complexity `n`
|
|
48
|
+
*/
|
|
49
|
+
const format = (cells, attr = {}, multi = false) => {
|
|
50
|
+
for (const [token, start] of cells) {
|
|
51
|
+
if (multi || start) {
|
|
52
|
+
if (typeof attr === 'string') {
|
|
53
|
+
token.setSyntax(attr);
|
|
54
|
+
} else {
|
|
55
|
+
for (const [k, v] of Object.entries(attr)) {
|
|
56
|
+
token.setAttr(k, v);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 填补缺失单元格
|
|
65
|
+
* @param {number} y 行号
|
|
66
|
+
* @param {TrToken} rowToken 表格行
|
|
67
|
+
* @param {TableCoords[][]} layout 表格布局
|
|
68
|
+
* @param {number} maxCol 最大列数
|
|
69
|
+
* @param {Token} token 待填充的单元格
|
|
70
|
+
* @complexity `n`
|
|
71
|
+
*/
|
|
72
|
+
const fill = (y, rowToken, layout, maxCol, token) => {
|
|
73
|
+
const rowLayout = layout[y],
|
|
74
|
+
{childNodes} = rowToken,
|
|
75
|
+
lastIndex = childNodes.findLastIndex(child => child instanceof TdToken && child.subtype !== 'caption') + 1
|
|
76
|
+
|| undefined;
|
|
77
|
+
Parser.run(() => {
|
|
78
|
+
for (let i = 0; i < maxCol; i++) {
|
|
79
|
+
if (!rowLayout[i]) {
|
|
80
|
+
rowToken.insertAt(token.cloneNode(), lastIndex);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
};
|
|
21
85
|
|
|
22
86
|
/** @extends {Array<TableCoords[]>} */
|
|
23
87
|
class Layout extends Array {
|
|
24
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* 打印表格布局
|
|
90
|
+
* @complexity `n`
|
|
91
|
+
*/
|
|
25
92
|
print() {
|
|
26
93
|
const hBorders = new Array(this.length + 1).fill().map((_, i) => {
|
|
27
94
|
const prev = this[i - 1] ?? [],
|
|
@@ -37,10 +104,9 @@ class Layout extends Array {
|
|
|
37
104
|
// eslint-disable-next-line no-sparse-arrays
|
|
38
105
|
border = [' ',,, '┌',, '┐', '─', '┬',, '│', '└', '├', '┘', '┤', '┴', '┼'];
|
|
39
106
|
for (let j = 0; j <= hBorder.length; j++) {
|
|
40
|
-
|
|
107
|
+
// eslint-disable-next-line no-bitwise
|
|
41
108
|
const bit = (vBorderTop[j] << 3) + (vBorderBottom[j] << 0) + (hBorder[j - 1] << 2) + (hBorder[j] << 1);
|
|
42
109
|
out += `${border[bit]}${hBorder[j] ? '─' : ' '}`;
|
|
43
|
-
/* eslint-enable no-bitwise */
|
|
44
110
|
}
|
|
45
111
|
out += '\n';
|
|
46
112
|
}
|
|
@@ -55,48 +121,63 @@ class Layout extends Array {
|
|
|
55
121
|
class TableToken extends TrToken {
|
|
56
122
|
type = 'table';
|
|
57
123
|
|
|
58
|
-
|
|
59
|
-
|
|
124
|
+
/** 表格是否闭合 */
|
|
125
|
+
get closed() {
|
|
126
|
+
return this.lastElementChild.type === 'table-syntax';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
set closed(closed) {
|
|
130
|
+
if (closed === true && !this.closed) {
|
|
131
|
+
this.close(this.closest('parameter') ? '\n{{!)}}' : '\n|}');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
60
134
|
|
|
61
135
|
/**
|
|
62
|
-
* @param {string} syntax
|
|
136
|
+
* @param {string} syntax 表格语法
|
|
137
|
+
* @param {string} attr 表格属性
|
|
63
138
|
* @param {accum} accum
|
|
64
139
|
*/
|
|
65
140
|
constructor(syntax, attr = '', config = Parser.getConfig(), accum = []) {
|
|
66
|
-
super(syntax, attr, config, accum,
|
|
141
|
+
super(syntax, attr, config, accum, openingPattern);
|
|
67
142
|
this.setAttribute('acceptable', {
|
|
68
143
|
Token: 2, SyntaxToken: [0, -1], AttributeToken: 1, TdToken: '2:', TrToken: '2:',
|
|
69
144
|
});
|
|
70
145
|
}
|
|
71
146
|
|
|
72
147
|
/**
|
|
148
|
+
* @override
|
|
73
149
|
* @template {TrToken|SyntaxToken} T
|
|
74
|
-
* @param {T} token
|
|
150
|
+
* @param {T} token 待插入的子节点
|
|
151
|
+
* @param {number} i 插入位置
|
|
75
152
|
* @returns {T}
|
|
76
153
|
* @complexity `n`
|
|
154
|
+
* @throws `SyntaxError` 表格的闭合部分非法
|
|
77
155
|
*/
|
|
78
156
|
insertAt(token, i = this.childNodes.length) {
|
|
79
|
-
const previous = this.children.at(i - 1)
|
|
80
|
-
{closingPattern} = TableToken;
|
|
157
|
+
const previous = this.children.at(i - 1);
|
|
81
158
|
if (token.type === 'td' && previous.type === 'tr') {
|
|
82
159
|
Parser.warn('改为将单元格插入当前行。');
|
|
83
160
|
return previous.appendChild(token);
|
|
84
161
|
} else if (!Parser.running && i === this.childNodes.length && token instanceof SyntaxToken
|
|
85
162
|
&& (token.getAttribute('pattern') !== closingPattern || !closingPattern.test(token.text()))
|
|
86
163
|
) {
|
|
87
|
-
throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(token
|
|
164
|
+
throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(String(token))}`);
|
|
88
165
|
}
|
|
89
166
|
return super.insertAt(token, i);
|
|
90
167
|
}
|
|
91
168
|
|
|
92
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* 闭合表格语法
|
|
171
|
+
* @complexity `n`
|
|
172
|
+
* @param {string} syntax 表格结尾语法
|
|
173
|
+
* @throws `SyntaxError` 表格的闭合部分不符合语法
|
|
174
|
+
*/
|
|
93
175
|
close(syntax = '\n|}', halfParsed = false) {
|
|
94
176
|
halfParsed &&= Parser.running;
|
|
95
177
|
const config = this.getAttribute('config'),
|
|
96
178
|
accum = this.getAttribute('accum'),
|
|
97
179
|
inner = !halfParsed && Parser.parse(syntax, this.getAttribute('include'), 2, config),
|
|
98
|
-
{lastElementChild} = this
|
|
99
|
-
{closingPattern} = TableToken;
|
|
180
|
+
{lastElementChild} = this;
|
|
100
181
|
if (!halfParsed && !closingPattern.test(inner.text())) {
|
|
101
182
|
throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(syntax)}`);
|
|
102
183
|
} else if (lastElementChild instanceof SyntaxToken) {
|
|
@@ -115,6 +196,7 @@ class TableToken extends TrToken {
|
|
|
115
196
|
}
|
|
116
197
|
|
|
117
198
|
/**
|
|
199
|
+
* @override
|
|
118
200
|
* @returns {number}
|
|
119
201
|
* @complexity `n`
|
|
120
202
|
*/
|
|
@@ -123,19 +205,29 @@ class TableToken extends TrToken {
|
|
|
123
205
|
+ this.children.filter(child => child.type === 'tr' && child.getRowCount()).length;
|
|
124
206
|
}
|
|
125
207
|
|
|
126
|
-
|
|
208
|
+
/** @override */
|
|
209
|
+
getPreviousRow() { // eslint-disable-line class-methods-use-this
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
127
212
|
|
|
128
|
-
/**
|
|
213
|
+
/**
|
|
214
|
+
* @override
|
|
215
|
+
* @complexity `n`
|
|
216
|
+
*/
|
|
129
217
|
getNextRow() {
|
|
130
218
|
return this.getNthRow(super.getRowCount() ? 1 : 0);
|
|
131
219
|
}
|
|
132
220
|
|
|
133
221
|
/**
|
|
134
|
-
*
|
|
222
|
+
* 获取第n行
|
|
223
|
+
* @param {number} n 行号
|
|
224
|
+
* @param {boolean} force 是否将表格自身视为第一行
|
|
225
|
+
* @param {boolean} insert 是否用于判断插入新行的位置
|
|
135
226
|
* @returns {TrToken}
|
|
136
227
|
* @complexity `n`
|
|
228
|
+
* @throws `RangeError` 不存在该行
|
|
137
229
|
*/
|
|
138
|
-
getNthRow(n, force
|
|
230
|
+
getNthRow(n, force, insert) {
|
|
139
231
|
if (typeof n !== 'number') {
|
|
140
232
|
this.typeError('getNthRow', 'Number');
|
|
141
233
|
}
|
|
@@ -159,9 +251,11 @@ class TableToken extends TrToken {
|
|
|
159
251
|
return child;
|
|
160
252
|
}
|
|
161
253
|
}
|
|
254
|
+
return undefined;
|
|
162
255
|
}
|
|
163
256
|
|
|
164
257
|
/**
|
|
258
|
+
* 获取所有行
|
|
165
259
|
* @returns {TrToken[]}
|
|
166
260
|
* @complexity `n`
|
|
167
261
|
*/
|
|
@@ -173,7 +267,8 @@ class TableToken extends TrToken {
|
|
|
173
267
|
}
|
|
174
268
|
|
|
175
269
|
/**
|
|
176
|
-
*
|
|
270
|
+
* 获取指定坐标的单元格
|
|
271
|
+
* @param {TableCoords & TableRenderedCoords} coords 表格坐标
|
|
177
272
|
* @complexity `n`
|
|
178
273
|
*/
|
|
179
274
|
getNthCell(coords) {
|
|
@@ -184,14 +279,15 @@ class TableToken extends TrToken {
|
|
|
184
279
|
}
|
|
185
280
|
|
|
186
281
|
/**
|
|
187
|
-
*
|
|
282
|
+
* 获取表格布局
|
|
283
|
+
* @param {TableCoords & TableRenderedCoords} stop 中止条件
|
|
188
284
|
* @complexity `n`
|
|
189
285
|
*/
|
|
190
286
|
getLayout(stop = {}) {
|
|
191
287
|
const rows = this.getAllRows(),
|
|
192
288
|
{length} = rows,
|
|
193
289
|
/** @type {Layout} */ layout = new Layout(length).fill().map(() => []);
|
|
194
|
-
for (
|
|
290
|
+
for (let i = 0; i < length; i++) {
|
|
195
291
|
if (i > (stop.row ?? stop.y)) {
|
|
196
292
|
break;
|
|
197
293
|
}
|
|
@@ -199,7 +295,7 @@ class TableToken extends TrToken {
|
|
|
199
295
|
let j = 0,
|
|
200
296
|
k = 0,
|
|
201
297
|
last;
|
|
202
|
-
for (const cell of
|
|
298
|
+
for (const cell of rows[i].children.slice(2)) {
|
|
203
299
|
if (cell instanceof TdToken) {
|
|
204
300
|
if (cell.isIndependent()) {
|
|
205
301
|
last = cell.subtype !== 'caption';
|
|
@@ -233,13 +329,17 @@ class TableToken extends TrToken {
|
|
|
233
329
|
return layout;
|
|
234
330
|
}
|
|
235
331
|
|
|
236
|
-
/**
|
|
332
|
+
/**
|
|
333
|
+
* 打印表格布局
|
|
334
|
+
* @complexity `n`
|
|
335
|
+
*/
|
|
237
336
|
printLayout() {
|
|
238
337
|
this.getLayout().print();
|
|
239
338
|
}
|
|
240
339
|
|
|
241
340
|
/**
|
|
242
|
-
*
|
|
341
|
+
* 转换为渲染后的表格坐标
|
|
342
|
+
* @param {TableCoords} coord wikitext中的表格坐标
|
|
243
343
|
* @returns {TableRenderedCoords}
|
|
244
344
|
* @complexity `n`
|
|
245
345
|
*/
|
|
@@ -253,7 +353,8 @@ class TableToken extends TrToken {
|
|
|
253
353
|
}
|
|
254
354
|
|
|
255
355
|
/**
|
|
256
|
-
*
|
|
356
|
+
* 转换为wikitext中的表格坐标
|
|
357
|
+
* @param {TableRenderedCoords} coord 渲染后的表格坐标
|
|
257
358
|
* @complexity `n`
|
|
258
359
|
*/
|
|
259
360
|
toRawCoords({x, y}) {
|
|
@@ -266,13 +367,15 @@ class TableToken extends TrToken {
|
|
|
266
367
|
return {...coords, start: coords.row === y && rowLayout[x - 1] !== coords};
|
|
267
368
|
} else if (!rowLayout && y === 0) {
|
|
268
369
|
return {row: 0, column: 0, start: true};
|
|
269
|
-
} else if (x === rowLayout?.length) {
|
|
270
|
-
return {row: y, column: (rowLayout.findLast(({row}) => row === y)?.column ?? -1) + 1, start: true};
|
|
271
370
|
}
|
|
371
|
+
return x === rowLayout?.length
|
|
372
|
+
? {row: y, column: (rowLayout.findLast(({row}) => row === y)?.column ?? -1) + 1, start: true}
|
|
373
|
+
: undefined;
|
|
272
374
|
}
|
|
273
375
|
|
|
274
376
|
/**
|
|
275
|
-
*
|
|
377
|
+
* 获取完整行
|
|
378
|
+
* @param {number} y 行号
|
|
276
379
|
* @complexity `n²`
|
|
277
380
|
*/
|
|
278
381
|
getFullRow(y) {
|
|
@@ -286,7 +389,8 @@ class TableToken extends TrToken {
|
|
|
286
389
|
}
|
|
287
390
|
|
|
288
391
|
/**
|
|
289
|
-
*
|
|
392
|
+
* 获取完整列
|
|
393
|
+
* @param {number} x 列号
|
|
290
394
|
* @complexity `n`
|
|
291
395
|
*/
|
|
292
396
|
getFullCol(x) {
|
|
@@ -294,7 +398,7 @@ class TableToken extends TrToken {
|
|
|
294
398
|
this.typeError('getFullCol', 'Number');
|
|
295
399
|
}
|
|
296
400
|
const layout = this.getLayout(),
|
|
297
|
-
colLayout = layout.map(row => row[x]).filter(
|
|
401
|
+
colLayout = layout.map(row => row[x]).filter(Boolean),
|
|
298
402
|
rows = this.getAllRows();
|
|
299
403
|
return new Map(
|
|
300
404
|
colLayout.map(coords => [rows[coords.row].getNthCol(coords.column), layout[coords.row][x - 1] !== coords]),
|
|
@@ -302,69 +406,33 @@ class TableToken extends TrToken {
|
|
|
302
406
|
}
|
|
303
407
|
|
|
304
408
|
/**
|
|
305
|
-
*
|
|
306
|
-
* @param {
|
|
307
|
-
* @
|
|
308
|
-
|
|
309
|
-
#format(cells, attr = {}, multi = false) {
|
|
310
|
-
for (const [token, start] of cells) {
|
|
311
|
-
if (multi || start) {
|
|
312
|
-
if (typeof attr === 'string') {
|
|
313
|
-
token.setSyntax(attr);
|
|
314
|
-
} else {
|
|
315
|
-
for (const [k, v] of Object.entries(attr)) {
|
|
316
|
-
token.setAttr(k, v);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* @param {number} y
|
|
325
|
-
* @param {string|Record<string, string|boolean>} attry
|
|
409
|
+
* 设置行格式
|
|
410
|
+
* @param {number} y 行号
|
|
411
|
+
* @param {string|Record<string, string|boolean>} attr 表格属性
|
|
412
|
+
* @param {boolean} multiRow 是否对所有单元格设置,或是仅对行首单元格设置
|
|
326
413
|
* @complexity `n²`
|
|
327
414
|
*/
|
|
328
415
|
formatTableRow(y, attr = {}, multiRow = false) {
|
|
329
|
-
|
|
416
|
+
format(this.getFullRow(y), attr, multiRow);
|
|
330
417
|
}
|
|
331
418
|
|
|
332
419
|
/**
|
|
333
|
-
*
|
|
334
|
-
* @param {
|
|
420
|
+
* 设置列格式
|
|
421
|
+
* @param {number} x 列号
|
|
422
|
+
* @param {string|Record<string, string|boolean>} attr 表格属性
|
|
423
|
+
* @param {boolean} multiCol 是否对所有单元格设置,或是仅对行首单元格设置
|
|
335
424
|
* @complexity `n`
|
|
336
425
|
*/
|
|
337
426
|
formatTableCol(x, attr = {}, multiCol = false) {
|
|
338
|
-
|
|
427
|
+
format(this.getFullCol(x), attr, multiCol);
|
|
339
428
|
}
|
|
340
429
|
|
|
341
430
|
/**
|
|
342
|
-
*
|
|
343
|
-
* @param {
|
|
344
|
-
* @param {
|
|
345
|
-
* @param {
|
|
346
|
-
* @param {
|
|
347
|
-
* @complexity `n`
|
|
348
|
-
*/
|
|
349
|
-
#fill(y, rowToken, layout, maxCol, token) {
|
|
350
|
-
const rowLayout = layout[y],
|
|
351
|
-
{childNodes} = rowToken;
|
|
352
|
-
let lastIndex = childNodes.findLastIndex(child => child instanceof TdToken && child.subtype !== 'caption');
|
|
353
|
-
lastIndex = lastIndex === -1 ? undefined : lastIndex - childNodes.length;
|
|
354
|
-
Parser.run(() => {
|
|
355
|
-
for (let i = 0; i < maxCol; i++) {
|
|
356
|
-
if (!rowLayout[i]) {
|
|
357
|
-
rowToken.insertAt(token.cloneNode(), lastIndex);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* @param {number} y
|
|
365
|
-
* @param {string|Token} inner
|
|
366
|
-
* @param {'td'|'th'|'caption'} subtype
|
|
367
|
-
* @param {Record<string, string>} attr
|
|
431
|
+
* 填补表格行
|
|
432
|
+
* @param {number} y 行号
|
|
433
|
+
* @param {string|Token} inner 填充内容
|
|
434
|
+
* @param {'td'|'th'|'caption'} subtype 单元格类型
|
|
435
|
+
* @param {Record<string, string>} attr 表格属性
|
|
368
436
|
* @complexity `n`
|
|
369
437
|
*/
|
|
370
438
|
fillTableRow(y, inner, subtype = 'td', attr = {}) {
|
|
@@ -372,13 +440,14 @@ class TableToken extends TrToken {
|
|
|
372
440
|
layout = this.getLayout({y}),
|
|
373
441
|
maxCol = Math.max(...layout.map(row => row.length)),
|
|
374
442
|
token = TdToken.create(inner, subtype, attr, this.getAttribute('include'), this.getAttribute('config'));
|
|
375
|
-
|
|
443
|
+
fill(y, rowToken, layout, maxCol, token);
|
|
376
444
|
}
|
|
377
445
|
|
|
378
446
|
/**
|
|
379
|
-
*
|
|
380
|
-
* @param {
|
|
381
|
-
* @param {
|
|
447
|
+
* 填补表格
|
|
448
|
+
* @param {string|Token} inner 填充内容
|
|
449
|
+
* @param {'td'|'th'|'caption'} subtype 单元格类型
|
|
450
|
+
* @param {Record<string, string>} attr 表格属性
|
|
382
451
|
* @complexity `n`
|
|
383
452
|
*/
|
|
384
453
|
fillTable(inner, subtype = 'td', attr = {}) {
|
|
@@ -386,35 +455,39 @@ class TableToken extends TrToken {
|
|
|
386
455
|
layout = this.getLayout(),
|
|
387
456
|
maxCol = Math.max(...layout.map(({length}) => length)),
|
|
388
457
|
token = TdToken.create(inner, subtype, attr, this.getAttribute('include'), this.getAttribute('config'));
|
|
389
|
-
for (
|
|
390
|
-
|
|
458
|
+
for (let y = 0; y < rowTokens.length; y++) {
|
|
459
|
+
fill(y, rowTokens[y], layout, maxCol, token);
|
|
391
460
|
}
|
|
392
461
|
}
|
|
393
462
|
|
|
394
463
|
/**
|
|
395
|
-
* @
|
|
396
|
-
* @param {
|
|
397
|
-
* @param {
|
|
398
|
-
* @param {
|
|
464
|
+
* @override
|
|
465
|
+
* @param {string|Token} inner 单元格内部wikitext
|
|
466
|
+
* @param {TableCoords & TableRenderedCoords} coords 单元格坐标
|
|
467
|
+
* @param {'td'|'th'|'caption'} subtype 单元格类型
|
|
468
|
+
* @param {Record<string, string|boolean>} attr 单元格属性
|
|
399
469
|
* @returns {TdToken}
|
|
400
470
|
* @complexity `n`
|
|
471
|
+
* @throws `RangeError` 指定的坐标不是单元格起始点
|
|
401
472
|
*/
|
|
402
473
|
insertTableCell(inner, coords, subtype = 'td', attr = {}) {
|
|
403
474
|
if (coords.column === undefined) {
|
|
404
475
|
const {x, y} = coords;
|
|
405
476
|
coords = this.toRawCoords(coords);
|
|
406
|
-
if (!coords?.start) {
|
|
477
|
+
if (!coords?.start) { // eslint-disable-line unicorn/consistent-destructuring
|
|
407
478
|
throw new RangeError(`指定的坐标不是单元格起始点:(${x}, ${y})`);
|
|
408
479
|
}
|
|
409
480
|
}
|
|
410
481
|
const rowToken = this.getNthRow(coords.row ?? 0, true);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return rowToken.insertTableCell(inner, coords, subtype, attr);
|
|
482
|
+
return rowToken === this
|
|
483
|
+
? super.insertTableCell(inner, coords, subtype, attr)
|
|
484
|
+
: rowToken.insertTableCell(inner, coords, subtype, attr);
|
|
415
485
|
}
|
|
416
486
|
|
|
417
|
-
/**
|
|
487
|
+
/**
|
|
488
|
+
* 在开头插入一行
|
|
489
|
+
* @complexity `n`
|
|
490
|
+
*/
|
|
418
491
|
#prependTableRow() {
|
|
419
492
|
const row = Parser.run(() => new TrToken('\n|-', undefined, this.getAttribute('config'))),
|
|
420
493
|
{children} = this,
|
|
@@ -434,11 +507,12 @@ class TableToken extends TrToken {
|
|
|
434
507
|
}
|
|
435
508
|
|
|
436
509
|
/**
|
|
437
|
-
*
|
|
438
|
-
* @param {
|
|
439
|
-
* @param {string|
|
|
440
|
-
* @param {
|
|
441
|
-
* @param {
|
|
510
|
+
* 插入表格行
|
|
511
|
+
* @param {number} y 行号
|
|
512
|
+
* @param {Record<string, string|boolean>} attr 表格行属性
|
|
513
|
+
* @param {string|Token} inner 内部wikitext
|
|
514
|
+
* @param {'td'|'th'|'caption'} subtype 单元格类型
|
|
515
|
+
* @param {Record<string, string|boolean>} innerAttr 单元格属性
|
|
442
516
|
* @complexity `n`
|
|
443
517
|
*/
|
|
444
518
|
insertTableRow(y, attr = {}, inner = undefined, subtype = 'td', innerAttr = {}) {
|
|
@@ -479,11 +553,13 @@ class TableToken extends TrToken {
|
|
|
479
553
|
}
|
|
480
554
|
|
|
481
555
|
/**
|
|
482
|
-
*
|
|
483
|
-
* @param {
|
|
484
|
-
* @param {
|
|
485
|
-
* @param {
|
|
556
|
+
* 插入表格列
|
|
557
|
+
* @param {number} x 列号
|
|
558
|
+
* @param {string|Token} inner 内部wikitext
|
|
559
|
+
* @param {'td'|'th'|'caption'} subtype 单元格类型
|
|
560
|
+
* @param {Record<string, string>} attr 单元格属性
|
|
486
561
|
* @complexity `n²`
|
|
562
|
+
* @throws `RangeError` 列号过大
|
|
487
563
|
*/
|
|
488
564
|
insertTableCol(x, inner, subtype = 'td', attr = {}) {
|
|
489
565
|
if (typeof x !== 'number') {
|
|
@@ -511,14 +587,17 @@ class TableToken extends TrToken {
|
|
|
511
587
|
}
|
|
512
588
|
|
|
513
589
|
/**
|
|
514
|
-
*
|
|
590
|
+
* 移除表格行
|
|
591
|
+
* @param {number} y 行号
|
|
515
592
|
* @complexity `n²`
|
|
516
593
|
*/
|
|
517
594
|
removeTableRow(y) {
|
|
518
595
|
const rows = this.getAllRows(),
|
|
519
596
|
layout = this.getLayout(),
|
|
597
|
+
rowLayout = layout[y],
|
|
520
598
|
/** @type {Set<TableCoords>} */ set = new Set();
|
|
521
|
-
for (
|
|
599
|
+
for (let x = rowLayout.length - 1; x >= 0; x--) {
|
|
600
|
+
const coords = rowLayout[x];
|
|
522
601
|
if (set.has(coords)) {
|
|
523
602
|
continue;
|
|
524
603
|
}
|
|
@@ -546,16 +625,17 @@ class TableToken extends TrToken {
|
|
|
546
625
|
}
|
|
547
626
|
|
|
548
627
|
/**
|
|
549
|
-
*
|
|
628
|
+
* 移除表格列
|
|
629
|
+
* @param {number} x 列号
|
|
550
630
|
* @complexity `n²`
|
|
551
631
|
*/
|
|
552
632
|
removeTableCol(x) {
|
|
553
633
|
for (const [token, start] of this.getFullCol(x)) {
|
|
554
|
-
const {colspan} = token;
|
|
634
|
+
const {colspan, lastElementChild} = token;
|
|
555
635
|
if (colspan > 1) {
|
|
556
636
|
token.colspan = colspan - 1;
|
|
557
637
|
if (start) {
|
|
558
|
-
|
|
638
|
+
lastElementChild.replaceChildren();
|
|
559
639
|
}
|
|
560
640
|
} else {
|
|
561
641
|
token.remove();
|
|
@@ -564,9 +644,11 @@ class TableToken extends TrToken {
|
|
|
564
644
|
}
|
|
565
645
|
|
|
566
646
|
/**
|
|
567
|
-
*
|
|
568
|
-
* @param {[number, number]}
|
|
647
|
+
* 合并单元格
|
|
648
|
+
* @param {[number, number]} xlim 列范围
|
|
649
|
+
* @param {[number, number]} ylim 行范围
|
|
569
650
|
* @complexity `n²`
|
|
651
|
+
* @throws `RangeError` 待合并区域与外侧区域有重叠
|
|
570
652
|
*/
|
|
571
653
|
mergeCells(xlim, ylim) {
|
|
572
654
|
if ([...xlim, ...ylim].some(arg => typeof arg !== 'number')) {
|
|
@@ -597,9 +679,11 @@ class TableToken extends TrToken {
|
|
|
597
679
|
}
|
|
598
680
|
|
|
599
681
|
/**
|
|
600
|
-
*
|
|
601
|
-
* @param {
|
|
682
|
+
* 分裂单元格
|
|
683
|
+
* @param {TableCoords & TableRenderedCoords} coords 单元格坐标
|
|
684
|
+
* @param {Set<'rowspan'|'colspan'>} dirs 分裂方向
|
|
602
685
|
* @complexity `n²`
|
|
686
|
+
* @throws `RangeError` 指定的坐标不是单元格起始点
|
|
603
687
|
*/
|
|
604
688
|
#split(coords, dirs) {
|
|
605
689
|
const cell = this.getNthCell(coords),
|
|
@@ -619,7 +703,7 @@ class TableToken extends TrToken {
|
|
|
619
703
|
if (x !== undefined) {
|
|
620
704
|
coords = this.toRawCoords(coords);
|
|
621
705
|
}
|
|
622
|
-
if (coords.start === false || x === undefined) {
|
|
706
|
+
if (coords.start === false || x === undefined) { // eslint-disable-line unicorn/consistent-destructuring
|
|
623
707
|
({x, y} = this.toRenderedCoords(coords));
|
|
624
708
|
}
|
|
625
709
|
const splitting = {rowspan: 1, colspan: 1};
|
|
@@ -634,10 +718,10 @@ class TableToken extends TrToken {
|
|
|
634
718
|
try {
|
|
635
719
|
this.insertTableCell('', {x: i, y: j}, subtype, attr);
|
|
636
720
|
} catch (e) {
|
|
637
|
-
if (
|
|
638
|
-
|
|
721
|
+
if (e instanceof RangeError && e.message.startsWith('指定的坐标不是单元格起始点:')) {
|
|
722
|
+
break;
|
|
639
723
|
}
|
|
640
|
-
|
|
724
|
+
throw e;
|
|
641
725
|
}
|
|
642
726
|
}
|
|
643
727
|
}
|
|
@@ -645,7 +729,8 @@ class TableToken extends TrToken {
|
|
|
645
729
|
}
|
|
646
730
|
|
|
647
731
|
/**
|
|
648
|
-
*
|
|
732
|
+
* 分裂成多行
|
|
733
|
+
* @param {TableCoords & TableRenderedCoords} coords 单元格坐标
|
|
649
734
|
* @complexity `n²`
|
|
650
735
|
*/
|
|
651
736
|
splitIntoRows(coords) {
|
|
@@ -653,7 +738,8 @@ class TableToken extends TrToken {
|
|
|
653
738
|
}
|
|
654
739
|
|
|
655
740
|
/**
|
|
656
|
-
*
|
|
741
|
+
* 分裂成多列
|
|
742
|
+
* @param {TableCoords & TableRenderedCoords} coords 单元格坐标
|
|
657
743
|
* @complexity `n²`
|
|
658
744
|
*/
|
|
659
745
|
splitIntoCols(coords) {
|
|
@@ -661,7 +747,8 @@ class TableToken extends TrToken {
|
|
|
661
747
|
}
|
|
662
748
|
|
|
663
749
|
/**
|
|
664
|
-
*
|
|
750
|
+
* 分裂成单元格
|
|
751
|
+
* @param {TableCoords & TableRenderedCoords} coords 单元格坐标
|
|
665
752
|
* @complexity `n²`
|
|
666
753
|
*/
|
|
667
754
|
splitIntoCells(coords) {
|
|
@@ -670,7 +757,7 @@ class TableToken extends TrToken {
|
|
|
670
757
|
|
|
671
758
|
/**
|
|
672
759
|
* 复制一行并插入该行之前
|
|
673
|
-
* @param {number} row
|
|
760
|
+
* @param {number} row 行号
|
|
674
761
|
* @complexity `n²`
|
|
675
762
|
*/
|
|
676
763
|
replicateTableRow(row) {
|
|
@@ -691,7 +778,7 @@ class TableToken extends TrToken {
|
|
|
691
778
|
|
|
692
779
|
/**
|
|
693
780
|
* 复制一列并插入该列之前
|
|
694
|
-
* @param {number} x
|
|
781
|
+
* @param {number} x 列号
|
|
695
782
|
* @complexity `n`
|
|
696
783
|
*/
|
|
697
784
|
replicateTableCol(x) {
|
|
@@ -710,20 +797,23 @@ class TableToken extends TrToken {
|
|
|
710
797
|
}
|
|
711
798
|
|
|
712
799
|
/**
|
|
713
|
-
*
|
|
714
|
-
* @param {number}
|
|
800
|
+
* 移动表格行
|
|
801
|
+
* @param {number} y 行号
|
|
802
|
+
* @param {number} before 新位置
|
|
715
803
|
* @complexity `n²`
|
|
804
|
+
* @throws `RangeError` 无法移动
|
|
716
805
|
*/
|
|
717
806
|
moveTableRowBefore(y, before) {
|
|
718
807
|
if (typeof y !== 'number' || typeof before !== 'number') {
|
|
719
808
|
this.typeError('moveTableRowBefore', 'Number');
|
|
720
809
|
}
|
|
721
|
-
const layout = this.getLayout()
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
810
|
+
const layout = this.getLayout();
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* @type {(i: number) => number[]}
|
|
814
|
+
* @complexity `n`
|
|
815
|
+
*/
|
|
816
|
+
const occupied = i => layout[i].map(({row}, j) => row === i ? j : null).filter(j => j !== null);
|
|
727
817
|
try {
|
|
728
818
|
assert.deepStrictEqual(occupied(y), occupied(before));
|
|
729
819
|
} catch (e) {
|
|
@@ -747,9 +837,11 @@ class TableToken extends TrToken {
|
|
|
747
837
|
}
|
|
748
838
|
|
|
749
839
|
/**
|
|
750
|
-
*
|
|
751
|
-
* @param {number}
|
|
840
|
+
* 移动表格行
|
|
841
|
+
* @param {number} y 行号
|
|
842
|
+
* @param {number} after 新位置
|
|
752
843
|
* @complexity `n²`
|
|
844
|
+
* @throws `RangeError` 无法移动
|
|
753
845
|
*/
|
|
754
846
|
moveTableRowAfter(y, after) {
|
|
755
847
|
if (typeof y !== 'number' || typeof after !== 'number') {
|
|
@@ -758,14 +850,15 @@ class TableToken extends TrToken {
|
|
|
758
850
|
const layout = this.getLayout(),
|
|
759
851
|
afterToken = this.getNthRow(after),
|
|
760
852
|
/** @type {TdToken[]} */
|
|
761
|
-
cells = afterToken.children.filter(child => child instanceof TdToken && child.subtype !== 'caption')
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
853
|
+
cells = afterToken.children.filter(child => child instanceof TdToken && child.subtype !== 'caption');
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* @type {(i: number, oneRow?: boolean) => number[]}
|
|
857
|
+
* @complexity `n`
|
|
858
|
+
*/
|
|
859
|
+
const occupied = (i, oneRow = false) => layout[i].map(
|
|
860
|
+
({row, column}, j) => row === i && (!oneRow || cells[column].rowspan === 1) ? j : null,
|
|
861
|
+
).filter(j => j !== null);
|
|
769
862
|
try {
|
|
770
863
|
assert.deepStrictEqual(occupied(y), occupied(after, true));
|
|
771
864
|
} catch (e) {
|
|
@@ -796,30 +889,29 @@ class TableToken extends TrToken {
|
|
|
796
889
|
}
|
|
797
890
|
|
|
798
891
|
/**
|
|
799
|
-
*
|
|
800
|
-
* @param {number}
|
|
892
|
+
* 移动表格列
|
|
893
|
+
* @param {number} x 列号
|
|
894
|
+
* @param {number} reference 新位置
|
|
895
|
+
* @param {boolean} after 在新位置之后或之前
|
|
801
896
|
* @complexity `n`
|
|
897
|
+
* @throws `RangeError` 无法移动
|
|
802
898
|
*/
|
|
803
899
|
#moveCol(x, reference, after = false) {
|
|
804
900
|
if (typeof x !== 'number' || typeof reference !== 'number') {
|
|
805
901
|
this.typeError(`moveTableCol${after ? 'After' : 'Before'}`, 'Number');
|
|
806
902
|
}
|
|
807
|
-
const layout = this.getLayout()
|
|
808
|
-
|
|
809
|
-
isStart = (rowLayout, i, oneCol = false) => {
|
|
810
|
-
const coords = rowLayout[i];
|
|
811
|
-
return rowLayout[i - 1] !== coords && (!oneCol || rowLayout[i + 1] !== coords);
|
|
812
|
-
};
|
|
813
|
-
if (layout.some(rowLayout => isStart(rowLayout, x) !== isStart(rowLayout, reference, after))) {
|
|
903
|
+
const layout = this.getLayout();
|
|
904
|
+
if (layout.some(rowLayout => isStartCol(rowLayout, x) !== isStartCol(rowLayout, reference, after))) {
|
|
814
905
|
throw new RangeError(`第 ${x} 列与第 ${reference} 列的构造不同,无法移动!`);
|
|
815
906
|
}
|
|
816
907
|
const /** @type {Set<TableCoords>} */ setX = new Set(),
|
|
817
908
|
/** @type {Set<TableCoords>} */ setRef = new Set(),
|
|
818
909
|
rows = this.getAllRows();
|
|
819
|
-
for (
|
|
820
|
-
const
|
|
910
|
+
for (let i = 0; i < layout.length; i++) {
|
|
911
|
+
const rowLayout = layout[i],
|
|
912
|
+
coords = rowLayout[x],
|
|
821
913
|
refCoords = rowLayout[reference],
|
|
822
|
-
start =
|
|
914
|
+
start = isStartCol(rowLayout, x);
|
|
823
915
|
if (refCoords && !start && !setRef.has(refCoords)) {
|
|
824
916
|
setRef.add(refCoords);
|
|
825
917
|
rows[refCoords.row].getNthCol(refCoords.column).colspan++;
|
|
@@ -840,9 +932,10 @@ class TableToken extends TrToken {
|
|
|
840
932
|
}
|
|
841
933
|
if (start) {
|
|
842
934
|
const col = rowLayout.slice(reference + Number(after)).find(({row}) => row === i)?.column;
|
|
843
|
-
rowToken.insertBefore(
|
|
844
|
-
|
|
845
|
-
|
|
935
|
+
rowToken.insertBefore(
|
|
936
|
+
token, col === undefined && rowToken.type === 'table'
|
|
937
|
+
? rowToken.children.slice(2).find(isRowEnd)
|
|
938
|
+
: col !== undefined && rowToken.getNthCol(col),
|
|
846
939
|
);
|
|
847
940
|
}
|
|
848
941
|
}
|
|
@@ -850,8 +943,9 @@ class TableToken extends TrToken {
|
|
|
850
943
|
}
|
|
851
944
|
|
|
852
945
|
/**
|
|
853
|
-
*
|
|
854
|
-
* @param {number}
|
|
946
|
+
* 移动表格列
|
|
947
|
+
* @param {number} x 列号
|
|
948
|
+
* @param {number} before 新位置
|
|
855
949
|
* @complexity `n`
|
|
856
950
|
*/
|
|
857
951
|
moveTableColBefore(x, before) {
|
|
@@ -859,8 +953,9 @@ class TableToken extends TrToken {
|
|
|
859
953
|
}
|
|
860
954
|
|
|
861
955
|
/**
|
|
862
|
-
*
|
|
863
|
-
* @param {number}
|
|
956
|
+
* 移动表格列
|
|
957
|
+
* @param {number} x 列号
|
|
958
|
+
* @param {number} after 新位置
|
|
864
959
|
* @complexity `n`
|
|
865
960
|
*/
|
|
866
961
|
moveTableColAfter(x, after) {
|