This repository has been archived on 2025-03-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
boilerplate/internal/extras/excel/export.go

241 lines
5.0 KiB
Go

package excel
import (
"app/internal/dialogs"
"fmt"
"github.com/xuri/excelize/v2"
"log/slog"
"reflect"
"slices"
"strings"
"time"
)
type TableHeaders struct {
Headers []string
IgnoredFieldsIndices []int
}
func isPrimitive(valueType reflect.Type) bool {
switch valueType.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64,
reflect.String:
return true
default:
return false
}
}
func ExportEntityToSpreadsheet[T any](filename, sheetName string, entity T, provider func() ([]*T, error)) error {
file := excelize.NewFile()
defer func() {
if err := file.Close(); err != nil {
slog.Error("Error while closing excel file: ", err)
}
}()
if _, err := file.NewSheet(sheetName); err != nil {
return err
}
if err := file.DeleteSheet("Sheet1"); err != nil {
return err
}
headers, err := WriteHeaders(sheetName, entity, file)
if err != nil {
return err
}
items, err := provider()
if err != nil {
return err
}
// TODO: process composite objects
// TODO: appearance
for i, item := range items {
structValue := reflect.ValueOf(item).Elem()
for j := 0; j < structValue.NumField(); j++ {
if slices.Contains(headers.IgnoredFieldsIndices, j) {
continue
}
field := structValue.Field(j)
if isPrimitive(field.Type()) {
fieldValue := field.Interface()
cellName, err := GetCellNameByIndices(j, i+1)
if err != nil {
return err
}
cellType := structValue.Type().Field(j).Tag.Get(CellTypeTag)
var cellValue any
switch cellType {
case TimestampTag:
cellValue = time.Unix(field.Int(), 0)
case DurationTag:
cellValue = time.Duration(field.Int())
default:
cellValue = fieldValue
}
slog.Debug("Field %s value: %v, %s\n", cellName, cellValue, cellType)
err = file.SetCellValue(sheetName, cellName, cellValue)
if err != nil {
return err
}
}
}
}
if err := WriteData(file, filename); err != nil {
return err
}
return nil
}
func GetHeaderCellNameByIndex(column int) (string, error) {
colName, err := excelize.ColumnNumberToName(column + 1)
if err != nil {
return "", err
}
cellName, err := excelize.JoinCellName(colName, 1)
if err != nil {
return "", err
}
return cellName, nil
}
func GetCellNameByIndices(column int, row int) (string, error) {
colName, err := excelize.ColumnNumberToName(column + 1)
if err != nil {
return "", err
}
cellName, err := excelize.JoinCellName(colName, row+1)
if err != nil {
return "", err
}
return cellName, nil
}
func ExportHeaders(entity any) TableHeaders {
headers := TableHeaders{}
v := reflect.TypeOf(entity)
for i := range v.NumField() {
field := v.Field(i)
displayName := field.Tag.Get("displayName")
if displayName != "" {
headers.Headers = append(headers.Headers, displayName)
} else {
headers.IgnoredFieldsIndices = append(headers.IgnoredFieldsIndices, i)
}
}
return headers
}
func WriteHeaders(sheetName string, entity any, file *excelize.File) (TableHeaders, error) {
headers := ExportHeaders(entity)
for i, header := range headers.Headers {
cellName, err := GetHeaderCellNameByIndex(i)
if err != nil {
return headers, err
}
err = file.SetCellValue(sheetName, cellName, header)
if err != nil {
return headers, err
}
}
err := ApplyStyleHeaders(file, sheetName, headers)
if err != nil {
return headers, err
}
return headers, nil
}
func GetStyleId(f *excelize.File, style *excelize.Style) (int, error) {
styleId, err := f.NewStyle(style)
if err != nil {
return 0, fmt.Errorf("ошибка при создании стиля: %w", err)
}
return styleId, nil
}
func LoadHeadersStyle(file *excelize.File) (int, error) {
headersStyle := excelize.Style{
Alignment: &excelize.Alignment{
Horizontal: "center",
Vertical: "center",
},
Border: []excelize.Border{
{
Type: "left",
Color: "000000",
Style: 1,
},
{
Type: "right",
Color: "000000",
Style: 1,
},
{
Type: "top",
Color: "000000",
Style: 1,
},
{
Type: "bottom",
Color: "000000",
Style: 1,
},
},
Font: &excelize.Font{
Bold: true,
VertAlign: "center",
},
}
return GetStyleId(file, &headersStyle)
}
func ApplyStyleHeaders(file *excelize.File, sheetName string, headers TableHeaders) error {
styleId, err := LoadHeadersStyle(file)
if err != nil {
return err
}
cellName, err := GetHeaderCellNameByIndex(len(headers.Headers) - 1)
if err != nil {
return err
}
err = file.SetCellStyle(sheetName, "A1", cellName, styleId)
if err != nil {
return err
}
return nil
}
func WriteData(file *excelize.File, filename string) error {
filepath, err := dialogs.SaveFileDialog("Экспорт данных", filename)
if !strings.HasSuffix(filepath, ".xlsx") {
filepath += ".xlsx"
}
if err != nil {
return err
}
if err := file.SaveAs(filepath); err != nil {
return err
}
return nil
}