mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-13 09:38:41 +08:00
305 lines
7.9 KiB
Go
305 lines
7.9 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package excelutils
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
excelize "github.com/xuri/excelize/v2"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/gotypes"
|
|
)
|
|
|
|
func decimalBaseMaxWidth(decNum int, base int) int {
|
|
if decNum == 0 {
|
|
return 1
|
|
}
|
|
width := 0
|
|
for decNum > 0 {
|
|
decNum = decNum / base
|
|
width += 1
|
|
}
|
|
return width
|
|
}
|
|
|
|
func decimalBaseN(decNum int, base int, width int) (int, int) {
|
|
b := 1
|
|
for i := 0; i < width-1; i += 1 {
|
|
decNum = decNum / base
|
|
b = b * base
|
|
}
|
|
return decNum, b
|
|
}
|
|
|
|
func decimal2Base(decNum int, base int) []int {
|
|
width := decimalBaseMaxWidth(decNum, base)
|
|
ret := make([]int, width)
|
|
for i := width; i > 0; i -= 1 {
|
|
ith, divider := decimalBaseN(decNum, base, i)
|
|
decNum -= ith * divider
|
|
ret[width-i] = ith
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func decimal2Alphabet(decNum int) string {
|
|
var buf bytes.Buffer
|
|
b26 := decimal2Base(decNum, 26)
|
|
for i := 0; i < len(b26); i += 1 {
|
|
if i == 0 && len(b26) > 1 {
|
|
buf.WriteByte(byte('A' + b26[i] - 1))
|
|
} else {
|
|
buf.WriteByte(byte('A' + b26[i]))
|
|
}
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func exportHeader(xlsx *excelize.File, texts []string, rowIndex int, sheet string) {
|
|
if len(sheet) == 0 {
|
|
sheet = DEFAULT_SHEET
|
|
}
|
|
for i := 0; i < len(texts); i += 1 {
|
|
cell := fmt.Sprintf("%s%d", decimal2Alphabet(i), rowIndex)
|
|
xlsx.SetCellValue(sheet, cell, texts[i])
|
|
}
|
|
}
|
|
|
|
func exportRow(xlsx *excelize.File, data jsonutils.JSONObject, keys []string, rowIndex int, sheet string) {
|
|
if len(sheet) == 0 {
|
|
sheet = DEFAULT_SHEET
|
|
}
|
|
for i := 0; i < len(keys); i += 1 {
|
|
cell := fmt.Sprintf("%s%d", decimal2Alphabet(i), rowIndex)
|
|
// var valStr string
|
|
var val jsonutils.JSONObject
|
|
if strings.Contains(keys[i], ".") {
|
|
val, _ = data.GetIgnoreCases(strings.Split(keys[i], ".")...)
|
|
// hack payment_bills
|
|
if !gotypes.IsNil(val) && strings.HasPrefix(keys[i], "tags.") {
|
|
vv := []string{}
|
|
val.Unmarshal(&vv)
|
|
if len(vv) > 0 || val.Equals(jsonutils.Marshal([]string{})) {
|
|
val = jsonutils.NewString(strings.Join(vv, ","))
|
|
}
|
|
}
|
|
} else {
|
|
val, _ = data.GetIgnoreCases(keys[i])
|
|
}
|
|
if val != nil {
|
|
// hack, make floating point number prettier
|
|
if fval, ok := val.(*jsonutils.JSONFloat); ok {
|
|
// 费用需要原样导出,避免数额不准
|
|
fvalResult, _ := fval.Float()
|
|
xlsx.SetCellValue(sheet, cell, fvalResult)
|
|
} else if ival, ok := val.(*jsonutils.JSONInt); ok {
|
|
i, _ := ival.Int()
|
|
xlsx.SetCellValue(sheet, cell, i)
|
|
} else if bval, ok := val.(*jsonutils.JSONBool); ok {
|
|
b, _ := bval.Bool()
|
|
xlsx.SetCellValue(sheet, cell, b)
|
|
} else {
|
|
s, _ := val.GetString()
|
|
xlsx.SetCellValue(sheet, cell, s)
|
|
}
|
|
} else {
|
|
xlsx.SetCellValue(sheet, cell, "")
|
|
}
|
|
}
|
|
}
|
|
|
|
func Export(data []jsonutils.JSONObject, keys []string, texts []string, writer io.Writer) error {
|
|
xlsx := excelize.NewFile()
|
|
exportHeader(xlsx, texts, 1, "")
|
|
for i := 0; i < len(data); i += 1 {
|
|
exportRow(xlsx, data[i], keys, i+2, "")
|
|
}
|
|
return xlsx.Write(writer)
|
|
}
|
|
|
|
// key:data中对应的key,text:头
|
|
func ExportFile(data []jsonutils.JSONObject, keys []string, texts []string, filename string) error {
|
|
writer, err := os.Create(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer writer.Close()
|
|
return Export(data, keys, texts, writer)
|
|
}
|
|
|
|
func ExportWriter(data []jsonutils.JSONObject, keys []string, texts []string, writer io.Writer) *excelize.File {
|
|
xlsx := excelize.NewFile()
|
|
exportHeader(xlsx, texts, 1, "")
|
|
for i := 0; i < len(data); i += 1 {
|
|
exportRow(xlsx, data[i], keys, i+2, "")
|
|
}
|
|
return xlsx
|
|
}
|
|
|
|
type SExcelChartSeries struct {
|
|
Name string
|
|
Categories string
|
|
Values string
|
|
}
|
|
|
|
type SExcelChartTitle struct {
|
|
Name string
|
|
}
|
|
|
|
type SExcelChartFormat struct {
|
|
XScale float64 `json:"x_scale"`
|
|
YScale float64 `json:"y_scale"`
|
|
XOffset float64 `json:"x_offset"`
|
|
YOffset float64 `json:"y_offset"`
|
|
PrintObj bool `json:"print_obj"`
|
|
LockAspectRatio bool `json:"lock_aspect_ratio"`
|
|
Locked bool `json:"locked"`
|
|
}
|
|
|
|
type SExcelChartParams struct {
|
|
Type ExcelChartType
|
|
Series []SExcelChartSeries
|
|
Title SExcelChartTitle
|
|
Format SExcelChartFormat
|
|
}
|
|
|
|
func AddNewSheet(data []jsonutils.JSONObject, keys []string, texts []string, sheet string, f *excelize.File) *excelize.File {
|
|
// 新建sheet
|
|
f.NewSheet(sheet)
|
|
exportHeader(f, texts, 1, sheet)
|
|
for i := 0; i < len(data); i += 1 {
|
|
exportRow(f, data[i], keys, i+2, sheet)
|
|
}
|
|
|
|
return f
|
|
}
|
|
|
|
func AddChartWithSheet(key, sheet string, chartType ExcelChartType, f *excelize.File) (*excelize.File, error) {
|
|
// 获取对应sheet中的所有header
|
|
keys, err := f.GetRows(sheet)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetRows")
|
|
}
|
|
if len(keys) == 0 {
|
|
return nil, errors.Errorf("empty sheet")
|
|
}
|
|
// 获取header对应的坐标
|
|
keyIndex := 'A'
|
|
for k, v := range keys[0] {
|
|
if v == key {
|
|
keyIndex = keyIndex + rune(k)
|
|
}
|
|
}
|
|
// 图表请求
|
|
params := SExcelChartParams{
|
|
Type: chartType,
|
|
Series: []SExcelChartSeries{
|
|
{
|
|
Name: fmt.Sprintf("'%s'!$%s$1", sheet, string(keyIndex)),
|
|
Categories: fmt.Sprintf("'%s'!$A$2:$A$%d", sheet, len(keys)),
|
|
Values: fmt.Sprintf("'%s'!$%s$2:$%s$%d", sheet, string(keyIndex), string(keyIndex), len(keys))},
|
|
},
|
|
Title: SExcelChartTitle{Name: fmt.Sprintf("%s - %s", key, ChartMap[chartType])},
|
|
Format: SExcelChartFormat{
|
|
XScale: 2.7,
|
|
YScale: 2.9,
|
|
PrintObj: true,
|
|
},
|
|
}
|
|
paramObj := jsonutils.Marshal(params)
|
|
excelChart := &excelize.Chart{}
|
|
paramObj.Unmarshal(excelChart)
|
|
err = f.AddChart(sheet, "A1", excelChart)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func CheckChartType(chartType string) bool {
|
|
_, isExist := ChartMap[ExcelChartType(chartType)]
|
|
return isExist
|
|
}
|
|
|
|
// 按列设置单元格格式
|
|
func SetCellStyleWithColumnKey(keys []string, sheet, style string, f *excelize.File) (*excelize.File, error) {
|
|
styleID, err := f.NewStyle(&excelize.Style{CustomNumFmt: &style})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "NewStyle")
|
|
}
|
|
// 优先按行获取所有数据
|
|
rows, err := f.GetRows(sheet)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetRows")
|
|
}
|
|
if len(rows) == 0 {
|
|
return nil, errors.Errorf("rows is empty")
|
|
}
|
|
// 所需设置key的索引
|
|
columnIndex := []int{}
|
|
// 遍历第一行的所有值
|
|
for index, value := range rows[0] {
|
|
for _, key := range keys {
|
|
// 若第一行的值与目标key相等
|
|
if key == value {
|
|
columnIndex = append(columnIndex, index)
|
|
}
|
|
}
|
|
}
|
|
// 遍历所需设置的索引
|
|
for _, index := range columnIndex {
|
|
// 根据索引获取excel的列名
|
|
colName, err := excelize.ColumnNumberToName(index + 1)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "ColumnNumberToName")
|
|
}
|
|
if err := f.SetCellStyle(sheet, fmt.Sprintf("%s%d", colName, 2), fmt.Sprintf("%s%d", colName, len(rows)), styleID); err != nil {
|
|
return nil, errors.Wrap(err, "SetCellStyle")
|
|
}
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// 按行读取excel文件,注:第一行为json的key
|
|
func ReadDataWithRow(f *excelize.File, sheet string) (jsonutils.JSONObject, error) {
|
|
datas := jsonutils.NewArray()
|
|
keys := []string{}
|
|
rows, err := f.GetRows(sheet)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "get rows")
|
|
}
|
|
for index, row := range rows {
|
|
data := jsonutils.NewDict()
|
|
if index == 0 {
|
|
keys = row
|
|
continue
|
|
}
|
|
for i, value := range row {
|
|
if i >= len(keys) {
|
|
break
|
|
}
|
|
data.Set(keys[i], jsonutils.NewString(value))
|
|
}
|
|
datas.Add(data)
|
|
}
|
|
return jsonutils.Marshal(datas), nil
|
|
}
|