pivotTable.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. // Copyright 2016 - 2023 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 and
  6. // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
  7. // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
  8. // Supports complex components by high compatibility, and provided streaming
  9. // API for generating or reading data from a worksheet with huge amounts of
  10. // data. This library needs Go version 1.16 or later.
  11. package excelize
  12. import (
  13. "encoding/xml"
  14. "fmt"
  15. "strconv"
  16. "strings"
  17. )
  18. // PivotTableOptions directly maps the format settings of the pivot table.
  19. //
  20. // PivotTableStyleName: The built-in pivot table style names
  21. //
  22. // PivotStyleLight1 - PivotStyleLight28
  23. // PivotStyleMedium1 - PivotStyleMedium28
  24. // PivotStyleDark1 - PivotStyleDark28
  25. type PivotTableOptions struct {
  26. pivotTableSheetName string
  27. DataRange string
  28. PivotTableRange string
  29. Rows []PivotTableField
  30. Columns []PivotTableField
  31. Data []PivotTableField
  32. Filter []PivotTableField
  33. RowGrandTotals bool
  34. ColGrandTotals bool
  35. ShowDrill bool
  36. UseAutoFormatting bool
  37. PageOverThenDown bool
  38. MergeItem bool
  39. CompactData bool
  40. ShowError bool
  41. ShowRowHeaders bool
  42. ShowColHeaders bool
  43. ShowRowStripes bool
  44. ShowColStripes bool
  45. ShowLastColumn bool
  46. PivotTableStyleName string
  47. }
  48. // PivotTableField directly maps the field settings of the pivot table.
  49. // Subtotal specifies the aggregation function that applies to this data
  50. // field. The default value is sum. The possible values for this attribute
  51. // are:
  52. //
  53. // Average
  54. // Count
  55. // CountNums
  56. // Max
  57. // Min
  58. // Product
  59. // StdDev
  60. // StdDevp
  61. // Sum
  62. // Var
  63. // Varp
  64. //
  65. // Name specifies the name of the data field. Maximum 255 characters
  66. // are allowed in data field name, excess characters will be truncated.
  67. type PivotTableField struct {
  68. Compact bool
  69. Data string
  70. Name string
  71. Outline bool
  72. Subtotal string
  73. DefaultSubtotal bool
  74. }
  75. // AddPivotTable provides the method to add pivot table by given pivot table
  76. // options. Note that the same fields can not in Columns, Rows and Filter
  77. // fields at the same time.
  78. //
  79. // For example, create a pivot table on the range reference Sheet1!$G$2:$M$34
  80. // with the range reference Sheet1!$A$1:$E$31 as the data source, summarize by
  81. // sum for sales:
  82. //
  83. // package main
  84. //
  85. // import (
  86. // "fmt"
  87. // "math/rand"
  88. //
  89. // "github.com/xuri/excelize/v2"
  90. // )
  91. //
  92. // func main() {
  93. // f := excelize.NewFile()
  94. // defer func() {
  95. // if err := f.Close(); err != nil {
  96. // fmt.Println(err)
  97. // }
  98. // }()
  99. // // Create some data in a sheet
  100. // month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
  101. // year := []int{2017, 2018, 2019}
  102. // types := []string{"Meat", "Dairy", "Beverages", "Produce"}
  103. // region := []string{"East", "West", "North", "South"}
  104. // f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
  105. // for row := 2; row < 32; row++ {
  106. // f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)])
  107. // f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)])
  108. // f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)])
  109. // f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000))
  110. // f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)])
  111. // }
  112. // if err := f.AddPivotTable(&excelize.PivotTableOptions{
  113. // DataRange: "Sheet1!$A$1:$E$31",
  114. // PivotTableRange: "Sheet1!$G$2:$M$34",
  115. // Rows: []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
  116. // Filter: []excelize.PivotTableField{{Data: "Region"}},
  117. // Columns: []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}},
  118. // Data: []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}},
  119. // RowGrandTotals: true,
  120. // ColGrandTotals: true,
  121. // ShowDrill: true,
  122. // ShowRowHeaders: true,
  123. // ShowColHeaders: true,
  124. // ShowLastColumn: true,
  125. // }); err != nil {
  126. // fmt.Println(err)
  127. // }
  128. // if err := f.SaveAs("Book1.xlsx"); err != nil {
  129. // fmt.Println(err)
  130. // }
  131. // }
  132. func (f *File) AddPivotTable(opts *PivotTableOptions) error {
  133. // parameter validation
  134. _, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opts)
  135. if err != nil {
  136. return err
  137. }
  138. pivotTableID := f.countPivotTables() + 1
  139. pivotCacheID := f.countPivotCache() + 1
  140. sheetRelationshipsPivotTableXML := "../pivotTables/pivotTable" + strconv.Itoa(pivotTableID) + ".xml"
  141. pivotTableXML := strings.ReplaceAll(sheetRelationshipsPivotTableXML, "..", "xl")
  142. pivotCacheXML := "xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(pivotCacheID) + ".xml"
  143. err = f.addPivotCache(pivotCacheXML, opts)
  144. if err != nil {
  145. return err
  146. }
  147. // workbook pivot cache
  148. workBookPivotCacheRID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipPivotCache, fmt.Sprintf("/xl/pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
  149. cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID)
  150. pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels"
  151. // rId not used
  152. _ = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
  153. err = f.addPivotTable(cacheID, pivotTableID, pivotTableXML, opts)
  154. if err != nil {
  155. return err
  156. }
  157. pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels"
  158. f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "")
  159. if err = f.addContentTypePart(pivotTableID, "pivotTable"); err != nil {
  160. return err
  161. }
  162. return f.addContentTypePart(pivotCacheID, "pivotCache")
  163. }
  164. // parseFormatPivotTableSet provides a function to validate pivot table
  165. // properties.
  166. func (f *File) parseFormatPivotTableSet(opts *PivotTableOptions) (*xlsxWorksheet, string, error) {
  167. if opts == nil {
  168. return nil, "", ErrParameterRequired
  169. }
  170. pivotTableSheetName, _, err := f.adjustRange(opts.PivotTableRange)
  171. if err != nil {
  172. return nil, "", fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
  173. }
  174. opts.pivotTableSheetName = pivotTableSheetName
  175. dataRange := f.getDefinedNameRefTo(opts.DataRange, pivotTableSheetName)
  176. if dataRange == "" {
  177. dataRange = opts.DataRange
  178. }
  179. dataSheetName, _, err := f.adjustRange(dataRange)
  180. if err != nil {
  181. return nil, "", fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
  182. }
  183. dataSheet, err := f.workSheetReader(dataSheetName)
  184. if err != nil {
  185. return dataSheet, "", err
  186. }
  187. pivotTableSheetPath, ok := f.getSheetXMLPath(pivotTableSheetName)
  188. if !ok {
  189. return dataSheet, pivotTableSheetPath, fmt.Errorf("sheet %s does not exist", pivotTableSheetName)
  190. }
  191. return dataSheet, pivotTableSheetPath, err
  192. }
  193. // adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31
  194. func (f *File) adjustRange(rangeStr string) (string, []int, error) {
  195. if len(rangeStr) < 1 {
  196. return "", []int{}, ErrParameterRequired
  197. }
  198. rng := strings.Split(rangeStr, "!")
  199. if len(rng) != 2 {
  200. return "", []int{}, ErrParameterInvalid
  201. }
  202. trimRng := strings.ReplaceAll(rng[1], "$", "")
  203. coordinates, err := rangeRefToCoordinates(trimRng)
  204. if err != nil {
  205. return rng[0], []int{}, err
  206. }
  207. x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  208. if x1 == x2 && y1 == y2 {
  209. return rng[0], []int{}, ErrParameterInvalid
  210. }
  211. // Correct the range, such correct C1:B3 to B1:C3.
  212. if x2 < x1 {
  213. x1, x2 = x2, x1
  214. }
  215. if y2 < y1 {
  216. y1, y2 = y2, y1
  217. }
  218. return rng[0], []int{x1, y1, x2, y2}, nil
  219. }
  220. // getPivotFieldsOrder provides a function to get order list of pivot table
  221. // fields.
  222. func (f *File) getPivotFieldsOrder(opts *PivotTableOptions) ([]string, error) {
  223. var order []string
  224. dataRange := f.getDefinedNameRefTo(opts.DataRange, opts.pivotTableSheetName)
  225. if dataRange == "" {
  226. dataRange = opts.DataRange
  227. }
  228. dataSheet, coordinates, err := f.adjustRange(dataRange)
  229. if err != nil {
  230. return order, fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
  231. }
  232. for col := coordinates[0]; col <= coordinates[2]; col++ {
  233. coordinate, _ := CoordinatesToCellName(col, coordinates[1])
  234. name, err := f.GetCellValue(dataSheet, coordinate)
  235. if err != nil {
  236. return order, err
  237. }
  238. order = append(order, name)
  239. }
  240. return order, nil
  241. }
  242. // addPivotCache provides a function to create a pivot cache by given properties.
  243. func (f *File) addPivotCache(pivotCacheXML string, opts *PivotTableOptions) error {
  244. // validate data range
  245. definedNameRef := true
  246. dataRange := f.getDefinedNameRefTo(opts.DataRange, opts.pivotTableSheetName)
  247. if dataRange == "" {
  248. definedNameRef = false
  249. dataRange = opts.DataRange
  250. }
  251. dataSheet, coordinates, err := f.adjustRange(dataRange)
  252. if err != nil {
  253. return fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
  254. }
  255. // data range has been checked
  256. order, _ := f.getPivotFieldsOrder(opts)
  257. hCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
  258. vCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
  259. pc := xlsxPivotCacheDefinition{
  260. SaveData: false,
  261. RefreshOnLoad: true,
  262. CreatedVersion: pivotTableVersion,
  263. RefreshedVersion: pivotTableVersion,
  264. MinRefreshableVersion: pivotTableVersion,
  265. CacheSource: &xlsxCacheSource{
  266. Type: "worksheet",
  267. WorksheetSource: &xlsxWorksheetSource{
  268. Ref: hCell + ":" + vCell,
  269. Sheet: dataSheet,
  270. },
  271. },
  272. CacheFields: &xlsxCacheFields{},
  273. }
  274. if definedNameRef {
  275. pc.CacheSource.WorksheetSource = &xlsxWorksheetSource{Name: opts.DataRange}
  276. }
  277. for _, name := range order {
  278. rowOptions, rowOk := f.getPivotTableFieldOptions(name, opts.Rows)
  279. columnOptions, colOk := f.getPivotTableFieldOptions(name, opts.Columns)
  280. sharedItems := xlsxSharedItems{
  281. Count: 0,
  282. }
  283. s := xlsxString{}
  284. if (rowOk && !rowOptions.DefaultSubtotal) || (colOk && !columnOptions.DefaultSubtotal) {
  285. s = xlsxString{
  286. V: "",
  287. }
  288. sharedItems.Count++
  289. sharedItems.S = &s
  290. }
  291. pc.CacheFields.CacheField = append(pc.CacheFields.CacheField, &xlsxCacheField{
  292. Name: name,
  293. SharedItems: &sharedItems,
  294. })
  295. }
  296. pc.CacheFields.Count = len(pc.CacheFields.CacheField)
  297. pivotCache, err := xml.Marshal(pc)
  298. f.saveFileList(pivotCacheXML, pivotCache)
  299. return err
  300. }
  301. // addPivotTable provides a function to create a pivot table by given pivot
  302. // table ID and properties.
  303. func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, opts *PivotTableOptions) error {
  304. // validate pivot table range
  305. _, coordinates, err := f.adjustRange(opts.PivotTableRange)
  306. if err != nil {
  307. return fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
  308. }
  309. hCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
  310. vCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
  311. pivotTableStyle := func() string {
  312. if opts.PivotTableStyleName == "" {
  313. return "PivotStyleLight16"
  314. }
  315. return opts.PivotTableStyleName
  316. }
  317. pt := xlsxPivotTableDefinition{
  318. Name: fmt.Sprintf("Pivot Table%d", pivotTableID),
  319. CacheID: cacheID,
  320. RowGrandTotals: &opts.RowGrandTotals,
  321. ColGrandTotals: &opts.ColGrandTotals,
  322. UpdatedVersion: pivotTableVersion,
  323. MinRefreshableVersion: pivotTableVersion,
  324. ShowDrill: &opts.ShowDrill,
  325. UseAutoFormatting: &opts.UseAutoFormatting,
  326. PageOverThenDown: &opts.PageOverThenDown,
  327. MergeItem: &opts.MergeItem,
  328. CreatedVersion: pivotTableVersion,
  329. CompactData: &opts.CompactData,
  330. ShowError: &opts.ShowError,
  331. DataCaption: "Values",
  332. Location: &xlsxLocation{
  333. Ref: hCell + ":" + vCell,
  334. FirstDataCol: 1,
  335. FirstDataRow: 1,
  336. FirstHeaderRow: 1,
  337. },
  338. PivotFields: &xlsxPivotFields{},
  339. RowItems: &xlsxRowItems{
  340. Count: 1,
  341. I: []*xlsxI{
  342. {
  343. []*xlsxX{{}, {}},
  344. },
  345. },
  346. },
  347. ColItems: &xlsxColItems{
  348. Count: 1,
  349. I: []*xlsxI{{}},
  350. },
  351. PivotTableStyleInfo: &xlsxPivotTableStyleInfo{
  352. Name: pivotTableStyle(),
  353. ShowRowHeaders: opts.ShowRowHeaders,
  354. ShowColHeaders: opts.ShowColHeaders,
  355. ShowRowStripes: opts.ShowRowStripes,
  356. ShowColStripes: opts.ShowColStripes,
  357. ShowLastColumn: opts.ShowLastColumn,
  358. },
  359. }
  360. // pivot fields
  361. _ = f.addPivotFields(&pt, opts)
  362. // count pivot fields
  363. pt.PivotFields.Count = len(pt.PivotFields.PivotField)
  364. // data range has been checked
  365. _ = f.addPivotRowFields(&pt, opts)
  366. _ = f.addPivotColFields(&pt, opts)
  367. _ = f.addPivotPageFields(&pt, opts)
  368. _ = f.addPivotDataFields(&pt, opts)
  369. pivotTable, err := xml.Marshal(pt)
  370. f.saveFileList(pivotTableXML, pivotTable)
  371. return err
  372. }
  373. // addPivotRowFields provides a method to add row fields for pivot table by
  374. // given pivot table options.
  375. func (f *File) addPivotRowFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
  376. // row fields
  377. rowFieldsIndex, err := f.getPivotFieldsIndex(opts.Rows, opts)
  378. if err != nil {
  379. return err
  380. }
  381. for _, fieldIdx := range rowFieldsIndex {
  382. if pt.RowFields == nil {
  383. pt.RowFields = &xlsxRowFields{}
  384. }
  385. pt.RowFields.Field = append(pt.RowFields.Field, &xlsxField{
  386. X: fieldIdx,
  387. })
  388. }
  389. // count row fields
  390. if pt.RowFields != nil {
  391. pt.RowFields.Count = len(pt.RowFields.Field)
  392. }
  393. return err
  394. }
  395. // addPivotPageFields provides a method to add page fields for pivot table by
  396. // given pivot table options.
  397. func (f *File) addPivotPageFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
  398. // page fields
  399. pageFieldsIndex, err := f.getPivotFieldsIndex(opts.Filter, opts)
  400. if err != nil {
  401. return err
  402. }
  403. pageFieldsName := f.getPivotTableFieldsName(opts.Filter)
  404. for idx, pageField := range pageFieldsIndex {
  405. if pt.PageFields == nil {
  406. pt.PageFields = &xlsxPageFields{}
  407. }
  408. pt.PageFields.PageField = append(pt.PageFields.PageField, &xlsxPageField{
  409. Name: pageFieldsName[idx],
  410. Fld: pageField,
  411. })
  412. }
  413. // count page fields
  414. if pt.PageFields != nil {
  415. pt.PageFields.Count = len(pt.PageFields.PageField)
  416. }
  417. return err
  418. }
  419. // addPivotDataFields provides a method to add data fields for pivot table by
  420. // given pivot table options.
  421. func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
  422. // data fields
  423. dataFieldsIndex, err := f.getPivotFieldsIndex(opts.Data, opts)
  424. if err != nil {
  425. return err
  426. }
  427. dataFieldsSubtotals := f.getPivotTableFieldsSubtotal(opts.Data)
  428. dataFieldsName := f.getPivotTableFieldsName(opts.Data)
  429. for idx, dataField := range dataFieldsIndex {
  430. if pt.DataFields == nil {
  431. pt.DataFields = &xlsxDataFields{}
  432. }
  433. pt.DataFields.DataField = append(pt.DataFields.DataField, &xlsxDataField{
  434. Name: dataFieldsName[idx],
  435. Fld: dataField,
  436. Subtotal: dataFieldsSubtotals[idx],
  437. })
  438. }
  439. // count data fields
  440. if pt.DataFields != nil {
  441. pt.DataFields.Count = len(pt.DataFields.DataField)
  442. }
  443. return err
  444. }
  445. // inPivotTableField provides a method to check if an element is present in
  446. // pivot table fields list, and return the index of its location, otherwise
  447. // return -1.
  448. func inPivotTableField(a []PivotTableField, x string) int {
  449. for idx, n := range a {
  450. if x == n.Data {
  451. return idx
  452. }
  453. }
  454. return -1
  455. }
  456. // addPivotColFields create pivot column fields by given pivot table
  457. // definition and option.
  458. func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
  459. if len(opts.Columns) == 0 {
  460. if len(opts.Data) <= 1 {
  461. return nil
  462. }
  463. pt.ColFields = &xlsxColFields{}
  464. // in order to create pivot table in case there is no input from Columns
  465. pt.ColFields.Count = 1
  466. pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
  467. X: -2,
  468. })
  469. return nil
  470. }
  471. pt.ColFields = &xlsxColFields{}
  472. // col fields
  473. colFieldsIndex, err := f.getPivotFieldsIndex(opts.Columns, opts)
  474. if err != nil {
  475. return err
  476. }
  477. for _, fieldIdx := range colFieldsIndex {
  478. pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
  479. X: fieldIdx,
  480. })
  481. }
  482. // in order to create pivot in case there is many Columns and Data
  483. if len(opts.Data) > 1 {
  484. pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
  485. X: -2,
  486. })
  487. }
  488. // count col fields
  489. pt.ColFields.Count = len(pt.ColFields.Field)
  490. return err
  491. }
  492. // addPivotFields create pivot fields based on the column order of the first
  493. // row in the data region by given pivot table definition and option.
  494. func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
  495. order, err := f.getPivotFieldsOrder(opts)
  496. if err != nil {
  497. return err
  498. }
  499. x := 0
  500. for _, name := range order {
  501. if inPivotTableField(opts.Rows, name) != -1 {
  502. rowOptions, ok := f.getPivotTableFieldOptions(name, opts.Rows)
  503. var items []*xlsxItem
  504. if !ok || !rowOptions.DefaultSubtotal {
  505. items = append(items, &xlsxItem{X: &x})
  506. } else {
  507. items = append(items, &xlsxItem{T: "default"})
  508. }
  509. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
  510. Name: f.getPivotTableFieldName(name, opts.Rows),
  511. Axis: "axisRow",
  512. DataField: inPivotTableField(opts.Data, name) != -1,
  513. Compact: &rowOptions.Compact,
  514. Outline: &rowOptions.Outline,
  515. DefaultSubtotal: &rowOptions.DefaultSubtotal,
  516. Items: &xlsxItems{
  517. Count: len(items),
  518. Item: items,
  519. },
  520. })
  521. continue
  522. }
  523. if inPivotTableField(opts.Filter, name) != -1 {
  524. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
  525. Axis: "axisPage",
  526. DataField: inPivotTableField(opts.Data, name) != -1,
  527. Name: f.getPivotTableFieldName(name, opts.Columns),
  528. Items: &xlsxItems{
  529. Count: 1,
  530. Item: []*xlsxItem{
  531. {T: "default"},
  532. },
  533. },
  534. })
  535. continue
  536. }
  537. if inPivotTableField(opts.Columns, name) != -1 {
  538. columnOptions, ok := f.getPivotTableFieldOptions(name, opts.Columns)
  539. var items []*xlsxItem
  540. if !ok || !columnOptions.DefaultSubtotal {
  541. items = append(items, &xlsxItem{X: &x})
  542. } else {
  543. items = append(items, &xlsxItem{T: "default"})
  544. }
  545. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
  546. Name: f.getPivotTableFieldName(name, opts.Columns),
  547. Axis: "axisCol",
  548. DataField: inPivotTableField(opts.Data, name) != -1,
  549. Compact: &columnOptions.Compact,
  550. Outline: &columnOptions.Outline,
  551. DefaultSubtotal: &columnOptions.DefaultSubtotal,
  552. Items: &xlsxItems{
  553. Count: len(items),
  554. Item: items,
  555. },
  556. })
  557. continue
  558. }
  559. if inPivotTableField(opts.Data, name) != -1 {
  560. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
  561. DataField: true,
  562. })
  563. continue
  564. }
  565. pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{})
  566. }
  567. return err
  568. }
  569. // countPivotTables provides a function to get drawing files count storage in
  570. // the folder xl/pivotTables.
  571. func (f *File) countPivotTables() int {
  572. count := 0
  573. f.Pkg.Range(func(k, v interface{}) bool {
  574. if strings.Contains(k.(string), "xl/pivotTables/pivotTable") {
  575. count++
  576. }
  577. return true
  578. })
  579. return count
  580. }
  581. // countPivotCache provides a function to get drawing files count storage in
  582. // the folder xl/pivotCache.
  583. func (f *File) countPivotCache() int {
  584. count := 0
  585. f.Pkg.Range(func(k, v interface{}) bool {
  586. if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") {
  587. count++
  588. }
  589. return true
  590. })
  591. return count
  592. }
  593. // getPivotFieldsIndex convert the column of the first row in the data region
  594. // to a sequential index by given fields and pivot option.
  595. func (f *File) getPivotFieldsIndex(fields []PivotTableField, opts *PivotTableOptions) ([]int, error) {
  596. var pivotFieldsIndex []int
  597. orders, err := f.getPivotFieldsOrder(opts)
  598. if err != nil {
  599. return pivotFieldsIndex, err
  600. }
  601. for _, field := range fields {
  602. if pos := inStrSlice(orders, field.Data, true); pos != -1 {
  603. pivotFieldsIndex = append(pivotFieldsIndex, pos)
  604. }
  605. }
  606. return pivotFieldsIndex, nil
  607. }
  608. // getPivotTableFieldsSubtotal prepare fields subtotal by given pivot table fields.
  609. func (f *File) getPivotTableFieldsSubtotal(fields []PivotTableField) []string {
  610. field := make([]string, len(fields))
  611. enums := []string{"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp", "sum", "var", "varp"}
  612. inEnums := func(enums []string, val string) string {
  613. for _, enum := range enums {
  614. if strings.EqualFold(enum, val) {
  615. return enum
  616. }
  617. }
  618. return "sum"
  619. }
  620. for idx, fld := range fields {
  621. field[idx] = inEnums(enums, fld.Subtotal)
  622. }
  623. return field
  624. }
  625. // getPivotTableFieldsName prepare fields name list by given pivot table
  626. // fields.
  627. func (f *File) getPivotTableFieldsName(fields []PivotTableField) []string {
  628. field := make([]string, len(fields))
  629. for idx, fld := range fields {
  630. if len(fld.Name) > MaxFieldLength {
  631. field[idx] = fld.Name[:MaxFieldLength]
  632. continue
  633. }
  634. field[idx] = fld.Name
  635. }
  636. return field
  637. }
  638. // getPivotTableFieldName prepare field name by given pivot table fields.
  639. func (f *File) getPivotTableFieldName(name string, fields []PivotTableField) string {
  640. fieldsName := f.getPivotTableFieldsName(fields)
  641. for idx, field := range fields {
  642. if field.Data == name {
  643. return fieldsName[idx]
  644. }
  645. }
  646. return ""
  647. }
  648. // getPivotTableFieldOptions return options for specific field by given field name.
  649. func (f *File) getPivotTableFieldOptions(name string, fields []PivotTableField) (options PivotTableField, ok bool) {
  650. for _, field := range fields {
  651. if field.Data == name {
  652. options, ok = field, true
  653. return
  654. }
  655. }
  656. return
  657. }
  658. // addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml.
  659. func (f *File) addWorkbookPivotCache(RID int) int {
  660. wb, _ := f.workbookReader()
  661. if wb.PivotCaches == nil {
  662. wb.PivotCaches = &xlsxPivotCaches{}
  663. }
  664. cacheID := 1
  665. for _, pivotCache := range wb.PivotCaches.PivotCache {
  666. if pivotCache.CacheID > cacheID {
  667. cacheID = pivotCache.CacheID
  668. }
  669. }
  670. cacheID++
  671. wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache, xlsxPivotCache{
  672. CacheID: cacheID,
  673. RID: fmt.Sprintf("rId%d", RID),
  674. })
  675. return cacheID
  676. }