package main import ( "encoding/csv" "fmt" "io" "regexp" "strconv" "time" ) // AlbumCsvReader can read Album definition from a csv file type AlbumCsvReader struct { r *csv.Reader columns map[string]int } const ( cID string = "IdAlbum" cISBN = "ISBN" cSeries = "Serie" cNum = "Num" cNumA = "NumA" cTitle = "Titre" cEditor = "Editeur" cCollection = "Collection" cLegalDeposit = "DL" cPrintDate = "AI" cState = "Etat" cPurchaseDate = "DateAchat" cPerso1 = "Perso1" cPerso2 = "Perso2" ) var requiredFields = []string{cID, cISBN, cSeries, cNum, cNumA, cTitle, cEditor, cCollection, cLegalDeposit, cPrintDate, cState, cPurchaseDate, cPerso1, cPerso2} // NewAlbumCsvReader creates a new AlbumCsvReader from a reader func NewAlbumCsvReader(r io.Reader) (*AlbumCsvReader, error) { res := &AlbumCsvReader{ r: csv.NewReader(r), columns: make(map[string]int), } res.r.Comma = ';' //reads the first line colNames, err := res.r.Read() if err != nil { return nil, fmt.Errorf("album_csv: could not read first line: %s", err) } for i, n := range colNames { res.columns[n] = i } missing := []string{} for _, f := range requiredFields { if _, ok := res.columns[f]; ok == false { missing = append(missing, f) } } if len(missing) != 0 { return nil, fmt.Errorf("Invalid CSV file, missing required fields: %v", missing) } return res, nil } var nullTimeRx = regexp.MustCompile(`(00/)+0000`) func safeParseTime(format, value string, result *time.Time) error { if len(value) == 0 || nullTimeRx.MatchString(value) == true { *result = time.Time{} return nil } var err error *result, err = time.ParseInLocation(format, value, time.UTC) if err != nil { return fmt.Errorf("AlbumCsvReader: %s: %s", cLegalDeposit, err) } return nil } // Read get the next line in the CSV and return an Album or an error, // or io.EOF if stream is closed func (r *AlbumCsvReader) Read() (*Album, error) { data, err := r.r.Read() if err != nil { return nil, err } res := &Album{ ISBN: data[r.columns[cISBN]], Series: data[r.columns[cSeries]], NumA: data[r.columns[cNumA]], Title: data[r.columns[cTitle]], Editor: data[r.columns[cEditor]], Collection: data[r.columns[cCollection]], } // simply skip new columns Name if data[0] == "IdRevue" || data[0] == "IdParaBD" { return r.Read() } ID, err := strconv.ParseInt(data[r.columns[cID]], 0, 64) if err != nil { return nil, fmt.Errorf("AlbumCsvReader: %s: %s", cID, err) } res.ID = AlbumID(ID) if len(data[r.columns[cNum]]) == 0 { res.Num = -1 } else { n, err := strconv.ParseInt(data[r.columns[cNum]], 0, 32) if err != nil { return nil, fmt.Errorf("AlbumCsvReader: %s: %s", cNum, err) } res.Num = int(n) } err = safeParseTime("01/2006", data[r.columns[cLegalDeposit]], &(res.LegalDeposit)) if err != nil { return nil, fmt.Errorf("AlbumCsvReader: %s: %s", cLegalDeposit, err) } err = safeParseTime("01/2006", data[r.columns[cPrintDate]], &(res.PrintDate)) if err != nil { return nil, fmt.Errorf("AlbumCsvReader: %s: %s", cPrintDate, err) } state, err := strconv.ParseInt(data[r.columns[cState]], 0, 32) if err != nil { return nil, fmt.Errorf("AlbumCsvReader: %s: %s", cState, err) } res.State = AlbumState(state) err = safeParseTime("02/01/2006", data[r.columns[cPurchaseDate]], &(res.PurchaseDate)) if err != nil { return nil, fmt.Errorf("AlbumCsvReader: %s: %s", cPurchaseDate, err) } res.SatID = fmt.Sprintf("%s-%s", data[r.columns[cPerso1]], data[r.columns[cPerso2]]) return res, nil }