| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- // 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
- }
|