evcc-io/util/export/xlsx/xlsx.go

131 lines
2.9 KiB
Go

package xlsx
import (
"context"
"io"
"time"
"github.com/evcc-io/evcc/util/export"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/xuri/excelize/v2"
)
// writer adapts excelize to export.RowWriter with native typed cells
type writer struct {
f *excelize.File
sheet string
dst io.Writer
row int
err error
localizer *i18n.Localizer
dateStyle int
durStyle int
}
// cellStyle returns the number format style for typed cells, 0 for default
func (x *writer) cellStyle(v any) int {
switch v.(type) {
case time.Time:
return x.dateStyle
case time.Duration:
return x.durStyle
default:
return 0
}
}
func (x *writer) Write(record []any) error {
for i, v := range record {
if v == nil {
continue
}
cell, err := excelize.CoordinatesToCellName(i+1, x.row)
if err != nil {
x.err = err
return err
}
if err := x.f.SetCellValue(x.sheet, cell, v); err != nil {
x.err = err
return err
}
if style := x.cellStyle(v); style != 0 {
if err := x.f.SetCellStyle(x.sheet, cell, cell, style); err != nil {
x.err = err
return err
}
}
}
if x.row == 1 {
if err := x.styleHeader(len(record)); err != nil {
x.err = err
return err
}
}
x.row++
return nil
}
// styleHeader makes the header row bold and freezes it below the viewport.
func (x *writer) styleHeader(cols int) error {
if cols == 0 {
return nil
}
style, err := x.f.NewStyle(&excelize.Style{Font: &excelize.Font{Bold: true}})
if err != nil {
return err
}
last, err := excelize.CoordinatesToCellName(cols, 1)
if err != nil {
return err
}
if err := x.f.SetCellStyle(x.sheet, "A1", last, style); err != nil {
return err
}
return x.f.SetPanes(x.sheet, &excelize.Panes{Freeze: true, YSplit: 1, TopLeftCell: "A2", ActivePane: "bottomLeft"})
}
func (x *writer) Flush() {
if x.err != nil {
return
}
if err := x.f.Write(x.dst); err != nil {
x.err = err
}
}
func (x *writer) Error() error { return x.err }
func (x *writer) Localizer() *i18n.Localizer { return x.localizer }
// New returns an xlsx-backed export.RowWriter. The context locale only affects header captions.
func New(ctx context.Context, w io.Writer) (export.RowWriter, error) {
f := excelize.NewFile()
dateFmt, durFmt := "yyyy-mm-dd hh:mm:ss", "[h]:mm:ss"
dateStyle, err := f.NewStyle(&excelize.Style{CustomNumFmt: &dateFmt})
if err != nil {
return nil, err
}
durStyle, err := f.NewStyle(&excelize.Style{CustomNumFmt: &durFmt})
if err != nil {
return nil, err
}
return &writer{
f: f,
sheet: f.GetSheetName(0),
dst: w,
row: 1,
localizer: export.Localizer(ctx),
dateStyle: dateStyle,
durStyle: durStyle,
}, nil
}
// WriteStructSlice writes a slice of structs to xlsx with localized headers.
func WriteStructSlice(ctx context.Context, w io.Writer, slice any, cfg export.Config) error {
ww, err := New(ctx, w)
if err != nil {
return err
}
return export.WriteStructSlice(ww, slice, cfg)
}