| 1 | /** A table preview is bounded independently of the source preview's byte limit. */ |
| 2 | export function filePreviewDelimiter(file: { name: string; mimeType?: string }): "," | "\t" | null { |
| 3 | const mime = file.mimeType?.split(";", 1)[0]?.trim().toLowerCase(); |
| 4 | if (mime === "text/csv") return ","; |
| 5 | if (mime === "text/tab-separated-values") return "\t"; |
| 6 | if (mime && mime !== "text/plain" && mime !== "application/octet-stream") return null; |
| 7 | if (/\.csv$/i.test(file.name)) return ","; |
| 8 | if (/\.tsv$/i.test(file.name)) return "\t"; |
| 9 | return null; |
| 10 | } |
| 11 | |
| 12 | /** Preserve quoted delimiters, escaped quotes and multiline cells; raw mode retains all text. */ |
| 13 | export function parseDelimitedPreview(text: string, delimiter: "," | "\t") { |
| 14 | const rows: string[][] = []; |
| 15 | let row: string[] = []; |
| 16 | let cell = ""; |
| 17 | let quoted = false; |
| 18 | let truncated = false; |
| 19 | let rowStart = text.charCodeAt(0) === 0xfeff ? 1 : 0; |
| 20 | const endCell = () => { |
| 21 | if (row.length < 30) row.push(cell); |
| 22 | else truncated = true; |
| 23 | cell = ""; |
| 24 | }; |
| 25 | for (let index = rowStart; index < text.length; index++) { |
| 26 | const char = text[index]; |
| 27 | if (char === '"') { |
| 28 | if (quoted && text[index + 1] === '"') { |
| 29 | if (cell.length < 2000) cell += '"'; |
| 30 | else truncated = true; |
| 31 | index++; |
| 32 | continue; |
| 33 | } |
| 34 | if (quoted || cell === "") { |
| 35 | quoted = !quoted; |
| 36 | continue; |
| 37 | } |
| 38 | } |
| 39 | if (!quoted && (char === delimiter || char === "\n" || char === "\r")) { |
| 40 | endCell(); |
| 41 | if (char !== delimiter) { |
| 42 | rows.push(row); |
| 43 | row = []; |
| 44 | if (char === "\r" && text[index + 1] === "\n") index++; |
| 45 | rowStart = index + 1; |
| 46 | if (rows.length === 100) return { rows, truncated: truncated || index < text.length - 1 }; |
| 47 | } |
| 48 | } else if (cell.length < 2000) cell += char; |
| 49 | else truncated = true; |
| 50 | } |
| 51 | if (rowStart < text.length) { |
| 52 | endCell(); |
| 53 | rows.push(row); |
| 54 | } |
| 55 | return { rows, truncated: truncated || quoted }; |
| 56 | } |