Parse decodes delimited CSV/TSV text. It strips/decodes UTF-8 and UTF-16 byte-order marks and auto-detects whether the delimiter is a comma or a tab.
(raw []byte)
| 43 | // Parse decodes delimited CSV/TSV text. It strips/decodes UTF-8 and UTF-16 |
| 44 | // byte-order marks and auto-detects whether the delimiter is a comma or a tab. |
| 45 | func Parse(raw []byte) (*Table, error) { |
| 46 | data, err := decode(raw) |
| 47 | if err != nil { |
| 48 | return nil, fmt.Errorf("decoding tabular input: %w", err) |
| 49 | } |
| 50 | |
| 51 | if len(bytes.TrimSpace(data)) == 0 { |
| 52 | return nil, errors.New("empty tabular input") |
| 53 | } |
| 54 | |
| 55 | r := csv.NewReader(bytes.NewReader(data)) |
| 56 | r.Comma = detectDelimiter(data) |
| 57 | r.LazyQuotes = true |
| 58 | r.FieldsPerRecord = -1 // tolerate ragged rows |
| 59 | |
| 60 | records, err := r.ReadAll() |
| 61 | if err != nil { |
| 62 | return nil, fmt.Errorf("parsing CSV/TSV: %w", err) |
| 63 | } |
| 64 | if len(records) == 0 { |
| 65 | return nil, errors.New("tabular input has no header row") |
| 66 | } |
| 67 | |
| 68 | header := records[0] |
| 69 | table := &Table{Header: header, Rows: make([]map[string]string, 0, len(records)-1)} |
| 70 | for _, rec := range records[1:] { |
| 71 | row := make(map[string]string, len(header)) |
| 72 | for i, col := range header { |
| 73 | if i < len(rec) { |
| 74 | row[col] = rec[i] |
| 75 | } else { |
| 76 | row[col] = "" |
| 77 | } |
| 78 | } |
| 79 | table.Rows = append(table.Rows, row) |
| 80 | } |
| 81 | |
| 82 | return table, nil |
| 83 | } |
| 84 | |
| 85 | // JSON marshals the table rows as a JSON array. A header-only table marshals |
| 86 | // to "[]". |