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