From 1264f9742c6fa3e7293ce56ff19ed5f47a9a8cd3 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Sun, 16 Aug 2026 11:21:13 +0200 Subject: [PATCH] fix: dropping a fully covered table row shrinks the spans across it A row holding only covered cells cannot exist in the editor and was dropped on ODT import, but every rowspan reaching over it kept its full count, leaving an inconsistent grid. Spans now shrink by the dropped rows they covered. --- src/lib/import/odt.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/import/odt.ts b/src/lib/import/odt.ts index da091f2..f09ec1c 100644 --- a/src/lib/import/odt.ts +++ b/src/lib/import/odt.ts @@ -2513,8 +2513,14 @@ function convertTable(el: Element, ctx: Ctx): Node | null { const weights = columnWeights(el, ctx.resolver); const rows: Node[] = []; + // Source-row bookkeeping: a row of only covered cells is dropped (the editor cannot + // hold a cell-less row), so every span reaching over it must shrink by one. + const placed: { attrs: Record; row: number; span: number }[] = []; + const dropped: number[] = []; + let srcRow = 0; let cellPad: CellPadding | null | undefined; const addRow = (rowEl: Element, header: boolean) => { + const rowIdx = srcRow++; const cells: Node[] = []; let colIndex = 0; for (const cellEl of Array.from(rowEl.children)) { @@ -2559,6 +2565,7 @@ function convertTable(el: Element, ctx: Ctx): Node | null { const cellFormat = formula ? cellNumberFormat(cellEl, ctx) : null; for (let r = 0; r < repeated; r++) { const attrs: Record = { colspan, rowspan, ...borders }; + if (rowspan > 1) placed.push({ attrs, row: rowIdx, span: rowspan }); if (formula) attrs.formula = formula; if (cellFormat) attrs.cellFormat = cellFormat; if (ownPad) attrs.cellPadding = ownPad; @@ -2573,7 +2580,7 @@ function convertTable(el: Element, ctx: Ctx): Node | null { colIndex += colspan; } } - if (cells.length === 0) return; + if (cells.length === 0) { dropped.push(rowIdx); return; } const row: Node = { type: 'tableRow', content: cells }; const heightCm = ctx.resolver.rowMinHeightCm(rowEl.getAttributeNS(NS.table, 'style-name')); @@ -2593,6 +2600,10 @@ function convertTable(el: Element, ctx: Ctx): Node | null { } if (rows.length === 0) return null; + if (dropped.length) for (const p of placed) { + const cut = dropped.filter((d) => d > p.row && d < p.row + p.span).length; + if (cut) p.attrs.rowspan = p.span - cut; + } // The named table style behind the automatic one. ODF stores no banding, so only the // name comes back — the look rides on the cell attrs above, and the editor re-derives // the regions from the registry (refreshTableStyles).