xlsx/export/xlsx.go

229 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package export
import (
"bytes"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"github.com/xuri/excelize/v2"
)
type Exporter struct {
File string
Path string
Sheets []string
Titles []Title
Data interface{}
xlsx *excelize.File
GlobalTitleStyle *excelize.Style
GlobalCellStyle *excelize.Style
preLocation *CellLocation
}
func NewExporter() *Exporter {
return &Exporter{
xlsx: excelize.NewFile(),
}
}
func DefaultExporter() *Exporter {
return &Exporter{
xlsx: excelize.NewFile(),
GlobalTitleStyle: DefaultTitleStyle(),
GlobalCellStyle: DefaultCellStyle(),
Sheets: []string{"Sheet1"},
}
}
func (e *Exporter) newSheet() error {
for _, sheet := range e.Sheets {
_, err := e.xlsx.NewSheet(sheet)
if err != nil {
return fmt.Errorf("init sheet error:%s", err)
}
}
return nil
}
func (e *Exporter) Export(sheetIndex int) error {
if err := e.export(sheetIndex); err != nil {
return err
}
return e.Save()
}
func (e *Exporter) ExportBuffer(sheetIndex int) (io.Writer, error) {
if err := e.export(sheetIndex); err != nil {
return nil, err
}
return e.SaveBuffer()
}
func (e *Exporter) export(sheetIndex int) error {
if len(e.Sheets) == 0 {
return fmt.Errorf("excel file has no sheet")
}
err := e.newSheet()
if err != nil {
return err
}
sheet := e.Sheets[sheetIndex]
if len(e.Titles) > 0 {
e.SetTitle(sheet)
}
v := reflect.ValueOf(e.Data)
if v.Kind() != reflect.Pointer {
return fmt.Errorf("data must be pointer")
}
switch v.Elem().Kind() {
case reflect.Slice:
for i := 0; i < v.Elem().Len(); i++ {
value := v.Elem().Index(i)
if value.Kind() != reflect.Struct {
return fmt.Errorf("element must be struct")
}
e.dealElement(sheet, value)
}
case reflect.Struct:
e.dealElement(sheet, v.Elem())
default:
return fmt.Errorf("data must be slice or struct")
}
return nil
}
func (e *Exporter) dealElement(sheet string, data reflect.Value) {
for i := 0; i < data.NumField(); i++ {
field := data.Field(i)
t := data.Type().Field(i)
tag, ok := t.Tag.Lookup("export")
switch field.Kind() {
case reflect.Struct:
// 如果是结构体类型但是没有export标签则进行递归否则直接处理例如time.Time类型是结构体但是可能需要导出
if !ok {
e.dealElement(sheet, field)
} else {
e.writeCell(sheet, field, tag)
}
// 该逻辑有待验证
case reflect.Slice:
for j := 0; j < field.Len(); j++ {
value := field.Index(j)
if value.Kind() == reflect.Struct {
e.dealElement(sheet, value)
}
}
default:
if ok {
e.writeCell(sheet, field, tag)
}
}
}
}
func (e *Exporter) writeCell(sheet string, field reflect.Value, tag string) {
tagMap := dealTag(tag)
x, _ := strconv.Atoi(tagMap["x"])
y, _ := strconv.Atoi(tagMap["y"])
colSpan, _ := strconv.Atoi(tagMap["colspan"])
rowSpan, _ := strconv.Atoi(tagMap["rowspan"])
loop := tagMap["loop"]
title := tagMap["title"]
if title != "" {
t := Title{
Name: title,
Location: Location{X: x, Y: 1},
Colspan: colSpan,
Rowspan: rowSpan,
}
e.WriteTitle(sheet, t)
}
var location Location
if e.preLocation != nil {
// loop不为空则表示该结构体需要循环输出x坐标需要重新计算(即x坐标需要在上一个结构体的右边,主要用于导出字段数量不确定用sliece类型的结构体存储)
if loop != "" {
y = e.preLocation.BottomY
if x <= e.preLocation.RightX {
x = e.preLocation.RightX + 1
}
} else {
if x > e.preLocation.RightX {
y = e.preLocation.TopY
} else if x <= e.preLocation.RightX { // 如果x坐标小于等于上一个单元格的x坐标则表示需要换行此时y坐标需要在上一个单元格的下面
y = e.preLocation.BottomY + 1
}
}
}
location = Location{
X: x,
Y: y,
}
value := value(field)
cell := Cell{
Location: location,
Rowspan: rowSpan,
Colspan: colSpan,
Value: value,
}
e.SetCell(sheet, cell)
}
func value(field reflect.Value) (val string) {
mthV := field.MethodByName("String")
if !mthV.IsValid() && field.CanAddr() {
mthV = field.Addr().MethodByName("String")
}
if mthV.IsValid() && mthV.Kind() == reflect.Func && mthV.Type().NumIn() == 0 {
callV := mthV.Call(nil)
if len(callV) > 0 {
val = fmt.Sprintf("%s", callV[0].String())
return
}
}
if field.CanUint() {
val = fmt.Sprintf("%d", field.Uint())
} else if field.CanInt() {
val = fmt.Sprintf("%d", field.Int())
} else if field.CanFloat() {
val = fmt.Sprintf("%f", field.Float())
} else {
val = fmt.Sprintf("%s", field.String())
}
return
}
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
}
func (e *Exporter) Save() error {
defer e.xlsx.Close()
return e.xlsx.SaveAs(fmt.Sprintf("%s/%s", e.Path, e.File))
}
func (e *Exporter) SaveBuffer() (io.Writer, error) {
defer e.xlsx.Close()
var buffer bytes.Buffer
err := e.xlsx.Write(&buffer)
if err != nil {
return nil, err
}
return &buffer, nil
}