123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- package gocsv
- import (
- "encoding/csv"
- "errors"
- "fmt"
- "io"
- "reflect"
- )
- // Decoder .
- type Decoder interface {
- getCSVRows() ([][]string, error)
- }
- // SimpleDecoder .
- type SimpleDecoder interface {
- getCSVRow() ([]string, error)
- }
- type decoder struct {
- in io.Reader
- csvDecoder *csvDecoder
- }
- func newDecoder(in io.Reader) *decoder {
- return &decoder{in: in}
- }
- func (decode *decoder) getCSVRows() ([][]string, error) {
- return getCSVReader(decode.in).ReadAll()
- }
- func (decode *decoder) getCSVRow() ([]string, error) {
- if decode.csvDecoder == nil {
- decode.csvDecoder = &csvDecoder{getCSVReader(decode.in)}
- }
- return decode.csvDecoder.Read()
- }
- type CSVReader interface {
- Read() ([]string, error)
- ReadAll() ([][]string, error)
- }
- type csvDecoder struct {
- CSVReader
- }
- func (c csvDecoder) getCSVRows() ([][]string, error) {
- return c.ReadAll()
- }
- func (c csvDecoder) getCSVRow() ([]string, error) {
- return c.Read()
- }
- func maybeMissingStructFields(structInfo []fieldInfo, headers []string) error {
- if len(structInfo) == 0 {
- return nil
- }
- headerMap := make(map[string]struct{}, len(headers))
- for idx := range headers {
- headerMap[headers[idx]] = struct{}{}
- }
- for _, info := range structInfo {
- found := false
- for _, key := range info.keys {
- if _, ok := headerMap[key]; ok {
- found = true
- break
- }
- }
- if !found {
- return fmt.Errorf("found unmatched struct field with tags %v", info.keys)
- }
- }
- return nil
- }
- // Check that no header name is repeated twice
- func maybeDoubleHeaderNames(headers []string) error {
- headerMap := make(map[string]bool, len(headers))
- for _, v := range headers {
- if _, ok := headerMap[v]; ok {
- return fmt.Errorf("Repeated header name: %v", v)
- }
- headerMap[v] = true
- }
- return nil
- }
- func readTo(decoder Decoder, out interface{}) error {
- outValue, outType := getConcreteReflectValueAndType(out) // Get the concrete type (not pointer) (Slice<?> or Array<?>)
- if err := ensureOutType(outType); err != nil {
- return err
- }
- outInnerWasPointer, outInnerType := getConcreteContainerInnerType(outType) // Get the concrete inner type (not pointer) (Container<"?">)
- if err := ensureOutInnerType(outInnerType); err != nil {
- return err
- }
- csvRows, err := decoder.getCSVRows() // Get the CSV csvRows
- if err != nil {
- return err
- }
- if len(csvRows) == 0 {
- return errors.New("empty csv file given")
- }
- if err := ensureOutCapacity(&outValue, len(csvRows)); err != nil { // Ensure the container is big enough to hold the CSV content
- return err
- }
- outInnerStructInfo := getStructInfo(outInnerType) // Get the inner struct info to get CSV annotations
- if len(outInnerStructInfo.Fields) == 0 {
- return errors.New("no csv struct tags found")
- }
- headers := csvRows[0]
- body := csvRows[1:]
- csvHeadersLabels := make(map[int]*fieldInfo, len(outInnerStructInfo.Fields)) // Used to store the correspondance header <-> position in CSV
- headerCount := map[string]int{}
- for i, csvColumnHeader := range headers {
- curHeaderCount := headerCount[csvColumnHeader]
- if fieldInfo := getCSVFieldPosition(csvColumnHeader, outInnerStructInfo, curHeaderCount); fieldInfo != nil {
- csvHeadersLabels[i] = fieldInfo
- if ShouldAlignDuplicateHeadersWithStructFieldOrder {
- curHeaderCount++
- headerCount[csvColumnHeader] = curHeaderCount
- }
- }
- }
- if FailIfUnmatchedStructTags {
- if err := maybeMissingStructFields(outInnerStructInfo.Fields, headers); err != nil {
- return err
- }
- }
- if FailIfDoubleHeaderNames {
- if err := maybeDoubleHeaderNames(headers); err != nil {
- return err
- }
- }
- for i, csvRow := range body {
- outInner := createNewOutInner(outInnerWasPointer, outInnerType)
- for j, csvColumnContent := range csvRow {
- if fieldInfo, ok := csvHeadersLabels[j]; ok { // Position found accordingly to header name
- if err := setInnerField(&outInner, outInnerWasPointer, fieldInfo.IndexChain, csvColumnContent, fieldInfo.omitEmpty); err != nil { // Set field of struct
- return &csv.ParseError{
- Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
- Column: j + 1,
- Err: err,
- }
- }
- }
- }
- outValue.Index(i).Set(outInner)
- }
- return nil
- }
- func readEach(decoder SimpleDecoder, c interface{}) error {
- headers, err := decoder.getCSVRow()
- if err != nil {
- return err
- }
- outValue, outType := getConcreteReflectValueAndType(c) // Get the concrete type (not pointer) (Slice<?> or Array<?>)
- if err := ensureOutType(outType); err != nil {
- return err
- }
- defer outValue.Close()
- outInnerWasPointer, outInnerType := getConcreteContainerInnerType(outType) // Get the concrete inner type (not pointer) (Container<"?">)
- if err := ensureOutInnerType(outInnerType); err != nil {
- return err
- }
- outInnerStructInfo := getStructInfo(outInnerType) // Get the inner struct info to get CSV annotations
- if len(outInnerStructInfo.Fields) == 0 {
- return errors.New("no csv struct tags found")
- }
- csvHeadersLabels := make(map[int]*fieldInfo, len(outInnerStructInfo.Fields)) // Used to store the correspondance header <-> position in CSV
- headerCount := map[string]int{}
- for i, csvColumnHeader := range headers {
- curHeaderCount := headerCount[csvColumnHeader]
- if fieldInfo := getCSVFieldPosition(csvColumnHeader, outInnerStructInfo, curHeaderCount); fieldInfo != nil {
- csvHeadersLabels[i] = fieldInfo
- if ShouldAlignDuplicateHeadersWithStructFieldOrder {
- curHeaderCount++
- headerCount[csvColumnHeader] = curHeaderCount
- }
- }
- }
- if err := maybeMissingStructFields(outInnerStructInfo.Fields, headers); err != nil {
- if FailIfUnmatchedStructTags {
- return err
- }
- }
- if FailIfDoubleHeaderNames {
- if err := maybeDoubleHeaderNames(headers); err != nil {
- return err
- }
- }
- i := 0
- for {
- line, err := decoder.getCSVRow()
- if err == io.EOF {
- break
- } else if err != nil {
- return err
- }
- outInner := createNewOutInner(outInnerWasPointer, outInnerType)
- for j, csvColumnContent := range line {
- if fieldInfo, ok := csvHeadersLabels[j]; ok { // Position found accordingly to header name
- if err := setInnerField(&outInner, outInnerWasPointer, fieldInfo.IndexChain, csvColumnContent, fieldInfo.omitEmpty); err != nil { // Set field of struct
- return &csv.ParseError{
- Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
- Column: j + 1,
- Err: err,
- }
- }
- }
- }
- outValue.Send(outInner)
- i++
- }
- return nil
- }
- // Check if the outType is an array or a slice
- func ensureOutType(outType reflect.Type) error {
- switch outType.Kind() {
- case reflect.Slice:
- fallthrough
- case reflect.Chan:
- fallthrough
- case reflect.Array:
- return nil
- }
- return fmt.Errorf("cannot use " + outType.String() + ", only slice or array supported")
- }
- // Check if the outInnerType is of type struct
- func ensureOutInnerType(outInnerType reflect.Type) error {
- switch outInnerType.Kind() {
- case reflect.Struct:
- return nil
- }
- return fmt.Errorf("cannot use " + outInnerType.String() + ", only struct supported")
- }
- func ensureOutCapacity(out *reflect.Value, csvLen int) error {
- switch out.Kind() {
- case reflect.Array:
- if out.Len() < csvLen-1 { // Array is not big enough to hold the CSV content (arrays are not addressable)
- return fmt.Errorf("array capacity problem: cannot store %d %s in %s", csvLen-1, out.Type().Elem().String(), out.Type().String())
- }
- case reflect.Slice:
- if !out.CanAddr() && out.Len() < csvLen-1 { // Slice is not big enough tho hold the CSV content and is not addressable
- return fmt.Errorf("slice capacity problem and is not addressable (did you forget &?)")
- } else if out.CanAddr() && out.Len() < csvLen-1 {
- out.Set(reflect.MakeSlice(out.Type(), csvLen-1, csvLen-1)) // Slice is not big enough, so grows it
- }
- }
- return nil
- }
- func getCSVFieldPosition(key string, structInfo *structInfo, curHeaderCount int) *fieldInfo {
- matchedFieldCount := 0
- for _, field := range structInfo.Fields {
- if field.matchesKey(key) {
- if matchedFieldCount >= curHeaderCount {
- return &field
- } else {
- matchedFieldCount++
- }
- }
- }
- return nil
- }
- func createNewOutInner(outInnerWasPointer bool, outInnerType reflect.Type) reflect.Value {
- if outInnerWasPointer {
- return reflect.New(outInnerType)
- }
- return reflect.New(outInnerType).Elem()
- }
- func setInnerField(outInner *reflect.Value, outInnerWasPointer bool, index []int, value string, omitEmpty bool) error {
- oi := *outInner
- if outInnerWasPointer {
- oi = outInner.Elem()
- }
- return setField(oi.FieldByIndex(index), value, omitEmpty)
- }
|