(data: string[][], headers: string[])
| 29 | * Detect column types from a sample of data rows |
| 30 | */ |
| 31 | const detectColumnTypes = (data: string[][], headers: string[]): ColumnType[] => { |
| 32 | if (!data || data.length === 0 || !headers) return []; |
| 33 | |
| 34 | const types = Array(headers.length).fill(ColumnType.Unknown); |
| 35 | |
| 36 | // Process rows to detect types |
| 37 | for (const row of data) { |
| 38 | for (let j = 0; j < Math.min(row.length, headers.length); j++) { |
| 39 | const value = row[j]?.trim(); |
| 40 | if (!value) continue; |
| 41 | |
| 42 | // If already detected as text, no need to check further |
| 43 | if (types[j] === ColumnType.Text) continue; |
| 44 | |
| 45 | // Try to parse as number |
| 46 | if (!isNaN(Number(value)) && value !== '') { |
| 47 | if (types[j] === ColumnType.Unknown) { |
| 48 | types[j] = ColumnType.Number; |
| 49 | } |
| 50 | continue; |
| 51 | } |
| 52 | |
| 53 | // Try to parse as boolean |
| 54 | if (/^(true|false|yes|no)$/i.test(value)) { |
| 55 | if (types[j] === ColumnType.Unknown) { |
| 56 | types[j] = ColumnType.Boolean; |
| 57 | } |
| 58 | continue; |
| 59 | } |
| 60 | |
| 61 | // Try to parse as date |
| 62 | if (/^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}/.test(value)) { |
| 63 | if (types[j] === ColumnType.Unknown || types[j] === ColumnType.Number) { |
| 64 | types[j] = ColumnType.Date; |
| 65 | } |
| 66 | continue; |
| 67 | } |
| 68 | |
| 69 | // Default to text |
| 70 | types[j] = ColumnType.Text; |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | return types; |
| 75 | }; |
| 76 | |
| 77 | /** |
| 78 | * Parse large CSV files in chunks with progress tracking and browser yielding |
no outgoing calls
no test coverage detected