cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / fileio / csvlocale.go
csvlocale.go 2.4K
1package fileio23import (4 "bytes"5 "encoding/csv"67 "src.rest/cat/012/internal/locale"8 "src.rest/cat/012/internal/value"9)1011// Delimited files are read and written in the importing workbook's12// locale, as Sheets does: 1,5 is a number in a CSV file read in de-DE.13// A file whose numbers are written with the other decimal separator is14// noticed from its sample and read that way, keeping the locale's date15// order and currency; and in a locale with a decimal comma, CSV files16// are written with ; between fields, as its spreadsheets write them.1718// numberSample is how many fields of the sample decide how numbers are19// written.20const numberSample = 20002122// numberLocale is the locale a delimited file's fields are read in: loc,23// or loc with the other decimal separator (1.5 and 1,5) when at least24// two of the sample's fields, and more than twice as many as the other25// way round, read as numbers only that way (a lone 1,5 may be text).26// other reports the second.27func numberLocale(sample []byte, comma rune, loc *locale.Locale) (read *locale.Locale, other bool) {28 loc = loc.Or()29 alt := swapDecimal(loc)30 r := csv.NewReader(bytes.NewReader(sample))31 r.Comma, r.LazyQuotes, r.FieldsPerRecord, r.ReuseRecord = comma, true, -1, true32 mine, theirs := 0, 033 for seen := 0; seen < numberSample; {34 rec, err := r.Read()35 if err != nil {36 break37 }38 for _, f := range rec {39 seen++40 _, _, inMine := value.ParseValueIn(f, loc)41 _, _, inTheirs := value.ParseValueIn(f, alt)42 switch {43 case inMine && !inTheirs:44 mine++45 case inTheirs && !inMine:46 theirs++47 }48 }49 }50 if theirs >= 2 && theirs > 2*mine {51 return alt, true52 }53 return loc, false54}5556// swapDecimal is loc with the other decimal separator, grouping with the57// one it gave up: a comma for a point, a point for a comma.58func swapDecimal(loc *locale.Locale) *locale.Locale {59 alt := *loc60 if loc.Decimal == ',' {61 alt.Decimal, alt.Group = '.', ","62 } else {63 alt.Decimal, alt.Group = ',', "."64 if alt.DateSep == '.' {65 alt.DateSep = '/'66 }67 }68 return &alt69}7071// csvComma separates the fields of a CSV file written in loc: ; where72// the decimal separator is a comma.73func csvComma(loc *locale.Locale) rune {74 if loc.Or().Decimal == ',' {75 return ';'76 }77 return ','78}7980// decimalNote says how a file's numbers were read when not in the81// locale's own way.82func decimalNote(loc *locale.Locale) string {83 if loc.Decimal == ',' {84 return "numbers with decimal commas"85 }86 return "numbers with decimal points"87}