pivotTable.go 18 KB

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