diff --git a/README.md b/README.md index aacbf41..76dcdb3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # xlsx -excel的导出导入(目前仅完成导出) +excel的导出和导入 ## 导出功能 如果要使用导出,则传入的数据必须为指针,类型可以是struct或者元素为struct的slice。 @@ -9,3 +9,10 @@ excel的导出导入(目前仅完成导出) 当导出的excel表格列数不确定时,tag使用`export:"loop:true"`,这样会将所有字段导出到excel表格中。 详情请看测试文件 + +## 导入功能 +如果要使用导入,则传入的数据必须为指针,类型是元素为struct的slice。 + +通过tag来确认需要导入的字段,内容为`import:"index:1"`,index为excel表格中的列数,从1开始。当需要导入的数据为日期时,结构体中必须为time.Time类型,不可以用自定义的日期类型,且excel表格中的日期格式必须为yyyy-mm-dd或yyyy-mm-dd hh:mm:ss。 + +详细请看测试文件 diff --git a/import1_test.go b/import1_test.go new file mode 100644 index 0000000..da53e93 --- /dev/null +++ b/import1_test.go @@ -0,0 +1,31 @@ +package xlsx + +import ( + "fmt" + "testing" + "time" + + "git.botann.com/lijun/xlsx/imports" +) + +type Data struct { + Autoseq int + Name string `import:"true,index:1"` + Unit string `import:"true,index:2"` + TestCol string + Invest int64 `import:"true,index:3"` + CreateTime time.Time `import:"true,index:4"` +} + +func TestMain(t *testing.T) { + var data []Data + importer, err := imports.NewFileImporter("./test7.xlsx", &data) + if err != nil { + fmt.Println(err) + } + err = importer.Import(0) + if err != nil { + fmt.Println(err) + } + fmt.Println(data) +} diff --git a/imports/xlsx.go b/imports/xlsx.go new file mode 100644 index 0000000..9d8d29f --- /dev/null +++ b/imports/xlsx.go @@ -0,0 +1,142 @@ +package imports + +import ( + "fmt" + "io" + "reflect" + "strconv" + "strings" + "time" + + "github.com/xuri/excelize/v2" +) + +type Importer struct { + Reader io.Reader + Sheets []string + xlsxFile *excelize.File + Data interface{} +} + +func NewFileImporter(file string, data any) (*Importer, error) { + f, err := excelize.OpenFile(file) + if err != nil { + return nil, err + } + sheets := f.GetSheetList() + return &Importer{ + xlsxFile: f, + Sheets: sheets, + Data: data, + }, nil +} + +func NewFileReaderImporter(reader io.Reader, data any) (*Importer, error) { + f, err := excelize.OpenReader(reader) + if err != nil { + return nil, err + } + sheets := f.GetSheetList() + return &Importer{ + xlsxFile: f, + Sheets: sheets, + Data: data, + }, nil +} + +func (i *Importer) Import(sheetIndex int) error { + v := reflect.ValueOf(i.Data) + if v.Kind() != reflect.Pointer { + return fmt.Errorf("data must be pointer") + } + switch v.Elem().Kind() { + case reflect.Slice: + return i.dealFile(sheetIndex) + default: + return fmt.Errorf("data must be slice") + } +} + +func (i *Importer) dealFile(sheetIndex int) error { + sheet := i.Sheets[sheetIndex] + rows, err := i.xlsxFile.Rows(sheet) + if err != nil { + return err + } + vdata := reflect.ValueOf(i.Data).Elem() + if vdata.Type().Elem().Kind() != reflect.Struct { + return fmt.Errorf("element must be struct") + } + testData := reflect.New(vdata.Type().Elem()) + tempDic := make(map[int]string) + for i := 0; i < testData.Elem().NumField(); i++ { + t := testData.Elem().Type().Field(i) + tag, ok := t.Tag.Lookup("import") + if ok { + tagMap := dealTag(tag) + index, _ := strconv.Atoi(tagMap["index"]) + tempDic[index] = t.Name + } + } + rowIndex := 0 + for rows.Next() { + rowIndex += 1 + // 第一行为表头 + if rowIndex > 1 { + row, err := rows.Columns() + if err != nil { + return err + } + newData := reflect.New(vdata.Type().Elem()) + for colIndex := range row { + colIndex += 1 + cellName, _ := excelize.CoordinatesToCellName(colIndex, rowIndex) + value, _ := i.xlsxFile.GetCellValue(sheet, cellName) + if fieldName, ok := tempDic[colIndex]; ok { + field := newData.Elem().FieldByName(fieldName) + switch field.Kind() { + case reflect.String: + field.SetString(value) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + intValue, _ := strconv.Atoi(value) + field.SetInt(int64(intValue)) + case reflect.Float32, reflect.Float64: + floatValue, _ := strconv.ParseFloat(value, 64) + field.SetFloat(floatValue) + case reflect.Bool: + boolValue, _ := strconv.ParseBool(value) + field.SetBool(boolValue) + // 如果是结构体,只支持时间类型,并且结构体的字段类型必须是time.Time,不能为自定的时间类型,并且excel里面的时间格式必须是yyyy-mm-dd或者yyyy-mm-dd hh:mm:ss + case reflect.Struct: + t := time.Time{} + if strings.Contains(value, ":") { + t, _ = time.Parse(time.DateTime, value) + } else { + t, _ = time.Parse(time.DateOnly, value) + } + field.Set(reflect.ValueOf(t)) + } + } + } + vdata.Set(reflect.Append(vdata, newData.Elem())) + } + } + if err = rows.Close(); err != nil { + return err + } + return nil +} + +func dealTag(tag string) map[string]string { + tag_list := strings.Split(tag, ",") + tag_map := make(map[string]string) + for _, tag := range tag_list { + tag_kv := strings.Split(tag, ":") + if len(tag_kv) != 2 { + continue + } + tag_map[tag_kv[0]] = tag_kv[1] + } + return tag_map +} diff --git a/test7.xlsx b/test7.xlsx new file mode 100644 index 0000000..f4d9aea Binary files /dev/null and b/test7.xlsx differ