File size: 1,999 Bytes
d8d3f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * 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, '"')),
  );
}