pivotTable.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. // Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
  2. // this source code is governed by a BSD-style license that can be found in
  3. // the LICENSE file.
  4. //
  5. // Package excelize providing a set of functions that allow you to write to
  6. // and read from XLSX files. Support reads and writes XLSX file generated by
  7. // Microsoft Excel™ 2007 and later. Support save file without losing original
  8. // charts of XLSX. This library needs Go version 1.10 or later.
  9. package excelize
  10. import (
  11. "encoding/xml"
  12. "errors"
  13. "fmt"
  14. "strconv"
  15. "strings"
  16. )
  17. // PivotTableOption directly maps the format settings of the pivot table.
  18. type PivotTableOption struct {
  19. DataRange string
  20. DataSubtotal string
  21. DataFieldName string
  22. PivotTableRange string
  23. Rows []string
  24. Columns []string
  25. Data []string
  26. Page []string
  27. }
  28. // AddPivotTable provides the method to add pivot table by given pivot table
  29. // options.
  30. //
  31. // DataSubtotal specifies the aggregation function that applies to this data
  32. // field. The default value is sum. The possible values for this attribute
  33. // are:
  34. //
  35. // Average
  36. // Count
  37. // CountNums
  38. // Max
  39. // Min
  40. // Product
  41. // StdDev
  42. // StdDevp
  43. // Sum
  44. // Var
  45. // Varp
  46. //
  47. // DataFieldName specifies the name of the data field. Maximum 255 characters
  48. // are allowed in data field name, excess characters will be truncated.
  49. //
  50. // For example, create a pivot table on the Sheet1!$G$2:$M$34 area with the
  51. // region Sheet1!$A$1:$E$31 as the data source, summarize by sum for sales:
  52. //
  53. // package main
  54. //
  55. // import (
  56. // "fmt"
  57. // "math/rand"
  58. //
  59. // "github.com/360EntSecGroup-Skylar/excelize"
  60. // )
  61. //
  62. // func main() {
  63. // f := excelize.NewFile()
  64. // // Create some data in a sheet
  65. // month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
  66. // year := []int{2017, 2018, 2019}
  67. // types := []string{"Meat", "Dairy", "Beverages", "Produce"}
  68. // region := []string{"East", "West", "North", "South"}
  69. // f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
  70. // for i := 0; i < 30; i++ {
  71. // f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)])
  72. // f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)])
  73. // f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)])
  74. // f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000))
  75. // f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)])
  76. // }
  77. // if err := f.AddPivotTable(&excelize.PivotTableOption{
  78. // DataRange: "Sheet1!$A$1:$E$31",
  79. // PivotTableRange: "Sheet1!$G$2:$M$34",
  80. // Rows: []string{"Month", "Year"},
  81. // Columns: []string{"Type"},
  82. // Data: []string{"Sales"},
  83. // DataSubtotal: "Sum",
  84. // DataFieldName: "Summarize as Sum",
  85. // }); err != nil {
  86. // fmt.Println(err)
  87. // }
  88. // if err := f.SaveAs("Book1.xlsx"); err != nil {
  89. // fmt.Println(err)
  90. // }
  91. // }
  92. //
  93. func (f *File) AddPivotTable(opt *PivotTableOption) error {
  94. // parameter validation
  95. dataSheet, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opt)
  96. if err != nil {
  97. return err
  98. }
  99. pivotTableID := f.countPivotTables() + 1
  100. pivotCacheID := f.countPivotCache() + 1
  101. sheetRelationshipsPivotTableXML := "../pivotTables/pivotTable" + strconv.Itoa(pivotTableID) + ".xml"
  102. pivotTableXML := strings.Replace(sheetRelationshipsPivotTableXML, "..", "xl", -1)
  103. pivotCacheXML := "xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(pivotCacheID) + ".xml"
  104. err = f.addPivotCache(pivotCacheID, pivotCacheXML, opt, dataSheet)
  105. if err != nil {
  106. return err
  107. }
  108. // workbook pivot cache
  109. workBookPivotCacheRID := f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipPivotCache, fmt.Sprintf("pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
  110. cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID)
  111. pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels"
  112. // rId not used
  113. _ = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
  114. err = f.addPivotTable(cacheID, pivotTableID, pivotTableXML, opt)
  115. if err != nil {
  116. return err
  117. }
  118. pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels"
  119. f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "")
  120. f.addContentTypePart(pivotTableID, "pivotTable")
  121. f.addContentTypePart(pivotCacheID, "pivotCache")
  122. return nil
  123. }
  124. // parseFormatPivotTableSet provides a function to validate pivot table
  125. // properties.
  126. func (f *File) parseFormatPivotTableSet(opt *PivotTableOption) (*xlsxWorksheet, string, error) {
  127. if opt == nil {
  128. return nil, "", errors.New("parameter is required")
  129. }
  130. dataSheetName, _, err := f.adjustRange(opt.DataRange)
  131. if err != nil {
  132. return nil, "", fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
  133. }
  134. pivotTableSheetName, _, err := f.adjustRange(opt.PivotTableRange)
  135. if err != nil {
  136. return nil, "", fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
  137. }
  138. dataSheet, err := f.workSheetReader(dataSheetName)
  139. if err != nil {
  140. return dataSheet, "", err
  141. }
  142. pivotTableSheetPath, ok := f.sheetMap[trimSheetName(pivotTableSheetName)]
  143. if !ok {
  144. return dataSheet, pivotTableSheetPath, fmt.Errorf("sheet %s is not exist", pivotTableSheetName)
  145. }
  146. return dataSheet, pivotTableSheetPath, err
  147. }
  148. // adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31
  149. func (f *File) adjustRange(rangeStr string) (string, []int, error) {
  150. if len(rangeStr) < 1 {
  151. return "", []int{}, errors.New("parameter is required")
  152. }
  153. rng := strings.Split(rangeStr, "!")
  154. if len(rng) != 2 {
  155. return "", []int{}, errors.New("parameter is invalid")
  156. }
  157. trimRng := strings.Replace(rng[1], "$", "", -1)
  158. coordinates, err := f.areaRefToCoordinates(trimRng)
  159. if err != nil {
  160. return rng[0], []int{}, err
  161. }
  162. x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  163. if x1 == x2 && y1 == y2 {
  164. return rng[0], []int{}, errors.New("parameter is invalid")
  165. }
  166. // Correct the coordinate area, such correct C1:B3 to B1:C3.
  167. if x2 < x1 {
  168. x1, x2 = x2, x1
  169. }
  170. if y2 < y1 {
  171. y1, y2 = y2, y1
  172. }
  173. return rng[0], []int{x1, y1, x2, y2}, nil
  174. }
  175. func (f *File) getPivotFieldsOrder(dataRange string) ([]string, error) {
  176. order := []string{}
  177. // data range has been checked
  178. dataSheet, coordinates, err := f.adjustRange(dataRange)
  179. if err != nil {
  180. return order, fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
  181. }
  182. for col := coordinates[0]; col <= coordinates[2]; col++ {
  183. coordinate, _ := CoordinatesToCellName(col, coordinates[1])
  184. name, err := f.GetCellValue(dataSheet, coordinate)
  185. if err != nil {
  186. return order, err
  187. }
  188. order = append(order, name)
  189. }
  190. return order, nil
  191. }
  192. // addPivotCache provides a function to create a pivot cache by given properties.
  193. func (f *File) addPivotCache(pivotCacheID int, pivotCacheXML string, opt *PivotTableOption, ws *xlsxWorksheet) error {
  194. // validate data range
  195. dataSheet, coordinates, err := f.adjustRange(opt.DataRange)
  196. if err != nil {
  197. return fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
  198. }
  199. order, err := f.getPivotFieldsOrder(opt.DataRange)
  200. if err != nil {
  201. return err
  202. }
  203. hcell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
  204. vcell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
  205. pc := xlsxPivotCacheDefinition{
  206. SaveData: false,
  207. RefreshOnLoad: true,
  208. CacheSource: &xlsxCacheSource{
  209. Type: "worksheet",
  210. WorksheetSource: &xlsxWorksheetSource{
  211. Ref: hcell + ":" + vcell,
  212. Sheet: dataSheet,
  213. },
  214. },
  215. CacheFields: &xlsxCacheFields{},
  216. }
  217. for _, name := range order {
  218. pc.CacheFields.CacheField = append(pc.CacheFields.CacheField, &xlsxCacheField{
  219. Name: name,
  220. SharedItems: &xlsxSharedItems{
  221. Count: 0,
  222. },
  223. })
  224. }
  225. pc.CacheFields.Count = len(pc.CacheFields.CacheField)
  226. pivotCache, err := xml.Marshal(pc)
  227. f.saveFileList(pivotCacheXML, pivotCache)
  228. return err
  229. }
  230. // addPivotTable provides a function to create a pivot table by given pivot
  231. // table ID and properties.
  232. func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, opt *PivotTableOption) error {
  233. // validate pivot table range
  234. _, coordinates, err := f.adjustRange(opt.PivotTableRange)
  235. if err != nil {
  236. return fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
  237. }
  238. hcell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
  239. vcell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
  240. pt := xlsxPivotTableDefinition{
  241. Name: fmt.Sprintf("Pivot Table%d", pivotTableID),
  242. CacheID: cacheID,
  243. DataCaption: "Values",
  244. Location: &xlsxLocation{
  245. Ref: hcell + ":" + vcell,
  246. FirstDataCol: 1,
  247. FirstDataRow: 1,
  248. FirstHeaderRow: 1,
  249. },
  250. PivotFields: &xlsxPivotFields{},
  251. RowFields: &xlsxRowFields{},
  252. RowItems: &xlsxRowItems{
  253. Count: 1,
  254. I: []*xlsxI{
  255. {
  256. []*xlsxX{{}, {}},
  257. },
  258. },
  259. },
  260. ColItems: &xlsxColItems{
  261. Count: 1,
  262. I: []*xlsxI{{}},
  263. },
  264. DataFields: &xlsxDataFields{},
  265. PivotTableStyleInfo: &xlsxPivotTableStyleInfo{
  266. Name: "PivotStyleLight16",
  267. ShowRowHeaders: true,
  268. ShowColHeaders: true,
  269. ShowLastColumn: true,
  270. },
  271. }
  272. // pivot fields
  273. err = f.addPivotFields(&pt, opt)
  274. if err != nil {
  275. return err
  276. }
  277. // count pivot fields
  278. pt.PivotFields.Count = len(pt.PivotFields.PivotField)
  279. // row fields
  280. rowFieldsIndex, err := f.getPivotFieldsIndex(opt.Rows, opt)
  281. if err != nil {
  282. return err
  283. }
  284. for _, fieldIdx := range rowFieldsIndex {
  285. pt.RowFields.Field = append(pt.RowFields.Field, &xlsxField{
  286. X: fieldIdx,
  287. })
  288. }
  289. // count row fields
  290. pt.RowFields.Count = len(pt.RowFields.Field)
  291. err = f.addPivotColFields(&pt, opt)
  292. if err != nil {
  293. return err
  294. }
  295. // data fields
  296. dataFieldsIndex, err := f.getPivotFieldsIndex(opt.Data, opt)
  297. if err != nil {
  298. return err
  299. }
  300. dataFieldName := opt.DataFieldName
  301. if len(dataFieldName) > 255 {
  302. dataFieldName = dataFieldName[0:255]
  303. }
  304. for _, dataField := range dataFieldsIndex {
  305. pt.DataFields.DataField = append(pt.DataFields.DataField, &xlsxDataField{
  306. Name: dataFieldName,
  307. Fld: dataField,
  308. Subtotal: f.getFieldsSubtotal(opt),
  309. })
  310. }
  311. // count data fields
  312. pt.DataFields.Count = len(pt.DataFields.DataField)
  313. pivotTable, err := xml.Marshal(pt)
  314. f.saveFileList(pivotTableXML, pivotTable)
  315. return err
  316. }
  317. // inStrSlice provides a method to check if an element is present in an array,
  318. // and return the index of its location, otherwise return -1.
  319. func inStrSlice(a []string, x string) int {
  320. for idx, n := range a {
  321. if x == n {
  322. return idx
  323. }
  324. }
  325. return -1
  326. }
  327. // addPivotColFields create pivot column fields by given pivot table
  328. // definition and option.
  329. func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error {
  330. if len(opt.Columns) == 0 {
  331. return nil
  332. }
  333. pt.ColFields = &xlsxColFields{}
  334. // col fields
  335. colFieldsIndex, err := f.getPivotFieldsIndex(opt.Columns, opt)
  336. if err != nil {
  337. return err
  338. }
  339. for _, fieldIdx := range colFieldsIndex {
  340. pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
  341. X: fieldIdx,
  342. })
  343. }
  344. // count col fields
  345. pt.ColFields.Count = len(pt.ColFields.Field)
  346. return err
  347. }
  348. // addPivotFields create pivot fields based on the column order of the first
  349. // row in the data region by given pivot table definition and option.
  350. func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error {
  351. order, err := f.getPivotFieldsOrder(opt.DataRange)
  352. if err != nil {
  353. return err
  354. }
  355. for _, name := range order {
  356. if inStrSlice(opt.Rows, name) != -1 {
  357. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
  358. Axis: "axisRow",
  359. Items: &xlsxItems{
  360. Count: 1,
  361. Item: []*xlsxItem{
  362. {T: "default"},
  363. },
  364. },
  365. })
  366. continue
  367. }
  368. if inStrSlice(opt.Columns, name) != -1 {
  369. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
  370. Axis: "axisCol",
  371. Items: &xlsxItems{
  372. Count: 1,
  373. Item: []*xlsxItem{
  374. {T: "default"},
  375. },
  376. },
  377. })
  378. continue
  379. }
  380. if inStrSlice(opt.Data, name) != -1 {
  381. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
  382. DataField: true,
  383. })
  384. continue
  385. }
  386. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{})
  387. }
  388. return err
  389. }
  390. // countPivotTables provides a function to get drawing files count storage in
  391. // the folder xl/pivotTables.
  392. func (f *File) countPivotTables() int {
  393. count := 0
  394. for k := range f.XLSX {
  395. if strings.Contains(k, "xl/pivotTables/pivotTable") {
  396. count++
  397. }
  398. }
  399. return count
  400. }
  401. // countPivotCache provides a function to get drawing files count storage in
  402. // the folder xl/pivotCache.
  403. func (f *File) countPivotCache() int {
  404. count := 0
  405. for k := range f.XLSX {
  406. if strings.Contains(k, "xl/pivotCache/pivotCacheDefinition") {
  407. count++
  408. }
  409. }
  410. return count
  411. }
  412. // getPivotFieldsIndex convert the column of the first row in the data region
  413. // to a sequential index by given fields and pivot option.
  414. func (f *File) getPivotFieldsIndex(fields []string, opt *PivotTableOption) ([]int, error) {
  415. pivotFieldsIndex := []int{}
  416. orders, err := f.getPivotFieldsOrder(opt.DataRange)
  417. if err != nil {
  418. return pivotFieldsIndex, err
  419. }
  420. for _, field := range fields {
  421. if pos := inStrSlice(orders, field); pos != -1 {
  422. pivotFieldsIndex = append(pivotFieldsIndex, pos)
  423. }
  424. }
  425. return pivotFieldsIndex, nil
  426. }
  427. // getFieldsSubtotal prepare data subtotal by given fields and pivot option.
  428. func (f *File) getFieldsSubtotal(opt *PivotTableOption) (subtotal string) {
  429. subtotal = "sum"
  430. for _, enum := range []string{"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp", "sum", "var", "varp"} {
  431. if strings.ToLower(enum) == strings.ToLower(opt.DataSubtotal) {
  432. subtotal = enum
  433. return
  434. }
  435. }
  436. return
  437. }
  438. // addWorkbookPivotCache add the association ID of the pivot cache in xl/workbook.xml.
  439. func (f *File) addWorkbookPivotCache(RID int) int {
  440. wb := f.workbookReader()
  441. if wb.PivotCaches == nil {
  442. wb.PivotCaches = &xlsxPivotCaches{}
  443. }
  444. cacheID := 1
  445. for _, pivotCache := range wb.PivotCaches.PivotCache {
  446. if pivotCache.CacheID > cacheID {
  447. cacheID = pivotCache.CacheID
  448. }
  449. }
  450. cacheID++
  451. wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache, xlsxPivotCache{
  452. CacheID: cacheID,
  453. RID: fmt.Sprintf("rId%d", RID),
  454. })
  455. return cacheID
  456. }