/* ObsiGate — XLSX A1 reference helpers (#154-A5). * * Pure functions (no DOM, no i18n): safe to unit-test in Node and shared by the * viewer. Extracted from frontend/js/viewer.js without behaviour change. */ /** Parse an A1 reference ("B12") into its 1-based (row, col) parts. */ export function parseRef(ref) { const m = /^([A-Z]+)(\d+)$/.exec(ref || ""); if (!m) return null; let col = 0; for (const ch of m[1]) col = col * 26 + (ch.charCodeAt(0) - 64); return { row: Number(m[2]), col }; } /** Column number (1-based) → letters ("A", "Z", "AA"). */ export function columnName(col) { let name = ""; while (col > 0) { const rem = (col - 1) % 26; name = String.fromCharCode(65 + rem) + name; col = Math.floor((col - 1) / 26); } return name; } /** Find a rendered cell inside a sheet panel by (row, col). */ export function findTd(panel, row, col) { return panel.querySelector(`td[data-cell="${columnName(col)}${row}"]`); } /** Sheet prefix of a range ref ("'Mon onglet'!$A$1" → "Mon onglet"), or null. */ export function sheetOfRef(ref) { const s = String(ref || ""); if (!s.includes("!")) return null; return s.split("!")[0].replace(/^'|'$/g, ""); } /** First cell of a range ref ("Data!$A$1:$B$5" → "A1"), or null. */ export function firstCellOfRange(ref) { const s = String(ref || ""); const body = s.includes("!") ? s.split("!").pop() : s; const m = /([A-Za-z]+)\$?(\d+)/.exec(body); return m ? `${m[1].toUpperCase()}${m[2]}` : null; }