All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 2m0s
301 lines
6.2 KiB
Go
301 lines
6.2 KiB
Go
package excelext
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"git.blackforestbytes.com/BlackForestBytes/goext/dataext"
|
|
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
|
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
|
excelize360 "github.com/360EntSecGroup-Skylar/excelize"
|
|
"github.com/xuri/excelize/v2"
|
|
)
|
|
|
|
type excelMapperColDefinition[T any] struct {
|
|
style *int
|
|
header string
|
|
width *float64
|
|
fn func(T) (any, error)
|
|
}
|
|
|
|
type ExcelMapper[T any] struct {
|
|
StyleDate *int
|
|
StyleDatetime *int
|
|
StyleEUR *int
|
|
StylePercentage *int
|
|
StyleHeader *int
|
|
StyleWSHeader *int
|
|
|
|
SkipColumnHeader bool
|
|
|
|
sheetName string
|
|
wsHeader []dataext.Tuple[string, *int]
|
|
colDefinitions []excelMapperColDefinition[T]
|
|
colFilter []func(v T) bool
|
|
}
|
|
|
|
func NewExcelMapper[T any]() (*ExcelMapper[T], error) {
|
|
|
|
em := &ExcelMapper[T]{
|
|
StyleDate: nil,
|
|
StyleDatetime: nil,
|
|
StyleEUR: nil,
|
|
StylePercentage: nil,
|
|
StyleHeader: nil,
|
|
StyleWSHeader: nil,
|
|
sheetName: "",
|
|
|
|
SkipColumnHeader: false,
|
|
|
|
wsHeader: make([]dataext.Tuple[string, *int], 0),
|
|
colDefinitions: make([]excelMapperColDefinition[T], 0),
|
|
}
|
|
|
|
return em, nil
|
|
}
|
|
|
|
func (em *ExcelMapper[T]) InitNewFile(sheetName string) (*excelize.File, error) {
|
|
f := excelize.NewFile()
|
|
|
|
defSheet := f.GetSheetList()[0]
|
|
|
|
sheet1 := sheetName
|
|
|
|
sheetIdx, err := f.NewSheet(sheet1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f.SetActiveSheet(sheetIdx)
|
|
err = f.DeleteSheet(defSheet)
|
|
|
|
err = em.InitStyles(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return f, nil
|
|
}
|
|
|
|
func (em *ExcelMapper[T]) InitStyles(f *excelize.File) error {
|
|
styleDate, err := f.NewStyle(&excelize.Style{
|
|
CustomNumFmt: langext.Ptr("dd.mm.yyyy"),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
styleDatetime, err := f.NewStyle(&excelize.Style{
|
|
NumFmt: 22,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
styleEUR, err := f.NewStyle(&excelize.Style{
|
|
NumFmt: 218,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stylePercentage, err := f.NewStyle(&excelize.Style{
|
|
NumFmt: 10,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
styleHeader, err := f.NewStyle(&excelize.Style{
|
|
Font: &excelize.Font{Bold: true, Size: 11},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
styleWSHeader, err := f.NewStyle(&excelize.Style{
|
|
Font: &excelize.Font{Bold: true, Size: 24},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
em.StyleDate = &styleDate
|
|
em.StyleDatetime = &styleDatetime
|
|
em.StyleEUR = &styleEUR
|
|
em.StylePercentage = &stylePercentage
|
|
em.StyleHeader = &styleHeader
|
|
em.StyleWSHeader = &styleWSHeader
|
|
|
|
return nil
|
|
}
|
|
|
|
func (em *ExcelMapper[T]) AddWorksheetHeader(header string, style *int) {
|
|
em.wsHeader = append(em.wsHeader, dataext.NewTuple(header, style))
|
|
}
|
|
|
|
func (em *ExcelMapper[T]) AddColumn(header string, style *int, width *float64, fn func(T) any) {
|
|
em.colDefinitions = append(em.colDefinitions, excelMapperColDefinition[T]{
|
|
style: style,
|
|
header: header,
|
|
width: width,
|
|
fn: func(t T) (any, error) { return fn(t), nil },
|
|
})
|
|
}
|
|
|
|
func (em *ExcelMapper[T]) AddColumnErr(header string, style *int, width *float64, fn func(T) (any, error)) {
|
|
em.colDefinitions = append(em.colDefinitions, excelMapperColDefinition[T]{
|
|
style: style,
|
|
header: header,
|
|
width: width,
|
|
fn: fn,
|
|
})
|
|
}
|
|
|
|
func (em *ExcelMapper[T]) Build(sheetName string, data []T) ([]byte, error) {
|
|
f, err := em.InitNewFile(sheetName)
|
|
if err != nil {
|
|
return nil, exerr.Wrap(err, "failed to init new file").Build()
|
|
}
|
|
|
|
err = em.BuildSingleSheet(f, sheetName, data)
|
|
if err != nil {
|
|
return nil, exerr.Wrap(err, "").Build()
|
|
}
|
|
|
|
buffer, err := f.WriteToBuffer()
|
|
if err != nil {
|
|
return nil, exerr.Wrap(err, "failed to build xls").Build()
|
|
}
|
|
|
|
return buffer.Bytes(), nil
|
|
}
|
|
|
|
func (em *ExcelMapper[T]) BuildSingleSheet(f *excelize.File, sheetName string, data []T) error {
|
|
if em.StyleHeader == nil || em.StyleDate == nil || em.StyleDatetime == nil || em.StyleEUR == nil || em.StylePercentage == nil || em.StyleWSHeader == nil {
|
|
err := em.InitStyles(f)
|
|
if err != nil {
|
|
return exerr.Wrap(err, "failed to init styles").Build()
|
|
}
|
|
}
|
|
|
|
rowOffset := 0
|
|
|
|
if len(em.wsHeader) > 0 {
|
|
for range em.wsHeader {
|
|
rowOffset += 1
|
|
}
|
|
rowOffset += 1
|
|
}
|
|
|
|
if !em.SkipColumnHeader {
|
|
for i, col := range em.colDefinitions {
|
|
err := f.SetCellValue(sheetName, c(rowOffset+1, i), col.header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
for i, col := range em.colDefinitions {
|
|
if col.style != nil {
|
|
err := f.SetColStyle(sheetName, excelize360.ToAlphaString(i), *col.style)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
for i, col := range em.colDefinitions {
|
|
if col.width != nil {
|
|
err := f.SetColWidth(sheetName, excelize360.ToAlphaString(i), excelize360.ToAlphaString(i), *col.width)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
err := f.SetRowStyle(sheetName, rowOffset+1, rowOffset+1, *em.StyleHeader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(em.wsHeader) > 0 {
|
|
for i, hdr := range em.wsHeader {
|
|
style := *langext.CoalesceOpt(hdr.V2, em.StyleWSHeader)
|
|
|
|
err = f.SetCellValue(sheetName, c(i+1, 0), hdr.V1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = f.MergeCell(sheetName, c(i+1, 0), c(i+1, len(em.colDefinitions)-1))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = f.SetRowStyle(sheetName, 1, 1, style)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
iRow := rowOffset + 1
|
|
if !em.SkipColumnHeader {
|
|
iRow += 1
|
|
}
|
|
|
|
for _, dat := range data {
|
|
|
|
skip := false
|
|
for _, filter := range em.colFilter {
|
|
if !filter(dat) {
|
|
skip = true
|
|
break
|
|
}
|
|
}
|
|
if skip {
|
|
continue
|
|
}
|
|
|
|
for iCol, col := range em.colDefinitions {
|
|
|
|
cellVal, err := col.fn(dat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for reflect.ValueOf(cellVal).Kind() == reflect.Pointer && !reflect.ValueOf(cellVal).IsNil() {
|
|
cellVal = reflect.ValueOf(cellVal).Elem().Interface()
|
|
}
|
|
|
|
if langext.IsNil(cellVal) {
|
|
err = f.SetCellValue(sheetName, c(iRow, iCol), "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
err = f.SetCellValue(sheetName, c(iRow, iCol), cellVal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
iRow++
|
|
}
|
|
|
|
//for i, col := range em.colDefinitions {
|
|
// if col.width == nil {
|
|
// //TODO https://github.com/qax-os/excelize/pull/1386
|
|
// }
|
|
//}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (em *ExcelMapper[T]) AddFilter(f func(v T) bool) {
|
|
em.colFilter = append(em.colFilter, f)
|
|
}
|