excelutils.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package excelutils
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io"
  19. "os"
  20. "strings"
  21. excelize "github.com/xuri/excelize/v2"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/gotypes"
  25. )
  26. func decimalBaseMaxWidth(decNum int, base int) int {
  27. if decNum == 0 {
  28. return 1
  29. }
  30. width := 0
  31. for decNum > 0 {
  32. decNum = decNum / base
  33. width += 1
  34. }
  35. return width
  36. }
  37. func decimalBaseN(decNum int, base int, width int) (int, int) {
  38. b := 1
  39. for i := 0; i < width-1; i += 1 {
  40. decNum = decNum / base
  41. b = b * base
  42. }
  43. return decNum, b
  44. }
  45. func decimal2Base(decNum int, base int) []int {
  46. width := decimalBaseMaxWidth(decNum, base)
  47. ret := make([]int, width)
  48. for i := width; i > 0; i -= 1 {
  49. ith, divider := decimalBaseN(decNum, base, i)
  50. decNum -= ith * divider
  51. ret[width-i] = ith
  52. }
  53. return ret
  54. }
  55. func decimal2Alphabet(decNum int) string {
  56. var buf bytes.Buffer
  57. b26 := decimal2Base(decNum, 26)
  58. for i := 0; i < len(b26); i += 1 {
  59. if i == 0 && len(b26) > 1 {
  60. buf.WriteByte(byte('A' + b26[i] - 1))
  61. } else {
  62. buf.WriteByte(byte('A' + b26[i]))
  63. }
  64. }
  65. return buf.String()
  66. }
  67. func exportHeader(xlsx *excelize.File, texts []string, rowIndex int, sheet string) {
  68. if len(sheet) == 0 {
  69. sheet = DEFAULT_SHEET
  70. }
  71. for i := 0; i < len(texts); i += 1 {
  72. cell := fmt.Sprintf("%s%d", decimal2Alphabet(i), rowIndex)
  73. xlsx.SetCellValue(sheet, cell, texts[i])
  74. }
  75. }
  76. func exportRow(xlsx *excelize.File, data jsonutils.JSONObject, keys []string, rowIndex int, sheet string) {
  77. if len(sheet) == 0 {
  78. sheet = DEFAULT_SHEET
  79. }
  80. for i := 0; i < len(keys); i += 1 {
  81. cell := fmt.Sprintf("%s%d", decimal2Alphabet(i), rowIndex)
  82. // var valStr string
  83. var val jsonutils.JSONObject
  84. if strings.Contains(keys[i], ".") {
  85. val, _ = data.GetIgnoreCases(strings.Split(keys[i], ".")...)
  86. // hack payment_bills
  87. if !gotypes.IsNil(val) && strings.HasPrefix(keys[i], "tags.") {
  88. vv := []string{}
  89. val.Unmarshal(&vv)
  90. if len(vv) > 0 || val.Equals(jsonutils.Marshal([]string{})) {
  91. val = jsonutils.NewString(strings.Join(vv, ","))
  92. }
  93. }
  94. } else {
  95. val, _ = data.GetIgnoreCases(keys[i])
  96. }
  97. if val != nil {
  98. // hack, make floating point number prettier
  99. if fval, ok := val.(*jsonutils.JSONFloat); ok {
  100. // 费用需要原样导出,避免数额不准
  101. fvalResult, _ := fval.Float()
  102. xlsx.SetCellValue(sheet, cell, fvalResult)
  103. } else if ival, ok := val.(*jsonutils.JSONInt); ok {
  104. i, _ := ival.Int()
  105. xlsx.SetCellValue(sheet, cell, i)
  106. } else if bval, ok := val.(*jsonutils.JSONBool); ok {
  107. b, _ := bval.Bool()
  108. xlsx.SetCellValue(sheet, cell, b)
  109. } else {
  110. s, _ := val.GetString()
  111. xlsx.SetCellValue(sheet, cell, s)
  112. }
  113. } else {
  114. xlsx.SetCellValue(sheet, cell, "")
  115. }
  116. }
  117. }
  118. func Export(data []jsonutils.JSONObject, keys []string, texts []string, writer io.Writer) error {
  119. xlsx := excelize.NewFile()
  120. exportHeader(xlsx, texts, 1, "")
  121. for i := 0; i < len(data); i += 1 {
  122. exportRow(xlsx, data[i], keys, i+2, "")
  123. }
  124. return xlsx.Write(writer)
  125. }
  126. // key:data中对应的key,text:头
  127. func ExportFile(data []jsonutils.JSONObject, keys []string, texts []string, filename string) error {
  128. writer, err := os.Create(filename)
  129. if err != nil {
  130. return err
  131. }
  132. defer writer.Close()
  133. return Export(data, keys, texts, writer)
  134. }
  135. func ExportWriter(data []jsonutils.JSONObject, keys []string, texts []string, writer io.Writer) *excelize.File {
  136. xlsx := excelize.NewFile()
  137. exportHeader(xlsx, texts, 1, "")
  138. for i := 0; i < len(data); i += 1 {
  139. exportRow(xlsx, data[i], keys, i+2, "")
  140. }
  141. return xlsx
  142. }
  143. type SExcelChartSeries struct {
  144. Name string
  145. Categories string
  146. Values string
  147. }
  148. type SExcelChartTitle struct {
  149. Name string
  150. }
  151. type SExcelChartFormat struct {
  152. XScale float64 `json:"x_scale"`
  153. YScale float64 `json:"y_scale"`
  154. XOffset float64 `json:"x_offset"`
  155. YOffset float64 `json:"y_offset"`
  156. PrintObj bool `json:"print_obj"`
  157. LockAspectRatio bool `json:"lock_aspect_ratio"`
  158. Locked bool `json:"locked"`
  159. }
  160. type SExcelChartParams struct {
  161. Type ExcelChartType
  162. Series []SExcelChartSeries
  163. Title SExcelChartTitle
  164. Format SExcelChartFormat
  165. }
  166. func AddNewSheet(data []jsonutils.JSONObject, keys []string, texts []string, sheet string, f *excelize.File) *excelize.File {
  167. // 新建sheet
  168. f.NewSheet(sheet)
  169. exportHeader(f, texts, 1, sheet)
  170. for i := 0; i < len(data); i += 1 {
  171. exportRow(f, data[i], keys, i+2, sheet)
  172. }
  173. return f
  174. }
  175. func AddChartWithSheet(key, sheet string, chartType ExcelChartType, f *excelize.File) (*excelize.File, error) {
  176. // 获取对应sheet中的所有header
  177. keys, err := f.GetRows(sheet)
  178. if err != nil {
  179. return nil, errors.Wrap(err, "GetRows")
  180. }
  181. if len(keys) == 0 {
  182. return nil, errors.Errorf("empty sheet")
  183. }
  184. // 获取header对应的坐标
  185. keyIndex := 'A'
  186. for k, v := range keys[0] {
  187. if v == key {
  188. keyIndex = keyIndex + rune(k)
  189. }
  190. }
  191. // 图表请求
  192. params := SExcelChartParams{
  193. Type: chartType,
  194. Series: []SExcelChartSeries{
  195. {
  196. Name: fmt.Sprintf("'%s'!$%s$1", sheet, string(keyIndex)),
  197. Categories: fmt.Sprintf("'%s'!$A$2:$A$%d", sheet, len(keys)),
  198. Values: fmt.Sprintf("'%s'!$%s$2:$%s$%d", sheet, string(keyIndex), string(keyIndex), len(keys))},
  199. },
  200. Title: SExcelChartTitle{Name: fmt.Sprintf("%s - %s", key, ChartMap[chartType])},
  201. Format: SExcelChartFormat{
  202. XScale: 2.7,
  203. YScale: 2.9,
  204. PrintObj: true,
  205. },
  206. }
  207. paramObj := jsonutils.Marshal(params)
  208. excelChart := &excelize.Chart{}
  209. paramObj.Unmarshal(excelChart)
  210. err = f.AddChart(sheet, "A1", excelChart)
  211. if err != nil {
  212. return nil, err
  213. }
  214. return f, nil
  215. }
  216. func CheckChartType(chartType string) bool {
  217. _, isExist := ChartMap[ExcelChartType(chartType)]
  218. return isExist
  219. }
  220. // 按列设置单元格格式
  221. func SetCellStyleWithColumnKey(keys []string, sheet, style string, f *excelize.File) (*excelize.File, error) {
  222. styleID, err := f.NewStyle(&excelize.Style{CustomNumFmt: &style})
  223. if err != nil {
  224. return nil, errors.Wrap(err, "NewStyle")
  225. }
  226. // 优先按行获取所有数据
  227. rows, err := f.GetRows(sheet)
  228. if err != nil {
  229. return nil, errors.Wrap(err, "GetRows")
  230. }
  231. if len(rows) == 0 {
  232. return nil, errors.Errorf("rows is empty")
  233. }
  234. // 所需设置key的索引
  235. columnIndex := []int{}
  236. // 遍历第一行的所有值
  237. for index, value := range rows[0] {
  238. for _, key := range keys {
  239. // 若第一行的值与目标key相等
  240. if key == value {
  241. columnIndex = append(columnIndex, index)
  242. }
  243. }
  244. }
  245. // 遍历所需设置的索引
  246. for _, index := range columnIndex {
  247. // 根据索引获取excel的列名
  248. colName, err := excelize.ColumnNumberToName(index + 1)
  249. if err != nil {
  250. return nil, errors.Wrap(err, "ColumnNumberToName")
  251. }
  252. if err := f.SetCellStyle(sheet, fmt.Sprintf("%s%d", colName, 2), fmt.Sprintf("%s%d", colName, len(rows)), styleID); err != nil {
  253. return nil, errors.Wrap(err, "SetCellStyle")
  254. }
  255. }
  256. return f, nil
  257. }
  258. // 按行读取excel文件,注:第一行为json的key
  259. func ReadDataWithRow(f *excelize.File, sheet string) (jsonutils.JSONObject, error) {
  260. datas := jsonutils.NewArray()
  261. keys := []string{}
  262. rows, err := f.GetRows(sheet)
  263. if err != nil {
  264. return nil, errors.Wrap(err, "get rows")
  265. }
  266. for index, row := range rows {
  267. data := jsonutils.NewDict()
  268. if index == 0 {
  269. keys = row
  270. continue
  271. }
  272. for i, value := range row {
  273. if i >= len(keys) {
  274. break
  275. }
  276. data.Set(keys[i], jsonutils.NewString(value))
  277. }
  278. datas.Add(data)
  279. }
  280. return jsonutils.Marshal(datas), nil
  281. }