pivotTable.go 13 KB

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