UNPKG

wikiparser-node

Version:

A Node.js parser for MediaWiki markup with AST

529 lines (528 loc) 20.7 kB
"use strict"; /* eslint @stylistic/operator-linebreak: [2, "before", {overrides: {"=": "after"}}] */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const strict_1 = __importDefault(require("assert/strict")); const debug_1 = require("../util/debug"); const constants_1 = require("../util/constants"); const index_1 = require("../src/index"); const tr_1 = require("../src/table/tr"); const index_2 = require("../src/table/index"); const td_1 = require("../src/table/td"); const trBase_1 = require("../src/table/trBase"); /** * 比较两个数 * @param a * @param b */ const compare = (a, b) => a - b; /** * 检查坐标形式 * @param coords 坐标 */ const isTableCoords = (coords) => coords.x === undefined; /** * 比较两个表格坐标 * @param coords1 坐标1 * @param coords2 坐标2 */ const cmpCoords = (coords1, coords2) => { const diff = coords1.row - coords2.row; return diff === 0 ? coords1.column - coords2.column : diff; }; /** * 是否是合并单元格的第一列 * @param rowLayout 行布局 * @param i 单元格序号 * @param oneCol 是否仅有一列 */ const isStartCol = (rowLayout, i, oneCol) => { const coords = rowLayout[i]; return rowLayout[i - 1] !== coords && (!oneCol || rowLayout[i + 1] !== coords); }; /** * 起点位于第`i`行的单元格序号 * @param layout 表格布局 * @param i 行号 * @param oneRow 是否要求单元格跨行数为1 * @param cells 改行全部单元格 * @throws `RangeError` 表格布局不包含第`i`行 */ const occupied = (layout, i, oneRow, cells) => { const rowLayout = layout[i]; if (rowLayout) { return rowLayout.map(({ row, column }, j) => row === i && (!oneRow || cells[column]?.rowspan === 1) ? j : undefined).filter((j) => j !== undefined); } throw new RangeError(`The table layout does not contain row ${i}!`); }; /** * 设置表格格式 * @param cells 单元格 * @param attr 属性 * @param multi 是否对所有单元格设置,或是仅对行首单元格设置 */ const format = (cells, attr = {}, multi) => { for (const [token, start] of cells) { if (multi || start) { if (typeof attr === 'string') { token.setSyntax(attr); } else { token.setAttr(attr); } } } }; /** * 填补缺失单元格 * @param y 行号 * @param rowToken 表格行 * @param layout 表格布局 * @param maxCol 最大列数 * @param token 待填充的单元格 */ const fill = (y, rowToken, layout, maxCol, token) => { const rowLayout = layout[y], index = rowToken.childNodes.findLastIndex(child => child instanceof td_1.TdToken && child.subtype !== 'caption'), pos = index > 0 ? index + 1 : undefined; debug_1.Shadow.run(() => { for (let i = 0; i < maxCol; i++) { if (!rowLayout[i]) { rowToken.insertAt(token.cloneNode(), pos); } } }); }; /** * 计算最大列数 * @param layout 表格布局 */ const getMaxCol = (layout) => { let maxCol = 0; for (const { length } of layout) { if (maxCol < length) { maxCol = length; } } return maxCol; }; index_2.TableToken.prototype.printLayout = /** @implements */ function () { this.getLayout().print(); }; index_2.TableToken.prototype.toRenderedCoords = /** @implements */ function ({ row, column }) { const rowLayout = this.getLayout({ row, column })[row], x = rowLayout?.findIndex(coords => cmpCoords(coords, { row, column }) === 0); return rowLayout && (x === -1 ? undefined : { y: row, x: x }); }; index_2.TableToken.prototype.toRawCoords = /** @implements */ function ({ x, y }) { const rowLayout = this.getLayout({ x, y })[y], coords = rowLayout?.[x]; if (coords) { return { ...coords, start: coords.row === y && rowLayout[x - 1] !== coords, }; } else if (rowLayout || y > 0) { return x === rowLayout?.length ? { row: y, column: (rowLayout.findLast(({ row }) => row === y)?.column ?? -1) + 1, start: true } : undefined; } return { row: 0, column: 0, start: true }; }; index_2.TableToken.prototype.getFullRow = /** @implements */ function (y) { const rows = this.getAllRows(); return new Map(this.getLayout({ y })[y]?.map(({ row, column }) => [rows[row].getNthCol(column), row === y])); }; index_2.TableToken.prototype.getFullCol = /** @implements */ function (x) { const layout = this.getLayout(), rows = this.getAllRows(); return new Map(layout.map(row => row[x]).filter(Boolean).map(coords => [rows[coords.row].getNthCol(coords.column), layout[coords.row][x - 1] !== coords])); }; index_2.TableToken.prototype.formatTableRow = /** @implements */ function (y, attr, multiRow) { format(this.getFullRow(y), attr, multiRow); }; index_2.TableToken.prototype.formatTableCol = /** @implements */ function (x, attr, multiCol) { format(this.getFullCol(x), attr, multiCol); }; index_2.TableToken.prototype.fillTableRow = /** @implements */ function (y, inner, subtype, attr) { const rowToken = this.getNthRow(y), layout = this.getLayout({ y }), maxCol = getMaxCol(layout), token = (0, td_1.createTd)(inner, subtype, attr, this.getAttribute('include'), this.getAttribute('config')); fill(y, rowToken, layout, maxCol, token); }; index_2.TableToken.prototype.fillTable = /** @implements */ function (inner, subtype, attr) { const rowTokens = this.getAllRows(), layout = this.getLayout(), maxCol = getMaxCol(layout), token = (0, td_1.createTd)(inner, subtype, attr, this.getAttribute('include'), this.getAttribute('config')); for (let y = 0; y < rowTokens.length; y++) { fill(y, rowTokens[y], layout, maxCol, token); } }; index_2.TableToken.prototype.insertTableCell = /** @implements */ function (inner, coords, subtype, attr) { let rawCoords; if (coords.column === undefined) { const { x, y } = coords; rawCoords = this.toRawCoords(coords); if (!rawCoords?.start) { throw new RangeError(`The specified coordinates are not the starting point of any cell: (${x}, ${y})`); } } else { rawCoords = coords; } const rowToken = this.getNthRow(rawCoords.row, true); return rowToken === this ? trBase_1.TrBaseToken.prototype.insertTableCell.call(this, inner, rawCoords, subtype, attr) : rowToken.insertTableCell(inner, rawCoords, subtype, attr); }; index_2.TableToken.prototype.prependTableRow = /** @implements */ function () { // @ts-expect-error abstract class const row = debug_1.Shadow.run(() => new tr_1.TrToken('\n|-', undefined, this.getAttribute('config'))), { childNodes } = this, [, , plain] = childNodes, start = plain?.constructor === index_1.Token ? 3 : 2, tdChildren = childNodes.slice(start), index = tdChildren.findIndex(({ type }) => type !== 'td'); this.insertAt(row, index === -1 ? -1 : index + start); debug_1.Shadow.run(() => { for (const cell of tdChildren.slice(0, index === -1 ? undefined : index)) { if (cell.subtype !== 'caption') { row.insertAt(cell); } } }); return row; }; index_2.TableToken.prototype.insertTableRow = /** @implements */ function (y, attr = {}, inner, subtype = 'td', innerAttr = {}) { let reference = this.getNthRow(y, false, true); // @ts-expect-error abstract class const token = debug_1.Shadow.run(() => new tr_1.TrToken('\n|-', undefined, this.getAttribute('config'))); token.setAttr(attr); if (reference?.is('table')) { // `row === 0`且表格自身是有效行 reference = this.prependTableRow(); } this.insertBefore(token, reference); if (inner !== undefined) { const td = token.insertTableCell(inner, { row: 0, column: 0 }, subtype, innerAttr), set = new WeakSet(), layout = this.getLayout({ y }), maxCol = getMaxCol(layout), rowLayout = layout[y]; debug_1.Shadow.run(() => { for (let i = 0; i < maxCol; i++) { const coords = rowLayout[i]; if (!coords) { token.insertAt(td.cloneNode()); } else if (!set.has(coords)) { set.add(coords); if (coords.row < y) { this.getNthCell(coords).rowspan++; } } } }); } return token; }; index_2.TableToken.prototype.insertTableCol = /** @implements */ function (x, inner, subtype, attr) { const layout = this.getLayout(), rowLength = layout.map(({ length }) => length); let minCol = Infinity; for (const length of rowLength) { if (minCol > length) { minCol = length; } } if (x > minCol) { throw new RangeError(`Row ${rowLength.indexOf(minCol)} has only ${minCol} column(s)!`); } const token = (0, td_1.createTd)(inner, subtype, attr, this.getAttribute('include'), this.getAttribute('config')); for (let i = 0; i < layout.length; i++) { const rowLayout = layout[i], coords = rowLayout[x], prevCoords = x === 0 ? true : rowLayout[x - 1]; if (!prevCoords) { continue; } else if (prevCoords !== coords) { const rowToken = this.getNthRow(i); rowToken.insertBefore(token.cloneNode(), rowToken.getNthCol(coords.column, true)); } else if (coords.row === i) { this.getNthCell(coords).colspan++; } } }; index_2.TableToken.prototype.removeTableRow = /** @implements */ function (y) { const rows = this.getAllRows(), layout = this.getLayout(), rowLayout = layout[y], set = new WeakSet(); for (let x = rowLayout.length - 1; x >= 0; x--) { const coords = rowLayout[x]; if (set.has(coords)) { continue; } set.add(coords); const token = rows[coords.row].getNthCol(coords.column); let { rowspan } = token; if (rowspan > 1) { token.rowspan = --rowspan; if (coords.row === y) { const { colspan, subtype } = token, attr = token.getAttrs(); for (let i = y + 1; rowspan && i < rows.length; i++, rowspan--) { const { column } = layout[i].slice(x + colspan).find(({ row }) => row === i) ?? {}; if (column !== undefined) { rows[i] .insertTableCell('', { row: 0, column }, subtype, { ...attr, rowspan }); break; } } } } } const row = rows[y], rowToken = row.is('tr') ? row : this.prependTableRow(); rowToken.remove(); return rowToken; }; index_2.TableToken.prototype.removeTableCol = /** @implements */ function (x) { for (const [token, start] of this.getFullCol(x)) { const { colspan, lastChild } = token; if (colspan > 1) { token.colspan = colspan - 1; if (start) { lastChild.replaceChildren(); } } else { token.remove(); } } }; index_2.TableToken.prototype.mergeCells = /** @implements */ function (xlim, ylim) { const layout = this.getLayout(), maxCol = getMaxCol(layout), [xmin, xmax] = xlim.map(x => x < 0 ? x + maxCol : x).sort(compare), [ymin, ymax] = ylim.map(y => y < 0 ? y + layout.length : y).sort(compare), set = new Set(layout.slice(ymin, ymax).flatMap(rowLayout => rowLayout.slice(xmin, xmax))); if ([...layout[ymin - 1] ?? [], ...layout[ymax] ?? []].some(coords => set.has(coords)) || layout.some(rowLayout => set.has(rowLayout[xmin - 1]) || set.has(rowLayout[xmax]))) { throw new RangeError('The area to be merged overlaps with the outer area!'); } const corner = layout[ymin][xmin], rows = this.getAllRows(), cornerCell = rows[corner.row].getNthCol(corner.column); cornerCell.rowspan = ymax - ymin; cornerCell.colspan = xmax - xmin; set.delete(corner); for (const token of [...set].map(({ row, column }) => rows[row].getNthCol(column))) { token.remove(); } return cornerCell; }; index_2.TableToken.prototype.split = /** @implements */ function (coords, dirs) { const cell = this.getNthCell(coords), attr = cell.getAttrs(), { subtype } = cell; attr.rowspan ||= 1; attr.colspan ||= 1; for (const dir of dirs) { if (attr[dir] === 1) { dirs.delete(dir); } } if (dirs.size === 0) { return; } let { x, y } = coords; const rawCoords = isTableCoords(coords) ? coords : this.toRawCoords(coords); if (rawCoords.start === false || x === undefined) { ({ x, y } = this.toRenderedCoords(rawCoords)); } const splitting = { rowspan: 1, colspan: 1 }; for (const dir of dirs) { cell.setAttr(dir, 1); splitting[dir] = attr[dir]; delete attr[dir]; } for (let j = y; j < y + splitting.rowspan; j++) { for (let i = x; i < x + splitting.colspan; i++) { if (i > x || j > y) { try { this.insertTableCell('', { x: i, y: j }, subtype, attr); } catch (e) { if (e instanceof RangeError && e.message.startsWith('The specified coordinates are not the starting point of a cell: ')) { break; } throw e; } } } } }; index_2.TableToken.prototype.splitIntoRows = /** @implements */ function (coords) { this.split(coords, new Set(['rowspan'])); }; index_2.TableToken.prototype.splitIntoCols = /** @implements */ function (coords) { this.split(coords, new Set(['colspan'])); }; index_2.TableToken.prototype.splitIntoCells = /** @implements */ function (coords) { this.split(coords, new Set(['rowspan', 'colspan'])); }; index_2.TableToken.prototype.replicateTableRow = /** @implements */ function (row) { let rowToken = this.getNthRow(row); if (rowToken.is('table')) { rowToken = this.prependTableRow(); } const replicated = this.insertBefore(rowToken.cloneNode(), rowToken); for (const [token, start] of this.getFullRow(row)) { if (start) { token.rowspan = 1; } else { token.rowspan++; } } return replicated; }; index_2.TableToken.prototype.replicateTableCol = /** @implements */ function (x) { const replicated = []; for (const [token, start] of this.getFullCol(x)) { if (start) { const newToken = token.cloneNode(); newToken.colspan = 1; token.before(newToken); replicated.push(newToken); } else { token.colspan++; } } return replicated; }; index_2.TableToken.prototype.moveTableRowBefore = /** @implements */ function (y, before) { const layout = this.getLayout(); try { strict_1.default.deepEqual(occupied(layout, y), occupied(layout, before)); } catch (e) { if (e instanceof strict_1.default.AssertionError) { throw new RangeError(`The structure of row ${y} is different from that of row ${before}, so it cannot be moved!`); } throw e; } const rowToken = this.removeTableRow(y); for (const coords of layout[before]) { if (coords.row < before) { this.getNthCell(coords).rowspan++; } } let beforeToken = this.getNthRow(before); if (beforeToken.is('table')) { beforeToken = this.prependTableRow(); } this.insertBefore(rowToken, beforeToken); return rowToken; }; index_2.TableToken.prototype.moveTableRowAfter = /** @implements */ function (y, after) { const layout = this.getLayout(), afterToken = this.getNthRow(after), cells = afterToken.childNodes.filter(child => child.is('td') && child.subtype !== 'caption'); try { strict_1.default.deepEqual(occupied(layout, y), occupied(layout, after, true, cells)); } catch (e) { if (e instanceof strict_1.default.AssertionError) { throw new RangeError(`The structure of row ${y} is different from that of row ${after}, so it cannot be moved!`); } throw e; } const rowToken = this.removeTableRow(y); for (const coords of layout[after]) { if (coords.row < after) { this.getNthCell(coords).rowspan++; } else { const cell = cells[coords.column], { rowspan } = cell; if (rowspan > 1) { cell.rowspan = rowspan + 1; } } } if (afterToken === this) { const index = this.childNodes.slice(2).findIndex(index_2.isRowEnd); this.insertAt(rowToken, index + 2); } else { this.insertBefore(rowToken, afterToken); } return rowToken; }; index_2.TableToken.prototype.moveCol = /** @implements */ function (x, reference, after) { const layout = this.getLayout(); if (layout.some(rowLayout => isStartCol(rowLayout, x) !== isStartCol(rowLayout, reference, after))) { throw new RangeError(`The structure of column ${x} is different from that of column ${reference}, so it cannot be moved!`); } const setX = new WeakSet(), setRef = new WeakSet(), rows = this.getAllRows(); for (let i = 0; i < layout.length; i++) { const rowLayout = layout[i], coords = rowLayout[x], refCoords = rowLayout[reference], start = isStartCol(rowLayout, x); if (refCoords && !start && !setRef.has(refCoords)) { setRef.add(refCoords); rows[refCoords.row].getNthCol(refCoords.column).colspan++; } if (coords && !setX.has(coords)) { setX.add(coords); const rowToken = rows[i]; let token = rowToken.getNthCol(coords.column); const { colspan } = token; if (colspan > 1) { token.colspan = colspan - 1; if (start) { const original = token; token = token.cloneNode(); original.lastChild.replaceChildren(); token.colspan = 1; } } if (start) { const col = rowLayout.slice(reference + Number(after)).find(({ row }) => row === i)?.column; rowToken.insertBefore(token, col === undefined ? rowToken.childNodes.slice(2).find(index_2.isRowEnd) : rowToken.getNthCol(col)); } } } }; index_2.TableToken.prototype.moveTableColBefore = /** @implements */ function (x, before) { this.moveCol(x, before); }; index_2.TableToken.prototype.moveTableColAfter = /** @implements */ function (x, after) { this.moveCol(x, after, true); }; constants_1.classes['ExtendedTableToken'] = __filename;