blog2video-test / frontend /src /utils /parseClipboardTable.ts
HumeraR's picture
data visualisation feature commit
d8d3f30
Raw
History Blame Contribute Delete
2 kB
/**
* Parse clipboard text into a 2-D matrix of cells.
* Recognizes three formats, in order:
* 1. Markdown / GFM pipe tables (`| col | col |` with optional `|---|---|` separator)
* 2. TSV (Excel / Google Sheets default copy format)
* 3. CSV with quoted-comma support
*/
export function parseClipboardTable(text: string): string[][] {
const stripped = text.replace(/\r\n?/g, "\n").replace(/\n+$/, "");
if (!stripped) return [];
const lines = stripped.split("\n");
// ── 1) Markdown pipe table ─────────────────────────────────────────────
const nonEmpty = lines.filter((l) => l.trim().length > 0);
const pipedLines = nonEmpty.filter((l) => l.includes("|"));
const pipeCount = (stripped.match(/\|/g) ?? []).length;
const tabCount = (stripped.match(/\t/g) ?? []).length;
const isMarkdownTable =
pipedLines.length >= 2 &&
pipedLines.length === nonEmpty.length &&
pipeCount > tabCount;
if (isMarkdownTable) {
const splitMdRow = (line: string): string[] => {
let s = line.trim();
if (s.startsWith("|")) s = s.slice(1);
if (s.endsWith("|")) s = s.slice(0, -1);
return s.split("|").map((c) => c.trim());
};
const isSeparatorRow = (cells: string[]) =>
cells.length > 0 && cells.every((c) => /^:?-{2,}:?$/.test(c.trim()));
return nonEmpty.map(splitMdRow).filter((cells) => !isSeparatorRow(cells));
}
// ── 2) TSV / 3) CSV ───────────────────────────────────────────────────
const hasTabs = stripped.includes("\t");
return lines.map((line) =>
hasTabs
? line.split("\t")
: (line.match(/("([^"]|"")*"|[^,]*)(,|$)/g) ?? [])
.filter((_, i, a) => i < a.length - 1 || a[i] !== "")
.map((tok) => tok.replace(/,$/, "").trim().replace(/^"(.*)"$/, "$1").replace(/""/g, '"')),
);
}