pivotTable.go 17 KB

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