pivotTable.go 18 KB

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