Repository navigation
Expand file tree
/
Copy pathdelimiter.go
More file actions
108 lines (84 loc) · 2.08 KB
/
Copy pathdelimiter.go
File metadata and controls
108 lines (84 loc) · 2.08 KB
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package multicsv
import (
"bytes"
"encoding/csv"
"io"
)
const detectDelimiterSampleSize = 32 * 1024
var delimiterCandidates = []rune{',', ';', '\t', '|'}
type delimiterScore struct {
consistentRecords int
fieldCount int
totalFields int
errors int
}
func detectDelimiter(r io.ReadSeeker) (rune, error) {
sample := make([]byte, detectDelimiterSampleSize)
n, err := r.Read(sample)
seekErr := resetReader(r)
if err != nil && err != io.EOF {
return 0, err
}
if seekErr != nil {
return 0, seekErr
}
return detectDelimiterFromSample(sample[:n]), nil
}
func resetReader(r io.Seeker) error {
_, err := r.Seek(0, io.SeekStart)
return err
}
func detectDelimiterFromSample(sample []byte) rune {
bestDelimiter := ','
bestScore := delimiterScore{}
for _, delimiter := range delimiterCandidates {
score := scoreDelimiter(sample, delimiter)
if isBetterDelimiterScore(score, bestScore) {
bestDelimiter = delimiter
bestScore = score
}
}
return bestDelimiter
}
func scoreDelimiter(sample []byte, delimiter rune) delimiterScore {
reader := csv.NewReader(bytes.NewReader(sample))
reader.Comma = delimiter
reader.FieldsPerRecord = -1
reader.LazyQuotes = true
reader.TrimLeadingSpace = true
fieldCounts := make(map[int]int)
score := delimiterScore{}
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
score.errors++
break
}
fieldCount := len(record)
if fieldCount <= 1 {
continue
}
fieldCounts[fieldCount]++
score.totalFields += fieldCount
if fieldCounts[fieldCount] > score.consistentRecords {
score.consistentRecords = fieldCounts[fieldCount]
score.fieldCount = fieldCount
}
}
return score
}
func isBetterDelimiterScore(score, best delimiterScore) bool {
if score.consistentRecords != best.consistentRecords {
return score.consistentRecords > best.consistentRecords
}
if score.fieldCount != best.fieldCount {
return score.fieldCount > best.fieldCount
}
if score.totalFields != best.totalFields {
return score.totalFields > best.totalFields
}
return score.errors < best.errors
}