Merge branch 'master' into fix/cell_lock

This commit is contained in:
xuri 2020-05-22 16:53:46 +08:00 committed by GitHub
commit ec14de32f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 15363 additions and 3456 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
~$*.xlsx
test/Test*.xlsx
*.out
*.test
*.test
.idea

View File

@ -4,18 +4,17 @@ install:
- go get -d -t -v ./... && go build -v ./...
go:
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- 1.14.x
os:
- linux
- osx
env:
matrix:
jobs:
- GOARCH=amd64
- GOARCH=386

View File

@ -40,7 +40,7 @@ Project maintainers who do not follow or enforce the Code of Conduct in good fai
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
[homepage]: https://www.contributor-covenant.org
[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct

View File

@ -234,7 +234,9 @@ By making a contribution to this project, I certify that:
Then you just add a line to every git commit message:
Signed-off-by: Ri Xu https://xuri.me
```text
Signed-off-by: Ri Xu https://xuri.me
```
Use your real name (sorry, no pseudonyms or anonymous contributions.)
@ -460,4 +462,4 @@ Do not use package math/rand to generate keys, even
throwaway ones. Unseeded, the generator is completely predictable.
Seeded with time.Nanoseconds(), there are just a few bits of entropy.
Instead, use crypto/rand's Reader, and if you need text, print to
hexadecimal or base64
hexadecimal or base64.

View File

@ -1,7 +1,6 @@
BSD 3-Clause License
Copyright (c) 2016-2019, 360 Enterprise Security Group, Endpoint Security, Inc.
Copyright (c) 2011-2017, Geoffrey J. Teale (complying with the tealeg/xlsx license)
Copyright (c) 2016-2020 The excelize Authors.
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -1,10 +1,10 @@
<p align="center"><img width="650" src="./excelize.png" alt="Excelize logo"></p>
<p align="center"><img width="650" src="./excelize.svg" alt="Excelize logo"></p>
<p align="center">
<a href="https://travis-ci.org/360EntSecGroup-Skylar/excelize"><img src="https://travis-ci.org/360EntSecGroup-Skylar/excelize.svg?branch=master" alt="Build Status"></a>
<a href="https://codecov.io/gh/360EntSecGroup-Skylar/excelize"><img src="https://codecov.io/gh/360EntSecGroup-Skylar/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
<a href="https://goreportcard.com/report/github.com/360EntSecGroup-Skylar/excelize"><img src="https://goreportcard.com/badge/github.com/360EntSecGroup-Skylar/excelize" alt="Go Report Card"></a>
<a href="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize"><img src="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize?status.svg" alt="GoDoc"></a>
<a href="https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
<a href="https://www.paypal.me/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
</p>
@ -13,8 +13,7 @@
## Introduction
Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLSX files. Supports reading and writing XLSX file generated by Microsoft Excel&trade; 2007 and later.
Supports saving a file without losing original charts of XLSX. This library needs Go version 1.8 or later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize) and [docs reference](https://xuri.me/excelize/).
Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLSX / XLSM / XLTM files. Supports reading and writing spreadsheet documents generated by Microsoft Excel&trade; 2007 and later. Supports complex components by high compatibility, and provided streaming API for generating or reading data from a worksheet with huge amounts of data. This library needs Go version 1.10 or later. The full API docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc) and [docs reference](https://xuri.me/excelize/).
## Basic Usage
@ -24,6 +23,12 @@ Supports saving a file without losing original charts of XLSX. This library need
go get github.com/360EntSecGroup-Skylar/excelize
```
- If your package management with [Go Modules](https://blog.golang.org/using-go-modules), please install with following command.
```bash
go get github.com/360EntSecGroup-Skylar/excelize/v2
```
### Create XLSX file
Here is a minimal example usage that will create XLSX file.
@ -47,8 +52,7 @@ func main() {
// Set active sheet of the workbook.
f.SetActiveSheet(index)
// Save xlsx file by the given path.
err := f.SaveAs("./Book1.xlsx")
if err != nil {
if err := f.SaveAs("Book1.xlsx"); err != nil {
fmt.Println(err)
}
}
@ -68,7 +72,7 @@ import (
)
func main() {
f, err := excelize.OpenFile("./Book1.xlsx")
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
fmt.Println(err)
return
@ -116,14 +120,12 @@ func main() {
for k, v := range values {
f.SetCellValue("Sheet1", k, v)
}
err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
if err != nil {
if err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`); err != nil {
fmt.Println(err)
return
}
// Save xlsx file by the given path.
err = f.SaveAs("./Book1.xlsx")
if err != nil {
if err := f.SaveAs("Book1.xlsx"); err != nil {
fmt.Println(err)
}
}
@ -144,29 +146,25 @@ import (
)
func main() {
f, err := excelize.OpenFile("./Book1.xlsx")
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
// Insert a picture.
err = f.AddPicture("Sheet1", "A2", "./image1.png", "")
if err != nil {
if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil {
fmt.Println(err)
}
// Insert a picture to worksheet with scaling.
err = f.AddPicture("Sheet1", "D2", "./image2.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`)
if err != nil {
if err := f.AddPicture("Sheet1", "D2", "image.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil {
fmt.Println(err)
}
// Insert a picture offset in the cell with printing support.
err = f.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`)
if err != nil {
if err := f.AddPicture("Sheet1", "H2", "image.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`); err != nil {
fmt.Println(err)
}
// Save the xlsx file with the origin path.
err = f.Save()
if err != nil {
if err = f.Save(); err != nil {
fmt.Println(err)
}
}
@ -182,6 +180,4 @@ This program is under the terms of the BSD 3-Clause License. See [https://openso
The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation.
Some struct of XML originally by [tealeg/xlsx](https://github.com/tealeg/xlsx). Licensed under the [BSD 3-Clause License](https://github.com/tealeg/xlsx/blob/master/LICENSE).
gopher.{ai,svg,png} was created by [Takuya Ueda](https://twitter.com/tenntenn). Licensed under the [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/).

View File

@ -1,10 +1,10 @@
<p align="center"><img width="650" src="./excelize.png" alt="Excelize logo"></p>
<p align="center"><img width="650" src="./excelize.svg" alt="Excelize logo"></p>
<p align="center">
<a href="https://travis-ci.org/360EntSecGroup-Skylar/excelize"><img src="https://travis-ci.org/360EntSecGroup-Skylar/excelize.svg?branch=master" alt="Build Status"></a>
<a href="https://codecov.io/gh/360EntSecGroup-Skylar/excelize"><img src="https://codecov.io/gh/360EntSecGroup-Skylar/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
<a href="https://goreportcard.com/report/github.com/360EntSecGroup-Skylar/excelize"><img src="https://goreportcard.com/badge/github.com/360EntSecGroup-Skylar/excelize" alt="Go Report Card"></a>
<a href="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize"><img src="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize?status.svg" alt="GoDoc"></a>
<a href="https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
<a href="https://www.paypal.me/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
</p>
@ -13,7 +13,7 @@
## 简介
Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 ECMA-376 Office OpenXML 标准。可以使用它来读取、写入由 Microsoft Excel&trade; 2007 及以上版本创建的 XLSX 文档。相比较其他的开源类库Excelize 支持写入原本带有图片(表)、透视表和切片器等复杂样式的文档,还支持向 Excel 文档中插入图片与图表,并且在保存后不会丢失文档原有样式,可以应用于各类报表系统中。使用本类库要求使用的 Go 语言为 1.8 或更高版本,完整的 API 使用文档请访问 [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize) 或查看 [参考文档](https://xuri.me/excelize/)。
Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel&trade; 2007 及以上版本创建的电子表格文档。支持 XLSX / XLSM / XLTM 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写 API用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.10 或更高版本,完整的 API 使用文档请访问 [go.dev](https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc) 或查看 [参考文档](https://xuri.me/excelize/)。
## 快速上手
@ -23,6 +23,12 @@ Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 E
go get github.com/360EntSecGroup-Skylar/excelize
```
- 如果您使用 [Go Modules](https://blog.golang.org/using-go-modules) 管理软件包,请使用下面的命令来安装最新版本。
```bash
go get github.com/360EntSecGroup-Skylar/excelize/v2
```
### 创建 Excel 文档
下面是一个创建 Excel 文档的简单例子:
@ -46,8 +52,7 @@ func main() {
// 设置工作簿的默认工作表
f.SetActiveSheet(index)
// 根据指定路径保存文件
err := f.SaveAs("./Book1.xlsx")
if err != nil {
if err := f.SaveAs("Book1.xlsx"); err != nil {
fmt.Println(err)
}
}
@ -67,7 +72,7 @@ import (
)
func main() {
f, err := excelize.OpenFile("./Book1.xlsx")
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
fmt.Println(err)
return
@ -115,18 +120,15 @@ func main() {
for k, v := range values {
f.SetCellValue("Sheet1", k, v)
}
err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
if err != nil {
if err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`); err != nil {
fmt.Println(err)
return
}
// 根据指定路径保存文件
err = f.SaveAs("./Book1.xlsx")
if err != nil {
if err := f.SaveAs("Book1.xlsx"); err != nil {
fmt.Println(err)
}
}
```
### 向 Excel 文档中插入图片
@ -144,29 +146,25 @@ import (
)
func main() {
f, err := excelize.OpenFile("./Book1.xlsx")
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
// 插入图片
err = f.AddPicture("Sheet1", "A2", "./image1.png", "")
if err != nil {
if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil {
fmt.Println(err)
}
// 在工作表中插入图片,并设置图片的缩放比例
err = f.AddPicture("Sheet1", "D2", "./image2.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`)
if err != nil {
if err := f.AddPicture("Sheet1", "D2", "image.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil {
fmt.Println(err)
}
// 在工作表中插入图片,并设置图片的打印属性
err = f.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`)
if err != nil {
if err := f.AddPicture("Sheet1", "H2", "image.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`); err != nil {
fmt.Println(err)
}
// 保存文件
err = f.Save()
if err != nil {
if err = f.Save(); err != nil {
fmt.Println(err)
}
}
@ -182,6 +180,4 @@ func main() {
Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。
本类库中部分 XML 结构体的定义参考了开源项目:[tealeg/xlsx](https://github.com/tealeg/xlsx),遵循 [BSD 3-Clause License](https://github.com/tealeg/xlsx/blob/master/LICENSE) 开源许可协议。
gopher.{ai,svg,png} 由 [Takuya Ueda](https://twitter.com/tenntenn) 创作,遵循 [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/) 创作共用授权条款。

9
SECURITY.md Normal file
View File

@ -0,0 +1,9 @@
# Security Policy
## Supported Versions
We will dive into any security-related issue as long as your Excelize version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information.
## Reporting a Vulnerability
Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you!

233
adjust.go
View File

@ -1,15 +1,18 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "strings"
import (
"errors"
"strings"
)
type adjustDirection bool
@ -27,8 +30,7 @@ const (
// row: Index number of the row we're inserting/deleting before
// offset: Number of rows/column to insert/delete negative values indicate deletion
//
// TODO: adjustCalcChain, adjustPageBreaks, adjustComments,
// adjustDataValidations, adjustProtectedCells
// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
//
func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
xlsx, err := f.workSheetReader(sheet)
@ -47,9 +49,16 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
if err = f.adjustAutoFilter(xlsx, dir, num, offset); err != nil {
return err
}
if err = f.adjustCalcChain(dir, num, offset); err != nil {
return err
}
checkSheet(xlsx)
checkRow(xlsx)
_ = checkRow(xlsx)
if xlsx.MergeCells != nil && len(xlsx.MergeCells.Cells) == 0 {
xlsx.MergeCells = nil
}
return nil
}
@ -71,9 +80,10 @@ func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, col, offset int) {
// adjustRowDimensions provides a function to update row dimensions when
// inserting or deleting rows or columns.
func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, row, offset int) {
for i, r := range xlsx.SheetData.Row {
for i := range xlsx.SheetData.Row {
r := &xlsx.SheetData.Row[i]
if newRow := r.R + offset; r.R >= row && newRow > 0 {
f.ajustSingleRowDimensions(&xlsx.SheetData.Row[i], newRow)
f.ajustSingleRowDimensions(r, newRow)
}
}
}
@ -139,48 +149,103 @@ func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, o
return nil
}
rng := strings.Split(xlsx.AutoFilter.Ref, ":")
firstCell := rng[0]
lastCell := rng[1]
firstCol, firstRow, err := CellNameToCoordinates(firstCell)
coordinates, err := f.areaRefToCoordinates(xlsx.AutoFilter.Ref)
if err != nil {
return err
}
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
lastCol, lastRow, err := CellNameToCoordinates(lastCell)
if err != nil {
return err
}
if (dir == rows && firstRow == num && offset < 0) || (dir == columns && firstCol == num && lastCol == num) {
if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) {
xlsx.AutoFilter = nil
for rowIdx := range xlsx.SheetData.Row {
rowData := &xlsx.SheetData.Row[rowIdx]
if rowData.R > firstRow && rowData.R <= lastRow {
if rowData.R > y1 && rowData.R <= y2 {
rowData.Hidden = false
}
}
return nil
}
coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if xlsx.AutoFilter.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil {
return err
}
return nil
}
// adjustAutoFilterHelper provides a function for adjusting auto filter to
// compare and calculate cell axis by the given adjust direction, operation
// axis and offset.
func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int {
if dir == rows {
if firstRow >= num {
firstCell, _ = CoordinatesToCellName(firstCol, firstRow+offset)
if coordinates[1] >= num {
coordinates[1] += offset
}
if lastRow >= num {
lastCell, _ = CoordinatesToCellName(lastCol, lastRow+offset)
if coordinates[3] >= num {
coordinates[3] += offset
}
} else {
if lastCol >= num {
lastCell, _ = CoordinatesToCellName(lastCol+offset, lastRow)
if coordinates[2] >= num {
coordinates[2] += offset
}
}
return coordinates
}
xlsx.AutoFilter.Ref = firstCell + ":" + lastCell
// areaRefToCoordinates provides a function to convert area reference to a
// pair of coordinates.
func (f *File) areaRefToCoordinates(ref string) ([]int, error) {
rng := strings.Split(ref, ":")
return areaRangeToCoordinates(rng[0], rng[1])
}
// areaRangeToCoordinates provides a function to convert cell range to a
// pair of coordinates.
func areaRangeToCoordinates(firstCell, lastCell string) ([]int, error) {
coordinates := make([]int, 4)
var err error
coordinates[0], coordinates[1], err = CellNameToCoordinates(firstCell)
if err != nil {
return coordinates, err
}
coordinates[2], coordinates[3], err = CellNameToCoordinates(lastCell)
return coordinates, err
}
// sortCoordinates provides a function to correct the coordinate area, such
// correct C1:B3 to B1:C3.
func sortCoordinates(coordinates []int) error {
if len(coordinates) != 4 {
return errors.New("coordinates length must be 4")
}
if coordinates[2] < coordinates[0] {
coordinates[2], coordinates[0] = coordinates[0], coordinates[2]
}
if coordinates[3] < coordinates[1] {
coordinates[3], coordinates[1] = coordinates[1], coordinates[3]
}
return nil
}
// coordinatesToAreaRef provides a function to convert a pair of coordinates
// to area reference.
func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) {
if len(coordinates) != 4 {
return "", errors.New("coordinates length must be 4")
}
firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1])
if err != nil {
return "", err
}
lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3])
if err != nil {
return "", err
}
return firstCell + ":" + lastCell, err
}
// adjustMergeCells provides a function to update merged cells when inserting
// or deleting rows or columns.
func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) error {
@ -188,58 +253,82 @@ func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, o
return nil
}
for i, areaData := range xlsx.MergeCells.Cells {
rng := strings.Split(areaData.Ref, ":")
firstCell := rng[0]
lastCell := rng[1]
firstCol, firstRow, err := CellNameToCoordinates(firstCell)
for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
areaData := xlsx.MergeCells.Cells[i]
coordinates, err := f.areaRefToCoordinates(areaData.Ref)
if err != nil {
return err
}
lastCol, lastRow, err := CellNameToCoordinates(lastCell)
if err != nil {
return err
}
adjust := func(v int) int {
if v >= num {
v += offset
if v < 1 {
return 1
}
return v
}
return v
}
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if dir == rows {
firstRow = adjust(firstRow)
lastRow = adjust(lastRow)
} else {
firstCol = adjust(firstCol)
lastCol = adjust(lastCol)
}
if firstCol == lastCol && firstRow == lastRow {
if len(xlsx.MergeCells.Cells) > 1 {
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
xlsx.MergeCells.Count = len(xlsx.MergeCells.Cells)
} else {
xlsx.MergeCells = nil
if y1 == num && y2 == num && offset < 0 {
f.deleteMergeCell(xlsx, i)
i--
}
y1 = f.adjustMergeCellsHelper(y1, num, offset)
y2 = f.adjustMergeCellsHelper(y2, num, offset)
} else {
if x1 == num && x2 == num && offset < 0 {
f.deleteMergeCell(xlsx, i)
i--
}
x1 = f.adjustMergeCellsHelper(x1, num, offset)
x2 = f.adjustMergeCellsHelper(x2, num, offset)
}
if firstCell, err = CoordinatesToCellName(firstCol, firstRow); err != nil {
if x1 == x2 && y1 == y2 {
f.deleteMergeCell(xlsx, i)
i--
}
if areaData.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil {
return err
}
if lastCell, err = CoordinatesToCellName(lastCol, lastRow); err != nil {
return err
}
areaData.Ref = firstCell + ":" + lastCell
}
return nil
}
// adjustMergeCellsHelper provides a function for adjusting merge cells to
// compare and calculate cell axis by the given pivot, operation axis and
// offset.
func (f *File) adjustMergeCellsHelper(pivot, num, offset int) int {
if pivot >= num {
pivot += offset
if pivot < 1 {
return 1
}
return pivot
}
return pivot
}
// deleteMergeCell provides a function to delete merged cell by given index.
func (f *File) deleteMergeCell(sheet *xlsxWorksheet, idx int) {
if len(sheet.MergeCells.Cells) > idx {
sheet.MergeCells.Cells = append(sheet.MergeCells.Cells[:idx], sheet.MergeCells.Cells[idx+1:]...)
sheet.MergeCells.Count = len(sheet.MergeCells.Cells)
}
}
// adjustCalcChain provides a function to update the calculation chain when
// inserting or deleting rows or columns.
func (f *File) adjustCalcChain(dir adjustDirection, num, offset int) error {
if f.CalcChain == nil {
return nil
}
for index, c := range f.CalcChain.C {
colNum, rowNum, err := CellNameToCoordinates(c.R)
if err != nil {
return err
}
if dir == rows && num <= rowNum {
if newRow := rowNum + offset; newRow > 0 {
f.CalcChain.C[index].R, _ = CoordinatesToCellName(colNum, newRow)
}
}
if dir == columns && num <= colNum {
if newCol := colNum + offset; newCol > 0 {
f.CalcChain.C[index].R, _ = CoordinatesToCellName(newCol, rowNum)
}
}
}
return nil
}

View File

@ -27,6 +27,24 @@ func TestAdjustMergeCells(t *testing.T) {
},
},
}, rows, 0, 0), `cannot convert cell "B" to coordinates: invalid cell name "B"`)
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:B1",
},
},
},
}, rows, 1, -1))
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:A2",
},
},
},
}, columns, 1, -1))
}
func TestAdjustAutoFilter(t *testing.T) {
@ -67,3 +85,36 @@ func TestAdjustHelper(t *testing.T) {
// testing adjustHelper on not exists worksheet.
assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN is not exist")
}
func TestAdjustCalcChain(t *testing.T) {
f := NewFile()
f.CalcChain = &xlsxCalcChain{
C: []xlsxCalcChainC{
{R: "B2"},
},
}
assert.NoError(t, f.InsertCol("Sheet1", "A"))
assert.NoError(t, f.InsertRow("Sheet1", 1))
f.CalcChain.C[0].R = "invalid coordinates"
assert.EqualError(t, f.InsertCol("Sheet1", "A"), `cannot convert cell "invalid coordinates" to coordinates: invalid cell name "invalid coordinates"`)
f.CalcChain = nil
assert.NoError(t, f.InsertCol("Sheet1", "A"))
}
func TestCoordinatesToAreaRef(t *testing.T) {
f := NewFile()
_, err := f.coordinatesToAreaRef([]int{})
assert.EqualError(t, err, "coordinates length must be 4")
_, err = f.coordinatesToAreaRef([]int{1, -1, 1, 1})
assert.EqualError(t, err, "invalid cell coordinates [1, -1]")
_, err = f.coordinatesToAreaRef([]int{1, 1, 1, -1})
assert.EqualError(t, err, "invalid cell coordinates [1, -1]")
ref, err := f.coordinatesToAreaRef([]int{1, 1, 1, 1})
assert.NoError(t, err)
assert.EqualValues(t, ref, "A1:A1")
}
func TestSortCoordinates(t *testing.T) {
assert.EqualError(t, sortCoordinates(make([]int, 3)), "coordinates length must be 4")
}

2630
calc.go Normal file

File diff suppressed because it is too large Load Diff

714
calc_test.go Normal file
View File

@ -0,0 +1,714 @@
package excelize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCalcCellValue(t *testing.T) {
prepareData := func() *File {
f := NewFile()
f.SetCellValue("Sheet1", "A1", 1)
f.SetCellValue("Sheet1", "A2", 2)
f.SetCellValue("Sheet1", "A3", 3)
f.SetCellValue("Sheet1", "A4", 0)
f.SetCellValue("Sheet1", "B1", 4)
f.SetCellValue("Sheet1", "B2", 5)
return f
}
mathCalc := map[string]string{
// ABS
"=ABS(-1)": "1",
"=ABS(-6.5)": "6.5",
"=ABS(6.5)": "6.5",
"=ABS(0)": "0",
"=ABS(2-4.5)": "2.5",
// ACOS
"=ACOS(-1)": "3.141592653589793",
"=ACOS(0)": "1.5707963267948966",
// ACOSH
"=ACOSH(1)": "0",
"=ACOSH(2.5)": "1.566799236972411",
"=ACOSH(5)": "2.2924316695611777",
// ACOT
"=_xlfn.ACOT(1)": "0.7853981633974483",
"=_xlfn.ACOT(-2)": "2.677945044588987",
"=_xlfn.ACOT(0)": "1.5707963267948966",
// ACOTH
"=_xlfn.ACOTH(-5)": "-0.2027325540540822",
"=_xlfn.ACOTH(1.1)": "1.5222612188617113",
"=_xlfn.ACOTH(2)": "0.5493061443340548",
// ARABIC
`=_xlfn.ARABIC("IV")`: "4",
`=_xlfn.ARABIC("-IV")`: "-4",
`=_xlfn.ARABIC("MCXX")`: "1120",
`=_xlfn.ARABIC("")`: "0",
// ASIN
"=ASIN(-1)": "-1.5707963267948966",
"=ASIN(0)": "0",
// ASINH
"=ASINH(0)": "0",
"=ASINH(-0.5)": "-0.48121182505960347",
"=ASINH(2)": "1.4436354751788103",
// ATAN
"=ATAN(-1)": "-0.7853981633974483",
"=ATAN(0)": "0",
"=ATAN(1)": "0.7853981633974483",
// ATANH
"=ATANH(-0.8)": "-1.0986122886681098",
"=ATANH(0)": "0",
"=ATANH(0.5)": "0.5493061443340548",
// ATAN2
"=ATAN2(1,1)": "0.7853981633974483",
"=ATAN2(1,-1)": "-0.7853981633974483",
"=ATAN2(4,0)": "0",
// BASE
"=BASE(12,2)": "1100",
"=BASE(12,2,8)": "00001100",
"=BASE(100000,16)": "186A0",
// CEILING
"=CEILING(22.25,0.1)": "22.3",
"=CEILING(22.25,0.5)": "22.5",
"=CEILING(22.25,1)": "23",
"=CEILING(22.25,10)": "30",
"=CEILING(22.25,20)": "40",
"=CEILING(-22.25,-0.1)": "-22.3",
"=CEILING(-22.25,-1)": "-23",
"=CEILING(-22.25,-5)": "-25",
"=CEILING(22.25)": "23",
// _xlfn.CEILING.MATH
"=_xlfn.CEILING.MATH(15.25,1)": "16",
"=_xlfn.CEILING.MATH(15.25,0.1)": "15.3",
"=_xlfn.CEILING.MATH(15.25,5)": "20",
"=_xlfn.CEILING.MATH(-15.25,1)": "-15",
"=_xlfn.CEILING.MATH(-15.25,1,1)": "-15", // should be 16
"=_xlfn.CEILING.MATH(-15.25,10)": "-10",
"=_xlfn.CEILING.MATH(-15.25)": "-15",
"=_xlfn.CEILING.MATH(-15.25,-5,-1)": "-10",
// _xlfn.CEILING.PRECISE
"=_xlfn.CEILING.PRECISE(22.25,0.1)": "22.3",
"=_xlfn.CEILING.PRECISE(22.25,0.5)": "22.5",
"=_xlfn.CEILING.PRECISE(22.25,1)": "23",
"=_xlfn.CEILING.PRECISE(22.25)": "23",
"=_xlfn.CEILING.PRECISE(22.25,10)": "30",
"=_xlfn.CEILING.PRECISE(22.25,0)": "0",
"=_xlfn.CEILING.PRECISE(-22.25,1)": "-22",
"=_xlfn.CEILING.PRECISE(-22.25,-1)": "-22",
"=_xlfn.CEILING.PRECISE(-22.25,5)": "-20",
// COMBIN
"=COMBIN(6,1)": "6",
"=COMBIN(6,2)": "15",
"=COMBIN(6,3)": "20",
"=COMBIN(6,4)": "15",
"=COMBIN(6,5)": "6",
"=COMBIN(6,6)": "1",
"=COMBIN(0,0)": "1",
// _xlfn.COMBINA
"=_xlfn.COMBINA(6,1)": "6",
"=_xlfn.COMBINA(6,2)": "21",
"=_xlfn.COMBINA(6,3)": "56",
"=_xlfn.COMBINA(6,4)": "126",
"=_xlfn.COMBINA(6,5)": "252",
"=_xlfn.COMBINA(6,6)": "462",
"=_xlfn.COMBINA(0,0)": "0",
// COS
"=COS(0.785398163)": "0.707106781467586",
"=COS(0)": "1",
// COSH
"=COSH(0)": "1",
"=COSH(0.5)": "1.1276259652063807",
"=COSH(-2)": "3.7621956910836314",
// _xlfn.COT
"=_xlfn.COT(0.785398163397448)": "0.9999999999999992",
// _xlfn.COTH
"=_xlfn.COTH(-3.14159265358979)": "-0.9962720762207499",
// _xlfn.CSC
"=_xlfn.CSC(-6)": "3.5788995472544056",
"=_xlfn.CSC(1.5707963267949)": "1",
// _xlfn.CSCH
"=_xlfn.CSCH(-3.14159265358979)": "-0.08658953753004724",
// _xlfn.DECIMAL
`=_xlfn.DECIMAL("1100",2)`: "12",
`=_xlfn.DECIMAL("186A0",16)`: "100000",
`=_xlfn.DECIMAL("31L0",32)`: "100000",
`=_xlfn.DECIMAL("70122",8)`: "28754",
`=_xlfn.DECIMAL("0x70122",8)`: "28754",
// DEGREES
"=DEGREES(1)": "57.29577951308232",
"=DEGREES(2.5)": "143.2394487827058",
// EVEN
"=EVEN(23)": "24",
"=EVEN(2.22)": "4",
"=EVEN(0)": "0",
"=EVEN(-0.3)": "-2",
"=EVEN(-11)": "-12",
"=EVEN(-4)": "-4",
// EXP
"=EXP(100)": "2.6881171418161356E+43",
"=EXP(0.1)": "1.1051709180756477",
"=EXP(0)": "1",
"=EXP(-5)": "0.006737946999085467",
// FACT
"=FACT(3)": "6",
"=FACT(6)": "720",
"=FACT(10)": "3.6288E+06",
// FACTDOUBLE
"=FACTDOUBLE(5)": "15",
"=FACTDOUBLE(8)": "384",
"=FACTDOUBLE(13)": "135135",
// FLOOR
"=FLOOR(26.75,0.1)": "26.700000000000003",
"=FLOOR(26.75,0.5)": "26.5",
"=FLOOR(26.75,1)": "26",
"=FLOOR(26.75,10)": "20",
"=FLOOR(26.75,20)": "20",
"=FLOOR(-26.75,-0.1)": "-26.700000000000003",
"=FLOOR(-26.75,-1)": "-26",
"=FLOOR(-26.75,-5)": "-25",
// _xlfn.FLOOR.MATH
"=_xlfn.FLOOR.MATH(58.55)": "58",
"=_xlfn.FLOOR.MATH(58.55,0.1)": "58.5",
"=_xlfn.FLOOR.MATH(58.55,5)": "55",
"=_xlfn.FLOOR.MATH(58.55,1,1)": "58",
"=_xlfn.FLOOR.MATH(-58.55,1)": "-59",
"=_xlfn.FLOOR.MATH(-58.55,1,-1)": "-58",
"=_xlfn.FLOOR.MATH(-58.55,1,1)": "-59", // should be -58
"=_xlfn.FLOOR.MATH(-58.55,10)": "-60",
// _xlfn.FLOOR.PRECISE
"=_xlfn.FLOOR.PRECISE(26.75,0.1)": "26.700000000000003",
"=_xlfn.FLOOR.PRECISE(26.75,0.5)": "26.5",
"=_xlfn.FLOOR.PRECISE(26.75,1)": "26",
"=_xlfn.FLOOR.PRECISE(26.75)": "26",
"=_xlfn.FLOOR.PRECISE(26.75,10)": "20",
"=_xlfn.FLOOR.PRECISE(26.75,0)": "0",
"=_xlfn.FLOOR.PRECISE(-26.75,1)": "-27",
"=_xlfn.FLOOR.PRECISE(-26.75,-1)": "-27",
"=_xlfn.FLOOR.PRECISE(-26.75,-5)": "-30",
// GCD
"=GCD(0)": "0",
`=GCD("",1)`: "1",
"=GCD(1,0)": "1",
"=GCD(1,5)": "1",
"=GCD(15,10,25)": "5",
"=GCD(0,8,12)": "4",
"=GCD(7,2)": "1",
// INT
"=INT(100.9)": "100",
"=INT(5.22)": "5",
"=INT(5.99)": "5",
"=INT(-6.1)": "-7",
"=INT(-100.9)": "-101",
// ISO.CEILING
"=ISO.CEILING(22.25)": "23",
"=ISO.CEILING(22.25,1)": "23",
"=ISO.CEILING(22.25,0.1)": "22.3",
"=ISO.CEILING(22.25,10)": "30",
"=ISO.CEILING(-22.25,1)": "-22",
"=ISO.CEILING(-22.25,0.1)": "-22.200000000000003",
"=ISO.CEILING(-22.25,5)": "-20",
"=ISO.CEILING(-22.25,0)": "0",
// LCM
"=LCM(1,5)": "5",
"=LCM(15,10,25)": "150",
"=LCM(1,8,12)": "24",
"=LCM(7,2)": "14",
"=LCM(7)": "7",
`=LCM("",1)`: "1",
`=LCM(0,0)`: "0",
// LN
"=LN(1)": "0",
"=LN(100)": "4.605170185988092",
"=LN(0.5)": "-0.6931471805599453",
// LOG
"=LOG(64,2)": "6",
"=LOG(100)": "2",
"=LOG(4,0.5)": "-2",
"=LOG(500)": "2.6989700043360183",
// LOG10
"=LOG10(100)": "2",
"=LOG10(1000)": "3",
"=LOG10(0.001)": "-3",
"=LOG10(25)": "1.3979400086720375",
// MOD
"=MOD(6,4)": "2",
"=MOD(6,3)": "0",
"=MOD(6,2.5)": "1",
"=MOD(6,1.333)": "0.6680000000000001",
"=MOD(-10.23,1)": "0.7699999999999996",
// MROUND
"=MROUND(333.7,0.5)": "333.5",
"=MROUND(333.8,1)": "334",
"=MROUND(333.3,2)": "334",
"=MROUND(555.3,400)": "400",
"=MROUND(555,1000)": "1000",
"=MROUND(-555.7,-1)": "-556",
"=MROUND(-555.4,-1)": "-555",
"=MROUND(-1555,-1000)": "-2000",
// MULTINOMIAL
"=MULTINOMIAL(3,1,2,5)": "27720",
`=MULTINOMIAL("",3,1,2,5)`: "27720",
// _xlfn.MUNIT
"=_xlfn.MUNIT(4)": "", // not support currently
// ODD
"=ODD(22)": "23",
"=ODD(1.22)": "3",
"=ODD(1.22+4)": "7",
"=ODD(0)": "1",
"=ODD(-1.3)": "-3",
"=ODD(-10)": "-11",
"=ODD(-3)": "-3",
// PI
"=PI()": "3.141592653589793",
// POWER
"=POWER(4,2)": "16",
// PRODUCT
"=PRODUCT(3,6)": "18",
`=PRODUCT("",3,6)`: "18",
// QUOTIENT
"=QUOTIENT(5,2)": "2",
"=QUOTIENT(4.5,3.1)": "1",
"=QUOTIENT(-10,3)": "-3",
// RADIANS
"=RADIANS(50)": "0.8726646259971648",
"=RADIANS(-180)": "-3.141592653589793",
"=RADIANS(180)": "3.141592653589793",
"=RADIANS(360)": "6.283185307179586",
// ROMAN
"=ROMAN(499,0)": "CDXCIX",
"=ROMAN(1999,0)": "MCMXCIX",
"=ROMAN(1999,1)": "MLMVLIV",
"=ROMAN(1999,2)": "MXMIX",
"=ROMAN(1999,3)": "MVMIV",
"=ROMAN(1999,4)": "MIM",
"=ROMAN(1999,-1)": "MCMXCIX",
"=ROMAN(1999,5)": "MIM",
// ROUND
"=ROUND(100.319,1)": "100.30000000000001",
"=ROUND(5.28,1)": "5.300000000000001",
"=ROUND(5.9999,3)": "6.000000000000002",
"=ROUND(99.5,0)": "100",
"=ROUND(-6.3,0)": "-6",
"=ROUND(-100.5,0)": "-101",
"=ROUND(-22.45,1)": "-22.5",
"=ROUND(999,-1)": "1000",
"=ROUND(991,-1)": "990",
// ROUNDDOWN
"=ROUNDDOWN(99.999,1)": "99.9",
"=ROUNDDOWN(99.999,2)": "99.99000000000002",
"=ROUNDDOWN(99.999,0)": "99",
"=ROUNDDOWN(99.999,-1)": "90",
"=ROUNDDOWN(-99.999,2)": "-99.99000000000002",
"=ROUNDDOWN(-99.999,-1)": "-90",
// ROUNDUP
"=ROUNDUP(11.111,1)": "11.200000000000001",
"=ROUNDUP(11.111,2)": "11.120000000000003",
"=ROUNDUP(11.111,0)": "12",
"=ROUNDUP(11.111,-1)": "20",
"=ROUNDUP(-11.111,2)": "-11.120000000000003",
"=ROUNDUP(-11.111,-1)": "-20",
// SEC
"=_xlfn.SEC(-3.14159265358979)": "-1",
"=_xlfn.SEC(0)": "1",
// SECH
"=_xlfn.SECH(-3.14159265358979)": "0.0862667383340547",
"=_xlfn.SECH(0)": "1",
// SIGN
"=SIGN(9.5)": "1",
"=SIGN(-9.5)": "-1",
"=SIGN(0)": "0",
"=SIGN(0.00000001)": "1",
"=SIGN(6-7)": "-1",
// SIN
"=SIN(0.785398163)": "0.7071067809055092",
// SINH
"=SINH(0)": "0",
"=SINH(0.5)": "0.5210953054937474",
"=SINH(-2)": "-3.626860407847019",
// SQRT
"=SQRT(4)": "2",
// SQRTPI
"=SQRTPI(5)": "3.963327297606011",
"=SQRTPI(0.2)": "0.7926654595212022",
"=SQRTPI(100)": "17.72453850905516",
"=SQRTPI(0)": "0",
// SUM
"=SUM(1,2)": "3",
`=SUM("",1,2)`: "3",
"=SUM(1,2+3)": "6",
"=SUM(SUM(1,2),2)": "5",
"=(-2-SUM(-4+7))*5": "-25",
"SUM(1,2,3,4,5,6,7)": "28",
"=SUM(1,2)+SUM(1,2)": "6",
"=1+SUM(SUM(1,2*3),4)": "12",
"=1+SUM(SUM(1,-2*3),4)": "0",
"=(-2-SUM(-4*(7+7)))*5": "270",
"=SUM(SUM(1+2/1)*2-3/2,2)": "6.5",
"=((3+5*2)+3)/5+(-6)/4*2+3": "3.2",
"=1+SUM(SUM(1,2*3),4)*-4/2+5+(4+2)*3": "2",
"=1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3": "38.666666666666664",
// SUMSQ
"=SUMSQ(A1:A4)": "14",
"=SUMSQ(A1,B1,A2,B2,6)": "82",
`=SUMSQ("",A1,B1,A2,B2,6)`: "82",
// TAN
"=TAN(1.047197551)": "1.732050806782486",
"=TAN(0)": "0",
// TANH
"=TANH(0)": "0",
"=TANH(0.5)": "0.46211715726000974",
"=TANH(-2)": "-0.9640275800758169",
// TRUNC
"=TRUNC(99.999,1)": "99.9",
"=TRUNC(99.999,2)": "99.99",
"=TRUNC(99.999)": "99",
"=TRUNC(99.999,-1)": "90",
"=TRUNC(-99.999,2)": "-99.99",
"=TRUNC(-99.999,-1)": "-90",
}
for formula, expected := range mathCalc {
f := prepareData()
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, expected, result, formula)
}
mathCalcError := map[string]string{
// ABS
"=ABS()": "ABS requires 1 numeric argument",
`=ABS("X")`: "#VALUE!",
"=ABS(~)": `cannot convert cell "~" to coordinates: invalid cell name "~"`,
// ACOS
"=ACOS()": "ACOS requires 1 numeric argument",
`=ACOS("X")`: "#VALUE!",
// ACOSH
"=ACOSH()": "ACOSH requires 1 numeric argument",
`=ACOSH("X")`: "#VALUE!",
// _xlfn.ACOT
"=_xlfn.ACOT()": "ACOT requires 1 numeric argument",
`=_xlfn.ACOT("X")`: "#VALUE!",
// _xlfn.ACOTH
"=_xlfn.ACOTH()": "ACOTH requires 1 numeric argument",
`=_xlfn.ACOTH("X")`: "#VALUE!",
// _xlfn.ARABIC
"=_xlfn.ARABIC()": "ARABIC requires 1 numeric argument",
// ASIN
"=ASIN()": "ASIN requires 1 numeric argument",
`=ASIN("X")`: "#VALUE!",
// ASINH
"=ASINH()": "ASINH requires 1 numeric argument",
`=ASINH("X")`: "#VALUE!",
// ATAN
"=ATAN()": "ATAN requires 1 numeric argument",
`=ATAN("X")`: "#VALUE!",
// ATANH
"=ATANH()": "ATANH requires 1 numeric argument",
`=ATANH("X")`: "#VALUE!",
// ATAN2
"=ATAN2()": "ATAN2 requires 2 numeric arguments",
`=ATAN2("X",0)`: "#VALUE!",
`=ATAN2(0,"X")`: "#VALUE!",
// BASE
"=BASE()": "BASE requires at least 2 arguments",
"=BASE(1,2,3,4)": "BASE allows at most 3 arguments",
"=BASE(1,1)": "radix must be an integer >= 2 and <= 36",
`=BASE("X",2)`: "#VALUE!",
`=BASE(1,"X")`: "#VALUE!",
`=BASE(1,2,"X")`: "#VALUE!",
// CEILING
"=CEILING()": "CEILING requires at least 1 argument",
"=CEILING(1,2,3)": "CEILING allows at most 2 arguments",
"=CEILING(1,-1)": "negative sig to CEILING invalid",
`=CEILING("X",0)`: "#VALUE!",
`=CEILING(0,"X")`: "#VALUE!",
// _xlfn.CEILING.MATH
"=_xlfn.CEILING.MATH()": "CEILING.MATH requires at least 1 argument",
"=_xlfn.CEILING.MATH(1,2,3,4)": "CEILING.MATH allows at most 3 arguments",
`=_xlfn.CEILING.MATH("X")`: "#VALUE!",
`=_xlfn.CEILING.MATH(1,"X")`: "#VALUE!",
`=_xlfn.CEILING.MATH(1,2,"X")`: "#VALUE!",
// _xlfn.CEILING.PRECISE
"=_xlfn.CEILING.PRECISE()": "CEILING.PRECISE requires at least 1 argument",
"=_xlfn.CEILING.PRECISE(1,2,3)": "CEILING.PRECISE allows at most 2 arguments",
`=_xlfn.CEILING.PRECISE("X",2)`: "#VALUE!",
`=_xlfn.CEILING.PRECISE(1,"X")`: "#VALUE!",
// COMBIN
"=COMBIN()": "COMBIN requires 2 argument",
"=COMBIN(-1,1)": "COMBIN requires number >= number_chosen",
`=COMBIN("X",1)`: "#VALUE!",
`=COMBIN(-1,"X")`: "#VALUE!",
// _xlfn.COMBINA
"=_xlfn.COMBINA()": "COMBINA requires 2 argument",
"=_xlfn.COMBINA(-1,1)": "COMBINA requires number > number_chosen",
"=_xlfn.COMBINA(-1,-1)": "COMBIN requires number >= number_chosen",
`=_xlfn.COMBINA("X",1)`: "#VALUE!",
`=_xlfn.COMBINA(-1,"X")`: "#VALUE!",
// COS
"=COS()": "COS requires 1 numeric argument",
`=COS("X")`: "#VALUE!",
// COSH
"=COSH()": "COSH requires 1 numeric argument",
`=COSH("X")`: "#VALUE!",
// _xlfn.COT
"=COT()": "COT requires 1 numeric argument",
`=COT("X")`: "#VALUE!",
"=COT(0)": "#DIV/0!",
// _xlfn.COTH
"=COTH()": "COTH requires 1 numeric argument",
`=COTH("X")`: "#VALUE!",
"=COTH(0)": "#DIV/0!",
// _xlfn.CSC
"=_xlfn.CSC()": "CSC requires 1 numeric argument",
`=_xlfn.CSC("X")`: "#VALUE!",
"=_xlfn.CSC(0)": "#DIV/0!",
// _xlfn.CSCH
"=_xlfn.CSCH()": "CSCH requires 1 numeric argument",
`=_xlfn.CSCH("X")`: "#VALUE!",
"=_xlfn.CSCH(0)": "#DIV/0!",
// _xlfn.DECIMAL
"=_xlfn.DECIMAL()": "DECIMAL requires 2 numeric arguments",
`=_xlfn.DECIMAL("X", 2)`: "#VALUE!",
`=_xlfn.DECIMAL(2000, "X")`: "#VALUE!",
// DEGREES
"=DEGREES()": "DEGREES requires 1 numeric argument",
`=DEGREES("X")`: "#VALUE!",
"=DEGREES(0)": "#DIV/0!",
// EVEN
"=EVEN()": "EVEN requires 1 numeric argument",
`=EVEN("X")`: "#VALUE!",
// EXP
"=EXP()": "EXP requires 1 numeric argument",
`=EXP("X")`: "#VALUE!",
// FACT
"=FACT()": "FACT requires 1 numeric argument",
`=FACT("X")`: "#VALUE!",
"=FACT(-1)": "#NUM!",
// FACTDOUBLE
"=FACTDOUBLE()": "FACTDOUBLE requires 1 numeric argument",
`=FACTDOUBLE("X")`: "#VALUE!",
"=FACTDOUBLE(-1)": "#NUM!",
// FLOOR
"=FLOOR()": "FLOOR requires 2 numeric arguments",
`=FLOOR("X",-1)`: "#VALUE!",
`=FLOOR(1,"X")`: "#VALUE!",
"=FLOOR(1,-1)": "#NUM!",
// _xlfn.FLOOR.MATH
"=_xlfn.FLOOR.MATH()": "FLOOR.MATH requires at least 1 argument",
"=_xlfn.FLOOR.MATH(1,2,3,4)": "FLOOR.MATH allows at most 3 arguments",
`=_xlfn.FLOOR.MATH("X",2,3)`: "#VALUE!",
`=_xlfn.FLOOR.MATH(1,"X",3)`: "#VALUE!",
`=_xlfn.FLOOR.MATH(1,2,"X")`: "#VALUE!",
// _xlfn.FLOOR.PRECISE
"=_xlfn.FLOOR.PRECISE()": "FLOOR.PRECISE requires at least 1 argument",
"=_xlfn.FLOOR.PRECISE(1,2,3)": "FLOOR.PRECISE allows at most 2 arguments",
`=_xlfn.FLOOR.PRECISE("X",2)`: "#VALUE!",
`=_xlfn.FLOOR.PRECISE(1,"X")`: "#VALUE!",
// GCD
"=GCD()": "GCD requires at least 1 argument",
"=GCD(-1)": "GCD only accepts positive arguments",
"=GCD(1,-1)": "GCD only accepts positive arguments",
`=GCD("X")`: "#VALUE!",
// INT
"=INT()": "INT requires 1 numeric argument",
`=INT("X")`: "#VALUE!",
// ISO.CEILING
"=ISO.CEILING()": "ISO.CEILING requires at least 1 argument",
"=ISO.CEILING(1,2,3)": "ISO.CEILING allows at most 2 arguments",
`=ISO.CEILING("X",2)`: "#VALUE!",
`=ISO.CEILING(1,"X")`: "#VALUE!",
// LCM
"=LCM()": "LCM requires at least 1 argument",
"=LCM(-1)": "LCM only accepts positive arguments",
"=LCM(1,-1)": "LCM only accepts positive arguments",
`=LCM("X")`: "#VALUE!",
// LN
"=LN()": "LN requires 1 numeric argument",
`=LN("X")`: "#VALUE!",
// LOG
"=LOG()": "LOG requires at least 1 argument",
"=LOG(1,2,3)": "LOG allows at most 2 arguments",
`=LOG("X",1)`: "#VALUE!",
`=LOG(1,"X")`: "#VALUE!",
"=LOG(0,0)": "#NUM!",
"=LOG(1,0)": "#NUM!",
"=LOG(1,1)": "#DIV/0!",
// LOG10
"=LOG10()": "LOG10 requires 1 numeric argument",
`=LOG10("X")`: "#VALUE!",
// MOD
"=MOD()": "MOD requires 2 numeric arguments",
"=MOD(6,0)": "#DIV/0!",
`=MOD("X",0)`: "#VALUE!",
`=MOD(6,"X")`: "#VALUE!",
// MROUND
"=MROUND()": "MROUND requires 2 numeric arguments",
"=MROUND(1,0)": "#NUM!",
"=MROUND(1,-1)": "#NUM!",
`=MROUND("X",0)`: "#VALUE!",
`=MROUND(1,"X")`: "#VALUE!",
// MULTINOMIAL
`=MULTINOMIAL("X")`: "#VALUE!",
// _xlfn.MUNIT
"=_xlfn.MUNIT()": "MUNIT requires 1 numeric argument", // not support currently
`=_xlfn.MUNIT("X")`: "#VALUE!", // not support currently
// ODD
"=ODD()": "ODD requires 1 numeric argument",
`=ODD("X")`: "#VALUE!",
// PI
"=PI(1)": "PI accepts no arguments",
// POWER
`=POWER("X",1)`: "#VALUE!",
`=POWER(1,"X")`: "#VALUE!",
"=POWER(0,0)": "#NUM!",
"=POWER(0,-1)": "#DIV/0!",
"=POWER(1)": "POWER requires 2 numeric arguments",
// PRODUCT
`=PRODUCT("X")`: "#VALUE!",
// QUOTIENT
`=QUOTIENT("X",1)`: "#VALUE!",
`=QUOTIENT(1,"X")`: "#VALUE!",
"=QUOTIENT(1,0)": "#DIV/0!",
"=QUOTIENT(1)": "QUOTIENT requires 2 numeric arguments",
// RADIANS
`=RADIANS("X")`: "#VALUE!",
"=RADIANS()": "RADIANS requires 1 numeric argument",
// RAND
"=RAND(1)": "RAND accepts no arguments",
// RANDBETWEEN
`=RANDBETWEEN("X",1)`: "#VALUE!",
`=RANDBETWEEN(1,"X")`: "#VALUE!",
"=RANDBETWEEN()": "RANDBETWEEN requires 2 numeric arguments",
"=RANDBETWEEN(2,1)": "#NUM!",
// ROMAN
"=ROMAN()": "ROMAN requires at least 1 argument",
"=ROMAN(1,2,3)": "ROMAN allows at most 2 arguments",
`=ROMAN("X")`: "#VALUE!",
`=ROMAN("X",1)`: "#VALUE!",
// ROUND
"=ROUND()": "ROUND requires 2 numeric arguments",
`=ROUND("X",1)`: "#VALUE!",
`=ROUND(1,"X")`: "#VALUE!",
// ROUNDDOWN
"=ROUNDDOWN()": "ROUNDDOWN requires 2 numeric arguments",
`=ROUNDDOWN("X",1)`: "#VALUE!",
`=ROUNDDOWN(1,"X")`: "#VALUE!",
// ROUNDUP
"=ROUNDUP()": "ROUNDUP requires 2 numeric arguments",
`=ROUNDUP("X",1)`: "#VALUE!",
`=ROUNDUP(1,"X")`: "#VALUE!",
// SEC
"=_xlfn.SEC()": "SEC requires 1 numeric argument",
`=_xlfn.SEC("X")`: "#VALUE!",
// _xlfn.SECH
"=_xlfn.SECH()": "SECH requires 1 numeric argument",
`=_xlfn.SECH("X")`: "#VALUE!",
// SIGN
"=SIGN()": "SIGN requires 1 numeric argument",
`=SIGN("X")`: "#VALUE!",
// SIN
"=SIN()": "SIN requires 1 numeric argument",
`=SIN("X")`: "#VALUE!",
// SINH
"=SINH()": "SINH requires 1 numeric argument",
`=SINH("X")`: "#VALUE!",
// SQRT
"=SQRT()": "SQRT requires 1 numeric argument",
`=SQRT("X")`: "#VALUE!",
"=SQRT(-1)": "#NUM!",
// SQRTPI
"=SQRTPI()": "SQRTPI requires 1 numeric argument",
`=SQRTPI("X")`: "#VALUE!",
// SUM
"=SUM((": "formula not valid",
"=SUM(-)": "formula not valid",
"=SUM(1+)": "formula not valid",
"=SUM(1-)": "formula not valid",
"=SUM(1*)": "formula not valid",
"=SUM(1/)": "formula not valid",
`=SUM("X")`: "#VALUE!",
// SUMSQ
`=SUMSQ("X")`: "#VALUE!",
// TAN
"=TAN()": "TAN requires 1 numeric argument",
`=TAN("X")`: "#VALUE!",
// TANH
"=TANH()": "TANH requires 1 numeric argument",
`=TANH("X")`: "#VALUE!",
// TRUNC
"=TRUNC()": "TRUNC requires at least 1 argument",
`=TRUNC("X")`: "#VALUE!",
`=TRUNC(1,"X")`: "#VALUE!",
}
for formula, expected := range mathCalcError {
f := prepareData()
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.EqualError(t, err, expected)
assert.Equal(t, "", result, formula)
}
referenceCalc := map[string]string{
// MDETERM
"=MDETERM(A1:B2)": "-3",
// PRODUCT
"=PRODUCT(Sheet1!A1:Sheet1!A1:A2,A2)": "4",
// SUM
"=A1/A3": "0.3333333333333333",
"=SUM(A1:A2)": "3",
"=SUM(Sheet1!A1,A2)": "3",
"=(-2-SUM(-4+A2))*5": "0",
"=SUM(Sheet1!A1:Sheet1!A1:A2,A2)": "5",
"=SUM(A1,A2,A3)*SUM(2,3)": "30",
"=1+SUM(SUM(A1+A2/A3)*(2-3),2)": "1.3333333333333335",
"=A1/A2/SUM(A1:A2:B1)": "0.041666666666666664",
"=A1/A2/SUM(A1:A2:B1)*A3": "0.125",
}
for formula, expected := range referenceCalc {
f := prepareData()
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, expected, result, formula)
}
referenceCalcError := map[string]string{
// MDETERM
"=MDETERM(A1:B3)": "#VALUE!",
// SUM
"=1+SUM(SUM(A1+A2/A4)*(2-3),2)": "#DIV/0!",
}
for formula, expected := range referenceCalcError {
f := prepareData()
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.EqualError(t, err, expected)
assert.Equal(t, "", result, formula)
}
volatileFuncs := []string{
"=RAND()",
"=RANDBETWEEN(1,2)",
}
for _, formula := range volatileFuncs {
f := prepareData()
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
_, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
}
// Test get calculated cell value on not formula cell.
f := prepareData()
result, err := f.CalcCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "", result)
// Test get calculated cell value on not exists worksheet.
f = prepareData()
_, err = f.CalcCellValue("SheetN", "A1")
assert.EqualError(t, err, "sheet SheetN is not exist")
// Test get calculated cell value with not support formula.
f = prepareData()
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "=UNSUPPORT(A1)"))
_, err = f.CalcCellValue("Sheet1", "A1")
assert.EqualError(t, err, "not support UNSUPPORT function")
}

View File

@ -1,24 +1,34 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
import (
"bytes"
"encoding/xml"
"io"
"log"
)
// calcChainReader provides a function to get the pointer to the structure
// after deserialization of xl/calcChain.xml.
func (f *File) calcChainReader() *xlsxCalcChain {
var err error
if f.CalcChain == nil {
var c xlsxCalcChain
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/calcChain.xml")), &c)
f.CalcChain = &c
f.CalcChain = new(xlsxCalcChain)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/calcChain.xml")))).
Decode(f.CalcChain); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
}
return f.CalcChain
}
@ -56,7 +66,7 @@ type xlsxCalcChainCollection []xlsxCalcChainC
// Filter provides a function to filter calculation chain.
func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCalcChainC {
results := make([]xlsxCalcChainC, 0)
var results []xlsxCalcChainC
for _, v := range c {
if fn(v) {
results = append(results, v)

19
calcchain_test.go Normal file
View File

@ -0,0 +1,19 @@
package excelize
import "testing"
func TestCalcChainReader(t *testing.T) {
f := NewFile()
f.CalcChain = nil
f.XLSX["xl/calcChain.xml"] = MacintoshCyrillicCharset
f.calcChainReader()
}
func TestDeleteCalcChain(t *testing.T) {
f := NewFile()
f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{}}
f.ContentTypes.Overrides = append(f.ContentTypes.Overrides, xlsxOverride{
PartName: "/xl/calcChain.xml",
})
f.deleteCalcChain(1, "A1")
}

385
cell.go
View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -47,7 +47,8 @@ func (f *File) GetCellValue(sheet, axis string) (string, error) {
})
}
// SetCellValue provides a function to set value of a cell. The following
// SetCellValue provides a function to set value of a cell. The specified
// coordinates should not be in the first row of the table. The following
// shows the supported data types:
//
// int
@ -85,7 +86,8 @@ func (f *File) SetCellValue(sheet, axis string, value interface{}) error {
case []byte:
err = f.SetCellStr(sheet, axis, string(v))
case time.Duration:
err = f.SetCellDefault(sheet, axis, strconv.FormatFloat(v.Seconds()/86400.0, 'f', -1, 32))
_, d := setCellDuration(v)
err = f.SetCellDefault(sheet, axis, d)
if err != nil {
return err
}
@ -97,7 +99,7 @@ func (f *File) SetCellValue(sheet, axis string, value interface{}) error {
case nil:
err = f.SetCellStr(sheet, axis, "")
default:
err = f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value))
err = f.SetCellStr(sheet, axis, fmt.Sprint(value))
}
return err
}
@ -133,28 +135,50 @@ func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error {
// setCellTimeFunc provides a method to process time type of value for
// SetCellValue.
func (f *File) setCellTimeFunc(sheet, axis string, value time.Time) error {
excelTime, err := timeToExcelTime(value)
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if excelTime > 0 {
err = f.SetCellDefault(sheet, axis, strconv.FormatFloat(excelTime, 'f', -1, 64))
if err != nil {
return err
}
cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
if err != nil {
return err
}
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
var isNum bool
cellData.T, cellData.V, isNum, err = setCellTime(value)
if err != nil {
return err
}
if isNum {
err = f.setDefaultTimeStyle(sheet, axis, 22)
if err != nil {
return err
}
} else {
err = f.SetCellStr(sheet, axis, value.Format(time.RFC3339Nano))
if err != nil {
return err
}
}
return err
}
func setCellTime(value time.Time) (t string, b string, isNum bool, err error) {
var excelTime float64
excelTime, err = timeToExcelTime(value)
if err != nil {
return
}
isNum = excelTime > 0
if isNum {
t, b = setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64))
} else {
t, b = setCellDefault(value.Format(time.RFC3339Nano))
}
return
}
func setCellDuration(value time.Duration) (t string, v string) {
v = strconv.FormatFloat(value.Seconds()/86400.0, 'f', -1, 32)
return
}
// SetCellInt provides a function to set int type value of a cell by given
// worksheet name, cell coordinates and cell value.
func (f *File) SetCellInt(sheet, axis string, value int) error {
@ -169,11 +193,15 @@ func (f *File) SetCellInt(sheet, axis string, value int) error {
return err
}
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
cellData.T = ""
cellData.V = strconv.Itoa(value)
cellData.T, cellData.V = setCellInt(value)
return err
}
func setCellInt(value int) (t string, v string) {
v = strconv.Itoa(value)
return
}
// SetCellBool provides a function to set bool type value of a cell by given
// worksheet name, cell name and cell value.
func (f *File) SetCellBool(sheet, axis string, value bool) error {
@ -188,15 +216,20 @@ func (f *File) SetCellBool(sheet, axis string, value bool) error {
return err
}
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
cellData.T = "b"
if value {
cellData.V = "1"
} else {
cellData.V = "0"
}
cellData.T, cellData.V = setCellBool(value)
return err
}
func setCellBool(value bool) (t string, v string) {
t = "b"
if value {
v = "1"
} else {
v = "0"
}
return
}
// SetCellFloat sets a floating point value into a cell. The prec parameter
// specifies how many places after the decimal will be shown while -1 is a
// special value that will use as many decimal places as necessary to
@ -218,11 +251,15 @@ func (f *File) SetCellFloat(sheet, axis string, value float64, prec, bitSize int
return err
}
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
cellData.T = ""
cellData.V = strconv.FormatFloat(value, 'f', prec, bitSize)
cellData.T, cellData.V = setCellFloat(value, prec, bitSize)
return err
}
func setCellFloat(value float64, prec, bitSize int) (t string, v string) {
v = strconv.FormatFloat(value, 'f', prec, bitSize)
return
}
// SetCellStr provides a function to set string type value of a cell. Total
// number of characters that a cell can contain 32767 characters.
func (f *File) SetCellStr(sheet, axis, value string) error {
@ -236,21 +273,25 @@ func (f *File) SetCellStr(sheet, axis, value string) error {
if err != nil {
return err
}
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
cellData.T, cellData.V, cellData.XMLSpace = setCellStr(value)
return err
}
func setCellStr(value string) (t string, v string, ns xml.Attr) {
if len(value) > 32767 {
value = value[0:32767]
}
// Leading space(s) character detection.
if len(value) > 0 && value[0] == 32 {
cellData.XMLSpace = xml.Attr{
// Leading and ending space(s) character detection.
if len(value) > 0 && (value[0] == 32 || value[len(value)-1] == 32) {
ns = xml.Attr{
Name: xml.Name{Space: NameSpaceXML, Local: "space"},
Value: "preserve",
}
}
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
cellData.T = "str"
cellData.V = value
return err
t = "str"
v = value
return
}
// SetCellDefault provides a function to set string type value of a cell as
@ -265,11 +306,15 @@ func (f *File) SetCellDefault(sheet, axis, value string) error {
return err
}
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
cellData.T = ""
cellData.V = value
cellData.T, cellData.V = setCellDefault(value)
return err
}
func setCellDefault(value string) (t string, v string) {
v = value
return
}
// GetCellFormula provides a function to get formula from cell by given
// worksheet name and axis in XLSX file.
func (f *File) GetCellFormula(sheet, axis string) (string, error) {
@ -284,9 +329,15 @@ func (f *File) GetCellFormula(sheet, axis string) (string, error) {
})
}
// FormulaOpts can be passed to SetCellFormula to use other formula types.
type FormulaOpts struct {
Type *string // Formula type
Ref *string // Shared formula ref
}
// SetCellFormula provides a function to set cell formula by given string and
// worksheet name.
func (f *File) SetCellFormula(sheet, axis, formula string) error {
func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) error {
rwMutex.Lock()
defer rwMutex.Unlock()
xlsx, err := f.workSheetReader(sheet)
@ -299,7 +350,7 @@ func (f *File) SetCellFormula(sheet, axis, formula string) error {
}
if formula == "" {
cellData.F = nil
f.deleteCalcChain(f.GetSheetIndex(sheet), axis)
f.deleteCalcChain(f.getSheetID(sheet), axis)
return err
}
@ -308,6 +359,17 @@ func (f *File) SetCellFormula(sheet, axis, formula string) error {
} else {
cellData.F = &xlsxF{Content: formula}
}
for _, o := range opts {
if o.Type != nil {
cellData.F.T = *o.Type
}
if o.Ref != nil {
cellData.F.Ref = *o.Ref
}
}
return err
}
@ -391,7 +453,9 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) error {
linkData = xlsxHyperlink{
Ref: axis,
}
rID := f.addSheetRelationships(sheet, SourceRelationshipHyperLink, link, linkType)
sheetPath := f.sheetMap[trimSheetName(sheet)]
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipHyperLink, link, linkType)
linkData.RID = "rId" + strconv.Itoa(rID)
case "Location":
linkData = xlsxHyperlink{
@ -406,64 +470,170 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) error {
return nil
}
// MergeCell provides a function to merge cells by given coordinate area and
// sheet name. For example create a merged cell of D3:E9 on Sheet1:
// SetCellRichText provides a function to set cell with rich text by given
// worksheet. For example, set rich text on the A1 cell of the worksheet named
// Sheet1:
//
// err := f.MergeCell("Sheet1", "D3", "E9")
// package main
//
// If you create a merged cell that overlaps with another existing merged cell,
// those merged cells that already exist will be removed.
func (f *File) MergeCell(sheet, hcell, vcell string) error {
hcol, hrow, err := CellNameToCoordinates(hcell)
// import (
// "fmt"
//
// "github.com/360EntSecGroup-Skylar/excelize"
// )
//
// func main() {
// f := excelize.NewFile()
// if err := f.SetRowHeight("Sheet1", 1, 35); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SetColWidth("Sheet1", "A", "A", 44); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SetCellRichText("Sheet1", "A1", []excelize.RichTextRun{
// {
// Text: "blod",
// Font: &excelize.Font{
// Bold: true,
// Color: "2354e8",
// Family: "Times New Roman",
// },
// },
// {
// Text: " and ",
// Font: &excelize.Font{
// Family: "Times New Roman",
// },
// },
// {
// Text: " italic",
// Font: &excelize.Font{
// Bold: true,
// Color: "e83723",
// Italic: true,
// Family: "Times New Roman",
// },
// },
// {
// Text: "text with color and font-family,",
// Font: &excelize.Font{
// Bold: true,
// Color: "2354e8",
// Family: "Times New Roman",
// },
// },
// {
// Text: "\r\nlarge text with ",
// Font: &excelize.Font{
// Size: 14,
// Color: "ad23e8",
// },
// },
// {
// Text: "strike",
// Font: &excelize.Font{
// Color: "e89923",
// Strike: true,
// },
// },
// {
// Text: " and ",
// Font: &excelize.Font{
// Size: 14,
// Color: "ad23e8",
// },
// },
// {
// Text: "underline.",
// Font: &excelize.Font{
// Color: "23e833",
// Underline: "single",
// },
// },
// }); err != nil {
// fmt.Println(err)
// return
// }
// style, err := f.NewStyle(&excelize.Style{
// Alignment: &excelize.Alignment{
// WrapText: true,
// },
// })
// if err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SetCellStyle("Sheet1", "A1", "A1", style); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
//
func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
vcol, vrow, err := CellNameToCoordinates(vcell)
cellData, col, _, err := f.prepareCell(ws, sheet, cell)
if err != nil {
return err
}
if hcol == vcol && hrow == vrow {
return err
}
// Correct the coordinate area, such correct C1:B3 to B1:C3.
if vcol < hcol {
hcol, vcol = vcol, hcol
}
if vrow < hrow {
hrow, vrow = vrow, hrow
}
hcell, _ = CoordinatesToCellName(hcol, hrow)
vcell, _ = CoordinatesToCellName(vcol, vrow)
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if xlsx.MergeCells != nil {
ref := hcell + ":" + vcell
// Delete the merged cells of the overlapping area.
for _, cellData := range xlsx.MergeCells.Cells {
cc := strings.Split(cellData.Ref, ":")
if len(cc) != 2 {
return fmt.Errorf("invalid area %q", cellData.Ref)
}
c1, _ := checkCellInArea(hcell, cellData.Ref)
c2, _ := checkCellInArea(vcell, cellData.Ref)
c3, _ := checkCellInArea(cc[0], ref)
c4, _ := checkCellInArea(cc[1], ref)
if !(!c1 && !c2 && !c3 && !c4) {
return nil
}
cellData.S = f.prepareCellStyle(ws, col, cellData.S)
si := xlsxSI{}
sst := f.sharedStringsReader()
textRuns := []xlsxR{}
for _, textRun := range runs {
run := xlsxR{T: &xlsxT{Val: textRun.Text}}
if strings.ContainsAny(textRun.Text, "\r\n ") {
run.T.Space = "preserve"
}
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
} else {
xlsx.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: hcell + ":" + vcell}}}
fnt := textRun.Font
if fnt != nil {
rpr := xlsxRPr{}
if fnt.Bold {
rpr.B = " "
}
if fnt.Italic {
rpr.I = " "
}
if fnt.Strike {
rpr.Strike = " "
}
if fnt.Underline != "" {
rpr.U = &attrValString{Val: &fnt.Underline}
}
if fnt.Family != "" {
rpr.RFont = &attrValString{Val: &fnt.Family}
}
if fnt.Size > 0.0 {
rpr.Sz = &attrValFloat{Val: &fnt.Size}
}
if fnt.Color != "" {
rpr.Color = &xlsxColor{RGB: getPaletteColor(fnt.Color)}
}
run.RPr = &rpr
}
textRuns = append(textRuns, run)
}
si.R = textRuns
sst.SI = append(sst.SI, si)
sst.Count++
sst.UniqueCount++
cellData.T, cellData.V = "s", strconv.Itoa(len(sst.SI)-1)
f.addContentTypePart(0, "sharedStrings")
rels := f.relsReader("xl/_rels/workbook.xml.rels")
for _, rel := range rels.Relationships {
if rel.Target == "sharedStrings.xml" {
return err
}
}
// Update xl/_rels/workbook.xml.rels
f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipSharedStrings, "sharedStrings.xml", "")
return err
}
@ -488,12 +658,14 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
for i := 0; i < v.Len(); i++ {
cell, err := CoordinatesToCellName(col+i, row)
// Error should never happens here. But keep ckecking to early detect regresions
// if it will be introduced in furure
// Error should never happens here. But keep checking to early detect regresions
// if it will be introduced in future.
if err != nil {
return err
}
f.SetCellValue(sheet, cell, v.Index(i).Interface())
if err := f.SetCellValue(sheet, cell, v.Index(i).Interface()); err != nil {
return err
}
}
return err
}
@ -571,9 +743,9 @@ func (f *File) formattedValue(s int, v string) string {
return v
}
styleSheet := f.stylesReader()
ok := builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID]
ok := builtInNumFmtFunc[*styleSheet.CellXfs.Xf[s].NumFmtID]
if ok != nil {
return ok(styleSheet.CellXfs.Xf[s].NumFmtID, v)
return ok(*styleSheet.CellXfs.Xf[s].NumFmtID, v)
}
return v
}
@ -597,7 +769,7 @@ func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) (string, error
axis = strings.ToUpper(axis)
if xlsx.MergeCells != nil {
for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
ok, err := checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref)
ok, err := f.checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref)
if err != nil {
return axis, err
}
@ -611,7 +783,7 @@ func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) (string, error
// checkCellInArea provides a function to determine if a given coordinate is
// within an area.
func checkCellInArea(cell, area string) (bool, error) {
func (f *File) checkCellInArea(cell, area string) (bool, error) {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return false, err
@ -621,11 +793,30 @@ func checkCellInArea(cell, area string) (bool, error) {
if len(rng) != 2 {
return false, err
}
coordinates, err := f.areaRefToCoordinates(area)
if err != nil {
return false, err
}
firstCol, firstRow, _ := CellNameToCoordinates(rng[0])
lastCol, lastRow, _ := CellNameToCoordinates(rng[1])
return cellInRef([]int{col, row}, coordinates), err
}
return col >= firstCol && col <= lastCol && row >= firstRow && row <= lastRow, err
// cellInRef provides a function to determine if a given range is within an
// range.
func cellInRef(cell, ref []int) bool {
return cell[0] >= ref[0] && cell[0] <= ref[2] && cell[1] >= ref[1] && cell[1] <= ref[3]
}
// isOverlap find if the given two rectangles overlap or not.
func isOverlap(rect1, rect2 []int) bool {
return cellInRef([]int{rect1[0], rect1[1]}, rect2) ||
cellInRef([]int{rect1[2], rect1[1]}, rect2) ||
cellInRef([]int{rect1[0], rect1[3]}, rect2) ||
cellInRef([]int{rect1[2], rect1[3]}, rect2) ||
cellInRef([]int{rect2[0], rect2[1]}, rect1) ||
cellInRef([]int{rect2[2], rect2[1]}, rect1) ||
cellInRef([]int{rect2[0], rect2[3]}, rect1) ||
cellInRef([]int{rect2[2], rect2[3]}, rect1)
}
// getSharedForumula find a cell contains the same formula as another cell,

View File

@ -2,12 +2,16 @@ package excelize
import (
"fmt"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCheckCellInArea(t *testing.T) {
f := NewFile()
expectedTrueCellInAreaList := [][2]string{
{"c2", "A1:AAZ32"},
{"B9", "A1:B9"},
@ -17,7 +21,7 @@ func TestCheckCellInArea(t *testing.T) {
for _, expectedTrueCellInArea := range expectedTrueCellInAreaList {
cell := expectedTrueCellInArea[0]
area := expectedTrueCellInArea[1]
ok, err := checkCellInArea(cell, area)
ok, err := f.checkCellInArea(cell, area)
assert.NoError(t, err)
assert.Truef(t, ok,
"Expected cell %v to be in area %v, got false\n", cell, area)
@ -32,13 +36,17 @@ func TestCheckCellInArea(t *testing.T) {
for _, expectedFalseCellInArea := range expectedFalseCellInAreaList {
cell := expectedFalseCellInArea[0]
area := expectedFalseCellInArea[1]
ok, err := checkCellInArea(cell, area)
ok, err := f.checkCellInArea(cell, area)
assert.NoError(t, err)
assert.Falsef(t, ok,
"Expected cell %v not to be inside of area %v, but got true\n", cell, area)
}
ok, err := checkCellInArea("AA0", "Z0:AB1")
ok, err := f.checkCellInArea("A1", "A:B")
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.False(t, ok)
ok, err = f.checkCellInArea("AA0", "Z0:AB1")
assert.EqualError(t, err, `cannot convert cell "AA0" to coordinates: invalid cell name "AA0"`)
assert.False(t, ok)
}
@ -47,8 +55,8 @@ func TestSetCellFloat(t *testing.T) {
sheet := "Sheet1"
t.Run("with no decimal", func(t *testing.T) {
f := NewFile()
f.SetCellFloat(sheet, "A1", 123.0, -1, 64)
f.SetCellFloat(sheet, "A2", 123.0, 1, 64)
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.0, -1, 64))
assert.NoError(t, f.SetCellFloat(sheet, "A2", 123.0, 1, 64))
val, err := f.GetCellValue(sheet, "A1")
assert.NoError(t, err)
assert.Equal(t, "123", val, "A1 should be 123")
@ -59,7 +67,7 @@ func TestSetCellFloat(t *testing.T) {
t.Run("with a decimal and precision limit", func(t *testing.T) {
f := NewFile()
f.SetCellFloat(sheet, "A1", 123.42, 1, 64)
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, 1, 64))
val, err := f.GetCellValue(sheet, "A1")
assert.NoError(t, err)
assert.Equal(t, "123.4", val, "A1 should be 123.4")
@ -67,17 +75,44 @@ func TestSetCellFloat(t *testing.T) {
t.Run("with a decimal and no limit", func(t *testing.T) {
f := NewFile()
f.SetCellFloat(sheet, "A1", 123.42, -1, 64)
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, -1, 64))
val, err := f.GetCellValue(sheet, "A1")
assert.NoError(t, err)
assert.Equal(t, "123.42", val, "A1 should be 123.42")
})
f := NewFile()
assert.EqualError(t, f.SetCellFloat(sheet, "A", 123.42, -1, 64), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}
func TestSetCellValue(t *testing.T) {
f := NewFile()
assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Now().UTC()), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}
func TestSetCellBool(t *testing.T) {
f := NewFile()
assert.EqualError(t, f.SetCellBool("Sheet1", "A", true), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}
func TestGetCellFormula(t *testing.T) {
// Test get cell formula on not exist worksheet.
f := NewFile()
_, err := f.GetCellFormula("SheetN", "A1")
assert.EqualError(t, err, "sheet SheetN is not exist")
// Test get cell formula on no formula cell.
assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
_, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
}
func ExampleFile_SetCellFloat() {
f := NewFile()
var x = 3.14159265
f.SetCellFloat("Sheet1", "A1", x, 2, 64)
if err := f.SetCellFloat("Sheet1", "A1", x, 2, 64); err != nil {
fmt.Println(err)
}
val, _ := f.GetCellValue("Sheet1", "A1")
fmt.Println(val)
// Output: 3.14
@ -88,9 +123,103 @@ func BenchmarkSetCellValue(b *testing.B) {
cols := []string{"A", "B", "C", "D", "E", "F"}
f := NewFile()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for i := 1; i <= b.N; i++ {
for j := 0; j < len(values); j++ {
f.SetCellValue("Sheet1", fmt.Sprint(cols[j], i), values[j])
if err := f.SetCellValue("Sheet1", cols[j]+strconv.Itoa(i), values[j]); err != nil {
b.Error(err)
}
}
}
}
func TestOverflowNumericCell(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "OverflowNumericCell.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
// GOARCH=amd64 - all ok; GOARCH=386 - actual: "-2147483648"
assert.Equal(t, "8595602512225", val, "A1 should be 8595602512225")
}
func TestSetCellRichText(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35))
assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 44))
richTextRun := []RichTextRun{
{
Text: "blod",
Font: &Font{
Bold: true,
Color: "2354e8",
Family: "Times New Roman",
},
},
{
Text: " and ",
Font: &Font{
Family: "Times New Roman",
},
},
{
Text: "italic ",
Font: &Font{
Bold: true,
Color: "e83723",
Italic: true,
Family: "Times New Roman",
},
},
{
Text: "text with color and font-family,",
Font: &Font{
Bold: true,
Color: "2354e8",
Family: "Times New Roman",
},
},
{
Text: "\r\nlarge text with ",
Font: &Font{
Size: 14,
Color: "ad23e8",
},
},
{
Text: "strike",
Font: &Font{
Color: "e89923",
Strike: true,
},
},
{
Text: " and ",
Font: &Font{
Size: 14,
Color: "ad23e8",
},
},
{
Text: "underline.",
Font: &Font{
Color: "23e833",
Underline: "single",
},
},
}
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", richTextRun))
assert.NoError(t, f.SetCellRichText("Sheet1", "A2", richTextRun))
style, err := f.NewStyle(&Style{
Alignment: &Alignment{
WrapText: true,
},
})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellRichText.xlsx")))
// Test set cell rich text on not exists worksheet
assert.EqualError(t, f.SetCellRichText("SheetN", "A1", richTextRun), "sheet SheetN is not exist")
// Test set cell rich text with illegal cell coordinates
assert.EqualError(t, f.SetCellRichText("Sheet1", "A", richTextRun), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}

1704
chart.go

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,8 @@ package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -10,7 +12,7 @@ import (
func TestChartSize(t *testing.T) {
xlsx := NewFile()
sheet1 := xlsx.GetSheetName(1)
sheet1 := xlsx.GetSheetName(0)
categories := map[string]string{
"A2": "Small",
@ -21,7 +23,7 @@ func TestChartSize(t *testing.T) {
"D1": "Pear",
}
for cell, v := range categories {
xlsx.SetCellValue(sheet1, cell, v)
assert.NoError(t, xlsx.SetCellValue(sheet1, cell, v))
}
values := map[string]int{
@ -36,29 +38,22 @@ func TestChartSize(t *testing.T) {
"D4": 8,
}
for cell, v := range values {
xlsx.SetCellValue(sheet1, cell, v)
assert.NoError(t, xlsx.SetCellValue(sheet1, cell, v))
}
xlsx.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+
assert.NoError(t, xlsx.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+
`"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},`+
`{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},`+
`{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],`+
`"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
`"title":{"name":"3D Clustered Column Chart"}}`))
var (
buffer bytes.Buffer
)
var buffer bytes.Buffer
// Save xlsx file by the given path.
err := xlsx.Write(&buffer)
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, xlsx.Write(&buffer))
newFile, err := OpenReader(&buffer)
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
chartsNum := newFile.countCharts()
if !assert.Equal(t, 1, chartsNum, "Expected 1 chart, actual %d", chartsNum) {
@ -101,3 +96,160 @@ func TestAddDrawingChart(t *testing.T) {
f := NewFile()
assert.EqualError(t, f.addDrawingChart("SheetN", "", "", 0, 0, 0, nil), `cannot convert cell "" to coordinates: invalid cell name ""`)
}
func TestAddChart(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
categories := map[string]string{"A30": "SS", "A31": "S", "A32": "M", "A33": "L", "A34": "LL", "A35": "XL", "A36": "XXL", "A37": "XXXL", "B29": "Apple", "C29": "Orange", "D29": "Pear"}
values := map[string]int{"B30": 1, "C30": 1, "D30": 1, "B31": 2, "C31": 2, "D31": 2, "B32": 3, "C32": 3, "D32": 3, "B33": 4, "C33": 4, "D33": 4, "B34": 5, "C34": 5, "D34": 5, "B35": 6, "C35": 6, "D35": 6, "B36": 7, "C36": 7, "D36": 7, "B37": 8, "C37": 8, "D37": 8}
for k, v := range categories {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
for k, v := range values {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
assert.EqualError(t, f.AddChart("Sheet1", "P1", ""), "unexpected end of JSON input")
// Test add chart on not exists worksheet.
assert.EqualError(t, f.AddChart("SheetN", "P1", "{}"), "sheet SheetN is not exist")
assert.NoError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "X1", `{"type":"colStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "P16", `{"type":"colPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"100% Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "X16", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"3D Clustered Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "P30", `{"type":"col3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "X30", `{"type":"col3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AF1", `{"type":"col3DConeStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AF16", `{"type":"col3DConeClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AF30", `{"type":"col3DConePercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AF45", `{"type":"col3DCone","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AN1", `{"type":"col3DPyramidStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AN16", `{"type":"col3DPyramidClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AN30", `{"type":"col3DPyramidPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AN45", `{"type":"col3DPyramid","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AV1", `{"type":"col3DCylinderStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AV16", `{"type":"col3DCylinderClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AV30", `{"type":"col3DCylinderPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "AV45", `{"type":"col3DCylinder","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet1", "P45", `{"type":"col3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P1", `{"type":"radar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top_right","show_legend_key":false},"title":{"name":"Radar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"span"}`))
assert.NoError(t, f.AddChart("Sheet2", "X1", `{"type":"scatter","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Scatter Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P16", `{"type":"doughnut","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"right","show_legend_key":false},"title":{"name":"Doughnut Chart"},"plotarea":{"show_bubble_size":false,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "X16", `{"type":"line","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37","line":{"width":0.25}}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top","show_legend_key":false},"title":{"name":"Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true,"minor_grid_lines":true,"tick_label_skip":1},"y_axis":{"major_grid_lines":true,"minor_grid_lines":true,"major_unit":1}}`))
assert.NoError(t, f.AddChart("Sheet2", "P32", `{"type":"pie3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"3D Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "X32", `{"type":"pie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"gap"}`))
assert.NoError(t, f.AddChart("Sheet2", "P48", `{"type":"bar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Clustered Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "X48", `{"type":"barStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P64", `{"type":"barPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked 100% Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "X64", `{"type":"bar3DClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Clustered Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P80", `{"type":"bar3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"maximum":7.5,"minimum":0.5}}`))
assert.NoError(t, f.AddChart("Sheet2", "X80", `{"type":"bar3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"reverse_order":true,"maximum":0,"minimum":0},"y_axis":{"reverse_order":true,"maximum":0,"minimum":0}}`))
// area series charts
assert.NoError(t, f.AddChart("Sheet2", "AF1", `{"type":"area","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN1", `{"type":"areaStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AF16", `{"type":"areaPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D 100% Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN16", `{"type":"area3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AF32", `{"type":"area3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN32", `{"type":"area3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
// cylinder series chart
assert.NoError(t, f.AddChart("Sheet2", "AF48", `{"type":"bar3DCylinderStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AF64", `{"type":"bar3DCylinderClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AF80", `{"type":"bar3DCylinderPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
// cone series chart
assert.NoError(t, f.AddChart("Sheet2", "AN48", `{"type":"bar3DConeStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN64", `{"type":"bar3DConeClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AN80", `{"type":"bar3DConePercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AV48", `{"type":"bar3DPyramidStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AV64", `{"type":"bar3DPyramidClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "AV80", `{"type":"bar3DPyramidPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
// surface series chart
assert.NoError(t, f.AddChart("Sheet2", "AV1", `{"type":"surface3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Surface Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"major_grid_lines":true}}`))
assert.NoError(t, f.AddChart("Sheet2", "AV16", `{"type":"wireframeSurface3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Wireframe Surface Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"major_grid_lines":true}}`))
assert.NoError(t, f.AddChart("Sheet2", "AV32", `{"type":"contour","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Contour Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "BD1", `{"type":"wireframeContour","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Wireframe Contour Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
// bubble chart
assert.NoError(t, f.AddChart("Sheet2", "BD16", `{"type":"bubble","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "BD32", `{"type":"bubble3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble 3D Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`))
// pie of pie chart
assert.NoError(t, f.AddChart("Sheet2", "BD48", `{"type":"pieOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Pie of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`))
// bar of pie chart
assert.NoError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`))
// combo chart
f.NewSheet("Combo Charts")
clusteredColumnCombo := map[string][]string{
"A1": {"line", "Clustered Column - Line Chart"},
"I1": {"bubble", "Clustered Column - Bubble Chart"},
"Q1": {"bubble3D", "Clustered Column - Bubble 3D Chart"},
"Y1": {"doughnut", "Clustered Column - Doughnut Chart"},
}
for axis, props := range clusteredColumnCombo {
assert.NoError(t, f.AddChart("Combo Charts", axis, fmt.Sprintf(`{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"%s"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[1]), fmt.Sprintf(`{"type":"%s","series":[{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[0])))
}
stackedAreaCombo := map[string][]string{
"A16": {"line", "Stacked Area - Line Chart"},
"I16": {"bubble", "Stacked Area - Bubble Chart"},
"Q16": {"bubble3D", "Stacked Area - Bubble 3D Chart"},
"Y16": {"doughnut", "Stacked Area - Doughnut Chart"},
}
for axis, props := range stackedAreaCombo {
assert.NoError(t, f.AddChart("Combo Charts", axis, fmt.Sprintf(`{"type":"areaStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"%s"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[1]), fmt.Sprintf(`{"type":"%s","series":[{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[0])))
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx")))
// Test with illegal cell coordinates
assert.EqualError(t, f.AddChart("Sheet2", "A", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
// Test with unsupported chart type
assert.EqualError(t, f.AddChart("Sheet2", "BD32", `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble 3D Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`), "unsupported chart type unknown")
// Test add combo chart with invalid format set
assert.EqualError(t, f.AddChart("Sheet2", "BD32", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`, ""), "unexpected end of JSON input")
// Test add combo chart with unsupported chart type
assert.EqualError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`, `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`), "unsupported chart type unknown")
}
func TestAddChartSheet(t *testing.T) {
categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
f := NewFile()
for k, v := range categories {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
for k, v := range values {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
assert.NoError(t, f.AddChartSheet("Chart1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`))
// Test set the chartsheet as active sheet
var sheetIdx int
for idx, sheetName := range f.GetSheetList() {
if sheetName != "Chart1" {
continue
}
sheetIdx = idx
}
f.SetActiveSheet(sheetIdx)
// Test cell value on chartsheet
assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is chart sheet")
// Test add chartsheet on already existing name sheet
assert.EqualError(t, f.AddChartSheet("Sheet1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), "the same name worksheet already exists")
// Test with unsupported chart type
assert.EqualError(t, f.AddChartSheet("Chart2", `{"type":"unknown","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), "unsupported chart type unknown")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartSheet.xlsx")))
}
func TestDeleteChart(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.DeleteChart("Sheet1", "A1"))
assert.NoError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.DeleteChart("Sheet1", "P1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteChart.xlsx")))
// Test delete chart on not exists worksheet.
assert.EqualError(t, f.DeleteChart("SheetN", "A1"), "sheet SheetN is not exist")
// Test delete chart with invalid coordinates.
assert.EqualError(t, f.DeleteChart("Sheet1", ""), `cannot convert cell "" to coordinates: invalid cell name ""`)
// Test delete chart on no chart worksheet.
assert.NoError(t, NewFile().DeleteChart("Sheet1", "A1"))
}

View File

@ -1,3 +0,0 @@
tenets:
- import: codelingo/effective-go
- import: codelingo/code-review-comments

206
col.go
View File

@ -1,15 +1,21 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "math"
import (
"errors"
"math"
"strings"
"github.com/mohae/deepcopy"
)
// Define the default cell size and EMU unit of measurement.
const (
@ -22,7 +28,7 @@ const (
// worksheet name and column name. For example, get visible state of column D
// in Sheet1:
//
// visiable, err := f.GetColVisible("Sheet1", "D")
// visible, err := f.GetColVisible("Sheet1", "D")
//
func (f *File) GetColVisible(sheet, col string) (bool, error) {
visible := true
@ -48,43 +54,64 @@ func (f *File) GetColVisible(sheet, col string) (bool, error) {
return visible, err
}
// SetColVisible provides a function to set visible of a single column by given
// worksheet name and column name. For example, hide column D in Sheet1:
// SetColVisible provides a function to set visible columns by given worksheet
// name, columns range and visibility.
//
// For example hide column D on Sheet1:
//
// err := f.SetColVisible("Sheet1", "D", false)
//
func (f *File) SetColVisible(sheet, col string, visible bool) error {
colNum, err := ColumnNameToNumber(col)
// Hide the columns from D to F (included):
//
// err := f.SetColVisible("Sheet1", "D:F", false)
//
func (f *File) SetColVisible(sheet, columns string, visible bool) error {
var max int
colsTab := strings.Split(columns, ":")
min, err := ColumnNameToNumber(colsTab[0])
if err != nil {
return err
}
colData := xlsxCol{
Min: colNum,
Max: colNum,
Hidden: !visible,
CustomWidth: true,
if len(colsTab) == 2 {
max, err = ColumnNameToNumber(colsTab[1])
if err != nil {
return err
}
} else {
max = min
}
if max < min {
min, max = max, min
}
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
}
colData := xlsxCol{
Min: min,
Max: max,
Width: 9, // default width
Hidden: !visible,
CustomWidth: true,
}
if xlsx.Cols == nil {
cols := xlsxCols{}
cols.Col = append(cols.Col, colData)
xlsx.Cols = &cols
return err
return nil
}
for v := range xlsx.Cols.Col {
if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max {
colData = xlsx.Cols.Col[v]
}
}
colData.Min = colNum
colData.Max = colNum
colData.Hidden = !visible
colData.CustomWidth = true
xlsx.Cols.Col = append(xlsx.Cols.Col, colData)
return err
xlsx.Cols.Col = flatCols(colData, xlsx.Cols.Col, func(fc, c xlsxCol) xlsxCol {
fc.BestFit = c.BestFit
fc.Collapsed = c.Collapsed
fc.CustomWidth = c.CustomWidth
fc.OutlineLevel = c.OutlineLevel
fc.Phonetic = c.Phonetic
fc.Style = c.Style
fc.Width = c.Width
return fc
})
return nil
}
// GetColOutlineLevel provides a function to get outline level of a single
@ -116,12 +143,15 @@ func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) {
}
// SetColOutlineLevel provides a function to set outline level of a single
// column by given worksheet name and column name. For example, set outline
// level of column D in Sheet1 to 2:
// column by given worksheet name and column name. The value of parameter
// 'level' is 1-7. For example, set outline level of column D in Sheet1 to 2:
//
// err := f.SetColOutlineLevel("Sheet1", "D", 2)
//
func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error {
if level > 7 || level < 1 {
return errors.New("invalid outline level")
}
colNum, err := ColumnNameToNumber(col)
if err != nil {
return err
@ -142,19 +172,76 @@ func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error {
xlsx.Cols = &cols
return err
}
for v := range xlsx.Cols.Col {
if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max {
colData = xlsx.Cols.Col[v]
}
}
colData.Min = colNum
colData.Max = colNum
colData.OutlineLevel = level
colData.CustomWidth = true
xlsx.Cols.Col = append(xlsx.Cols.Col, colData)
xlsx.Cols.Col = flatCols(colData, xlsx.Cols.Col, func(fc, c xlsxCol) xlsxCol {
fc.BestFit = c.BestFit
fc.Collapsed = c.Collapsed
fc.CustomWidth = c.CustomWidth
fc.Hidden = c.Hidden
fc.Phonetic = c.Phonetic
fc.Style = c.Style
fc.Width = c.Width
return fc
})
return err
}
// SetColStyle provides a function to set style of columns by given worksheet
// name, columns range and style ID.
//
// For example set style of column H on Sheet1:
//
// err = f.SetColStyle("Sheet1", "H", style)
//
// Set style of columns C:F on Sheet1:
//
// err = f.SetColStyle("Sheet1", "C:F", style)
//
func (f *File) SetColStyle(sheet, columns string, styleID int) error {
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
}
var c1, c2 string
var min, max int
cols := strings.Split(columns, ":")
c1 = cols[0]
min, err = ColumnNameToNumber(c1)
if err != nil {
return err
}
if len(cols) == 2 {
c2 = cols[1]
max, err = ColumnNameToNumber(c2)
if err != nil {
return err
}
} else {
max = min
}
if max < min {
min, max = max, min
}
if xlsx.Cols == nil {
xlsx.Cols = &xlsxCols{}
}
xlsx.Cols.Col = flatCols(xlsxCol{
Min: min,
Max: max,
Width: 9,
Style: styleID,
}, xlsx.Cols.Col, func(fc, c xlsxCol) xlsxCol {
fc.BestFit = c.BestFit
fc.Collapsed = c.Collapsed
fc.CustomWidth = c.CustomWidth
fc.Hidden = c.Hidden
fc.OutlineLevel = c.OutlineLevel
fc.Phonetic = c.Phonetic
fc.Width = c.Width
return fc
})
return nil
}
// SetColWidth provides a function to set the width of a single column or
// multiple columns. For example:
//
@ -184,16 +271,55 @@ func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) error
Width: width,
CustomWidth: true,
}
if xlsx.Cols != nil {
xlsx.Cols.Col = append(xlsx.Cols.Col, col)
} else {
if xlsx.Cols == nil {
cols := xlsxCols{}
cols.Col = append(cols.Col, col)
xlsx.Cols = &cols
return err
}
xlsx.Cols.Col = flatCols(col, xlsx.Cols.Col, func(fc, c xlsxCol) xlsxCol {
fc.BestFit = c.BestFit
fc.Collapsed = c.Collapsed
fc.Hidden = c.Hidden
fc.OutlineLevel = c.OutlineLevel
fc.Phonetic = c.Phonetic
fc.Style = c.Style
return fc
})
return err
}
// flatCols provides a method for the column's operation functions to flatten
// and check the worksheet columns.
func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) []xlsxCol {
fc := []xlsxCol{}
for i := col.Min; i <= col.Max; i++ {
c := deepcopy.Copy(col).(xlsxCol)
c.Min, c.Max = i, i
fc = append(fc, c)
}
inFlat := func(colID int, cols []xlsxCol) (int, bool) {
for idx, c := range cols {
if c.Max == colID && c.Min == colID {
return idx, true
}
}
return -1, false
}
for _, column := range cols {
for i := column.Min; i <= column.Max; i++ {
if idx, ok := inFlat(i, fc); ok {
fc[idx] = replacer(fc[idx], column)
continue
}
c := deepcopy.Copy(column).(xlsxCol)
c.Min, c.Max = i, i
fc = append(fc, c)
}
}
return fc
}
// positionObjectPixels calculate the vertices that define the position of a
// graphical object within the worksheet in pixels.
//

211
col_test.go Normal file
View File

@ -0,0 +1,211 @@
package excelize
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestColumnVisibility(t *testing.T) {
t.Run("TestBook1", func(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
// Hide/display a column with SetColVisible
assert.NoError(t, f.SetColVisible("Sheet1", "F", false))
assert.NoError(t, f.SetColVisible("Sheet1", "F", true))
visible, err := f.GetColVisible("Sheet1", "F")
assert.Equal(t, true, visible)
assert.NoError(t, err)
// Test hiding a few columns SetColVisible(...false)...
assert.NoError(t, f.SetColVisible("Sheet1", "F:V", false))
visible, err = f.GetColVisible("Sheet1", "F")
assert.Equal(t, false, visible)
assert.NoError(t, err)
visible, err = f.GetColVisible("Sheet1", "U")
assert.Equal(t, false, visible)
assert.NoError(t, err)
visible, err = f.GetColVisible("Sheet1", "V")
assert.Equal(t, false, visible)
assert.NoError(t, err)
// ...and displaying them back SetColVisible(...true)
assert.NoError(t, f.SetColVisible("Sheet1", "V:F", true))
visible, err = f.GetColVisible("Sheet1", "F")
assert.Equal(t, true, visible)
assert.NoError(t, err)
visible, err = f.GetColVisible("Sheet1", "U")
assert.Equal(t, true, visible)
assert.NoError(t, err)
visible, err = f.GetColVisible("Sheet1", "G")
assert.Equal(t, true, visible)
assert.NoError(t, err)
// Test get column visible on an inexistent worksheet.
_, err = f.GetColVisible("SheetN", "F")
assert.EqualError(t, err, "sheet SheetN is not exist")
// Test get column visible with illegal cell coordinates.
_, err = f.GetColVisible("Sheet1", "*")
assert.EqualError(t, err, `invalid column name "*"`)
assert.EqualError(t, f.SetColVisible("Sheet1", "*", false), `invalid column name "*"`)
f.NewSheet("Sheet3")
assert.NoError(t, f.SetColVisible("Sheet3", "E", false))
assert.EqualError(t, f.SetColVisible("Sheet1", "A:-1", true), "invalid column name \"-1\"")
assert.EqualError(t, f.SetColVisible("SheetN", "E", false), "sheet SheetN is not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestColumnVisibility.xlsx")))
})
t.Run("TestBook3", func(t *testing.T) {
f, err := prepareTestBook3()
assert.NoError(t, err)
visible, err := f.GetColVisible("Sheet1", "B")
assert.Equal(t, true, visible)
assert.NoError(t, err)
})
}
func TestOutlineLevel(t *testing.T) {
f := NewFile()
level, err := f.GetColOutlineLevel("Sheet1", "D")
assert.Equal(t, uint8(0), level)
assert.NoError(t, err)
f.NewSheet("Sheet2")
assert.NoError(t, f.SetColOutlineLevel("Sheet1", "D", 4))
level, err = f.GetColOutlineLevel("Sheet1", "D")
assert.Equal(t, uint8(4), level)
assert.NoError(t, err)
level, err = f.GetColOutlineLevel("Shee2", "A")
assert.Equal(t, uint8(0), level)
assert.EqualError(t, err, "sheet Shee2 is not exist")
assert.NoError(t, f.SetColWidth("Sheet2", "A", "D", 13))
assert.NoError(t, f.SetColOutlineLevel("Sheet2", "B", 2))
assert.NoError(t, f.SetRowOutlineLevel("Sheet1", 2, 7))
assert.EqualError(t, f.SetColOutlineLevel("Sheet1", "D", 8), "invalid outline level")
assert.EqualError(t, f.SetRowOutlineLevel("Sheet1", 2, 8), "invalid outline level")
// Test set row outline level on not exists worksheet.
assert.EqualError(t, f.SetRowOutlineLevel("SheetN", 1, 4), "sheet SheetN is not exist")
// Test get row outline level on not exists worksheet.
_, err = f.GetRowOutlineLevel("SheetN", 1)
assert.EqualError(t, err, "sheet SheetN is not exist")
// Test set and get column outline level with illegal cell coordinates.
assert.EqualError(t, f.SetColOutlineLevel("Sheet1", "*", 1), `invalid column name "*"`)
_, err = f.GetColOutlineLevel("Sheet1", "*")
assert.EqualError(t, err, `invalid column name "*"`)
// Test set column outline level on not exists worksheet.
assert.EqualError(t, f.SetColOutlineLevel("SheetN", "E", 2), "sheet SheetN is not exist")
assert.EqualError(t, f.SetRowOutlineLevel("Sheet1", 0, 1), "invalid row number 0")
level, err = f.GetRowOutlineLevel("Sheet1", 2)
assert.NoError(t, err)
assert.Equal(t, uint8(7), level)
_, err = f.GetRowOutlineLevel("Sheet1", 0)
assert.EqualError(t, err, `invalid row number 0`)
level, err = f.GetRowOutlineLevel("Sheet1", 10)
assert.NoError(t, err)
assert.Equal(t, uint8(0), level)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestOutlineLevel.xlsx")))
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetColOutlineLevel("Sheet2", "B", 2))
}
func TestSetColStyle(t *testing.T) {
f := NewFile()
style, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#94d3a2"],"pattern":1}}`)
assert.NoError(t, err)
// Test set column style on not exists worksheet.
assert.EqualError(t, f.SetColStyle("SheetN", "E", style), "sheet SheetN is not exist")
// Test set column style with illegal cell coordinates.
assert.EqualError(t, f.SetColStyle("Sheet1", "*", style), `invalid column name "*"`)
assert.EqualError(t, f.SetColStyle("Sheet1", "A:*", style), `invalid column name "*"`)
assert.NoError(t, f.SetColStyle("Sheet1", "B", style))
// Test set column style with already exists column with style.
assert.NoError(t, f.SetColStyle("Sheet1", "B", style))
assert.NoError(t, f.SetColStyle("Sheet1", "D:C", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetColStyle.xlsx")))
}
func TestColWidth(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetColWidth("Sheet1", "B", "A", 12))
assert.NoError(t, f.SetColWidth("Sheet1", "A", "B", 12))
width, err := f.GetColWidth("Sheet1", "A")
assert.Equal(t, float64(12), width)
assert.NoError(t, err)
width, err = f.GetColWidth("Sheet1", "C")
assert.Equal(t, float64(64), width)
assert.NoError(t, err)
// Test set and get column width with illegal cell coordinates.
width, err = f.GetColWidth("Sheet1", "*")
assert.Equal(t, float64(64), width)
assert.EqualError(t, err, `invalid column name "*"`)
assert.EqualError(t, f.SetColWidth("Sheet1", "*", "B", 1), `invalid column name "*"`)
assert.EqualError(t, f.SetColWidth("Sheet1", "A", "*", 1), `invalid column name "*"`)
// Test set column width on not exists worksheet.
assert.EqualError(t, f.SetColWidth("SheetN", "B", "A", 12), "sheet SheetN is not exist")
// Test get column width on not exists worksheet.
_, err = f.GetColWidth("SheetN", "A")
assert.EqualError(t, err, "sheet SheetN is not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestColWidth.xlsx")))
convertRowHeightToPixels(0)
}
func TestInsertCol(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
fillCells(f, sheet1, 10, 10)
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))
assert.NoError(t, f.MergeCell(sheet1, "A1", "C3"))
assert.NoError(t, f.AutoFilter(sheet1, "A2", "B2", `{"column":"B","expression":"x != blanks"}`))
assert.NoError(t, f.InsertCol(sheet1, "A"))
// Test insert column with illegal cell coordinates.
assert.EqualError(t, f.InsertCol("Sheet1", "*"), `invalid column name "*"`)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertCol.xlsx")))
}
func TestRemoveCol(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
fillCells(f, sheet1, 10, 15)
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))
assert.NoError(t, f.SetCellHyperLink(sheet1, "C5", "https://github.com", "External"))
assert.NoError(t, f.MergeCell(sheet1, "A1", "B1"))
assert.NoError(t, f.MergeCell(sheet1, "A2", "B2"))
assert.NoError(t, f.RemoveCol(sheet1, "A"))
assert.NoError(t, f.RemoveCol(sheet1, "A"))
// Test remove column with illegal cell coordinates.
assert.EqualError(t, f.RemoveCol("Sheet1", "*"), `invalid column name "*"`)
// Test remove column on not exists worksheet.
assert.EqualError(t, f.RemoveCol("SheetN", "B"), "sheet SheetN is not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveCol.xlsx")))
}

View File

@ -1,18 +1,22 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"log"
"path/filepath"
"strconv"
"strings"
)
@ -32,8 +36,8 @@ func parseFormatCommentsSet(formatSet string) (*formatComment, error) {
// the worksheet comments.
func (f *File) GetComments() (comments map[string][]Comment) {
comments = map[string][]Comment{}
for n := range f.sheetMap {
if d := f.commentsReader("xl" + strings.TrimPrefix(f.getSheetComments(f.GetSheetIndex(n)), "..")); d != nil {
for n, path := range f.sheetMap {
if d := f.commentsReader("xl" + strings.TrimPrefix(f.getSheetComments(filepath.Base(path)), "..")); d != nil {
sheetComments := []Comment{}
for _, comment := range d.CommentList.Comment {
sheetComment := Comment{}
@ -42,8 +46,13 @@ func (f *File) GetComments() (comments map[string][]Comment) {
}
sheetComment.Ref = comment.Ref
sheetComment.AuthorID = comment.AuthorID
if comment.Text.T != nil {
sheetComment.Text += *comment.Text.T
}
for _, text := range comment.Text.R {
sheetComment.Text += text.T
if text.T != nil {
sheetComment.Text += text.T.Val
}
}
sheetComments = append(sheetComments, sheetComment)
}
@ -54,10 +63,10 @@ func (f *File) GetComments() (comments map[string][]Comment) {
}
// getSheetComments provides the method to get the target comment reference by
// given worksheet index.
func (f *File) getSheetComments(sheetID int) string {
var rels = "xl/worksheets/_rels/sheet" + strconv.Itoa(sheetID) + ".xml.rels"
if sheetRels := f.workSheetRelsReader(rels); sheetRels != nil {
// given worksheet file path.
func (f *File) getSheetComments(sheetFile string) string {
var rels = "xl/worksheets/_rels/" + sheetFile + ".rels"
if sheetRels := f.relsReader(rels); sheetRels != nil {
for _, v := range sheetRels.Relationships {
if v.Type == SourceRelationshipComments {
return v.Target
@ -95,12 +104,12 @@ func (f *File) AddComment(sheet, cell, format string) error {
drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1)
} else {
// Add first comment for given sheet.
rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
f.addSheetRelationships(sheet, SourceRelationshipComments, sheetRelationshipsComments, "")
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
f.addSheetLegacyDrawing(sheet, rID)
}
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
f.addComment(commentsXML, cell, formatSet)
var colCount int
for i, l := range strings.Split(formatSet.Text, "\n") {
if ll := len(l); ll > colCount {
@ -114,6 +123,7 @@ func (f *File) AddComment(sheet, cell, format string) error {
if err != nil {
return err
}
f.addComment(commentsXML, cell, formatSet)
f.addContentTypePart(commentID, "comments")
return err
}
@ -239,6 +249,7 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
},
}
}
defaultFont := f.GetDefaultFont()
cmt := xlsxComment{
Ref: cell,
AuthorID: 0,
@ -247,25 +258,25 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
{
RPr: &xlsxRPr{
B: " ",
Sz: &attrValFloat{Val: 9},
Sz: &attrValFloat{Val: float64Ptr(9)},
Color: &xlsxColor{
Indexed: 81,
},
RFont: &attrValString{Val: "Calibri"},
Family: &attrValInt{Val: 2},
RFont: &attrValString{Val: stringPtr(defaultFont)},
Family: &attrValInt{Val: intPtr(2)},
},
T: a,
T: &xlsxT{Val: a},
},
{
RPr: &xlsxRPr{
Sz: &attrValFloat{Val: 9},
Sz: &attrValFloat{Val: float64Ptr(9)},
Color: &xlsxColor{
Indexed: 81,
},
RFont: &attrValString{Val: "Calibri"},
Family: &attrValInt{Val: 2},
RFont: &attrValString{Val: stringPtr(defaultFont)},
Family: &attrValInt{Val: intPtr(2)},
},
T: t,
T: &xlsxT{Val: t},
},
},
},
@ -277,24 +288,36 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
// countComments provides a function to get comments files count storage in
// the folder xl.
func (f *File) countComments() int {
count := 0
c1, c2 := 0, 0
for k := range f.XLSX {
if strings.Contains(k, "xl/comments") {
count++
c1++
}
}
return count
for rel := range f.Comments {
if strings.Contains(rel, "xl/comments") {
c2++
}
}
if c1 < c2 {
return c2
}
return c1
}
// decodeVMLDrawingReader provides a function to get the pointer to the
// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing {
var err error
if f.DecodeVMLDrawing[path] == nil {
c, ok := f.XLSX[path]
if ok {
d := decodeVmlDrawing{}
_ = xml.Unmarshal(namespaceStrictToTransitional(c), &d)
f.DecodeVMLDrawing[path] = &d
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c))).
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
}
}
return f.DecodeVMLDrawing[path]
@ -314,12 +337,16 @@ func (f *File) vmlDrawingWriter() {
// commentsReader provides a function to get the pointer to the structure
// after deserialization of xl/comments%d.xml.
func (f *File) commentsReader(path string) *xlsxComments {
var err error
if f.Comments[path] == nil {
content, ok := f.XLSX[path]
if ok {
c := xlsxComments{}
_ = xml.Unmarshal(namespaceStrictToTransitional(content), &c)
f.Comments[path] = &c
f.Comments[path] = new(xlsxComments)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content))).
Decode(f.Comments[path]); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
}
}
return f.Comments[path]

63
comment_test.go Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddComments(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
s := strings.Repeat("c", 32768)
assert.NoError(t, f.AddComment("Sheet1", "A30", `{"author":"`+s+`","text":"`+s+`"}`))
assert.NoError(t, f.AddComment("Sheet2", "B7", `{"author":"Excelize: ","text":"This is a comment."}`))
// Test add comment on not exists worksheet.
assert.EqualError(t, f.AddComment("SheetN", "B7", `{"author":"Excelize: ","text":"This is a comment."}`), "sheet SheetN is not exist")
// Test add comment on with illegal cell coordinates
assert.EqualError(t, f.AddComment("Sheet1", "A", `{"author":"Excelize: ","text":"This is a comment."}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) {
assert.Len(t, f.GetComments(), 2)
}
f.Comments["xl/comments2.xml"] = nil
f.XLSX["xl/comments2.xml"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors><author>Excelize: </author></authors><commentList><comment ref="B7" authorId="0"><text><t>Excelize: </t></text></comment></commentList></comments>`)
comments := f.GetComments()
assert.EqualValues(t, 2, len(comments["Sheet1"]))
assert.EqualValues(t, 1, len(comments["Sheet2"]))
}
func TestDecodeVMLDrawingReader(t *testing.T) {
f := NewFile()
path := "xl/drawings/vmlDrawing1.xml"
f.XLSX[path] = MacintoshCyrillicCharset
f.decodeVMLDrawingReader(path)
}
func TestCommentsReader(t *testing.T) {
f := NewFile()
path := "xl/comments1.xml"
f.XLSX[path] = MacintoshCyrillicCharset
f.commentsReader(path)
}
func TestCountComments(t *testing.T) {
f := NewFile()
f.Comments["xl/comments1.xml"] = nil
assert.Equal(t, f.countComments(), 1)
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -112,7 +112,7 @@ func (dd *DataValidation) SetDropList(keys []string) error {
if dataValidationFormulaStrLen < len(formula) {
return fmt.Errorf(dataValidationFormulaStrLenErr)
}
dd.Formula1 = formula
dd.Formula1 = fmt.Sprintf("<formula1>%s</formula1>", formula)
dd.Type = convDataValidationType(typeList)
return nil
}
@ -121,12 +121,12 @@ func (dd *DataValidation) SetDropList(keys []string) error {
func (dd *DataValidation) SetRange(f1, f2 int, t DataValidationType, o DataValidationOperator) error {
formula1 := fmt.Sprintf("%d", f1)
formula2 := fmt.Sprintf("%d", f2)
if dataValidationFormulaStrLen < len(dd.Formula1) || dataValidationFormulaStrLen < len(dd.Formula2) {
if dataValidationFormulaStrLen+21 < len(dd.Formula1) || dataValidationFormulaStrLen+21 < len(dd.Formula2) {
return fmt.Errorf(dataValidationFormulaStrLenErr)
}
dd.Formula1 = formula1
dd.Formula2 = formula2
dd.Formula1 = fmt.Sprintf("<formula1>%s</formula1>", formula1)
dd.Formula2 = fmt.Sprintf("<formula2>%s</formula2>", formula2)
dd.Type = convDataValidationType(t)
dd.Operator = convDataValidationOperatior(o)
return nil
@ -141,12 +141,12 @@ func (dd *DataValidation) SetRange(f1, f2 int, t DataValidationType, o DataValid
//
// dvRange := excelize.NewDataValidation(true)
// dvRange.Sqref = "A7:B8"
// dvRange.SetSqrefDropList("E1:E3", true)
// dvRange.SetSqrefDropList("$E$1:$E$3", true)
// f.AddDataValidation("Sheet1", dvRange)
//
func (dd *DataValidation) SetSqrefDropList(sqref string, isCurrentSheet bool) error {
if isCurrentSheet {
dd.Formula1 = sqref
dd.Formula1 = fmt.Sprintf("<formula1>%s</formula1>", sqref)
dd.Type = convDataValidationType(typeList)
return nil
}
@ -228,14 +228,38 @@ func convDataValidationOperatior(o DataValidationOperator) string {
// err = f.AddDataValidation("Sheet1", dvRange)
//
func (f *File) AddDataValidation(sheet string, dv *DataValidation) error {
xlsx, err := f.workSheetReader(sheet)
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if nil == xlsx.DataValidations {
xlsx.DataValidations = new(xlsxDataValidations)
if nil == ws.DataValidations {
ws.DataValidations = new(xlsxDataValidations)
}
xlsx.DataValidations.DataValidation = append(xlsx.DataValidations.DataValidation, dv)
xlsx.DataValidations.Count = len(xlsx.DataValidations.DataValidation)
ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, dv)
ws.DataValidations.Count = len(ws.DataValidations.DataValidation)
return err
}
// DeleteDataValidation delete data validation by given worksheet name and
// reference sequence.
func (f *File) DeleteDataValidation(sheet, sqref string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if ws.DataValidations == nil {
return nil
}
dv := ws.DataValidations
for i := 0; i < len(dv.DataValidation); i++ {
if dv.DataValidation[i].Sqref == sqref {
dv.DataValidation = append(dv.DataValidation[:i], dv.DataValidation[i+1:]...)
i--
}
}
dv.Count = len(dv.DataValidation)
if dv.Count == 0 {
ws.DataValidations = nil
}
return nil
}

View File

@ -1,16 +1,17 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -19,57 +20,49 @@ import (
func TestDataValidation(t *testing.T) {
resultFile := filepath.Join("test", "TestDataValidation.xlsx")
xlsx := NewFile()
f := NewFile()
dvRange := NewDataValidation(true)
dvRange.Sqref = "A1:B2"
dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween))
dvRange.SetError(DataValidationErrorStyleStop, "error title", "error body")
dvRange.SetError(DataValidationErrorStyleWarning, "error title", "error body")
dvRange.SetError(DataValidationErrorStyleInformation, "error title", "error body")
xlsx.AddDataValidation("Sheet1", dvRange)
if !assert.NoError(t, xlsx.SaveAs(resultFile)) {
t.FailNow()
}
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
assert.NoError(t, f.SaveAs(resultFile))
dvRange = NewDataValidation(true)
dvRange.Sqref = "A3:B4"
dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
dvRange.SetInput("input title", "input body")
xlsx.AddDataValidation("Sheet1", dvRange)
if !assert.NoError(t, xlsx.SaveAs(resultFile)) {
t.FailNow()
}
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
assert.NoError(t, f.SaveAs(resultFile))
dvRange = NewDataValidation(true)
dvRange.Sqref = "A5:B6"
dvRange.SetDropList([]string{"1", "2", "3"})
xlsx.AddDataValidation("Sheet1", dvRange)
if !assert.NoError(t, xlsx.SaveAs(resultFile)) {
t.FailNow()
}
assert.NoError(t, dvRange.SetDropList([]string{"1", "2", "3"}))
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
assert.NoError(t, f.SaveAs(resultFile))
}
func TestDataValidationError(t *testing.T) {
resultFile := filepath.Join("test", "TestDataValidationError.xlsx")
xlsx := NewFile()
xlsx.SetCellStr("Sheet1", "E1", "E1")
xlsx.SetCellStr("Sheet1", "E2", "E2")
xlsx.SetCellStr("Sheet1", "E3", "E3")
f := NewFile()
assert.NoError(t, f.SetCellStr("Sheet1", "E1", "E1"))
assert.NoError(t, f.SetCellStr("Sheet1", "E2", "E2"))
assert.NoError(t, f.SetCellStr("Sheet1", "E3", "E3"))
dvRange := NewDataValidation(true)
dvRange.SetSqref("A7:B8")
dvRange.SetSqref("A7:B8")
dvRange.SetSqrefDropList("$E$1:$E$3", true)
assert.NoError(t, dvRange.SetSqrefDropList("$E$1:$E$3", true))
err := dvRange.SetSqrefDropList("$E$1:$E$3", false)
assert.EqualError(t, err, "cross-sheet sqref cell are not supported")
xlsx.AddDataValidation("Sheet1", dvRange)
if !assert.NoError(t, xlsx.SaveAs(resultFile)) {
t.FailNow()
}
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
assert.NoError(t, f.SaveAs(resultFile))
dvRange = NewDataValidation(true)
err = dvRange.SetDropList(make([]string, 258))
@ -78,11 +71,34 @@ func TestDataValidationError(t *testing.T) {
return
}
assert.EqualError(t, err, "data validation must be 0-255 characters")
dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
dvRange.SetSqref("A9:B10")
xlsx.AddDataValidation("Sheet1", dvRange)
if !assert.NoError(t, xlsx.SaveAs(resultFile)) {
t.FailNow()
}
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
assert.NoError(t, f.SaveAs(resultFile))
// Test width invalid data validation formula.
dvRange.Formula1 = strings.Repeat("s", dataValidationFormulaStrLen+22)
assert.EqualError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan), "data validation must be 0-255 characters")
// Test add data validation on no exists worksheet.
f = NewFile()
assert.EqualError(t, f.AddDataValidation("SheetN", nil), "sheet SheetN is not exist")
}
func TestDeleteDataValidation(t *testing.T) {
f := NewFile()
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2"))
dvRange := NewDataValidation(true)
dvRange.Sqref = "A1:B2"
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween))
dvRange.SetInput("input title", "input body")
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteDataValidation.xlsx")))
// Test delete data validation on no exists worksheet.
assert.EqualError(t, f.DeleteDataValidation("SheetN", "A1:B2"), "sheet SheetN is not exist")
}

12
date.go
View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -172,3 +172,11 @@ func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
durationPart := time.Duration(dayNanoSeconds * floatPart)
return date.Add(durationDays).Add(durationPart)
}
// ExcelDateToTime converts a float-based excel date representation to a time.Time.
func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
if excelDate < 0 {
return time.Time{}, newInvalidExcelDateError(excelDate)
}
return timeFromExcelTime(excelDate, use1904Format), nil
}

View File

@ -28,6 +28,14 @@ var trueExpectedDateList = []dateTest{
{401769.00000000000, time.Date(3000, time.January, 1, 0, 0, 0, 0, time.UTC)},
}
var excelTimeInputList = []dateTest{
{0.0, time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)},
{60.0, time.Date(1900, 2, 28, 0, 0, 0, 0, time.UTC)},
{61.0, time.Date(1900, 3, 1, 0, 0, 0, 0, time.UTC)},
{41275.0, time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC)},
{401769.0, time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)},
}
func TestTimeToExcelTime(t *testing.T) {
for i, test := range trueExpectedDateList {
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
@ -53,15 +61,7 @@ func TestTimeToExcelTime_Timezone(t *testing.T) {
}
func TestTimeFromExcelTime(t *testing.T) {
trueExpectedInputList := []dateTest{
{0.0, time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)},
{60.0, time.Date(1900, 2, 28, 0, 0, 0, 0, time.UTC)},
{61.0, time.Date(1900, 3, 1, 0, 0, 0, 0, time.UTC)},
{41275.0, time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC)},
{401769.0, time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)},
}
for i, test := range trueExpectedInputList {
for i, test := range excelTimeInputList {
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
assert.Equal(t, test.GoValue, timeFromExcelTime(test.ExcelValue, false))
})
@ -73,3 +73,17 @@ func TestTimeFromExcelTime_1904(t *testing.T) {
timeFromExcelTime(61, true)
timeFromExcelTime(62, true)
}
func TestExcelDateToTime(t *testing.T) {
// Check normal case
for i, test := range excelTimeInputList {
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
timeValue, err := ExcelDateToTime(test.ExcelValue, false)
assert.Equal(t, test.GoValue, timeValue)
assert.NoError(t, err)
})
}
// Check error case
_, err := ExcelDateToTime(-1, false)
assert.EqualError(t, err, "invalid date value -1.000000, negative values are not supported supported")
}

156
docProps.go Normal file
View File

@ -0,0 +1,156 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"reflect"
)
// SetDocProps provides a function to set document core properties. The
// properties that can be set are:
//
// Property | Description
// ----------------+-----------------------------------------------------------------------------
// Title | The name given to the resource.
// |
// Subject | The topic of the content of the resource.
// |
// Creator | An entity primarily responsible for making the content of the resource.
// |
// Keywords | A delimited set of keywords to support searching and indexing. This is
// | typically a list of terms that are not available elsewhere in the properties.
// |
// Description | An explanation of the content of the resource.
// |
// LastModifiedBy | The user who performed the last modification. The identification is
// | environment-specific.
// |
// Language | The language of the intellectual content of the resource.
// |
// Identifier | An unambiguous reference to the resource within a given context.
// |
// Revision | The topic of the content of the resource.
// |
// ContentStatus | The status of the content. For example: Values might include "Draft",
// | "Reviewed" and "Final"
// |
// Category | A categorization of the content of this package.
// |
// Version | The version number. This value is set by the user or by the application.
//
// For example:
//
// err := f.SetDocProps(&excelize.DocProperties{
// Category: "category",
// ContentStatus: "Draft",
// Created: "2019-06-04T22:00:10Z",
// Creator: "Go Excelize",
// Description: "This file created by Go Excelize",
// Identifier: "xlsx",
// Keywords: "Spreadsheet",
// LastModifiedBy: "Go Author",
// Modified: "2019-06-04T22:00:10Z",
// Revision: "0",
// Subject: "Test Subject",
// Title: "Test Title",
// Language: "en-US",
// Version: "1.0.0",
// })
//
func (f *File) SetDocProps(docProperties *DocProperties) (err error) {
var (
core *decodeCoreProperties
newProps *xlsxCoreProperties
fields []string
output []byte
immutable, mutable reflect.Value
field, val string
)
core = new(decodeCoreProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("docProps/core.xml")))).
Decode(core); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
}
newProps, err = &xlsxCoreProperties{
Dc: NameSpaceDublinCore,
Dcterms: NameSpaceDublinCoreTerms,
Dcmitype: NameSpaceDublinCoreMetadataIntiative,
XSI: NameSpaceXMLSchemaInstance,
Title: core.Title,
Subject: core.Subject,
Creator: core.Creator,
Keywords: core.Keywords,
Description: core.Description,
LastModifiedBy: core.LastModifiedBy,
Language: core.Language,
Identifier: core.Identifier,
Revision: core.Revision,
ContentStatus: core.ContentStatus,
Category: core.Category,
Version: core.Version,
}, nil
newProps.Created.Text, newProps.Created.Type, newProps.Modified.Text, newProps.Modified.Type =
core.Created.Text, core.Created.Type, core.Modified.Text, core.Modified.Type
fields = []string{
"Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords",
"LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version",
}
immutable, mutable = reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem()
for _, field = range fields {
if val = immutable.FieldByName(field).String(); val != "" {
mutable.FieldByName(field).SetString(val)
}
}
if docProperties.Created != "" {
newProps.Created.Text = docProperties.Created
}
if docProperties.Modified != "" {
newProps.Modified.Text = docProperties.Modified
}
output, err = xml.Marshal(newProps)
f.saveFileList("docProps/core.xml", output)
return
}
// GetDocProps provides a function to get document core properties.
func (f *File) GetDocProps() (ret *DocProperties, err error) {
var core = new(decodeCoreProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("docProps/core.xml")))).
Decode(core); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
}
ret, err = &DocProperties{
Category: core.Category,
ContentStatus: core.ContentStatus,
Created: core.Created.Text,
Creator: core.Creator,
Description: core.Description,
Identifier: core.Identifier,
Keywords: core.Keywords,
LastModifiedBy: core.LastModifiedBy,
Modified: core.Modified.Text,
Revision: core.Revision,
Subject: core.Subject,
Title: core.Title,
Language: core.Language,
Version: core.Version,
}, nil
return
}

69
docProps_test.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
var MacintoshCyrillicCharset = []byte{0x8F, 0xF0, 0xE8, 0xE2, 0xE5, 0xF2, 0x20, 0xEC, 0xE8, 0xF0}
func TestSetDocProps(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.SetDocProps(&DocProperties{
Category: "category",
ContentStatus: "Draft",
Created: "2019-06-04T22:00:10Z",
Creator: "Go Excelize",
Description: "This file created by Go Excelize",
Identifier: "xlsx",
Keywords: "Spreadsheet",
LastModifiedBy: "Go Author",
Modified: "2019-06-04T22:00:10Z",
Revision: "0",
Subject: "Test Subject",
Title: "Test Title",
Language: "en-US",
Version: "1.0.0",
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx")))
f.XLSX["docProps/core.xml"] = nil
assert.NoError(t, f.SetDocProps(&DocProperties{}))
// Test unsupport charset
f = NewFile()
f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset
assert.EqualError(t, f.SetDocProps(&DocProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8")
}
func TestGetDocProps(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
props, err := f.GetDocProps()
assert.NoError(t, err)
assert.Equal(t, props.Creator, "Microsoft Office User")
f.XLSX["docProps/core.xml"] = nil
_, err = f.GetDocProps()
assert.NoError(t, err)
// Test unsupport charset
f = NewFile()
f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset
_, err = f.GetDocProps()
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
}

1305
drawing.go Normal file

File diff suppressed because it is too large Load Diff

27
drawing_test.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"testing"
)
func TestDrawingParser(t *testing.T) {
f := File{
Drawings: make(map[string]*xlsxWsDr),
XLSX: map[string][]byte{
"charset": MacintoshCyrillicCharset,
"wsDr": []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`)},
}
// Test with one cell anchor
f.drawingParser("wsDr")
// Test with unsupport charset
f.drawingParser("charset")
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -22,3 +22,7 @@ func newInvalidRowNumberError(row int) error {
func newInvalidCellNameError(cell string) error {
return fmt.Errorf("invalid cell name %q", cell)
}
func newInvalidExcelDateError(dateValue float64) error {
return fmt.Errorf("invalid date value %f, negative values are not supported supported", dateValue)
}

View File

@ -19,3 +19,7 @@ func TestNewInvalidCellNameError(t *testing.T) {
assert.EqualError(t, newInvalidCellNameError("A"), "invalid cell name \"A\"")
assert.EqualError(t, newInvalidCellNameError(""), "invalid cell name \"\"")
}
func TestNewInvalidExcelDateError(t *testing.T) {
assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported supported")
}

View File

@ -1,11 +1,13 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
// spreadsheet documents generated by Microsoft Exce™ 2007 and later. Supports
// complex components by high compatibility, and provided streaming API for
// generating or reading data from a worksheet with huge amounts of data. This
// library needs Go version 1.10 or later.
//
// See https://xuri.me/excelize for more information about this package.
package excelize
@ -14,22 +16,25 @@ import (
"archive/zip"
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"golang.org/x/net/html/charset"
)
// File define a populated XLSX file struct.
// File define a populated spreadsheet file struct.
type File struct {
checked map[string]bool
sheetMap map[string]string
CalcChain *xlsxCalcChain
Comments map[string]*xlsxComments
ContentTypes *xlsxTypes
DrawingRels map[string]*xlsxWorkbookRels
Drawings map[string]*xlsxWsDr
Path string
SharedStrings *xlsxSST
@ -40,13 +45,15 @@ type File struct {
DecodeVMLDrawing map[string]*decodeVmlDrawing
VMLDrawing map[string]*vmlDrawing
WorkBook *xlsxWorkbook
WorkBookRels *xlsxWorkbookRels
WorkSheetRels map[string]*xlsxWorkbookRels
Relationships map[string]*xlsxRelationships
XLSX map[string][]byte
CharsetReader charsetTranscoderFn
}
// OpenFile take the name of an XLSX file and returns a populated XLSX file
// struct for it.
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
// OpenFile take the name of an spreadsheet file and returns a populated
// spreadsheet file struct for it.
func OpenFile(filename string) (*File, error) {
file, err := os.Open(filename)
if err != nil {
@ -61,7 +68,23 @@ func OpenFile(filename string) (*File, error) {
return f, nil
}
// OpenReader take an io.Reader and return a populated XLSX file.
// newFile is object builder
func newFile() *File {
return &File{
checked: make(map[string]bool),
sheetMap: make(map[string]string),
Comments: make(map[string]*xlsxComments),
Drawings: make(map[string]*xlsxWsDr),
Sheet: make(map[string]*xlsxWorksheet),
DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
VMLDrawing: make(map[string]*vmlDrawing),
Relationships: make(map[string]*xlsxRelationships),
CharsetReader: charset.NewReaderLabel,
}
}
// OpenReader read data stream from io.Reader and return a populated
// spreadsheet file.
func OpenReader(r io.Reader) (*File, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
@ -70,6 +93,17 @@ func OpenReader(r io.Reader) (*File, error) {
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
if err != nil {
identifier := []byte{
// checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier
0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
}
if bytes.Contains(b, identifier) {
return nil, errors.New("not support encrypted file currently")
}
return nil, err
}
@ -77,18 +111,8 @@ func OpenReader(r io.Reader) (*File, error) {
if err != nil {
return nil, err
}
f := &File{
checked: make(map[string]bool),
Comments: make(map[string]*xlsxComments),
DrawingRels: make(map[string]*xlsxWorkbookRels),
Drawings: make(map[string]*xlsxWsDr),
Sheet: make(map[string]*xlsxWorksheet),
SheetCount: sheetCount,
DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
VMLDrawing: make(map[string]*vmlDrawing),
WorkSheetRels: make(map[string]*xlsxWorkbookRels),
XLSX: file,
}
f := newFile()
f.SheetCount, f.XLSX = sheetCount, file
f.CalcChain = f.calcChainReader()
f.sheetMap = f.getSheetMap()
f.Styles = f.stylesReader()
@ -96,6 +120,17 @@ func OpenReader(r io.Reader) (*File, error) {
return f, nil
}
// CharsetTranscoder Set user defined codepage transcoder function for open
// XLSX from non UTF-8 encoding.
func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f }
// Creates new XML decoder with charset reader.
func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
ret = xml.NewDecoder(rdr)
ret.CharsetReader = f.CharsetReader
return
}
// setDefaultTimeStyle provides a function to set default numbers format for
// time.Time type cell value by given worksheet name, cell coordinates and
// number format code.
@ -105,34 +140,50 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
return err
}
if s == 0 {
style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`)
f.SetCellStyle(sheet, axis, axis, style)
style, _ := f.NewStyle(&Style{NumFmt: format})
_ = f.SetCellStyle(sheet, axis, axis, style)
}
return err
}
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
func (f *File) workSheetReader(sheet string) (*xlsxWorksheet, error) {
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
return nil, fmt.Errorf("sheet %s is not exist", sheet)
func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
var (
name string
ok bool
)
if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {
err = fmt.Errorf("sheet %s is not exist", sheet)
return
}
if f.Sheet[name] == nil {
var xlsx xlsxWorksheet
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(name)), &xlsx)
if xlsx = f.Sheet[name]; f.Sheet[name] == nil {
if strings.HasPrefix(name, "xl/chartsheets") {
err = fmt.Errorf("sheet %s is chart sheet", sheet)
return
}
xlsx = new(xlsxWorksheet)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))).
Decode(xlsx); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
}
err = nil
if f.checked == nil {
f.checked = make(map[string]bool)
}
ok := f.checked[name]
if !ok {
checkSheet(&xlsx)
checkRow(&xlsx)
if ok = f.checked[name]; !ok {
checkSheet(xlsx)
if err = checkRow(xlsx); err != nil {
return
}
f.checked[name] = true
}
f.Sheet[name] = &xlsx
f.Sheet[name] = xlsx
}
return f.Sheet[name], nil
return
}
// checkSheet provides a function to fill each row element and make that is
@ -145,32 +196,50 @@ func checkSheet(xlsx *xlsxWorksheet) {
row = lastRow
}
}
sheetData := xlsxSheetData{}
existsRows := map[int]int{}
for k := range xlsx.SheetData.Row {
existsRows[xlsx.SheetData.Row[k].R] = k
sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
for _, r := range xlsx.SheetData.Row {
sheetData.Row[r.R-1] = r
}
for i := 0; i < row; i++ {
_, ok := existsRows[i+1]
if ok {
sheetData.Row = append(sheetData.Row, xlsx.SheetData.Row[existsRows[i+1]])
} else {
sheetData.Row = append(sheetData.Row, xlsxRow{
R: i + 1,
})
}
for i := 1; i <= row; i++ {
sheetData.Row[i-1].R = i
}
xlsx.SheetData = sheetData
}
// replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
// xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
// Office Excel 2007.
func replaceWorkSheetsRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
var oldXmlns = []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
var newXmlns = []byte(`<worksheet xr:uid="{00000000-0001-0000-0000-000000000000}" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" mc:Ignorable="x14ac xr xr2 xr3" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
workbookMarshal = bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
return workbookMarshal
// addRels provides a function to add relationships by given XML path,
// relationship type, target and target mode.
func (f *File) addRels(relPath, relType, target, targetMode string) int {
rels := f.relsReader(relPath)
if rels == nil {
rels = &xlsxRelationships{}
}
var rID int
for _, rel := range rels.Relationships {
ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
if ID > rID {
rID = ID
}
}
rID++
var ID bytes.Buffer
ID.WriteString("rId")
ID.WriteString(strconv.Itoa(rID))
rels.Relationships = append(rels.Relationships, xlsxRelationship{
ID: ID.String(),
Type: relType,
Target: target,
TargetMode: targetMode,
})
f.Relationships[relPath] = rels
return rID
}
// replaceRelationshipsNameSpaceBytes provides a function to replace
// XML tags to self-closing for compatible Microsoft Office Excel 2007.
func replaceRelationshipsNameSpaceBytes(contentMarshal []byte) []byte {
var oldXmlns = stringToBytes(` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
var newXmlns = []byte(templateNamespaceIDMap)
return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1)
}
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
@ -199,7 +268,10 @@ func replaceWorkSheetsRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte
// </row>
//
func (f *File) UpdateLinkedValue() error {
for _, name := range f.GetSheetMap() {
wb := f.workbookReader()
// recalculate formulas
wb.CalcPr = nil
for _, name := range f.GetSheetList() {
xlsx, err := f.workSheetReader(name)
if err != nil {
return err
@ -216,48 +288,74 @@ func (f *File) UpdateLinkedValue() error {
return nil
}
// GetMergeCells provides a function to get all merged cells from a worksheet
// currently.
func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
var mergeCells []MergeCell
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return mergeCells, err
// AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be .xlsm. For example:
//
// if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil {
// fmt.Println(err)
// }
// if err := f.AddVBAProject("vbaProject.bin"); err != nil {
// fmt.Println(err)
// }
// if err := f.SaveAs("macros.xlsm"); err != nil {
// fmt.Println(err)
// }
//
func (f *File) AddVBAProject(bin string) error {
var err error
// Check vbaProject.bin exists first.
if _, err = os.Stat(bin); os.IsNotExist(err) {
return err
}
if xlsx.MergeCells != nil {
mergeCells = make([]MergeCell, 0, len(xlsx.MergeCells.Cells))
for i := range xlsx.MergeCells.Cells {
ref := xlsx.MergeCells.Cells[i].Ref
axis := strings.Split(ref, ":")[0]
val, _ := f.GetCellValue(sheet, axis)
mergeCells = append(mergeCells, []string{ref, val})
if path.Ext(bin) != ".bin" {
return errors.New("unsupported VBA project extension")
}
f.setContentTypePartVBAProjectExtensions()
wb := f.relsReader("xl/_rels/workbook.xml.rels")
var rID int
var ok bool
for _, rel := range wb.Relationships {
if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {
ok = true
continue
}
t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
if t > rID {
rID = t
}
}
return mergeCells, err
rID++
if !ok {
wb.Relationships = append(wb.Relationships, xlsxRelationship{
ID: "rId" + strconv.Itoa(rID),
Target: "vbaProject.bin",
Type: SourceRelationshipVBAProject,
})
}
file, _ := ioutil.ReadFile(bin)
f.XLSX["xl/vbaProject.bin"] = file
return err
}
// MergeCell define a merged cell data.
// It consists of the following structure.
// example: []string{"D4:E10", "cell value"}
type MergeCell []string
// GetCellValue returns merged cell value.
func (m *MergeCell) GetCellValue() string {
return (*m)[1]
}
// GetStartAxis returns the merge start axis.
// example: "C2"
func (m *MergeCell) GetStartAxis() string {
axis := strings.Split((*m)[0], ":")
return axis[0]
}
// GetEndAxis returns the merge end axis.
// example: "D4"
func (m *MergeCell) GetEndAxis() string {
axis := strings.Split((*m)[0], ":")
return axis[1]
// setContentTypePartVBAProjectExtensions provides a function to set the
// content type for relationship parts and the main document part.
func (f *File) setContentTypePartVBAProjectExtensions() {
var ok bool
content := f.contentTypesReader()
for _, v := range content.Defaults {
if v.Extension == "bin" {
ok = true
}
}
for idx, o := range content.Overrides {
if o.PartName == "/xl/workbook.xml" {
content.Overrides[idx].ContentType = ContentTypeMacro
}
}
if !ok {
content.Defaults = append(content.Defaults, xlsxDefault{
Extension: "bin",
ContentType: ContentTypeVBA,
})
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

1
excelize.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because it is too large Load Diff

24
file.go
View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -33,23 +33,18 @@ func NewFile() *File {
file["xl/styles.xml"] = []byte(XMLHeader + templateStyles)
file["xl/workbook.xml"] = []byte(XMLHeader + templateWorkbook)
file["[Content_Types].xml"] = []byte(XMLHeader + templateContentTypes)
f := &File{
sheetMap: make(map[string]string),
Sheet: make(map[string]*xlsxWorksheet),
SheetCount: 1,
XLSX: file,
}
f := newFile()
f.SheetCount, f.XLSX = 1, file
f.CalcChain = f.calcChainReader()
f.Comments = make(map[string]*xlsxComments)
f.ContentTypes = f.contentTypesReader()
f.DrawingRels = make(map[string]*xlsxWorkbookRels)
f.Drawings = make(map[string]*xlsxWsDr)
f.Styles = f.stylesReader()
f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing)
f.VMLDrawing = make(map[string]*vmlDrawing)
f.WorkBook = f.workbookReader()
f.WorkBookRels = f.workbookRelsReader()
f.WorkSheetRels = make(map[string]*xlsxWorkbookRels)
f.Relationships = make(map[string]*xlsxRelationships)
f.Relationships["xl/_rels/workbook.xml.rels"] = f.relsReader("xl/_rels/workbook.xml.rels")
f.Sheet["xl/worksheets/sheet1.xml"], _ = f.workSheetReader("Sheet1")
f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml"
f.Theme = f.themeReader()
@ -97,22 +92,23 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
f.calcChainWriter()
f.commentsWriter()
f.contentTypesWriter()
f.drawingRelsWriter()
f.drawingsWriter()
f.vmlDrawingWriter()
f.workBookWriter()
f.workBookRelsWriter()
f.workSheetWriter()
f.workSheetRelsWriter()
f.relsWriter()
f.sharedStringsWriter()
f.styleSheetWriter()
for path, content := range f.XLSX {
fi, err := zw.Create(path)
if err != nil {
zw.Close()
return buf, err
}
_, err = fi.Write(content)
if err != nil {
zw.Close()
return buf, err
}
}

48
file_test.go Normal file
View File

@ -0,0 +1,48 @@
package excelize
import (
"bufio"
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func BenchmarkWrite(b *testing.B) {
const s = "This is test data"
for i := 0; i < b.N; i++ {
f := NewFile()
for row := 1; row <= 10000; row++ {
for col := 1; col <= 20; col++ {
val, err := CoordinatesToCellName(col, row)
if err != nil {
b.Error(err)
}
if err := f.SetCellDefault("Sheet1", val, s); err != nil {
b.Error(err)
}
}
}
// Save xlsx file by the given path.
err := f.SaveAs("./test.xlsx")
if err != nil {
b.Error(err)
}
}
}
func TestWriteTo(t *testing.T) {
f := File{}
buf := bytes.Buffer{}
f.XLSX = make(map[string][]byte, 0)
f.XLSX["/d/"] = []byte("s")
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: write to directory")
delete(f.XLSX, "/d/")
// Test file path overflow
const maxUint16 = 1<<16 - 1
f.XLSX[strings.Repeat("s", maxUint16+1)] = nil
_, err = f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: FileHeader.Name too long")
}

14
go.mod
View File

@ -1,7 +1,17 @@
module github.com/360EntSecGroup-Skylar/excelize
module github.com/360EntSecGroup-Skylar/excelize/v2
go 1.12
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/stretchr/testify v1.3.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/stretchr/testify v1.5.1
github.com/xuri/efp v0.0.0-20191019043341-b7dc4fe9aa91
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
golang.org/x/text v0.3.2 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)

33
go.sum
View File

@ -1,10 +1,39 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/xuri/efp v0.0.0-20191019043341-b7dc4fe9aa91 h1:gp02YctZuIPTk0t7qI+wvg3VQwTPyNmSGG6ZqOsjSL8=
github.com/xuri/efp v0.0.0-20191019043341-b7dc4fe9aa91/go.mod h1:uBiSUepVYMhGTfDeBKKasV4GpgBlzJ46gXUBAqV8qLk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

120
lib.go
View File

@ -1,35 +1,35 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"archive/zip"
"bytes"
"container/list"
"fmt"
"io"
"log"
"strconv"
"strings"
"unsafe"
)
// ReadZipReader can be used to read an XLSX in memory without touching the
// ReadZipReader can be used to read the spreadsheet in memory without touching the
// filesystem.
func ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
fileList := make(map[string][]byte)
fileList := make(map[string][]byte, len(r.File))
worksheets := 0
for _, v := range r.File {
fileList[v.Name] = readFile(v)
if len(v.Name) > 18 {
if v.Name[0:19] == "xl/worksheets/sheet" {
worksheets++
}
if strings.HasPrefix(v.Name, "xl/worksheets/sheet") {
worksheets++
}
}
return fileList, worksheets, nil
@ -58,7 +58,8 @@ func readFile(file *zip.File) []byte {
if err != nil {
log.Fatal(err)
}
buff := bytes.NewBuffer(nil)
dat := make([]byte, 0, file.FileInfo().Size())
buff := bytes.NewBuffer(dat)
_, _ = io.Copy(buff, rc)
rc.Close()
return buff.Bytes()
@ -104,7 +105,7 @@ func JoinCellName(col string, row int) (string, error) {
if row < 1 {
return "", newInvalidRowNumberError(row)
}
return fmt.Sprintf("%s%d", normCol, row), nil
return normCol + strconv.Itoa(row), nil
}
// ColumnNameToNumber provides a function to convert Excel sheet column name
@ -159,8 +160,8 @@ func ColumnNumberToName(num int) (string, error) {
//
// Example:
//
// CellCoordinates("A1") // returns 1, 1, nil
// CellCoordinates("Z3") // returns 26, 3, nil
// excelize.CellNameToCoordinates("A1") // returns 1, 1, nil
// excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil
//
func CellNameToCoordinates(cell string) (int, int, error) {
const msg = "cannot convert cell %q to coordinates: %v"
@ -183,7 +184,7 @@ func CellNameToCoordinates(cell string) (int, int, error) {
//
// Example:
//
// CoordinatesToCellName(1, 1) // returns "A1", nil
// excelize.CoordinatesToCellName(1, 1) // returns "A1", nil
//
func CoordinatesToCellName(col, row int) (string, error) {
if col < 1 || row < 1 {
@ -191,6 +192,7 @@ func CoordinatesToCellName(col, row int) (string, error) {
}
colname, err := ColumnNumberToName(col)
if err != nil {
// Error should never happens here.
return "", fmt.Errorf("invalid cell coordinates [%d, %d]: %v", col, row, err)
}
return fmt.Sprintf("%s%d", colname, row), nil
@ -199,6 +201,15 @@ func CoordinatesToCellName(col, row int) (string, error) {
// boolPtr returns a pointer to a bool with the given value.
func boolPtr(b bool) *bool { return &b }
// intPtr returns a pointer to a int with the given value.
func intPtr(i int) *int { return &i }
// float64Ptr returns a pofloat64er to a float64 with the given value.
func float64Ptr(f float64) *float64 { return &f }
// stringPtr returns a pointer to a string with the given value.
func stringPtr(s string) *string { return &s }
// defaultTrue returns true if b is nil, or the pointed value.
func defaultTrue(b *bool) bool {
if b == nil {
@ -227,11 +238,47 @@ func namespaceStrictToTransitional(content []byte) []byte {
StrictNameSpaceSpreadSheet: NameSpaceSpreadSheet,
}
for s, n := range namespaceTranslationDic {
content = bytes.Replace(content, []byte(s), []byte(n), -1)
content = bytesReplace(content, stringToBytes(s), stringToBytes(n), -1)
}
return content
}
// stringToBytes cast a string to bytes pointer and assign the value of this
// pointer.
func stringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&s))
}
// bytesReplace replace old bytes with given new.
func bytesReplace(s, old, new []byte, n int) []byte {
if n == 0 {
return s
}
if len(old) < len(new) {
return bytes.Replace(s, old, new, n)
}
if n < 0 {
n = len(s)
}
var wid, i, j, w int
for i, j = 0, 0; i < len(s) && j < n; j++ {
wid = bytes.Index(s[i:], old)
if wid < 0 {
break
}
w += copy(s[w:], s[i:i+wid])
w += copy(s[w:], new)
i += wid + len(old)
}
w += copy(s[w:], s[i:])
return s[0:w]
}
// genSheetPasswd provides a method to generate password for worksheet
// protection by given plaintext. When an Excel sheet is being protected with
// a password, a 16-bit (two byte) long hash is generated. To verify a
@ -259,3 +306,48 @@ func genSheetPasswd(plaintext string) string {
password ^= 0xCE4B
return strings.ToUpper(strconv.FormatInt(password, 16))
}
// Stack defined an abstract data type that serves as a collection of elements.
type Stack struct {
list *list.List
}
// NewStack create a new stack.
func NewStack() *Stack {
list := list.New()
return &Stack{list}
}
// Push a value onto the top of the stack.
func (stack *Stack) Push(value interface{}) {
stack.list.PushBack(value)
}
// Pop the top item of the stack and return it.
func (stack *Stack) Pop() interface{} {
e := stack.list.Back()
if e != nil {
stack.list.Remove(e)
return e.Value
}
return nil
}
// Peek view the top item on the stack.
func (stack *Stack) Peek() interface{} {
e := stack.list.Back()
if e != nil {
return e.Value
}
return nil
}
// Len return the number of items in the stack.
func (stack *Stack) Len() int {
return stack.list.Len()
}
// Empty the stack.
func (stack *Stack) Empty() bool {
return stack.list.Len() == 0
}

View File

@ -203,3 +203,8 @@ func TestCoordinatesToCellName_Error(t *testing.T) {
}
}
}
func TestBytesReplace(t *testing.T) {
s := []byte{0x01}
assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0))
}

194
merge.go Normal file
View File

@ -0,0 +1,194 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"fmt"
"strings"
)
// MergeCell provides a function to merge cells by given coordinate area and
// sheet name. For example create a merged cell of D3:E9 on Sheet1:
//
// err := f.MergeCell("Sheet1", "D3", "E9")
//
// If you create a merged cell that overlaps with another existing merged cell,
// those merged cells that already exist will be removed.
//
// B1(x1,y1) D1(x2,y1)
// +------------------------+
// | |
// A4(x3,y3) | C4(x4,y3) |
// +------------------------+ |
// | | | |
// | |B5(x1,y2) | D5(x2,y2)|
// | +------------------------+
// | |
// |A8(x3,y4) C8(x4,y4)|
// +------------------------+
//
func (f *File) MergeCell(sheet, hcell, vcell string) error {
rect1, err := f.areaRefToCoordinates(hcell + ":" + vcell)
if err != nil {
return err
}
// Correct the coordinate area, such correct C1:B3 to B1:C3.
_ = sortCoordinates(rect1)
hcell, _ = CoordinatesToCellName(rect1[0], rect1[1])
vcell, _ = CoordinatesToCellName(rect1[2], rect1[3])
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ref := hcell + ":" + vcell
if xlsx.MergeCells != nil {
for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
cellData := xlsx.MergeCells.Cells[i]
if cellData == nil {
continue
}
cc := strings.Split(cellData.Ref, ":")
if len(cc) != 2 {
return fmt.Errorf("invalid area %q", cellData.Ref)
}
rect2, err := f.areaRefToCoordinates(cellData.Ref)
if err != nil {
return err
}
// Delete the merged cells of the overlapping area.
if isOverlap(rect1, rect2) {
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
i--
if rect1[0] > rect2[0] {
rect1[0], rect2[0] = rect2[0], rect1[0]
}
if rect1[2] < rect2[2] {
rect1[2], rect2[2] = rect2[2], rect1[2]
}
if rect1[1] > rect2[1] {
rect1[1], rect2[1] = rect2[1], rect1[1]
}
if rect1[3] < rect2[3] {
rect1[3], rect2[3] = rect2[3], rect1[3]
}
hcell, _ = CoordinatesToCellName(rect1[0], rect1[1])
vcell, _ = CoordinatesToCellName(rect1[2], rect1[3])
ref = hcell + ":" + vcell
}
}
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
} else {
xlsx.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref}}}
}
return err
}
// UnmergeCell provides a function to unmerge a given coordinate area.
// For example unmerge area D3:E9 on Sheet1:
//
// err := f.UnmergeCell("Sheet1", "D3", "E9")
//
// Attention: overlapped areas will also be unmerged.
func (f *File) UnmergeCell(sheet string, hcell, vcell string) error {
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
}
rect1, err := f.areaRefToCoordinates(hcell + ":" + vcell)
if err != nil {
return err
}
// Correct the coordinate area, such correct C1:B3 to B1:C3.
_ = sortCoordinates(rect1)
// return nil since no MergeCells in the sheet
if xlsx.MergeCells == nil {
return nil
}
i := 0
for _, cellData := range xlsx.MergeCells.Cells {
if cellData == nil {
continue
}
cc := strings.Split(cellData.Ref, ":")
if len(cc) != 2 {
return fmt.Errorf("invalid area %q", cellData.Ref)
}
rect2, err := f.areaRefToCoordinates(cellData.Ref)
if err != nil {
return err
}
if isOverlap(rect1, rect2) {
continue
}
xlsx.MergeCells.Cells[i] = cellData
i++
}
xlsx.MergeCells.Cells = xlsx.MergeCells.Cells[:i]
return nil
}
// GetMergeCells provides a function to get all merged cells from a worksheet
// currently.
func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
var mergeCells []MergeCell
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return mergeCells, err
}
if xlsx.MergeCells != nil {
mergeCells = make([]MergeCell, 0, len(xlsx.MergeCells.Cells))
for i := range xlsx.MergeCells.Cells {
ref := xlsx.MergeCells.Cells[i].Ref
axis := strings.Split(ref, ":")[0]
val, _ := f.GetCellValue(sheet, axis)
mergeCells = append(mergeCells, []string{ref, val})
}
}
return mergeCells, err
}
// MergeCell define a merged cell data.
// It consists of the following structure.
// example: []string{"D4:E10", "cell value"}
type MergeCell []string
// GetCellValue returns merged cell value.
func (m *MergeCell) GetCellValue() string {
return (*m)[1]
}
// GetStartAxis returns the merge start axis.
// example: "C2"
func (m *MergeCell) GetStartAxis() string {
axis := strings.Split((*m)[0], ":")
return axis[0]
}
// GetEndAxis returns the merge end axis.
// example: "D4"
func (m *MergeCell) GetEndAxis() string {
axis := strings.Split((*m)[0], ":")
return axis[1]
}

169
merge_test.go Normal file
View File

@ -0,0 +1,169 @@
package excelize
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMergeCell(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.EqualError(t, f.MergeCell("Sheet1", "A", "B"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.NoError(t, f.MergeCell("Sheet1", "D9", "D9"))
assert.NoError(t, f.MergeCell("Sheet1", "D9", "E9"))
assert.NoError(t, f.MergeCell("Sheet1", "H14", "G13"))
assert.NoError(t, f.MergeCell("Sheet1", "C9", "D8"))
assert.NoError(t, f.MergeCell("Sheet1", "F11", "G13"))
assert.NoError(t, f.MergeCell("Sheet1", "H7", "B15"))
assert.NoError(t, f.MergeCell("Sheet1", "D11", "F13"))
assert.NoError(t, f.MergeCell("Sheet1", "G10", "K12"))
assert.NoError(t, f.SetCellValue("Sheet1", "G11", "set value in merged cell"))
assert.NoError(t, f.SetCellInt("Sheet1", "H11", 100))
assert.NoError(t, f.SetCellValue("Sheet1", "I11", float64(0.5)))
assert.NoError(t, f.SetCellHyperLink("Sheet1", "J11", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))
assert.NoError(t, f.SetCellFormula("Sheet1", "G12", "SUM(Sheet1!B19,Sheet1!C19)"))
value, err := f.GetCellValue("Sheet1", "H11")
assert.Equal(t, "0.5", value)
assert.NoError(t, err)
value, err = f.GetCellValue("Sheet2", "A6") // Merged cell ref is single coordinate.
assert.Equal(t, "", value)
assert.NoError(t, err)
value, err = f.GetCellFormula("Sheet1", "G12")
assert.Equal(t, "SUM(Sheet1!B19,Sheet1!C19)", value)
assert.NoError(t, err)
f.NewSheet("Sheet3")
assert.NoError(t, f.MergeCell("Sheet3", "D11", "F13"))
assert.NoError(t, f.MergeCell("Sheet3", "G10", "K12"))
assert.NoError(t, f.MergeCell("Sheet3", "B1", "D5")) // B1:D5
assert.NoError(t, f.MergeCell("Sheet3", "E1", "F5")) // E1:F5
assert.NoError(t, f.MergeCell("Sheet3", "H2", "I5"))
assert.NoError(t, f.MergeCell("Sheet3", "I4", "J6")) // H2:J6
assert.NoError(t, f.MergeCell("Sheet3", "M2", "N5"))
assert.NoError(t, f.MergeCell("Sheet3", "L4", "M6")) // L2:N6
assert.NoError(t, f.MergeCell("Sheet3", "P4", "Q7"))
assert.NoError(t, f.MergeCell("Sheet3", "O2", "P5")) // O2:Q7
assert.NoError(t, f.MergeCell("Sheet3", "A9", "B12"))
assert.NoError(t, f.MergeCell("Sheet3", "B7", "C9")) // A7:C12
assert.NoError(t, f.MergeCell("Sheet3", "E9", "F10"))
assert.NoError(t, f.MergeCell("Sheet3", "D8", "G12"))
assert.NoError(t, f.MergeCell("Sheet3", "I8", "I12"))
assert.NoError(t, f.MergeCell("Sheet3", "I10", "K10"))
assert.NoError(t, f.MergeCell("Sheet3", "M8", "Q13"))
assert.NoError(t, f.MergeCell("Sheet3", "N10", "O11"))
// Test get merged cells on not exists worksheet.
assert.EqualError(t, f.MergeCell("SheetN", "N10", "O11"), "sheet SheetN is not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCell.xlsx")))
f = NewFile()
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
assert.EqualError(t, f.MergeCell("Sheet1", "A2", "B3"), `invalid area "A1"`)
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.MergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}
func TestGetMergeCells(t *testing.T) {
wants := []struct {
value string
start string
end string
}{{
value: "A1",
start: "A1",
end: "B1",
}, {
value: "A2",
start: "A2",
end: "A3",
}, {
value: "A4",
start: "A4",
end: "B5",
}, {
value: "A7",
start: "A7",
end: "C10",
}}
f, err := OpenFile(filepath.Join("test", "MergeCell.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
sheet1 := f.GetSheetName(0)
mergeCells, err := f.GetMergeCells(sheet1)
if !assert.Len(t, mergeCells, len(wants)) {
t.FailNow()
}
assert.NoError(t, err)
for i, m := range mergeCells {
assert.Equal(t, wants[i].value, m.GetCellValue())
assert.Equal(t, wants[i].start, m.GetStartAxis())
assert.Equal(t, wants[i].end, m.GetEndAxis())
}
// Test get merged cells on not exists worksheet.
_, err = f.GetMergeCells("SheetN")
assert.EqualError(t, err, "sheet SheetN is not exist")
}
func TestUnmergeCell(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "MergeCell.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
sheet1 := f.GetSheetName(0)
xlsx, err := f.workSheetReader(sheet1)
assert.NoError(t, err)
mergeCellNum := len(xlsx.MergeCells.Cells)
assert.EqualError(t, f.UnmergeCell("Sheet1", "A", "A"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
// unmerge the mergecell that contains A1
assert.NoError(t, f.UnmergeCell(sheet1, "A1", "A1"))
if len(xlsx.MergeCells.Cells) != mergeCellNum-1 {
t.FailNow()
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnmergeCell.xlsx")))
f = NewFile()
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
// Test unmerged area on not exists worksheet.
assert.EqualError(t, f.UnmergeCell("SheetN", "A1", "A1"), "sheet SheetN is not exist")
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = nil
assert.NoError(t, f.UnmergeCell("Sheet1", "H7", "B15"))
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
assert.NoError(t, f.UnmergeCell("Sheet1", "H15", "B7"))
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `invalid area "A1"`)
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -14,7 +14,9 @@ import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"image"
"io"
"io/ioutil"
"os"
"path"
@ -30,6 +32,7 @@ func parseFormatPictureSet(formatSet string) (*formatPicture, error) {
FPrintsWithSheet: true,
FLocksWithSheet: false,
NoChangeAspect: false,
Autofit: false,
OffsetX: 0,
OffsetY: 0,
XScale: 1.0,
@ -46,7 +49,6 @@ func parseFormatPictureSet(formatSet string) (*formatPicture, error) {
// package main
//
// import (
// "fmt"
// _ "image/gif"
// _ "image/jpeg"
// _ "image/png"
@ -57,22 +59,18 @@ func parseFormatPictureSet(formatSet string) (*formatPicture, error) {
// func main() {
// f := excelize.NewFile()
// // Insert a picture.
// err := f.AddPicture("Sheet1", "A2", "./image1.jpg", "")
// if err != nil {
// if err := f.AddPicture("Sheet1", "A2", "image.jpg", ""); err != nil {
// fmt.Println(err)
// }
// // Insert a picture scaling in the cell with location hyperlink.
// err = f.AddPicture("Sheet1", "D2", "./image1.png", `{"x_scale": 0.5, "y_scale": 0.5, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`)
// if err != nil {
// if err := f.AddPicture("Sheet1", "D2", "image.png", `{"x_scale": 0.5, "y_scale": 0.5, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`); err != nil {
// fmt.Println(err)
// }
// // Insert a picture offset in the cell with external hyperlink, printing and positioning support.
// err = f.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "hyperlink": "https://github.com/360EntSecGroup-Skylar/excelize", "hyperlink_type": "External", "print_obj": true, "lock_aspect_ratio": false, "locked": false, "positioning": "oneCell"}`)
// if err != nil {
// if err := f.AddPicture("Sheet1", "H2", "image.gif", `{"x_offset": 15, "y_offset": 10, "hyperlink": "https://github.com/360EntSecGroup-Skylar/excelize", "hyperlink_type": "External", "print_obj": true, "lock_aspect_ratio": false, "locked": false, "positioning": "oneCell"}`); err != nil {
// fmt.Println(err)
// }
// err = f.SaveAs("./Book1.xlsx")
// if err != nil {
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
@ -117,16 +115,14 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error {
// func main() {
// f := excelize.NewFile()
//
// file, err := ioutil.ReadFile("./image1.jpg")
// file, err := ioutil.ReadFile("image.jpg")
// if err != nil {
// fmt.Println(err)
// }
// err = f.AddPictureFromBytes("Sheet1", "A2", "", "Excel Logo", ".jpg", file)
// if err != nil {
// if err := f.AddPictureFromBytes("Sheet1", "A2", "", "Excel Logo", ".jpg", file); err != nil {
// fmt.Println(err)
// }
// err = f.SaveAs("./Book1.xlsx")
// if err != nil {
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
@ -155,14 +151,15 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
drawingID := f.countDrawings() + 1
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
drawingID, drawingXML = f.prepareDrawing(xlsx, drawingID, sheet, drawingXML)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
mediaStr := ".." + strings.TrimPrefix(f.addMedia(file, ext), "xl")
drawingRID := f.addDrawingRelationships(drawingID, SourceRelationshipImage, mediaStr, hyperlinkType)
drawingRID := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType)
// Add picture with hyperlink.
if formatSet.Hyperlink != "" && formatSet.HyperlinkType != "" {
if formatSet.HyperlinkType == "External" {
hyperlinkType = formatSet.HyperlinkType
}
drawingHyperlinkRID = f.addDrawingRelationships(drawingID, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType)
drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType)
}
err = f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet)
if err != nil {
@ -172,37 +169,6 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
return err
}
// addSheetRelationships provides a function to add
// xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name, relationship
// type and target.
func (f *File) addSheetRelationships(sheet, relType, target, targetMode string) int {
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
name = strings.ToLower(sheet) + ".xml"
}
var rels = "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels := f.workSheetRelsReader(rels)
if sheetRels == nil {
sheetRels = &xlsxWorkbookRels{}
}
var rID = 1
var ID bytes.Buffer
ID.WriteString("rId")
ID.WriteString(strconv.Itoa(rID))
ID.Reset()
rID = len(sheetRels.Relationships) + 1
ID.WriteString("rId")
ID.WriteString(strconv.Itoa(rID))
sheetRels.Relationships = append(sheetRels.Relationships, xlsxWorkbookRelation{
ID: ID.String(),
Type: relType,
Target: target,
TargetMode: targetMode,
})
f.WorkSheetRels[rels] = sheetRels
return rID
}
// deleteSheetRelationships provides a function to delete relationships in
// xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and
// relationship index.
@ -212,16 +178,16 @@ func (f *File) deleteSheetRelationships(sheet, rID string) {
name = strings.ToLower(sheet) + ".xml"
}
var rels = "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels := f.workSheetRelsReader(rels)
sheetRels := f.relsReader(rels)
if sheetRels == nil {
sheetRels = &xlsxWorkbookRels{}
sheetRels = &xlsxRelationships{}
}
for k, v := range sheetRels.Relationships {
if v.ID == rID {
sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...)
}
}
f.WorkSheetRels[rels] = sheetRels
f.Relationships[rels] = sheetRels
}
// addSheetLegacyDrawing provides a function to add legacy drawing element to
@ -279,8 +245,12 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he
if err != nil {
return err
}
width = int(float64(width) * formatSet.XScale)
height = int(float64(height) * formatSet.YScale)
if formatSet.Autofit {
width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), formatSet)
if err != nil {
return err
}
}
col--
row--
colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
@ -302,7 +272,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he
twoCellAnchor.To = &to
pic := xlsxPic{}
pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = formatSet.NoChangeAspect
pic.NvPicPr.CNvPr.ID = f.countCharts() + f.countMedia() + 1
pic.NvPicPr.CNvPr.ID = cNvPrID
pic.NvPicPr.CNvPr.Descr = file
pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID)
if hyperlinkRID != 0 {
@ -325,33 +295,6 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he
return err
}
// addDrawingRelationships provides a function to add image part relationships
// in the file xl/drawings/_rels/drawing%d.xml.rels by given drawing index,
// relationship type and target.
func (f *File) addDrawingRelationships(index int, relType, target, targetMode string) int {
var rels = "xl/drawings/_rels/drawing" + strconv.Itoa(index) + ".xml.rels"
var rID = 1
var ID bytes.Buffer
ID.WriteString("rId")
ID.WriteString(strconv.Itoa(rID))
drawingRels := f.drawingRelsReader(rels)
if drawingRels == nil {
drawingRels = &xlsxWorkbookRels{}
}
ID.Reset()
rID = len(drawingRels.Relationships) + 1
ID.WriteString("rId")
ID.WriteString(strconv.Itoa(rID))
drawingRels.Relationships = append(drawingRels.Relationships, xlsxWorkbookRelation{
ID: ID.String(),
Type: relType,
Target: target,
TargetMode: targetMode,
})
f.DrawingRels[rels] = drawingRels
return rID
}
// countMedia provides a function to get media files count storage in the
// folder xl/media/image.
func (f *File) countMedia() int {
@ -385,7 +328,7 @@ func (f *File) addMedia(file []byte, ext string) string {
// setContentTypePartImageExtensions provides a function to set the content
// type for relationship parts and the Main Document part.
func (f *File) setContentTypePartImageExtensions() {
var imageTypes = map[string]bool{"jpeg": false, "png": false, "gif": false}
var imageTypes = map[string]bool{"jpeg": false, "png": false, "gif": false, "tiff": false}
content := f.contentTypesReader()
for _, v := range content.Defaults {
_, ok := imageTypes[v.Extension]
@ -416,7 +359,7 @@ func (f *File) setContentTypePartVMLExtensions() {
if !vml {
content.Defaults = append(content.Defaults, xlsxDefault{
Extension: "vml",
ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing",
ContentType: ContentTypeVML,
})
}
}
@ -429,16 +372,24 @@ func (f *File) addContentTypePart(index int, contentType string) {
"drawings": f.setContentTypePartImageExtensions,
}
partNames := map[string]string{
"chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml",
"comments": "/xl/comments" + strconv.Itoa(index) + ".xml",
"drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml",
"table": "/xl/tables/table" + strconv.Itoa(index) + ".xml",
"chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml",
"chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml",
"comments": "/xl/comments" + strconv.Itoa(index) + ".xml",
"drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml",
"table": "/xl/tables/table" + strconv.Itoa(index) + ".xml",
"pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml",
"pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml",
"sharedStrings": "/xl/sharedStrings.xml",
}
contentTypes := map[string]string{
"chart": "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
"comments": "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
"drawings": "application/vnd.openxmlformats-officedocument.drawing+xml",
"table": "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml",
"chart": ContentTypeDrawingML,
"chartsheet": ContentTypeSpreadSheetMLChartsheet,
"comments": ContentTypeSpreadSheetMLComments,
"drawings": ContentTypeDrawing,
"table": ContentTypeSpreadSheetMLTable,
"pivotTable": ContentTypeSpreadSheetMLPivotTable,
"pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition,
"sharedStrings": ContentTypeSpreadSheetMLSharedStrings,
}
s, ok := setContentType[contentType]
if ok {
@ -465,9 +416,9 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
name = strings.ToLower(sheet) + ".xml"
}
var rels = "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels := f.workSheetRelsReader(rels)
sheetRels := f.relsReader(rels)
if sheetRels == nil {
sheetRels = &xlsxWorkbookRels{}
sheetRels = &xlsxRelationships{}
}
for _, v := range sheetRels.Relationships {
if v.ID == rID {
@ -481,7 +432,7 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
// embed in XLSX by given worksheet and cell name. This function returns the
// file name in XLSX and file contents as []byte data types. For example:
//
// f, err := excelize.OpenFile("./Book1.xlsx")
// f, err := excelize.OpenFile("Book1.xlsx")
// if err != nil {
// fmt.Println(err)
// return
@ -491,76 +442,128 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
// fmt.Println(err)
// return
// }
// err = ioutil.WriteFile(file, raw, 0644)
// if err != nil {
// if err := ioutil.WriteFile(file, raw, 0644); err != nil {
// fmt.Println(err)
// }
//
func (f *File) GetPicture(sheet, cell string) (string, []byte, error) {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return "", []byte{}, err
return "", nil, err
}
col--
row--
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return "", []byte{}, err
return "", nil, err
}
if xlsx.Drawing == nil {
return "", []byte{}, err
return "", nil, err
}
target := f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID)
drawingXML := strings.Replace(target, "..", "xl", -1)
drawingRelationships := strings.Replace(
strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1)
wsDr, _ := f.drawingParser(drawingXML)
for _, anchor := range wsDr.TwoCellAnchor {
if anchor.From != nil && anchor.Pic != nil {
if anchor.From.Col == col && anchor.From.Row == row {
xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships,
anchor.Pic.BlipFill.Blip.Embed)
_, ok := supportImageTypes[filepath.Ext(xlsxWorkbookRelation.Target)]
if ok {
return filepath.Base(xlsxWorkbookRelation.Target),
[]byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target,
"..", "xl", -1)]), err
}
}
}
}
_, ok := f.XLSX[drawingXML]
if !ok {
return "", nil, err
}
decodeWsDr := decodeWsDr{}
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(drawingXML)), &decodeWsDr)
for _, anchor := range decodeWsDr.TwoCellAnchor {
decodeTwoCellAnchor := decodeTwoCellAnchor{}
_ = xml.Unmarshal([]byte("<decodeTwoCellAnchor>"+anchor.Content+"</decodeTwoCellAnchor>"), &decodeTwoCellAnchor)
if decodeTwoCellAnchor.From != nil && decodeTwoCellAnchor.Pic != nil {
if decodeTwoCellAnchor.From.Col == col && decodeTwoCellAnchor.From.Row == row {
xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships, decodeTwoCellAnchor.Pic.BlipFill.Blip.Embed)
_, ok := supportImageTypes[filepath.Ext(xlsxWorkbookRelation.Target)]
if ok {
return filepath.Base(xlsxWorkbookRelation.Target), []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, "..", "xl", -1)]), err
drawingRelationships := strings.Replace(
strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1)
return f.getPicture(row, col, drawingXML, drawingRelationships)
}
// DeletePicture provides a function to delete charts in XLSX by given
// worksheet and cell name. Note that the image file won't be deleted from the
// document currently.
func (f *File) DeletePicture(sheet, cell string) (err error) {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return
}
col--
row--
ws, err := f.workSheetReader(sheet)
if err != nil {
return
}
if ws.Drawing == nil {
return
}
drawingXML := strings.Replace(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl", -1)
return f.deleteDrawing(col, row, drawingXML, "Pic")
}
// getPicture provides a function to get picture base name and raw content
// embed in XLSX by given coordinates and drawing relationships.
func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (ret string, buf []byte, err error) {
var (
wsDr *xlsxWsDr
ok bool
deWsDr *decodeWsDr
drawRel *xlsxRelationship
deTwoCellAnchor *decodeTwoCellAnchor
)
wsDr, _ = f.drawingParser(drawingXML)
if ret, buf = f.getPictureFromWsDr(row, col, drawingRelationships, wsDr); len(buf) > 0 {
return
}
deWsDr = new(decodeWsDr)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))).
Decode(deWsDr); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
}
err = nil
for _, anchor := range deWsDr.TwoCellAnchor {
deTwoCellAnchor = new(decodeTwoCellAnchor)
if err = f.xmlNewDecoder(strings.NewReader("<decodeTwoCellAnchor>" + anchor.Content + "</decodeTwoCellAnchor>")).
Decode(deTwoCellAnchor); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
}
if err = nil; deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil {
if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row {
drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed)
if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok {
ret, buf = filepath.Base(drawRel.Target), f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)]
return
}
}
}
}
return "", []byte{}, err
return
}
// getPictureFromWsDr provides a function to get picture base name and raw
// content in worksheet drawing by given coordinates and drawing
// relationships.
func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsDr *xlsxWsDr) (ret string, buf []byte) {
var (
ok bool
anchor *xdrCellAnchor
drawRel *xlsxRelationship
)
for _, anchor = range wsDr.TwoCellAnchor {
if anchor.From != nil && anchor.Pic != nil {
if anchor.From.Col == col && anchor.From.Row == row {
drawRel = f.getDrawingRelationships(drawingRelationships,
anchor.Pic.BlipFill.Blip.Embed)
if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok {
ret, buf = filepath.Base(drawRel.Target), f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)]
return
}
}
}
}
return
}
// getDrawingRelationships provides a function to get drawing relationships
// from xl/drawings/_rels/drawing%s.xml.rels by given file name and
// relationship ID.
func (f *File) getDrawingRelationships(rels, rID string) *xlsxWorkbookRelation {
if drawingRels := f.drawingRelsReader(rels); drawingRels != nil {
func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship {
if drawingRels := f.relsReader(rels); drawingRels != nil {
for _, v := range drawingRels.Relationships {
if v.ID == rID {
return &v
@ -570,31 +573,6 @@ func (f *File) getDrawingRelationships(rels, rID string) *xlsxWorkbookRelation {
return nil
}
// drawingRelsReader provides a function to get the pointer to the structure
// after deserialization of xl/drawings/_rels/drawing%d.xml.rels.
func (f *File) drawingRelsReader(rel string) *xlsxWorkbookRels {
if f.DrawingRels[rel] == nil {
_, ok := f.XLSX[rel]
if ok {
d := xlsxWorkbookRels{}
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(rel)), &d)
f.DrawingRels[rel] = &d
}
}
return f.DrawingRels[rel]
}
// drawingRelsWriter provides a function to save
// xl/drawings/_rels/drawing%d.xml.rels after serialize structure.
func (f *File) drawingRelsWriter() {
for path, d := range f.DrawingRels {
if d != nil {
v, _ := xml.Marshal(d)
f.saveFileList(path, v)
}
}
}
// drawingsWriter provides a function to save xl/drawings/drawing%d.xml after
// serialize structure.
func (f *File) drawingsWriter() {
@ -605,3 +583,48 @@ func (f *File) drawingsWriter() {
}
}
}
// drawingResize calculate the height and width after resizing.
func (f *File) drawingResize(sheet string, cell string, width, height float64, formatSet *formatPicture) (w, h, c, r int, err error) {
var mergeCells []MergeCell
mergeCells, err = f.GetMergeCells(sheet)
if err != nil {
return
}
var rng []int
var inMergeCell bool
if c, r, err = CellNameToCoordinates(cell); err != nil {
return
}
cellWidth, cellHeight := f.getColWidth(sheet, c), f.getRowHeight(sheet, r)
for _, mergeCell := range mergeCells {
if inMergeCell, err = f.checkCellInArea(cell, mergeCell[0]); err != nil {
return
}
if inMergeCell {
rng, _ = areaRangeToCoordinates(mergeCell.GetStartAxis(), mergeCell.GetEndAxis())
sortCoordinates(rng)
}
}
if inMergeCell {
cellWidth, cellHeight = 0, 0
c, r = rng[0], rng[1]
for col := rng[0] - 1; col < rng[2]; col++ {
cellWidth += f.getColWidth(sheet, col)
}
for row := rng[1] - 1; row < rng[3]; row++ {
cellHeight += f.getRowHeight(sheet, row)
}
}
if float64(cellWidth) < width {
asp := float64(cellWidth) / width
width, height = float64(cellWidth), height*asp
}
if float64(cellHeight) < height {
asp := float64(cellHeight) / height
height, width = float64(cellHeight), width*asp
}
width, height = width-float64(formatSet.OffsetX), height-float64(formatSet.OffsetY)
w, h = int(width*formatSet.XScale), int(height*formatSet.YScale)
return
}

View File

@ -1,8 +1,13 @@
package excelize
import (
"fmt"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
_ "golang.org/x/image/tiff"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@ -20,49 +25,53 @@ func BenchmarkAddPictureFromBytes(b *testing.B) {
}
b.ResetTimer()
for i := 1; i <= b.N; i++ {
f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), "", "excel", ".png", imgFile)
if err := f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), "", "excel", ".png", imgFile); err != nil {
b.Error(err)
}
}
}
func TestAddPicture(t *testing.T) {
xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
// Test add picture to worksheet with offset and location hyperlink.
err = xlsx.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"),
`{"x_offset": 140, "y_offset": 120, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"),
`{"x_offset": 140, "y_offset": 120, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`))
// Test add picture to worksheet with offset, external hyperlink and positioning.
err = xlsx.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"),
`{"x_offset": 10, "y_offset": 10, "hyperlink": "https://github.com/360EntSecGroup-Skylar/excelize", "hyperlink_type": "External", "positioning": "oneCell"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"),
`{"x_offset": 10, "y_offset": 10, "hyperlink": "https://github.com/360EntSecGroup-Skylar/excelize", "hyperlink_type": "External", "positioning": "oneCell"}`))
file, err := ioutil.ReadFile(filepath.Join("test", "images", "excel.jpg"))
if !assert.NoError(t, err) {
t.FailNow()
}
file, err := ioutil.ReadFile(filepath.Join("test", "images", "excel.png"))
assert.NoError(t, err)
// Test add picture to worksheet with autofit.
assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), `{"x_offset": 10, "y_offset": 10, "autofit": true}`))
f.NewSheet("AddPicture")
assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30))
assert.NoError(t, f.MergeCell("AddPicture", "B3", "D9"))
assert.NoError(t, f.AddPicture("AddPicture", "C6", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
// Test add picture to worksheet from bytes.
assert.NoError(t, xlsx.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".jpg", file))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".png", file))
// Test add picture to worksheet from bytes with illegal cell coordinates.
assert.EqualError(t, xlsx.AddPictureFromBytes("Sheet1", "A", "", "Excel Logo", ".jpg", file), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "A", "", "Excel Logo", ".png", file), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.NoError(t, f.AddPicture("Sheet1", "Q8", filepath.Join("test", "images", "excel.gif"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q15", filepath.Join("test", "images", "excel.jpg"), ""))
assert.NoError(t, f.AddPicture("Sheet1", "Q22", filepath.Join("test", "images", "excel.tif"), ""))
// Test write file to given path.
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestAddPicture.xlsx")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture.xlsx")))
}
func TestAddPictureErrors(t *testing.T) {
xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, err)
// Test add picture to worksheet with invalid file path.
err = xlsx.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), "")
@ -83,12 +92,12 @@ func TestAddPictureErrors(t *testing.T) {
}
func TestGetPicture(t *testing.T) {
xlsx, err := prepareTestBook1()
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
file, raw, err := xlsx.GetPicture("Sheet1", "F21")
file, raw, err := f.GetPicture("Sheet1", "F21")
assert.NoError(t, err)
if !assert.NotEmpty(t, filepath.Join("test", file)) || !assert.NotEmpty(t, raw) ||
!assert.NoError(t, ioutil.WriteFile(filepath.Join("test", file), raw, 0644)) {
@ -97,37 +106,33 @@ func TestGetPicture(t *testing.T) {
}
// Try to get picture from a worksheet with illegal cell coordinates.
_, _, err = xlsx.GetPicture("Sheet1", "A")
_, _, err = f.GetPicture("Sheet1", "A")
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
// Try to get picture from a worksheet that doesn't contain any images.
file, raw, err = xlsx.GetPicture("Sheet3", "I9")
file, raw, err = f.GetPicture("Sheet3", "I9")
assert.EqualError(t, err, "sheet Sheet3 is not exist")
assert.Empty(t, file)
assert.Empty(t, raw)
// Try to get picture from a cell that doesn't contain an image.
file, raw, err = xlsx.GetPicture("Sheet2", "A2")
file, raw, err = f.GetPicture("Sheet2", "A2")
assert.NoError(t, err)
assert.Empty(t, file)
assert.Empty(t, raw)
xlsx.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8")
xlsx.getDrawingRelationships("", "")
xlsx.getSheetRelationshipsTargetByID("", "")
xlsx.deleteSheetRelationships("", "")
f.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8")
f.getDrawingRelationships("", "")
f.getSheetRelationshipsTargetByID("", "")
f.deleteSheetRelationships("", "")
// Try to get picture from a local storage file.
if !assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestGetPicture.xlsx"))) {
t.FailNow()
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGetPicture.xlsx")))
xlsx, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
assert.NoError(t, err)
file, raw, err = xlsx.GetPicture("Sheet1", "F21")
file, raw, err = f.GetPicture("Sheet1", "F21")
assert.NoError(t, err)
if !assert.NotEmpty(t, filepath.Join("test", file)) || !assert.NotEmpty(t, raw) ||
!assert.NoError(t, ioutil.WriteFile(filepath.Join("test", file), raw, 0644)) {
@ -136,7 +141,14 @@ func TestGetPicture(t *testing.T) {
}
// Try to get picture from a local storage file that doesn't contain an image.
file, raw, err = xlsx.GetPicture("Sheet1", "F22")
file, raw, err = f.GetPicture("Sheet1", "F22")
assert.NoError(t, err)
assert.Empty(t, file)
assert.Empty(t, raw)
// Test get picture from none drawing worksheet.
f = NewFile()
file, raw, err = f.GetPicture("Sheet1", "F22")
assert.NoError(t, err)
assert.Empty(t, file)
assert.Empty(t, raw)
@ -151,11 +163,9 @@ func TestAddDrawingPicture(t *testing.T) {
func TestAddPictureFromBytes(t *testing.T) {
f := NewFile()
imgFile, err := ioutil.ReadFile("logo.png")
if err != nil {
t.Error("Unable to load logo for test")
}
f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile)
f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile)
assert.NoError(t, err, "Unable to load logo for test")
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile))
imageCount := 0
for fileName := range f.XLSX {
if strings.Contains(fileName, "media/image") {
@ -163,4 +173,32 @@ func TestAddPictureFromBytes(t *testing.T) {
}
}
assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.")
assert.EqualError(t, f.AddPictureFromBytes("SheetN", fmt.Sprint("A", 1), "", "logo", ".png", imgFile), "sheet SheetN is not exist")
}
func TestDeletePicture(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.DeletePicture("Sheet1", "A1"))
assert.NoError(t, f.AddPicture("Sheet1", "P1", filepath.Join("test", "images", "excel.jpg"), ""))
assert.NoError(t, f.DeletePicture("Sheet1", "P1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture.xlsx")))
// Test delete picture on not exists worksheet.
assert.EqualError(t, f.DeletePicture("SheetN", "A1"), "sheet SheetN is not exist")
// Test delete picture with invalid coordinates.
assert.EqualError(t, f.DeletePicture("Sheet1", ""), `cannot convert cell "" to coordinates: invalid cell name ""`)
// Test delete picture on no chart worksheet.
assert.NoError(t, NewFile().DeletePicture("Sheet1", "A1"))
}
func TestDrawingResize(t *testing.T) {
f := NewFile()
// Test calculate drawing resize on not exists worksheet.
_, _, _, _, err := f.drawingResize("SheetN", "A1", 1, 1, nil)
assert.EqualError(t, err, "sheet SheetN is not exist")
// Test calculate drawing resize with invalid coordinates.
_, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil)
assert.EqualError(t, err, `cannot convert cell "" to coordinates: invalid cell name ""`)
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}

593
pivotTable.go Normal file
View File

@ -0,0 +1,593 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"encoding/xml"
"errors"
"fmt"
"strconv"
"strings"
)
// PivotTableOption directly maps the format settings of the pivot table.
type PivotTableOption struct {
DataRange string
PivotTableRange string
Rows []PivotTableField
Columns []PivotTableField
Data []PivotTableField
Filter []PivotTableField
}
// PivotTableField directly maps the field settings of the pivot table.
// Subtotal specifies the aggregation function that applies to this data
// field. The default value is sum. The possible values for this attribute
// are:
//
// Average
// Count
// CountNums
// Max
// Min
// Product
// StdDev
// StdDevp
// Sum
// Var
// Varp
//
// Name specifies the name of the data field. Maximum 255 characters
// are allowed in data field name, excess characters will be truncated.
type PivotTableField struct {
Data string
Name string
Subtotal string
}
// AddPivotTable provides the method to add pivot table by given pivot table
// options.
//
// For example, create a pivot table on the Sheet1!$G$2:$M$34 area with the
// region Sheet1!$A$1:$E$31 as the data source, summarize by sum for sales:
//
// package main
//
// import (
// "fmt"
// "math/rand"
//
// "github.com/360EntSecGroup-Skylar/excelize"
// )
//
// func main() {
// f := excelize.NewFile()
// // Create some data in a sheet
// month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
// year := []int{2017, 2018, 2019}
// types := []string{"Meat", "Dairy", "Beverages", "Produce"}
// region := []string{"East", "West", "North", "South"}
// f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
// for i := 0; i < 30; i++ {
// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)])
// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)])
// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)])
// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000))
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)])
// }
// if err := f.AddPivotTable(&excelize.PivotTableOption{
// DataRange: "Sheet1!$A$1:$E$31",
// PivotTableRange: "Sheet1!$G$2:$M$34",
// Rows: []excelize.PivotTableField{{Data: "Month"}, {Data: "Year"}},
// Filter: []excelize.PivotTableField{{Data: "Region"}},
// Columns: []excelize.PivotTableField{{Data: "Type"}},
// Data: []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}},
// }); err != nil {
// fmt.Println(err)
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
//
func (f *File) AddPivotTable(opt *PivotTableOption) error {
// parameter validation
dataSheet, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opt)
if err != nil {
return err
}
pivotTableID := f.countPivotTables() + 1
pivotCacheID := f.countPivotCache() + 1
sheetRelationshipsPivotTableXML := "../pivotTables/pivotTable" + strconv.Itoa(pivotTableID) + ".xml"
pivotTableXML := strings.Replace(sheetRelationshipsPivotTableXML, "..", "xl", -1)
pivotCacheXML := "xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(pivotCacheID) + ".xml"
err = f.addPivotCache(pivotCacheID, pivotCacheXML, opt, dataSheet)
if err != nil {
return err
}
// workbook pivot cache
workBookPivotCacheRID := f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipPivotCache, fmt.Sprintf("pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID)
pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels"
// rId not used
_ = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
err = f.addPivotTable(cacheID, pivotTableID, pivotTableXML, opt)
if err != nil {
return err
}
pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels"
f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "")
f.addContentTypePart(pivotTableID, "pivotTable")
f.addContentTypePart(pivotCacheID, "pivotCache")
return nil
}
// parseFormatPivotTableSet provides a function to validate pivot table
// properties.
func (f *File) parseFormatPivotTableSet(opt *PivotTableOption) (*xlsxWorksheet, string, error) {
if opt == nil {
return nil, "", errors.New("parameter is required")
}
dataSheetName, _, err := f.adjustRange(opt.DataRange)
if err != nil {
return nil, "", fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
}
pivotTableSheetName, _, err := f.adjustRange(opt.PivotTableRange)
if err != nil {
return nil, "", fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
}
dataSheet, err := f.workSheetReader(dataSheetName)
if err != nil {
return dataSheet, "", err
}
pivotTableSheetPath, ok := f.sheetMap[trimSheetName(pivotTableSheetName)]
if !ok {
return dataSheet, pivotTableSheetPath, fmt.Errorf("sheet %s is not exist", pivotTableSheetName)
}
return dataSheet, pivotTableSheetPath, err
}
// adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31
func (f *File) adjustRange(rangeStr string) (string, []int, error) {
if len(rangeStr) < 1 {
return "", []int{}, errors.New("parameter is required")
}
rng := strings.Split(rangeStr, "!")
if len(rng) != 2 {
return "", []int{}, errors.New("parameter is invalid")
}
trimRng := strings.Replace(rng[1], "$", "", -1)
coordinates, err := f.areaRefToCoordinates(trimRng)
if err != nil {
return rng[0], []int{}, err
}
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if x1 == x2 && y1 == y2 {
return rng[0], []int{}, errors.New("parameter is invalid")
}
// Correct the coordinate area, such correct C1:B3 to B1:C3.
if x2 < x1 {
x1, x2 = x2, x1
}
if y2 < y1 {
y1, y2 = y2, y1
}
return rng[0], []int{x1, y1, x2, y2}, nil
}
// getPivotFieldsOrder provides a function to get order list of pivot table
// fields.
func (f *File) getPivotFieldsOrder(dataRange string) ([]string, error) {
order := []string{}
dataSheet, coordinates, err := f.adjustRange(dataRange)
if err != nil {
return order, fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
}
for col := coordinates[0]; col <= coordinates[2]; col++ {
coordinate, _ := CoordinatesToCellName(col, coordinates[1])
name, err := f.GetCellValue(dataSheet, coordinate)
if err != nil {
return order, err
}
order = append(order, name)
}
return order, nil
}
// addPivotCache provides a function to create a pivot cache by given properties.
func (f *File) addPivotCache(pivotCacheID int, pivotCacheXML string, opt *PivotTableOption, ws *xlsxWorksheet) error {
// validate data range
dataSheet, coordinates, err := f.adjustRange(opt.DataRange)
if err != nil {
return fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error())
}
// data range has been checked
order, _ := f.getPivotFieldsOrder(opt.DataRange)
hcell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
vcell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
pc := xlsxPivotCacheDefinition{
SaveData: false,
RefreshOnLoad: true,
CacheSource: &xlsxCacheSource{
Type: "worksheet",
WorksheetSource: &xlsxWorksheetSource{
Ref: hcell + ":" + vcell,
Sheet: dataSheet,
},
},
CacheFields: &xlsxCacheFields{},
}
for _, name := range order {
pc.CacheFields.CacheField = append(pc.CacheFields.CacheField, &xlsxCacheField{
Name: name,
SharedItems: &xlsxSharedItems{
Count: 0,
},
})
}
pc.CacheFields.Count = len(pc.CacheFields.CacheField)
pivotCache, err := xml.Marshal(pc)
f.saveFileList(pivotCacheXML, pivotCache)
return err
}
// addPivotTable provides a function to create a pivot table by given pivot
// table ID and properties.
func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, opt *PivotTableOption) error {
// validate pivot table range
_, coordinates, err := f.adjustRange(opt.PivotTableRange)
if err != nil {
return fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
}
hcell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
vcell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
pt := xlsxPivotTableDefinition{
Name: fmt.Sprintf("Pivot Table%d", pivotTableID),
CacheID: cacheID,
DataCaption: "Values",
Location: &xlsxLocation{
Ref: hcell + ":" + vcell,
FirstDataCol: 1,
FirstDataRow: 1,
FirstHeaderRow: 1,
},
PivotFields: &xlsxPivotFields{},
RowItems: &xlsxRowItems{
Count: 1,
I: []*xlsxI{
{
[]*xlsxX{{}, {}},
},
},
},
ColItems: &xlsxColItems{
Count: 1,
I: []*xlsxI{{}},
},
PivotTableStyleInfo: &xlsxPivotTableStyleInfo{
Name: "PivotStyleLight16",
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
},
}
// pivot fields
_ = f.addPivotFields(&pt, opt)
// count pivot fields
pt.PivotFields.Count = len(pt.PivotFields.PivotField)
// data range has been checked
_ = f.addPivotRowFields(&pt, opt)
_ = f.addPivotColFields(&pt, opt)
_ = f.addPivotPageFields(&pt, opt)
_ = f.addPivotDataFields(&pt, opt)
pivotTable, err := xml.Marshal(pt)
f.saveFileList(pivotTableXML, pivotTable)
return err
}
// addPivotRowFields provides a method to add row fields for pivot table by
// given pivot table options.
func (f *File) addPivotRowFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error {
// row fields
rowFieldsIndex, err := f.getPivotFieldsIndex(opt.Rows, opt)
if err != nil {
return err
}
for _, fieldIdx := range rowFieldsIndex {
if pt.RowFields == nil {
pt.RowFields = &xlsxRowFields{}
}
pt.RowFields.Field = append(pt.RowFields.Field, &xlsxField{
X: fieldIdx,
})
}
// count row fields
if pt.RowFields != nil {
pt.RowFields.Count = len(pt.RowFields.Field)
}
return err
}
// addPivotPageFields provides a method to add page fields for pivot table by
// given pivot table options.
func (f *File) addPivotPageFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error {
// page fields
pageFieldsIndex, err := f.getPivotFieldsIndex(opt.Filter, opt)
if err != nil {
return err
}
pageFieldsName := f.getPivotTableFieldsName(opt.Filter)
for idx, pageField := range pageFieldsIndex {
if pt.PageFields == nil {
pt.PageFields = &xlsxPageFields{}
}
pt.PageFields.PageField = append(pt.PageFields.PageField, &xlsxPageField{
Name: pageFieldsName[idx],
Fld: pageField,
})
}
// count page fields
if pt.PageFields != nil {
pt.PageFields.Count = len(pt.PageFields.PageField)
}
return err
}
// addPivotDataFields provides a method to add data fields for pivot table by
// given pivot table options.
func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error {
// data fields
dataFieldsIndex, err := f.getPivotFieldsIndex(opt.Data, opt)
if err != nil {
return err
}
dataFieldsSubtotals := f.getPivotTableFieldsSubtotal(opt.Data)
dataFieldsName := f.getPivotTableFieldsName(opt.Data)
for idx, dataField := range dataFieldsIndex {
if pt.DataFields == nil {
pt.DataFields = &xlsxDataFields{}
}
pt.DataFields.DataField = append(pt.DataFields.DataField, &xlsxDataField{
Name: dataFieldsName[idx],
Fld: dataField,
Subtotal: dataFieldsSubtotals[idx],
})
}
// count data fields
if pt.DataFields != nil {
pt.DataFields.Count = len(pt.DataFields.DataField)
}
return err
}
// inStrSlice provides a method to check if an element is present in an array,
// and return the index of its location, otherwise return -1.
func inStrSlice(a []string, x string) int {
for idx, n := range a {
if x == n {
return idx
}
}
return -1
}
// inPivotTableField provides a method to check if an element is present in
// pivot table fields list, and return the index of its location, otherwise
// return -1.
func inPivotTableField(a []PivotTableField, x string) int {
for idx, n := range a {
if x == n.Data {
return idx
}
}
return -1
}
// addPivotColFields create pivot column fields by given pivot table
// definition and option.
func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error {
if len(opt.Columns) == 0 {
return nil
}
pt.ColFields = &xlsxColFields{}
// col fields
colFieldsIndex, err := f.getPivotFieldsIndex(opt.Columns, opt)
if err != nil {
return err
}
for _, fieldIdx := range colFieldsIndex {
pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
X: fieldIdx,
})
}
// count col fields
pt.ColFields.Count = len(pt.ColFields.Field)
return err
}
// addPivotFields create pivot fields based on the column order of the first
// row in the data region by given pivot table definition and option.
func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error {
order, err := f.getPivotFieldsOrder(opt.DataRange)
if err != nil {
return err
}
for _, name := range order {
if inPivotTableField(opt.Rows, name) != -1 {
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
Axis: "axisRow",
Name: f.getPivotTableFieldName(name, opt.Rows),
Items: &xlsxItems{
Count: 1,
Item: []*xlsxItem{
{T: "default"},
},
},
})
continue
}
if inPivotTableField(opt.Filter, name) != -1 {
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
Axis: "axisPage",
Name: f.getPivotTableFieldName(name, opt.Columns),
Items: &xlsxItems{
Count: 1,
Item: []*xlsxItem{
{T: "default"},
},
},
})
continue
}
if inPivotTableField(opt.Columns, name) != -1 {
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
Axis: "axisCol",
Name: f.getPivotTableFieldName(name, opt.Columns),
Items: &xlsxItems{
Count: 1,
Item: []*xlsxItem{
{T: "default"},
},
},
})
continue
}
if inPivotTableField(opt.Data, name) != -1 {
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
DataField: true,
})
continue
}
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{})
}
return err
}
// countPivotTables provides a function to get drawing files count storage in
// the folder xl/pivotTables.
func (f *File) countPivotTables() int {
count := 0
for k := range f.XLSX {
if strings.Contains(k, "xl/pivotTables/pivotTable") {
count++
}
}
return count
}
// countPivotCache provides a function to get drawing files count storage in
// the folder xl/pivotCache.
func (f *File) countPivotCache() int {
count := 0
for k := range f.XLSX {
if strings.Contains(k, "xl/pivotCache/pivotCacheDefinition") {
count++
}
}
return count
}
// getPivotFieldsIndex convert the column of the first row in the data region
// to a sequential index by given fields and pivot option.
func (f *File) getPivotFieldsIndex(fields []PivotTableField, opt *PivotTableOption) ([]int, error) {
pivotFieldsIndex := []int{}
orders, err := f.getPivotFieldsOrder(opt.DataRange)
if err != nil {
return pivotFieldsIndex, err
}
for _, field := range fields {
if pos := inStrSlice(orders, field.Data); pos != -1 {
pivotFieldsIndex = append(pivotFieldsIndex, pos)
}
}
return pivotFieldsIndex, nil
}
// getPivotTableFieldsSubtotal prepare fields subtotal by given pivot table fields.
func (f *File) getPivotTableFieldsSubtotal(fields []PivotTableField) []string {
field := make([]string, len(fields))
enums := []string{"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp", "sum", "var", "varp"}
inEnums := func(enums []string, val string) string {
for _, enum := range enums {
if strings.ToLower(enum) == strings.ToLower(val) {
return enum
}
}
return "sum"
}
for idx, fld := range fields {
field[idx] = inEnums(enums, fld.Subtotal)
}
return field
}
// getPivotTableFieldsName prepare fields name list by given pivot table
// fields.
func (f *File) getPivotTableFieldsName(fields []PivotTableField) []string {
field := make([]string, len(fields))
for idx, fld := range fields {
if len(fld.Name) > 255 {
field[idx] = fld.Name[0:255]
continue
}
field[idx] = fld.Name
}
return field
}
// getPivotTableFieldName prepare field name by given pivot table fields.
func (f *File) getPivotTableFieldName(name string, fields []PivotTableField) string {
fieldsName := f.getPivotTableFieldsName(fields)
for idx, field := range fields {
if field.Data == name {
return fieldsName[idx]
}
}
return ""
}
// addWorkbookPivotCache add the association ID of the pivot cache in xl/workbook.xml.
func (f *File) addWorkbookPivotCache(RID int) int {
wb := f.workbookReader()
if wb.PivotCaches == nil {
wb.PivotCaches = &xlsxPivotCaches{}
}
cacheID := 1
for _, pivotCache := range wb.PivotCaches.PivotCache {
if pivotCache.CacheID > cacheID {
cacheID = pivotCache.CacheID
}
}
cacheID++
wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache, xlsxPivotCache{
CacheID: cacheID,
RID: fmt.Sprintf("rId%d", RID),
})
return cacheID
}

229
pivotTable_test.go Normal file
View File

@ -0,0 +1,229 @@
package excelize
import (
"fmt"
"math/rand"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddPivotTable(t *testing.T) {
f := NewFile()
// Create some data in a sheet
month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
year := []int{2017, 2018, 2019}
types := []string{"Meat", "Dairy", "Beverages", "Produce"}
region := []string{"East", "West", "North", "South"}
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}))
for i := 0; i < 30; i++ {
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000)))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)]))
}
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet1!$G$2:$M$34",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Filter: []PivotTableField{{Data: "Region"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum"}},
}))
// Use different order of coordinate tests
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet1!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Average", Name: "Summarize by Average"}},
}))
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet1!$W$2:$AC$34",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Region"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Count", Name: "Summarize by Count"}},
}))
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet1!$G$37:$W$50",
Rows: []PivotTableField{{Data: "Month"}},
Columns: []PivotTableField{{Data: "Region"}, {Data: "Year"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "CountNums", Name: "Summarize by CountNums"}},
}))
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet1!$AE$2:$AG$33",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Max", Name: "Summarize by Max"}},
}))
f.NewSheet("Sheet2")
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet2!$A$1:$AR$15",
Rows: []PivotTableField{{Data: "Month"}},
Columns: []PivotTableField{{Data: "Region"}, {Data: "Type"}, {Data: "Year"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Min", Name: "Summarize by Min"}},
}))
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet2!$A$18:$AR$54",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Type"}},
Columns: []PivotTableField{{Data: "Region"}, {Data: "Year"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Product", Name: "Summarize by Product"}},
}))
// Test empty pivot table options
assert.EqualError(t, f.AddPivotTable(nil), "parameter is required")
// Test invalid data range
assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$A$1",
PivotTableRange: "Sheet1!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}), `parameter 'DataRange' parsing error: parameter is invalid`)
// Test the data range of the worksheet that is not declared
assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "$A$1:$E$31",
PivotTableRange: "Sheet1!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}), `parameter 'DataRange' parsing error: parameter is invalid`)
// Test the worksheet declared in the data range does not exist
assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "SheetN!$A$1:$E$31",
PivotTableRange: "Sheet1!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}), "sheet SheetN is not exist")
// Test the pivot table range of the worksheet that is not declared
assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}), `parameter 'PivotTableRange' parsing error: parameter is invalid`)
// Test the worksheet declared in the pivot table range does not exist
assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "SheetN!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}), "sheet SheetN is not exist")
// Test not exists worksheet in data range
assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "SheetN!$A$1:$E$31",
PivotTableRange: "Sheet1!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}), "sheet SheetN is not exist")
// Test invalid row number in data range
assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$0:$E$31",
PivotTableRange: "Sheet1!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}), `parameter 'DataRange' parsing error: cannot convert cell "A0" to coordinates: invalid cell name "A0"`)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPivotTable1.xlsx")))
// Test with field names that exceed the length limit and invalid subtotal
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet1!$G$2:$M$34",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "-", Name: strings.Repeat("s", 256)}},
}))
// Test adjust range with invalid range
_, _, err := f.adjustRange("")
assert.EqualError(t, err, "parameter is required")
// Test get pivot fields order with empty data range
_, err = f.getPivotFieldsOrder("")
assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`)
// Test add pivot cache with empty data range
assert.EqualError(t, f.addPivotCache(0, "", &PivotTableOption{}, nil), "parameter 'DataRange' parsing error: parameter is required")
// Test add pivot cache with invalid data range
assert.EqualError(t, f.addPivotCache(0, "", &PivotTableOption{
DataRange: "$A$1:$E$31",
PivotTableRange: "Sheet1!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}, nil), "parameter 'DataRange' parsing error: parameter is invalid")
// Test add pivot table with empty options
assert.EqualError(t, f.addPivotTable(0, 0, "", &PivotTableOption{}), "parameter 'PivotTableRange' parsing error: parameter is required")
// Test add pivot table with invalid data range
assert.EqualError(t, f.addPivotTable(0, 0, "", &PivotTableOption{}), "parameter 'PivotTableRange' parsing error: parameter is required")
// Test add pivot fields with empty data range
assert.EqualError(t, f.addPivotFields(nil, &PivotTableOption{
DataRange: "$A$1:$E$31",
PivotTableRange: "Sheet1!$U$34:$O$2",
Rows: []PivotTableField{{Data: "Month"}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type"}},
Data: []PivotTableField{{Data: "Sales"}},
}), `parameter 'DataRange' parsing error: parameter is invalid`)
// Test get pivot fields index with empty data range
_, err = f.getPivotFieldsIndex([]PivotTableField{}, &PivotTableOption{})
assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`)
}
func TestAddPivotRowFields(t *testing.T) {
f := NewFile()
// Test invalid data range
assert.EqualError(t, f.addPivotRowFields(&xlsxPivotTableDefinition{}, &PivotTableOption{
DataRange: "Sheet1!$A$1:$A$1",
}), `parameter 'DataRange' parsing error: parameter is invalid`)
}
func TestAddPivotPageFields(t *testing.T) {
f := NewFile()
// Test invalid data range
assert.EqualError(t, f.addPivotPageFields(&xlsxPivotTableDefinition{}, &PivotTableOption{
DataRange: "Sheet1!$A$1:$A$1",
}), `parameter 'DataRange' parsing error: parameter is invalid`)
}
func TestAddPivotDataFields(t *testing.T) {
f := NewFile()
// Test invalid data range
assert.EqualError(t, f.addPivotDataFields(&xlsxPivotTableDefinition{}, &PivotTableOption{
DataRange: "Sheet1!$A$1:$A$1",
}), `parameter 'DataRange' parsing error: parameter is invalid`)
}
func TestAddPivotColFields(t *testing.T) {
f := NewFile()
// Test invalid data range
assert.EqualError(t, f.addPivotColFields(&xlsxPivotTableDefinition{}, &PivotTableOption{
DataRange: "Sheet1!$A$1:$A$1",
Columns: []PivotTableField{{Data: "Type"}},
}), `parameter 'DataRange' parsing error: parameter is invalid`)
}
func TestGetPivotFieldsOrder(t *testing.T) {
f := NewFile()
// Test get pivot fields order with not exist worksheet
_, err := f.getPivotFieldsOrder("SheetN!$A$1:$E$31")
assert.EqualError(t, err, "sheet SheetN is not exist")
}
func TestInStrSlice(t *testing.T) {
assert.EqualValues(t, -1, inStrSlice([]string{}, ""))
}
func TestGetPivotTableFieldName(t *testing.T) {
f := NewFile()
f.getPivotTableFieldName("-", []PivotTableField{})
}

346
rows.go
View File

@ -1,19 +1,21 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"math"
"strconv"
)
@ -21,8 +23,16 @@ import (
// GetRows return all the rows in a sheet by given worksheet name (case
// sensitive). For example:
//
// rows, err := f.GetRows("Sheet1")
// for _, row := range rows {
// rows, err := f.Rows("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for rows.Next() {
// row, err := rows.Columns()
// if err != nil {
// fmt.Println(err)
// }
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
@ -30,95 +40,38 @@ import (
// }
//
func (f *File) GetRows(sheet string) ([][]string, error) {
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
return nil, nil
}
xlsx, err := f.workSheetReader(sheet)
rows, err := f.Rows(sheet)
if err != nil {
return nil, err
}
if xlsx != nil {
output, _ := xml.Marshal(f.Sheet[name])
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
}
xml.NewDecoder(bytes.NewReader(f.readXML(name)))
d := f.sharedStringsReader()
var (
inElement string
rowData xlsxRow
)
rowCount, colCount, err := f.getTotalRowsCols(name)
if err != nil {
return nil, nil
}
rows := make([][]string, rowCount)
for i := range rows {
rows[i] = make([]string, colCount)
}
var row int
decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name)))
for {
token, _ := decoder.Token()
if token == nil {
results := make([][]string, 0, 64)
for rows.Next() {
if rows.Error() != nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
inElement = startElement.Name.Local
if inElement == "row" {
rowData = xlsxRow{}
_ = decoder.DecodeElement(&rowData, &startElement)
cr := rowData.R - 1
for _, colCell := range rowData.C {
col, _, err := CellNameToCoordinates(colCell.R)
if err != nil {
return nil, err
}
val, _ := colCell.getValueFrom(f, d)
rows[cr][col-1] = val
if val != "" {
row = rowData.R
}
}
}
default:
row, err := rows.Columns()
if err != nil {
break
}
results = append(results, row)
}
return rows[:row], nil
return results, nil
}
// Rows defines an iterator to a sheet
type Rows struct {
decoder *xml.Decoder
token xml.Token
err error
f *File
err error
curRow, totalRow, stashRow int
sheet string
rows []xlsxRow
f *File
decoder *xml.Decoder
}
// Next will return true if find the next row element.
func (rows *Rows) Next() bool {
for {
rows.token, rows.err = rows.decoder.Token()
if rows.err == io.EOF {
rows.err = nil
}
if rows.token == nil {
return false
}
switch startElement := rows.token.(type) {
case xml.StartElement:
inElement := startElement.Name.Local
if inElement == "row" {
return true
}
}
}
rows.curRow++
return rows.curRow <= rows.totalRow
}
// Error will return the error when the find next row element
@ -128,23 +81,62 @@ func (rows *Rows) Error() error {
// Columns return the current row's column values
func (rows *Rows) Columns() ([]string, error) {
if rows.token == nil {
return []string{}, nil
var (
err error
inElement string
row, cellCol int
columns []string
)
if rows.stashRow >= rows.curRow {
return columns, err
}
startElement := rows.token.(xml.StartElement)
r := xlsxRow{}
_ = rows.decoder.DecodeElement(&r, &startElement)
d := rows.f.sharedStringsReader()
columns := make([]string, len(r.C))
for _, colCell := range r.C {
col, _, err := CellNameToCoordinates(colCell.R)
if err != nil {
return columns, err
for {
token, _ := rows.decoder.Token()
if token == nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
inElement = startElement.Name.Local
if inElement == "row" {
for _, attr := range startElement.Attr {
if attr.Name.Local == "r" {
row, err = strconv.Atoi(attr.Value)
if err != nil {
return columns, err
}
if row > rows.curRow {
rows.stashRow = row - 1
return columns, err
}
}
}
}
if inElement == "c" {
colCell := xlsxC{}
_ = rows.decoder.DecodeElement(&colCell, &startElement)
cellCol, _, err = CellNameToCoordinates(colCell.R)
if err != nil {
return columns, err
}
blank := cellCol - len(columns)
for i := 1; i < blank; i++ {
columns = append(columns, "")
}
val, _ := colCell.getValueFrom(rows.f, d)
columns = append(columns, val)
}
case xml.EndElement:
inElement = startElement.Name.Local
if inElement == "row" {
return columns, err
}
}
val, _ := colCell.getValueFrom(rows.f, d)
columns[col-1] = val
}
return columns, nil
return columns, err
}
// ErrSheetNotExist defines an error of sheet is not exist
@ -153,14 +145,22 @@ type ErrSheetNotExist struct {
}
func (err ErrSheetNotExist) Error() string {
return fmt.Sprintf("Sheet %s is not exist", string(err.SheetName))
return fmt.Sprintf("sheet %s is not exist", string(err.SheetName))
}
// Rows return a rows iterator. For example:
// Rows returns a rows iterator, used for streaming reading data for a
// worksheet with a large data. For example:
//
// rows, err := f.Rows("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for rows.Next() {
// row, err := rows.Columns()
// row, err := rows.Columns()
// if err != nil {
// fmt.Println(err)
// }
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
@ -168,31 +168,22 @@ func (err ErrSheetNotExist) Error() string {
// }
//
func (f *File) Rows(sheet string) (*Rows, error) {
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return nil, err
}
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
return nil, ErrSheetNotExist{sheet}
}
if xlsx != nil {
if f.Sheet[name] != nil {
// flush data
output, _ := xml.Marshal(f.Sheet[name])
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
}
return &Rows{
f: f,
decoder: xml.NewDecoder(bytes.NewReader(f.readXML(name))),
}, nil
}
// getTotalRowsCols provides a function to get total columns and rows in a
// worksheet.
func (f *File) getTotalRowsCols(name string) (int, int, error) {
decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name)))
var inElement string
var r xlsxRow
var tr, tc int
var (
err error
inElement string
row int
rows Rows
)
decoder := f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
for {
token, _ := decoder.Token()
if token == nil {
@ -202,23 +193,23 @@ func (f *File) getTotalRowsCols(name string) (int, int, error) {
case xml.StartElement:
inElement = startElement.Name.Local
if inElement == "row" {
r = xlsxRow{}
_ = decoder.DecodeElement(&r, &startElement)
tr = r.R
for _, colCell := range r.C {
col, _, err := CellNameToCoordinates(colCell.R)
if err != nil {
return tr, tc, err
}
if col > tc {
tc = col
for _, attr := range startElement.Attr {
if attr.Name.Local == "r" {
row, err = strconv.Atoi(attr.Value)
if err != nil {
return &rows, err
}
}
}
rows.totalRow = row
}
default:
}
}
return tr, tc, nil
rows.f = f
rows.sheet = name
rows.decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
return &rows, nil
}
// SetRowHeight provides a function to set the height of a single row. For
@ -248,7 +239,8 @@ func (f *File) SetRowHeight(sheet string, row int, height float64) error {
// name and row index.
func (f *File) getRowHeight(sheet string, row int) int {
xlsx, _ := f.workSheetReader(sheet)
for _, v := range xlsx.SheetData.Row {
for i := range xlsx.SheetData.Row {
v := &xlsx.SheetData.Row[i]
if v.R == row+1 && v.Ht != 0 {
return int(convertRowHeightToPixels(v.Ht))
}
@ -286,15 +278,21 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
// sharedStringsReader provides a function to get the pointer to the structure
// after deserialization of xl/sharedStrings.xml.
func (f *File) sharedStringsReader() *xlsxSST {
var err error
if f.SharedStrings == nil {
var sharedStrings xlsxSST
ss := f.readXML("xl/sharedStrings.xml")
if len(ss) == 0 {
ss = f.readXML("xl/SharedStrings.xml")
}
_ = xml.Unmarshal(namespaceStrictToTransitional(ss), &sharedStrings)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))).
Decode(&sharedStrings); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
f.SharedStrings = &sharedStrings
}
return f.SharedStrings
}
@ -304,20 +302,21 @@ func (f *File) sharedStringsReader() *xlsxSST {
func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
switch xlsx.T {
case "s":
xlsxSI := 0
xlsxSI, _ = strconv.Atoi(xlsx.V)
if len(d.SI[xlsxSI].R) > 0 {
value := ""
for _, v := range d.SI[xlsxSI].R {
value += v.T
if xlsx.V != "" {
xlsxSI := 0
xlsxSI, _ = strconv.Atoi(xlsx.V)
if len(d.SI) > xlsxSI {
return f.formattedValue(xlsx.S, d.SI[xlsxSI].String()), nil
}
return value, nil
}
return f.formattedValue(xlsx.S, d.SI[xlsxSI].T), nil
return f.formattedValue(xlsx.S, xlsx.V), nil
case "str":
return f.formattedValue(xlsx.S, xlsx.V), nil
case "inlineStr":
return f.formattedValue(xlsx.S, xlsx.IS.T), nil
if xlsx.IS != nil {
return f.formattedValue(xlsx.S, xlsx.IS.String()), nil
}
return f.formattedValue(xlsx.S, xlsx.V), nil
default:
return f.formattedValue(xlsx.S, xlsx.V), nil
}
@ -364,8 +363,8 @@ func (f *File) GetRowVisible(sheet string, row int) (bool, error) {
}
// SetRowOutlineLevel provides a function to set outline level number of a
// single row by given worksheet name and Excel row number. For example,
// outline row 2 in Sheet1 to level 1:
// single row by given worksheet name and Excel row number. The value of
// parameter 'level' is 1-7. For example, outline row 2 in Sheet1 to level 1:
//
// err := f.SetRowOutlineLevel("Sheet1", 2, 1)
//
@ -373,6 +372,9 @@ func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error {
if row < 1 {
return newInvalidRowNumberError(row)
}
if level > 7 || level < 1 {
return errors.New("invalid outline level")
}
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
@ -421,16 +423,18 @@ func (f *File) RemoveRow(sheet string, row int) error {
return err
}
if row > len(xlsx.SheetData.Row) {
return nil
return f.adjustHelper(sheet, rows, row, -1)
}
for rowIdx := range xlsx.SheetData.Row {
if xlsx.SheetData.Row[rowIdx].R == row {
xlsx.SheetData.Row = append(xlsx.SheetData.Row[:rowIdx],
xlsx.SheetData.Row[rowIdx+1:]...)[:len(xlsx.SheetData.Row)-1]
return f.adjustHelper(sheet, rows, row, -1)
keep := 0
for rowIdx := 0; rowIdx < len(xlsx.SheetData.Row); rowIdx++ {
v := &xlsx.SheetData.Row[rowIdx]
if v.R != row {
xlsx.SheetData.Row[keep] = *v
keep++
}
}
return nil
xlsx.SheetData.Row = xlsx.SheetData.Row[:keep]
return f.adjustHelper(sheet, rows, row, -1)
}
// InsertRow provides a function to insert a new row after given Excel row
@ -439,6 +443,10 @@ func (f *File) RemoveRow(sheet string, row int) error {
//
// err := f.InsertRow("Sheet1", 3)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
func (f *File) InsertRow(sheet string, row int) error {
if row < 1 {
return newInvalidRowNumberError(row)
@ -517,6 +525,40 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
} else {
xlsx.SheetData.Row = append(xlsx.SheetData.Row, rowCopy)
}
return f.duplicateMergeCells(sheet, xlsx, row, row2)
}
// duplicateMergeCells merge cells in the destination row if there are single
// row merged cells in the copied row.
func (f *File) duplicateMergeCells(sheet string, xlsx *xlsxWorksheet, row, row2 int) error {
if xlsx.MergeCells == nil {
return nil
}
if row > row2 {
row++
}
for _, rng := range xlsx.MergeCells.Cells {
coordinates, err := f.areaRefToCoordinates(rng.Ref)
if err != nil {
return err
}
if coordinates[1] < row2 && row2 < coordinates[3] {
return nil
}
}
for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
areaData := xlsx.MergeCells.Cells[i]
coordinates, _ := f.areaRefToCoordinates(areaData.Ref)
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if y1 == y2 && y1 == row {
from, _ := CoordinatesToCellName(x1, row2)
to, _ := CoordinatesToCellName(x2, row2)
if err := f.MergeCell(sheet, from, to); err != nil {
return err
}
i++
}
}
return nil
}
@ -552,6 +594,22 @@ func checkRow(xlsx *xlsxWorksheet) error {
if colCount == 0 {
continue
}
// check and fill the cell without r attribute in a row element
rCount := 0
for idx, cell := range rowData.C {
rCount++
if cell.R != "" {
lastR, _, err := CellNameToCoordinates(cell.R)
if err != nil {
return err
}
if lastR > rCount {
rCount = lastR
}
continue
}
rowData.C[idx].R, _ = CoordinatesToCellName(rCount, rowIdx+1)
}
lastCol, _, err := CellNameToCoordinates(rowData.C[colCount-1].R)
if err != nil {
return err

View File

@ -1,27 +1,29 @@
package excelize
import (
"bytes"
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRows(t *testing.T) {
const sheet2 = "Sheet2"
xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
rows, err := xlsx.Rows(sheet2)
rows, err := f.Rows(sheet2)
if !assert.NoError(t, err) {
t.FailNow()
}
collectedRows := make([][]string, 0)
var collectedRows [][]string
for rows.Next() {
columns, err := rows.Columns()
assert.NoError(t, err)
@ -31,7 +33,7 @@ func TestRows(t *testing.T) {
t.FailNow()
}
returnedRows, err := xlsx.GetRows(sheet2)
returnedRows, err := f.GetRows(sheet2)
assert.NoError(t, err)
for i := range returnedRows {
returnedRows[i] = trimSliceSpace(returnedRows[i])
@ -40,8 +42,43 @@ func TestRows(t *testing.T) {
t.FailNow()
}
r := Rows{}
r.Columns()
f = NewFile()
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)
_, err = f.Rows("Sheet1")
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
}
func TestRowsIterator(t *testing.T) {
const (
sheet2 = "Sheet2"
expectedNumRow = 11
)
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
require.NoError(t, err)
rows, err := f.Rows(sheet2)
require.NoError(t, err)
var rowCount int
for rows.Next() {
rowCount++
require.True(t, rowCount <= expectedNumRow, "rowCount is greater than expected")
}
assert.Equal(t, expectedNumRow, rowCount)
// Valued cell sparse distribution test
f = NewFile()
cells := []string{"C1", "E1", "A3", "B3", "C3", "D3", "E3"}
for _, cell := range cells {
assert.NoError(t, f.SetCellValue("Sheet1", cell, 1))
}
rows, err = f.Rows("Sheet1")
require.NoError(t, err)
rowCount = 0
for rows.Next() {
rowCount++
require.True(t, rowCount <= 3, "rowCount is greater than expected")
}
assert.Equal(t, 3, rowCount)
}
func TestRowsError(t *testing.T) {
@ -55,7 +92,7 @@ func TestRowsError(t *testing.T) {
func TestRowHeight(t *testing.T) {
xlsx := NewFile()
sheet1 := xlsx.GetSheetName(1)
sheet1 := xlsx.GetSheetName(0)
assert.EqualError(t, xlsx.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0), "invalid row number 0")
@ -95,86 +132,135 @@ func TestRowHeight(t *testing.T) {
convertColWidthToPixels(0)
}
func TestColumns(t *testing.T) {
f := NewFile()
rows, err := f.Rows("Sheet1")
assert.NoError(t, err)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="2"><c r="A1" t="s"><v>1</v></c></row></sheetData></worksheet>`)))
_, err = rows.Columns()
assert.NoError(t, err)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="2"><c r="A1" t="s"><v>1</v></c></row></sheetData></worksheet>`)))
rows.curRow = 1
_, err = rows.Columns()
assert.NoError(t, err)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="A"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)))
rows.stashRow, rows.curRow = 0, 1
_, err = rows.Columns()
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)))
_, err = rows.Columns()
assert.NoError(t, err)
rows.curRow = 3
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="1"><c r="A" t="s"><v>1</v></c></row></sheetData></worksheet>`)))
_, err = rows.Columns()
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
// Test token is nil
rows.decoder = f.xmlNewDecoder(bytes.NewReader(nil))
_, err = rows.Columns()
assert.NoError(t, err)
}
func TestSharedStringsReader(t *testing.T) {
f := NewFile()
f.XLSX["xl/sharedStrings.xml"] = MacintoshCyrillicCharset
f.sharedStringsReader()
}
func TestRowVisibility(t *testing.T) {
xlsx, err := prepareTestBook1()
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
xlsx.NewSheet("Sheet3")
assert.NoError(t, xlsx.SetRowVisible("Sheet3", 2, false))
assert.NoError(t, xlsx.SetRowVisible("Sheet3", 2, true))
xlsx.GetRowVisible("Sheet3", 2)
xlsx.GetRowVisible("Sheet3", 25)
assert.EqualError(t, xlsx.SetRowVisible("Sheet3", 0, true), "invalid row number 0")
f.NewSheet("Sheet3")
assert.NoError(t, f.SetRowVisible("Sheet3", 2, false))
assert.NoError(t, f.SetRowVisible("Sheet3", 2, true))
visiable, err := f.GetRowVisible("Sheet3", 2)
assert.Equal(t, true, visiable)
assert.NoError(t, err)
visiable, err = f.GetRowVisible("Sheet3", 25)
assert.Equal(t, false, visiable)
assert.NoError(t, err)
assert.EqualError(t, f.SetRowVisible("Sheet3", 0, true), "invalid row number 0")
assert.EqualError(t, f.SetRowVisible("SheetN", 2, false), "sheet SheetN is not exist")
visible, err := xlsx.GetRowVisible("Sheet3", 0)
visible, err := f.GetRowVisible("Sheet3", 0)
assert.Equal(t, false, visible)
assert.EqualError(t, err, "invalid row number 0")
_, err = f.GetRowVisible("SheetN", 1)
assert.EqualError(t, err, "sheet SheetN is not exist")
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
}
func TestRemoveRow(t *testing.T) {
xlsx := NewFile()
sheet1 := xlsx.GetSheetName(1)
r, err := xlsx.workSheetReader(sheet1)
f := NewFile()
sheet1 := f.GetSheetName(0)
r, err := f.workSheetReader(sheet1)
assert.NoError(t, err)
const (
colCount = 10
rowCount = 10
)
fillCells(xlsx, sheet1, colCount, rowCount)
fillCells(f, sheet1, colCount, rowCount)
xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))
assert.EqualError(t, xlsx.RemoveRow(sheet1, -1), "invalid row number -1")
assert.EqualError(t, f.RemoveRow(sheet1, -1), "invalid row number -1")
assert.EqualError(t, xlsx.RemoveRow(sheet1, 0), "invalid row number 0")
assert.EqualError(t, f.RemoveRow(sheet1, 0), "invalid row number 0")
assert.NoError(t, xlsx.RemoveRow(sheet1, 4))
assert.NoError(t, f.RemoveRow(sheet1, 4))
if !assert.Len(t, r.SheetData.Row, rowCount-1) {
t.FailNow()
}
xlsx.MergeCell(sheet1, "B3", "B5")
assert.NoError(t, f.MergeCell(sheet1, "B3", "B5"))
assert.NoError(t, xlsx.RemoveRow(sheet1, 2))
assert.NoError(t, f.RemoveRow(sheet1, 2))
if !assert.Len(t, r.SheetData.Row, rowCount-2) {
t.FailNow()
}
assert.NoError(t, xlsx.RemoveRow(sheet1, 4))
assert.NoError(t, f.RemoveRow(sheet1, 4))
if !assert.Len(t, r.SheetData.Row, rowCount-3) {
t.FailNow()
}
err = xlsx.AutoFilter(sheet1, "A2", "A2", `{"column":"A","expression":"x != blanks"}`)
err = f.AutoFilter(sheet1, "A2", "A2", `{"column":"A","expression":"x != blanks"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, xlsx.RemoveRow(sheet1, 1))
assert.NoError(t, f.RemoveRow(sheet1, 1))
if !assert.Len(t, r.SheetData.Row, rowCount-4) {
t.FailNow()
}
assert.NoError(t, xlsx.RemoveRow(sheet1, 2))
assert.NoError(t, f.RemoveRow(sheet1, 2))
if !assert.Len(t, r.SheetData.Row, rowCount-5) {
t.FailNow()
}
assert.NoError(t, xlsx.RemoveRow(sheet1, 1))
assert.NoError(t, f.RemoveRow(sheet1, 1))
if !assert.Len(t, r.SheetData.Row, rowCount-6) {
t.FailNow()
}
assert.NoError(t, xlsx.RemoveRow(sheet1, 10))
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRemoveRow.xlsx")))
assert.NoError(t, f.RemoveRow(sheet1, 10))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveRow.xlsx")))
// Test remove row on not exist worksheet
assert.EqualError(t, f.RemoveRow("SheetN", 1), `sheet SheetN is not exist`)
}
func TestInsertRow(t *testing.T) {
xlsx := NewFile()
sheet1 := xlsx.GetSheetName(1)
sheet1 := xlsx.GetSheetName(0)
r, err := xlsx.workSheetReader(sheet1)
assert.NoError(t, err)
const (
@ -183,7 +269,7 @@ func TestInsertRow(t *testing.T) {
)
fillCells(xlsx, sheet1, colCount, rowCount)
xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
assert.NoError(t, xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))
assert.EqualError(t, xlsx.InsertRow(sheet1, -1), "invalid row number -1")
@ -206,7 +292,7 @@ func TestInsertRow(t *testing.T) {
// It is important for insert workflow to be constant to avoid side effect with functions related to internal structure.
func TestInsertRowInEmptyFile(t *testing.T) {
xlsx := NewFile()
sheet1 := xlsx.GetSheetName(1)
sheet1 := xlsx.GetSheetName(0)
r, err := xlsx.workSheetReader(sheet1)
assert.NoError(t, err)
assert.NoError(t, xlsx.InsertRow(sheet1, 1))
@ -233,8 +319,8 @@ func TestDuplicateRowFromSingleRow(t *testing.T) {
t.Run("FromSingleRow", func(t *testing.T) {
xlsx := NewFile()
xlsx.SetCellStr(sheet, "A1", cells["A1"])
xlsx.SetCellStr(sheet, "B1", cells["B1"])
assert.NoError(t, xlsx.SetCellStr(sheet, "A1", cells["A1"]))
assert.NoError(t, xlsx.SetCellStr(sheet, "B1", cells["B1"]))
assert.NoError(t, xlsx.DuplicateRow(sheet, 1))
if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.FromSingleRow_1"))) {
@ -286,13 +372,13 @@ func TestDuplicateRowUpdateDuplicatedRows(t *testing.T) {
t.Run("UpdateDuplicatedRows", func(t *testing.T) {
xlsx := NewFile()
xlsx.SetCellStr(sheet, "A1", cells["A1"])
xlsx.SetCellStr(sheet, "B1", cells["B1"])
assert.NoError(t, xlsx.SetCellStr(sheet, "A1", cells["A1"]))
assert.NoError(t, xlsx.SetCellStr(sheet, "B1", cells["B1"]))
assert.NoError(t, xlsx.DuplicateRow(sheet, 1))
xlsx.SetCellStr(sheet, "A2", cells["A2"])
xlsx.SetCellStr(sheet, "B2", cells["B2"])
assert.NoError(t, xlsx.SetCellStr(sheet, "A2", cells["A2"]))
assert.NoError(t, xlsx.SetCellStr(sheet, "B2", cells["B2"]))
if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.UpdateDuplicatedRows"))) {
t.FailNow()
@ -327,8 +413,7 @@ func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)
assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
@ -442,8 +527,7 @@ func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)
assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
@ -488,8 +572,7 @@ func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)
assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
@ -534,8 +617,7 @@ func TestDuplicateRowInsertBefore(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)
assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
@ -581,8 +663,7 @@ func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)
assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
@ -612,6 +693,55 @@ func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
})
}
func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
assert.NoError(t, f.MergeCell(sheet, "B2", "C2"))
assert.NoError(t, f.MergeCell(sheet, "C6", "C8"))
return f
}
t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
xlsx := newFileWithDefaults()
assert.NoError(t, xlsx.DuplicateRowTo(sheet, 2, 1))
assert.NoError(t, xlsx.DuplicateRowTo(sheet, 1, 8))
if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.InsertBeforeWithMergeCells"))) {
t.FailNow()
}
expect := []MergeCell{
{"B3:C3", "B2 Value"},
{"C7:C10", ""},
{"B1:C1", "B2 Value"},
}
mergeCells, err := xlsx.GetMergeCells(sheet)
assert.NoError(t, err)
for idx, val := range expect {
if !assert.Equal(t, val, mergeCells[idx]) {
t.FailNow()
}
}
})
}
func TestDuplicateRowInvalidRownum(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRowInvalidRownum.%s.xlsx")
@ -632,7 +762,7 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
t.Run(name, func(t *testing.T) {
xlsx := NewFile()
for col, val := range cells {
xlsx.SetCellStr(sheet, col, val)
assert.NoError(t, xlsx.SetCellStr(sheet, col, val))
}
assert.EqualError(t, xlsx.DuplicateRow(sheet, row), fmt.Sprintf("invalid row number %d", row))
@ -654,7 +784,7 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
t.Run(name, func(t *testing.T) {
xlsx := NewFile()
for col, val := range cells {
xlsx.SetCellStr(sheet, col, val)
assert.NoError(t, xlsx.SetCellStr(sheet, col, val))
}
assert.EqualError(t, xlsx.DuplicateRowTo(sheet, row1, row2), fmt.Sprintf("invalid row number %d", row1))
@ -672,6 +802,61 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
}
}
func TestDuplicateRowTo(t *testing.T) {
f := File{}
assert.EqualError(t, f.DuplicateRowTo("SheetN", 1, 2), "sheet SheetN is not exist")
}
func TestDuplicateMergeCells(t *testing.T) {
f := File{}
xlsx := &xlsxWorksheet{MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{{Ref: "A1:-"}},
}}
assert.EqualError(t, f.duplicateMergeCells("Sheet1", xlsx, 0, 0), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
xlsx.MergeCells.Cells[0].Ref = "A1:B1"
assert.EqualError(t, f.duplicateMergeCells("SheetN", xlsx, 1, 2), "sheet SheetN is not exist")
}
func TestGetValueFrom(t *testing.T) {
c := &xlsxC{T: "inlineStr"}
f := NewFile()
d := &xlsxSST{}
val, err := c.getValueFrom(f, d)
assert.NoError(t, err)
assert.Equal(t, "", val)
}
func TestErrSheetNotExistError(t *testing.T) {
err := ErrSheetNotExist{SheetName: "Sheet1"}
assert.EqualValues(t, err.Error(), "sheet Sheet1 is not exist")
}
func TestCheckRow(t *testing.T) {
f := NewFile()
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ><sheetData><row r="2"><c><v>1</v></c><c r="F2"><v>2</v></c><c><v>3</v></c><c><v>4</v></c><c r="M2"><v>5</v></c></row></sheetData></worksheet>`)
_, err := f.GetRows("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", false))
f = NewFile()
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ><sheetData><row r="2"><c><v>1</v></c><c r="-"><v>2</v></c><c><v>3</v></c><c><v>4</v></c><c r="M2"><v>5</v></c></row></sheetData></worksheet>`)
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
}
func BenchmarkRows(b *testing.B) {
f, _ := OpenFile(filepath.Join("test", "Book1.xlsx"))
for i := 0; i < b.N; i++ {
rows, _ := f.Rows("Sheet2")
for rows.Next() {
row, _ := rows.Columns()
for i := range row {
if i >= 0 {
continue
}
}
}
}
}
func trimSliceSpace(s []string) []string {
for {
if len(s) > 0 && s[len(s)-1] == "" {

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -40,7 +40,7 @@ func parseFormatShapeSet(formatSet string) (*formatShape, error) {
// print settings) and properties set. For example, add text box (rect shape)
// in Sheet1:
//
// err := f.AddShape("Sheet1", "G6", `{"type":"rect","color":{"line":"#4286F4","fill":"#8eb9ff"},"paragraph":[{"text":"Rectangle Shape","font":{"bold":true,"italic":true,"family":"Berlin Sans FB Demi","size":36,"color":"#777777","underline":"sng"}}],"width":180,"height": 90}`)
// err := f.AddShape("Sheet1", "G6", `{"type":"rect","color":{"line":"#4286F4","fill":"#8eb9ff"},"paragraph":[{"text":"Rectangle Shape","font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777","underline":"sng"}}],"width":180,"height": 90}`)
//
// The following shows the type of shape supported by excelize:
//
@ -275,7 +275,8 @@ func (f *File) AddShape(sheet, cell, format string) error {
drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1)
} else {
// Add first shape for given sheet.
rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
f.addSheetDrawing(sheet, rID)
}
err = f.addDrawingShape(sheet, drawingXML, cell, formatSet)
@ -360,7 +361,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format
FontRef: &aFontRef{
Idx: "minor",
SchemeClr: &attrValString{
Val: "tx1",
Val: stringPtr("tx1"),
},
},
},
@ -377,11 +378,11 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format
if len(formatSet.Paragraph) < 1 {
formatSet.Paragraph = []formatShapeParagraph{
{
Font: formatFont{
Font: Font{
Bold: false,
Italic: false,
Underline: "none",
Family: "Calibri",
Family: f.GetDefaultFont(),
Size: 11,
Color: "#000000",
},
@ -409,11 +410,6 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format
U: u,
Sz: p.Font.Size * 100,
Latin: &aLatin{Typeface: p.Font.Family},
SolidFill: &aSolidFill{
SrgbClr: &attrValString{
Val: strings.Replace(strings.ToUpper(p.Font.Color), "#", "", -1),
},
},
},
T: text,
},
@ -421,6 +417,14 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format
Lang: "en-US",
},
}
srgbClr := strings.Replace(strings.ToUpper(p.Font.Color), "#", "", -1)
if len(srgbClr) == 6 {
paragraph.R.RPr.SolidFill = &aSolidFill{
SrgbClr: &attrValString{
Val: stringPtr(srgbClr),
},
}
}
shape.TxBody.P = append(shape.TxBody.P, paragraph)
}
twoCellAnchor.Sp = &shape
@ -449,7 +453,7 @@ func setShapeRef(color string, i int) *aRef {
return &aRef{
Idx: i,
SrgbClr: &attrValString{
Val: strings.Replace(strings.ToUpper(color), "#", "", -1),
Val: stringPtr(strings.Replace(strings.ToUpper(color), "#", "", -1)),
},
}
}

28
shape_test.go Normal file
View File

@ -0,0 +1,28 @@
package excelize
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddShape(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.AddShape("Sheet1", "A30", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`))
assert.NoError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`))
assert.NoError(t, f.AddShape("Sheet1", "C30", `{"type":"rect","paragraph":[]}`))
assert.EqualError(t, f.AddShape("Sheet3", "H1", `{"type":"ellipseRibbon", "color":{"line":"#4286f4","fill":"#8eb9ff"}, "paragraph":[{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777","underline":"single"}}], "height": 90}`), "sheet Sheet3 is not exist")
assert.EqualError(t, f.AddShape("Sheet3", "H1", ""), "unexpected end of JSON input")
assert.EqualError(t, f.AddShape("Sheet1", "A", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx")))
// Test add first shape for given sheet.
f = NewFile()
assert.NoError(t, f.AddShape("Sheet1", "A1", `{"type":"ellipseRibbon", "color":{"line":"#4286f4","fill":"#8eb9ff"}, "paragraph":[{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777","underline":"single"}}], "height": 90}`))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
}

983
sheet.go

File diff suppressed because it is too large Load Diff

View File

@ -2,50 +2,91 @@ package excelize_test
import (
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/360EntSecGroup-Skylar/excelize"
"github.com/360EntSecGroup-Skylar/excelize/v2"
"github.com/mohae/deepcopy"
"github.com/stretchr/testify/assert"
)
func ExampleFile_SetPageLayout() {
xl := excelize.NewFile()
f := excelize.NewFile()
if err := xl.SetPageLayout(
if err := f.SetPageLayout(
"Sheet1",
excelize.PageLayoutOrientation(excelize.OrientationLandscape),
); err != nil {
panic(err)
fmt.Println(err)
}
if err := xl.SetPageLayout(
if err := f.SetPageLayout(
"Sheet1",
excelize.PageLayoutPaperSize(10),
excelize.FitToHeight(2),
excelize.FitToWidth(2),
); err != nil {
panic(err)
fmt.Println(err)
}
// Output:
}
func ExampleFile_GetPageLayout() {
xl := excelize.NewFile()
f := excelize.NewFile()
var (
orientation excelize.PageLayoutOrientation
paperSize excelize.PageLayoutPaperSize
fitToHeight excelize.FitToHeight
fitToWidth excelize.FitToWidth
)
if err := xl.GetPageLayout("Sheet1", &orientation); err != nil {
panic(err)
if err := f.GetPageLayout("Sheet1", &orientation); err != nil {
fmt.Println(err)
}
if err := xl.GetPageLayout("Sheet1", &paperSize); err != nil {
panic(err)
if err := f.GetPageLayout("Sheet1", &paperSize); err != nil {
fmt.Println(err)
}
if err := f.GetPageLayout("Sheet1", &fitToHeight); err != nil {
fmt.Println(err)
}
if err := f.GetPageLayout("Sheet1", &fitToWidth); err != nil {
fmt.Println(err)
}
fmt.Println("Defaults:")
fmt.Printf("- orientation: %q\n", orientation)
fmt.Printf("- paper size: %d\n", paperSize)
fmt.Printf("- fit to height: %d\n", fitToHeight)
fmt.Printf("- fit to width: %d\n", fitToWidth)
// Output:
// Defaults:
// - orientation: "portrait"
// - paper size: 1
// - fit to height: 1
// - fit to width: 1
}
func TestNewSheet(t *testing.T) {
f := excelize.NewFile()
sheetID := f.NewSheet("Sheet2")
f.SetActiveSheet(sheetID)
// delete original sheet
f.DeleteSheet(f.GetSheetName(f.GetSheetIndex("Sheet1")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx")))
}
func TestSetPane(t *testing.T) {
f := excelize.NewFile()
assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":false,"split":false}`))
f.NewSheet("Panes 2")
assert.NoError(t, f.SetPanes("Panes 2", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`))
f.NewSheet("Panes 3")
assert.NoError(t, f.SetPanes("Panes 3", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`))
f.NewSheet("Panes 4")
assert.NoError(t, f.SetPanes("Panes 4", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`))
assert.NoError(t, f.SetPanes("Panes 4", ""))
assert.EqualError(t, f.SetPanes("SheetN", ""), "sheet SheetN is not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx")))
}
func TestPageLayoutOption(t *testing.T) {
@ -57,6 +98,8 @@ func TestPageLayoutOption(t *testing.T) {
}{
{new(excelize.PageLayoutOrientation), excelize.PageLayoutOrientation(excelize.OrientationLandscape)},
{new(excelize.PageLayoutPaperSize), excelize.PageLayoutPaperSize(10)},
{new(excelize.FitToHeight), excelize.FitToHeight(2)},
{new(excelize.FitToWidth), excelize.FitToWidth(2)},
}
for i, test := range testData {
@ -69,26 +112,26 @@ func TestPageLayoutOption(t *testing.T) {
val1 := deepcopy.Copy(def).(excelize.PageLayoutOptionPtr)
val2 := deepcopy.Copy(def).(excelize.PageLayoutOptionPtr)
xl := excelize.NewFile()
f := excelize.NewFile()
// Get the default value
assert.NoError(t, xl.GetPageLayout(sheet, def), opt)
assert.NoError(t, f.GetPageLayout(sheet, def), opt)
// Get again and check
assert.NoError(t, xl.GetPageLayout(sheet, val1), opt)
assert.NoError(t, f.GetPageLayout(sheet, val1), opt)
if !assert.Equal(t, val1, def, opt) {
t.FailNow()
}
// Set the same value
assert.NoError(t, xl.SetPageLayout(sheet, val1), opt)
assert.NoError(t, f.SetPageLayout(sheet, val1), opt)
// Get again and check
assert.NoError(t, xl.GetPageLayout(sheet, val1), opt)
assert.NoError(t, f.GetPageLayout(sheet, val1), opt)
if !assert.Equal(t, val1, def, "%T: value should not have changed", opt) {
t.FailNow()
}
// Set a different value
assert.NoError(t, xl.SetPageLayout(sheet, test.nonDefault), opt)
assert.NoError(t, xl.GetPageLayout(sheet, val1), opt)
assert.NoError(t, f.SetPageLayout(sheet, test.nonDefault), opt)
assert.NoError(t, f.GetPageLayout(sheet, val1), opt)
// Get again and compare
assert.NoError(t, xl.GetPageLayout(sheet, val2), opt)
assert.NoError(t, f.GetPageLayout(sheet, val2), opt)
if !assert.Equal(t, val1, val2, "%T: value should not have changed", opt) {
t.FailNow()
}
@ -97,8 +140,8 @@ func TestPageLayoutOption(t *testing.T) {
t.FailNow()
}
// Restore the default value
assert.NoError(t, xl.SetPageLayout(sheet, def), opt)
assert.NoError(t, xl.GetPageLayout(sheet, val1), opt)
assert.NoError(t, f.SetPageLayout(sheet, def), opt)
assert.NoError(t, f.GetPageLayout(sheet, val1), opt)
if !assert.Equal(t, def, val1) {
t.FailNow()
}
@ -106,6 +149,35 @@ func TestPageLayoutOption(t *testing.T) {
}
}
func TestSearchSheet(t *testing.T) {
f, err := excelize.OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
// Test search in a not exists worksheet.
_, err = f.SearchSheet("Sheet4", "")
assert.EqualError(t, err, "sheet Sheet4 is not exist")
var expected []string
// Test search a not exists value.
result, err := f.SearchSheet("Sheet1", "X")
assert.NoError(t, err)
assert.EqualValues(t, expected, result)
result, err = f.SearchSheet("Sheet1", "A")
assert.NoError(t, err)
assert.EqualValues(t, []string{"A1"}, result)
// Test search the coordinates where the numerical value in the range of
// "0-9" of Sheet1 is described by regular expression:
result, err = f.SearchSheet("Sheet1", "[0-9]", true)
assert.NoError(t, err)
assert.EqualValues(t, expected, result)
// Test search worksheet data after set cell value
f = excelize.NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
_, err = f.SearchSheet("Sheet1", "")
assert.NoError(t, err)
}
func TestSetPageLayout(t *testing.T) {
f := excelize.NewFile()
// Test set page layout on not exists worksheet.
@ -117,3 +189,135 @@ func TestGetPageLayout(t *testing.T) {
// Test get page layout on not exists worksheet.
assert.EqualError(t, f.GetPageLayout("SheetN"), "sheet SheetN is not exist")
}
func TestSetHeaderFooter(t *testing.T) {
f := excelize.NewFile()
assert.NoError(t, f.SetCellStr("Sheet1", "A1", "Test SetHeaderFooter"))
// Test set header and footer on not exists worksheet.
assert.EqualError(t, f.SetHeaderFooter("SheetN", nil), "sheet SheetN is not exist")
// Test set header and footer with illegal setting.
assert.EqualError(t, f.SetHeaderFooter("Sheet1", &excelize.FormatHeaderFooter{
OddHeader: strings.Repeat("c", 256),
}), "field OddHeader must be less than 255 characters")
assert.NoError(t, f.SetHeaderFooter("Sheet1", nil))
assert.NoError(t, f.SetHeaderFooter("Sheet1", &excelize.FormatHeaderFooter{
DifferentFirst: true,
DifferentOddEven: true,
OddHeader: "&R&P",
OddFooter: "&C&F",
EvenHeader: "&L&P",
EvenFooter: "&L&D&R&T",
FirstHeader: `&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D`,
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetHeaderFooter.xlsx")))
}
func TestDefinedName(t *testing.T) {
f := excelize.NewFile()
assert.NoError(t, f.SetDefinedName(&excelize.DefinedName{
Name: "Amount",
RefersTo: "Sheet1!$A$2:$D$5",
Comment: "defined name comment",
Scope: "Sheet1",
}))
assert.NoError(t, f.SetDefinedName(&excelize.DefinedName{
Name: "Amount",
RefersTo: "Sheet1!$A$2:$D$5",
Comment: "defined name comment",
}))
assert.EqualError(t, f.SetDefinedName(&excelize.DefinedName{
Name: "Amount",
RefersTo: "Sheet1!$A$2:$D$5",
Comment: "defined name comment",
}), "the same name already exists on the scope")
assert.EqualError(t, f.DeleteDefinedName(&excelize.DefinedName{
Name: "No Exist Defined Name",
}), "no defined name on the scope")
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[1].RefersTo)
assert.NoError(t, f.DeleteDefinedName(&excelize.DefinedName{
Name: "Amount",
}))
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo)
assert.Exactly(t, 1, len(f.GetDefinedName()))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx")))
}
func TestGroupSheets(t *testing.T) {
f := excelize.NewFile()
sheets := []string{"Sheet2", "Sheet3"}
for _, sheet := range sheets {
f.NewSheet(sheet)
}
assert.EqualError(t, f.GroupSheets([]string{"Sheet1", "SheetN"}), "sheet SheetN is not exist")
assert.EqualError(t, f.GroupSheets([]string{"Sheet2", "Sheet3"}), "group worksheet must contain an active worksheet")
assert.NoError(t, f.GroupSheets([]string{"Sheet1", "Sheet2"}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGroupSheets.xlsx")))
}
func TestUngroupSheets(t *testing.T) {
f := excelize.NewFile()
sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"}
for _, sheet := range sheets {
f.NewSheet(sheet)
}
assert.NoError(t, f.UngroupSheets())
}
func TestInsertPageBreak(t *testing.T) {
f := excelize.NewFile()
assert.NoError(t, f.InsertPageBreak("Sheet1", "A1"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "B2"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "C3"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "C3"))
assert.EqualError(t, f.InsertPageBreak("Sheet1", "A"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.InsertPageBreak("SheetN", "C3"), "sheet SheetN is not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertPageBreak.xlsx")))
}
func TestRemovePageBreak(t *testing.T) {
f := excelize.NewFile()
assert.NoError(t, f.RemovePageBreak("Sheet1", "A2"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "A2"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "B2"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "A1"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "B2"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "C3"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "C3"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "A3"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "B3"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "A3"))
f.NewSheet("Sheet2")
assert.NoError(t, f.InsertPageBreak("Sheet2", "B2"))
assert.NoError(t, f.InsertPageBreak("Sheet2", "C2"))
assert.NoError(t, f.RemovePageBreak("Sheet2", "B2"))
assert.EqualError(t, f.RemovePageBreak("Sheet1", "A"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.RemovePageBreak("SheetN", "C3"), "sheet SheetN is not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemovePageBreak.xlsx")))
}
func TestGetSheetName(t *testing.T) {
f, _ := excelize.OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.Equal(t, "Sheet1", f.GetSheetName(0))
assert.Equal(t, "Sheet2", f.GetSheetName(1))
assert.Equal(t, "", f.GetSheetName(-1))
assert.Equal(t, "", f.GetSheetName(2))
}
func TestGetSheetMap(t *testing.T) {
expectedMap := map[int]string{
1: "Sheet1",
2: "Sheet2",
}
f, _ := excelize.OpenFile(filepath.Join("test", "Book1.xlsx"))
sheetMap := f.GetSheetMap()
for idx, name := range sheetMap {
assert.Equal(t, expectedMap[idx], name)
}
assert.Equal(t, len(sheetMap), 2)
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -191,3 +191,362 @@ func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error {
}
return err
}
type (
// PageMarginBottom specifies the bottom margin for the page.
PageMarginBottom float64
// PageMarginFooter specifies the footer margin for the page.
PageMarginFooter float64
// PageMarginHeader specifies the header margin for the page.
PageMarginHeader float64
// PageMarginLeft specifies the left margin for the page.
PageMarginLeft float64
// PageMarginRight specifies the right margin for the page.
PageMarginRight float64
// PageMarginTop specifies the top margin for the page.
PageMarginTop float64
)
// setPageMargins provides a method to set the bottom margin for the worksheet.
func (p PageMarginBottom) setPageMargins(pm *xlsxPageMargins) {
pm.Bottom = float64(p)
}
// setPageMargins provides a method to get the bottom margin for the worksheet.
func (p *PageMarginBottom) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.75
if pm == nil || pm.Bottom == 0 {
*p = 0.75
return
}
*p = PageMarginBottom(pm.Bottom)
}
// setPageMargins provides a method to set the footer margin for the worksheet.
func (p PageMarginFooter) setPageMargins(pm *xlsxPageMargins) {
pm.Footer = float64(p)
}
// setPageMargins provides a method to get the footer margin for the worksheet.
func (p *PageMarginFooter) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.3
if pm == nil || pm.Footer == 0 {
*p = 0.3
return
}
*p = PageMarginFooter(pm.Footer)
}
// setPageMargins provides a method to set the header margin for the worksheet.
func (p PageMarginHeader) setPageMargins(pm *xlsxPageMargins) {
pm.Header = float64(p)
}
// setPageMargins provides a method to get the header margin for the worksheet.
func (p *PageMarginHeader) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.3
if pm == nil || pm.Header == 0 {
*p = 0.3
return
}
*p = PageMarginHeader(pm.Header)
}
// setPageMargins provides a method to set the left margin for the worksheet.
func (p PageMarginLeft) setPageMargins(pm *xlsxPageMargins) {
pm.Left = float64(p)
}
// setPageMargins provides a method to get the left margin for the worksheet.
func (p *PageMarginLeft) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.7
if pm == nil || pm.Left == 0 {
*p = 0.7
return
}
*p = PageMarginLeft(pm.Left)
}
// setPageMargins provides a method to set the right margin for the worksheet.
func (p PageMarginRight) setPageMargins(pm *xlsxPageMargins) {
pm.Right = float64(p)
}
// setPageMargins provides a method to get the right margin for the worksheet.
func (p *PageMarginRight) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.7
if pm == nil || pm.Right == 0 {
*p = 0.7
return
}
*p = PageMarginRight(pm.Right)
}
// setPageMargins provides a method to set the top margin for the worksheet.
func (p PageMarginTop) setPageMargins(pm *xlsxPageMargins) {
pm.Top = float64(p)
}
// setPageMargins provides a method to get the top margin for the worksheet.
func (p *PageMarginTop) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.75
if pm == nil || pm.Top == 0 {
*p = 0.75
return
}
*p = PageMarginTop(pm.Top)
}
// PageMarginsOptions is an option of a page margin of a worksheet. See
// SetPageMargins().
type PageMarginsOptions interface {
setPageMargins(layout *xlsxPageMargins)
}
// PageMarginsOptionsPtr is a writable PageMarginsOptions. See
// GetPageMargins().
type PageMarginsOptionsPtr interface {
PageMarginsOptions
getPageMargins(layout *xlsxPageMargins)
}
// SetPageMargins provides a function to set worksheet page margins.
//
// Available options:
// PageMarginBottom(float64)
// PageMarginFooter(float64)
// PageMarginHeader(float64)
// PageMarginLeft(float64)
// PageMarginRight(float64)
// PageMarginTop(float64)
func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error {
s, err := f.workSheetReader(sheet)
if err != nil {
return err
}
pm := s.PageMargins
if pm == nil {
pm = new(xlsxPageMargins)
s.PageMargins = pm
}
for _, opt := range opts {
opt.setPageMargins(pm)
}
return err
}
// GetPageMargins provides a function to get worksheet page margins.
//
// Available options:
// PageMarginBottom(float64)
// PageMarginFooter(float64)
// PageMarginHeader(float64)
// PageMarginLeft(float64)
// PageMarginRight(float64)
// PageMarginTop(float64)
func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error {
s, err := f.workSheetReader(sheet)
if err != nil {
return err
}
pm := s.PageMargins
for _, opt := range opts {
opt.getPageMargins(pm)
}
return err
}
// SheetFormatPrOptions is an option of the formatting properties of a
// worksheet. See SetSheetFormatPr().
type SheetFormatPrOptions interface {
setSheetFormatPr(formatPr *xlsxSheetFormatPr)
}
// SheetFormatPrOptionsPtr is a writable SheetFormatPrOptions. See
// GetSheetFormatPr().
type SheetFormatPrOptionsPtr interface {
SheetFormatPrOptions
getSheetFormatPr(formatPr *xlsxSheetFormatPr)
}
type (
// BaseColWidth specifies the number of characters of the maximum digit width
// of the normal style's font. This value does not include margin padding or
// extra padding for gridlines. It is only the number of characters.
BaseColWidth uint8
// DefaultColWidth specifies the default column width measured as the number
// of characters of the maximum digit width of the normal style's font.
DefaultColWidth float64
// DefaultRowHeight specifies the default row height measured in point size.
// Optimization so we don't have to write the height on all rows. This can be
// written out if most rows have custom height, to achieve the optimization.
DefaultRowHeight float64
// CustomHeight specifies the custom height.
CustomHeight bool
// ZeroHeight specifies if rows are hidden.
ZeroHeight bool
// ThickTop specifies if rows have a thick top border by default.
ThickTop bool
// ThickBottom specifies if rows have a thick bottom border by default.
ThickBottom bool
)
// setSheetFormatPr provides a method to set the number of characters of the
// maximum digit width of the normal style's font.
func (p BaseColWidth) setSheetFormatPr(fp *xlsxSheetFormatPr) {
fp.BaseColWidth = uint8(p)
}
// setSheetFormatPr provides a method to set the number of characters of the
// maximum digit width of the normal style's font.
func (p *BaseColWidth) getSheetFormatPr(fp *xlsxSheetFormatPr) {
if fp == nil {
*p = 0
return
}
*p = BaseColWidth(fp.BaseColWidth)
}
// setSheetFormatPr provides a method to set the default column width measured
// as the number of characters of the maximum digit width of the normal
// style's font.
func (p DefaultColWidth) setSheetFormatPr(fp *xlsxSheetFormatPr) {
fp.DefaultColWidth = float64(p)
}
// getSheetFormatPr provides a method to get the default column width measured
// as the number of characters of the maximum digit width of the normal
// style's font.
func (p *DefaultColWidth) getSheetFormatPr(fp *xlsxSheetFormatPr) {
if fp == nil {
*p = 0
return
}
*p = DefaultColWidth(fp.DefaultColWidth)
}
// setSheetFormatPr provides a method to set the default row height measured
// in point size.
func (p DefaultRowHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) {
fp.DefaultRowHeight = float64(p)
}
// getSheetFormatPr provides a method to get the default row height measured
// in point size.
func (p *DefaultRowHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) {
if fp == nil {
*p = 15
return
}
*p = DefaultRowHeight(fp.DefaultRowHeight)
}
// setSheetFormatPr provides a method to set the custom height.
func (p CustomHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) {
fp.CustomHeight = bool(p)
}
// getSheetFormatPr provides a method to get the custom height.
func (p *CustomHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) {
if fp == nil {
*p = false
return
}
*p = CustomHeight(fp.CustomHeight)
}
// setSheetFormatPr provides a method to set if rows are hidden.
func (p ZeroHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) {
fp.ZeroHeight = bool(p)
}
// getSheetFormatPr provides a method to get if rows are hidden.
func (p *ZeroHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) {
if fp == nil {
*p = false
return
}
*p = ZeroHeight(fp.ZeroHeight)
}
// setSheetFormatPr provides a method to set if rows have a thick top border
// by default.
func (p ThickTop) setSheetFormatPr(fp *xlsxSheetFormatPr) {
fp.ThickTop = bool(p)
}
// getSheetFormatPr provides a method to get if rows have a thick top border
// by default.
func (p *ThickTop) getSheetFormatPr(fp *xlsxSheetFormatPr) {
if fp == nil {
*p = false
return
}
*p = ThickTop(fp.ThickTop)
}
// setSheetFormatPr provides a method to set if rows have a thick bottom
// border by default.
func (p ThickBottom) setSheetFormatPr(fp *xlsxSheetFormatPr) {
fp.ThickBottom = bool(p)
}
// setSheetFormatPr provides a method to set if rows have a thick bottom
// border by default.
func (p *ThickBottom) getSheetFormatPr(fp *xlsxSheetFormatPr) {
if fp == nil {
*p = false
return
}
*p = ThickBottom(fp.ThickBottom)
}
// SetSheetFormatPr provides a function to set worksheet formatting properties.
//
// Available options:
// BaseColWidth(uint8)
// DefaultColWidth(float64)
// DefaultRowHeight(float64)
// CustomHeight(bool)
// ZeroHeight(bool)
// ThickTop(bool)
// ThickBottom(bool)
func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOptions) error {
s, err := f.workSheetReader(sheet)
if err != nil {
return err
}
fp := s.SheetFormatPr
if fp == nil {
fp = new(xlsxSheetFormatPr)
s.SheetFormatPr = fp
}
for _, opt := range opts {
opt.setSheetFormatPr(fp)
}
return err
}
// GetSheetFormatPr provides a function to get worksheet formatting properties.
//
// Available options:
// BaseColWidth(uint8)
// DefaultColWidth(float64)
// DefaultRowHeight(float64)
// CustomHeight(bool)
// ZeroHeight(bool)
// ThickTop(bool)
// ThickBottom(bool)
func (f *File) GetSheetFormatPr(sheet string, opts ...SheetFormatPrOptionsPtr) error {
s, err := f.workSheetReader(sheet)
if err != nil {
return err
}
fp := s.SheetFormatPr
for _, opt := range opts {
opt.getSheetFormatPr(fp)
}
return err
}

View File

@ -7,7 +7,7 @@ import (
"github.com/mohae/deepcopy"
"github.com/stretchr/testify/assert"
"github.com/360EntSecGroup-Skylar/excelize"
"github.com/360EntSecGroup-Skylar/excelize/v2"
)
var _ = []excelize.SheetPrOption{
@ -29,10 +29,10 @@ var _ = []excelize.SheetPrOptionPtr{
}
func ExampleFile_SetSheetPrOptions() {
xl := excelize.NewFile()
f := excelize.NewFile()
const sheet = "Sheet1"
if err := xl.SetSheetPrOptions(sheet,
if err := f.SetSheetPrOptions(sheet,
excelize.CodeName("code"),
excelize.EnableFormatConditionsCalculation(false),
excelize.Published(false),
@ -40,13 +40,13 @@ func ExampleFile_SetSheetPrOptions() {
excelize.AutoPageBreaks(true),
excelize.OutlineSummaryBelow(false),
); err != nil {
panic(err)
fmt.Println(err)
}
// Output:
}
func ExampleFile_GetSheetPrOptions() {
xl := excelize.NewFile()
f := excelize.NewFile()
const sheet = "Sheet1"
var (
@ -58,7 +58,7 @@ func ExampleFile_GetSheetPrOptions() {
outlineSummaryBelow excelize.OutlineSummaryBelow
)
if err := xl.GetSheetPrOptions(sheet,
if err := f.GetSheetPrOptions(sheet,
&codeName,
&enableFormatConditionsCalculation,
&published,
@ -66,7 +66,7 @@ func ExampleFile_GetSheetPrOptions() {
&autoPageBreaks,
&outlineSummaryBelow,
); err != nil {
panic(err)
fmt.Println(err)
}
fmt.Println("Defaults:")
fmt.Printf("- codeName: %q\n", codeName)
@ -110,26 +110,26 @@ func TestSheetPrOptions(t *testing.T) {
val1 := deepcopy.Copy(def).(excelize.SheetPrOptionPtr)
val2 := deepcopy.Copy(def).(excelize.SheetPrOptionPtr)
xl := excelize.NewFile()
f := excelize.NewFile()
// Get the default value
assert.NoError(t, xl.GetSheetPrOptions(sheet, def), opt)
assert.NoError(t, f.GetSheetPrOptions(sheet, def), opt)
// Get again and check
assert.NoError(t, xl.GetSheetPrOptions(sheet, val1), opt)
assert.NoError(t, f.GetSheetPrOptions(sheet, val1), opt)
if !assert.Equal(t, val1, def, opt) {
t.FailNow()
}
// Set the same value
assert.NoError(t, xl.SetSheetPrOptions(sheet, val1), opt)
assert.NoError(t, f.SetSheetPrOptions(sheet, val1), opt)
// Get again and check
assert.NoError(t, xl.GetSheetPrOptions(sheet, val1), opt)
assert.NoError(t, f.GetSheetPrOptions(sheet, val1), opt)
if !assert.Equal(t, val1, def, "%T: value should not have changed", opt) {
t.FailNow()
}
// Set a different value
assert.NoError(t, xl.SetSheetPrOptions(sheet, test.nonDefault), opt)
assert.NoError(t, xl.GetSheetPrOptions(sheet, val1), opt)
assert.NoError(t, f.SetSheetPrOptions(sheet, test.nonDefault), opt)
assert.NoError(t, f.GetSheetPrOptions(sheet, val1), opt)
// Get again and compare
assert.NoError(t, xl.GetSheetPrOptions(sheet, val2), opt)
assert.NoError(t, f.GetSheetPrOptions(sheet, val2), opt)
if !assert.Equal(t, val1, val2, "%T: value should not have changed", opt) {
t.FailNow()
}
@ -138,11 +138,332 @@ func TestSheetPrOptions(t *testing.T) {
t.FailNow()
}
// Restore the default value
assert.NoError(t, xl.SetSheetPrOptions(sheet, def), opt)
assert.NoError(t, xl.GetSheetPrOptions(sheet, val1), opt)
assert.NoError(t, f.SetSheetPrOptions(sheet, def), opt)
assert.NoError(t, f.GetSheetPrOptions(sheet, val1), opt)
if !assert.Equal(t, def, val1) {
t.FailNow()
}
})
}
}
func TestSetSheetrOptions(t *testing.T) {
f := excelize.NewFile()
// Test SetSheetrOptions on not exists worksheet.
assert.EqualError(t, f.SetSheetPrOptions("SheetN"), "sheet SheetN is not exist")
}
func TestGetSheetPrOptions(t *testing.T) {
f := excelize.NewFile()
// Test GetSheetPrOptions on not exists worksheet.
assert.EqualError(t, f.GetSheetPrOptions("SheetN"), "sheet SheetN is not exist")
}
var _ = []excelize.PageMarginsOptions{
excelize.PageMarginBottom(1.0),
excelize.PageMarginFooter(1.0),
excelize.PageMarginHeader(1.0),
excelize.PageMarginLeft(1.0),
excelize.PageMarginRight(1.0),
excelize.PageMarginTop(1.0),
}
var _ = []excelize.PageMarginsOptionsPtr{
(*excelize.PageMarginBottom)(nil),
(*excelize.PageMarginFooter)(nil),
(*excelize.PageMarginHeader)(nil),
(*excelize.PageMarginLeft)(nil),
(*excelize.PageMarginRight)(nil),
(*excelize.PageMarginTop)(nil),
}
func ExampleFile_SetPageMargins() {
f := excelize.NewFile()
const sheet = "Sheet1"
if err := f.SetPageMargins(sheet,
excelize.PageMarginBottom(1.0),
excelize.PageMarginFooter(1.0),
excelize.PageMarginHeader(1.0),
excelize.PageMarginLeft(1.0),
excelize.PageMarginRight(1.0),
excelize.PageMarginTop(1.0),
); err != nil {
fmt.Println(err)
}
// Output:
}
func ExampleFile_GetPageMargins() {
f := excelize.NewFile()
const sheet = "Sheet1"
var (
marginBottom excelize.PageMarginBottom
marginFooter excelize.PageMarginFooter
marginHeader excelize.PageMarginHeader
marginLeft excelize.PageMarginLeft
marginRight excelize.PageMarginRight
marginTop excelize.PageMarginTop
)
if err := f.GetPageMargins(sheet,
&marginBottom,
&marginFooter,
&marginHeader,
&marginLeft,
&marginRight,
&marginTop,
); err != nil {
fmt.Println(err)
}
fmt.Println("Defaults:")
fmt.Println("- marginBottom:", marginBottom)
fmt.Println("- marginFooter:", marginFooter)
fmt.Println("- marginHeader:", marginHeader)
fmt.Println("- marginLeft:", marginLeft)
fmt.Println("- marginRight:", marginRight)
fmt.Println("- marginTop:", marginTop)
// Output:
// Defaults:
// - marginBottom: 0.75
// - marginFooter: 0.3
// - marginHeader: 0.3
// - marginLeft: 0.7
// - marginRight: 0.7
// - marginTop: 0.75
}
func TestPageMarginsOption(t *testing.T) {
const sheet = "Sheet1"
testData := []struct {
container excelize.PageMarginsOptionsPtr
nonDefault excelize.PageMarginsOptions
}{
{new(excelize.PageMarginTop), excelize.PageMarginTop(1.0)},
{new(excelize.PageMarginBottom), excelize.PageMarginBottom(1.0)},
{new(excelize.PageMarginLeft), excelize.PageMarginLeft(1.0)},
{new(excelize.PageMarginRight), excelize.PageMarginRight(1.0)},
{new(excelize.PageMarginHeader), excelize.PageMarginHeader(1.0)},
{new(excelize.PageMarginFooter), excelize.PageMarginFooter(1.0)},
}
for i, test := range testData {
t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
opt := test.nonDefault
t.Logf("option %T", opt)
def := deepcopy.Copy(test.container).(excelize.PageMarginsOptionsPtr)
val1 := deepcopy.Copy(def).(excelize.PageMarginsOptionsPtr)
val2 := deepcopy.Copy(def).(excelize.PageMarginsOptionsPtr)
f := excelize.NewFile()
// Get the default value
assert.NoError(t, f.GetPageMargins(sheet, def), opt)
// Get again and check
assert.NoError(t, f.GetPageMargins(sheet, val1), opt)
if !assert.Equal(t, val1, def, opt) {
t.FailNow()
}
// Set the same value
assert.NoError(t, f.SetPageMargins(sheet, val1), opt)
// Get again and check
assert.NoError(t, f.GetPageMargins(sheet, val1), opt)
if !assert.Equal(t, val1, def, "%T: value should not have changed", opt) {
t.FailNow()
}
// Set a different value
assert.NoError(t, f.SetPageMargins(sheet, test.nonDefault), opt)
assert.NoError(t, f.GetPageMargins(sheet, val1), opt)
// Get again and compare
assert.NoError(t, f.GetPageMargins(sheet, val2), opt)
if !assert.Equal(t, val1, val2, "%T: value should not have changed", opt) {
t.FailNow()
}
// Value should not be the same as the default
if !assert.NotEqual(t, def, val1, "%T: value should have changed from default", opt) {
t.FailNow()
}
// Restore the default value
assert.NoError(t, f.SetPageMargins(sheet, def), opt)
assert.NoError(t, f.GetPageMargins(sheet, val1), opt)
if !assert.Equal(t, def, val1) {
t.FailNow()
}
})
}
}
func TestSetPageMargins(t *testing.T) {
f := excelize.NewFile()
// Test set page margins on not exists worksheet.
assert.EqualError(t, f.SetPageMargins("SheetN"), "sheet SheetN is not exist")
}
func TestGetPageMargins(t *testing.T) {
f := excelize.NewFile()
// Test get page margins on not exists worksheet.
assert.EqualError(t, f.GetPageMargins("SheetN"), "sheet SheetN is not exist")
}
func ExampleFile_SetSheetFormatPr() {
f := excelize.NewFile()
const sheet = "Sheet1"
if err := f.SetSheetFormatPr(sheet,
excelize.BaseColWidth(1.0),
excelize.DefaultColWidth(1.0),
excelize.DefaultRowHeight(1.0),
excelize.CustomHeight(true),
excelize.ZeroHeight(true),
excelize.ThickTop(true),
excelize.ThickBottom(true),
); err != nil {
fmt.Println(err)
}
// Output:
}
func ExampleFile_GetSheetFormatPr() {
f := excelize.NewFile()
const sheet = "Sheet1"
var (
baseColWidth excelize.BaseColWidth
defaultColWidth excelize.DefaultColWidth
defaultRowHeight excelize.DefaultRowHeight
customHeight excelize.CustomHeight
zeroHeight excelize.ZeroHeight
thickTop excelize.ThickTop
thickBottom excelize.ThickBottom
)
if err := f.GetSheetFormatPr(sheet,
&baseColWidth,
&defaultColWidth,
&defaultRowHeight,
&customHeight,
&zeroHeight,
&thickTop,
&thickBottom,
); err != nil {
fmt.Println(err)
}
fmt.Println("Defaults:")
fmt.Println("- baseColWidth:", baseColWidth)
fmt.Println("- defaultColWidth:", defaultColWidth)
fmt.Println("- defaultRowHeight:", defaultRowHeight)
fmt.Println("- customHeight:", customHeight)
fmt.Println("- zeroHeight:", zeroHeight)
fmt.Println("- thickTop:", thickTop)
fmt.Println("- thickBottom:", thickBottom)
// Output:
// Defaults:
// - baseColWidth: 0
// - defaultColWidth: 0
// - defaultRowHeight: 15
// - customHeight: false
// - zeroHeight: false
// - thickTop: false
// - thickBottom: false
}
func TestSheetFormatPrOptions(t *testing.T) {
const sheet = "Sheet1"
testData := []struct {
container excelize.SheetFormatPrOptionsPtr
nonDefault excelize.SheetFormatPrOptions
}{
{new(excelize.BaseColWidth), excelize.BaseColWidth(1.0)},
{new(excelize.DefaultColWidth), excelize.DefaultColWidth(1.0)},
{new(excelize.DefaultRowHeight), excelize.DefaultRowHeight(1.0)},
{new(excelize.CustomHeight), excelize.CustomHeight(true)},
{new(excelize.ZeroHeight), excelize.ZeroHeight(true)},
{new(excelize.ThickTop), excelize.ThickTop(true)},
{new(excelize.ThickBottom), excelize.ThickBottom(true)},
}
for i, test := range testData {
t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
opt := test.nonDefault
t.Logf("option %T", opt)
def := deepcopy.Copy(test.container).(excelize.SheetFormatPrOptionsPtr)
val1 := deepcopy.Copy(def).(excelize.SheetFormatPrOptionsPtr)
val2 := deepcopy.Copy(def).(excelize.SheetFormatPrOptionsPtr)
f := excelize.NewFile()
// Get the default value
assert.NoError(t, f.GetSheetFormatPr(sheet, def), opt)
// Get again and check
assert.NoError(t, f.GetSheetFormatPr(sheet, val1), opt)
if !assert.Equal(t, val1, def, opt) {
t.FailNow()
}
// Set the same value
assert.NoError(t, f.SetSheetFormatPr(sheet, val1), opt)
// Get again and check
assert.NoError(t, f.GetSheetFormatPr(sheet, val1), opt)
if !assert.Equal(t, val1, def, "%T: value should not have changed", opt) {
t.FailNow()
}
// Set a different value
assert.NoError(t, f.SetSheetFormatPr(sheet, test.nonDefault), opt)
assert.NoError(t, f.GetSheetFormatPr(sheet, val1), opt)
// Get again and compare
assert.NoError(t, f.GetSheetFormatPr(sheet, val2), opt)
if !assert.Equal(t, val1, val2, "%T: value should not have changed", opt) {
t.FailNow()
}
// Value should not be the same as the default
if !assert.NotEqual(t, def, val1, "%T: value should have changed from default", opt) {
t.FailNow()
}
// Restore the default value
assert.NoError(t, f.SetSheetFormatPr(sheet, def), opt)
assert.NoError(t, f.GetSheetFormatPr(sheet, val1), opt)
if !assert.Equal(t, def, val1) {
t.FailNow()
}
})
}
}
func TestSetSheetFormatPr(t *testing.T) {
f := excelize.NewFile()
assert.NoError(t, f.GetSheetFormatPr("Sheet1"))
f.Sheet["xl/worksheets/sheet1.xml"].SheetFormatPr = nil
assert.NoError(t, f.SetSheetFormatPr("Sheet1", excelize.BaseColWidth(1.0)))
// Test set formatting properties on not exists worksheet.
assert.EqualError(t, f.SetSheetFormatPr("SheetN"), "sheet SheetN is not exist")
}
func TestGetSheetFormatPr(t *testing.T) {
f := excelize.NewFile()
assert.NoError(t, f.GetSheetFormatPr("Sheet1"))
f.Sheet["xl/worksheets/sheet1.xml"].SheetFormatPr = nil
var (
baseColWidth excelize.BaseColWidth
defaultColWidth excelize.DefaultColWidth
defaultRowHeight excelize.DefaultRowHeight
customHeight excelize.CustomHeight
zeroHeight excelize.ZeroHeight
thickTop excelize.ThickTop
thickBottom excelize.ThickBottom
)
assert.NoError(t, f.GetSheetFormatPr("Sheet1",
&baseColWidth,
&defaultColWidth,
&defaultRowHeight,
&customHeight,
&zeroHeight,
&thickTop,
&thickBottom,
))
// Test get formatting properties on not exists worksheet.
assert.EqualError(t, f.GetSheetFormatPr("SheetN"), "sheet SheetN is not exist")
}

View File

@ -1,47 +1,68 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "fmt"
// SheetViewOption is an option of a view of a worksheet. See SetSheetViewOptions().
// SheetViewOption is an option of a view of a worksheet. See
// SetSheetViewOptions().
type SheetViewOption interface {
setSheetViewOption(view *xlsxSheetView)
}
// SheetViewOptionPtr is a writable SheetViewOption. See GetSheetViewOptions().
// SheetViewOptionPtr is a writable SheetViewOption. See
// GetSheetViewOptions().
type SheetViewOptionPtr interface {
SheetViewOption
getSheetViewOption(view *xlsxSheetView)
}
type (
// DefaultGridColor is a SheetViewOption.
// DefaultGridColor is a SheetViewOption. It specifies a flag indicating that
// the consuming application should use the default grid lines color (system
// dependent). Overrides any color specified in colorId.
DefaultGridColor bool
// RightToLeft is a SheetViewOption.
// RightToLeft is a SheetViewOption. It specifies a flag indicating whether
// the sheet is in 'right to left' display mode. When in this mode, Column A
// is on the far right, Column B ;is one column left of Column A, and so on.
// Also, information in cells is displayed in the Right to Left format.
RightToLeft bool
// ShowFormulas is a SheetViewOption.
// ShowFormulas is a SheetViewOption. It specifies a flag indicating whether
// this sheet should display formulas.
ShowFormulas bool
// ShowGridLines is a SheetViewOption.
// ShowGridLines is a SheetViewOption. It specifies a flag indicating whether
// this sheet should display gridlines.
ShowGridLines bool
// ShowRowColHeaders is a SheetViewOption.
// ShowRowColHeaders is a SheetViewOption. It specifies a flag indicating
// whether the sheet should display row and column headings.
ShowRowColHeaders bool
// ZoomScale is a SheetViewOption.
// ZoomScale is a SheetViewOption. It specifies a window zoom magnification
// for current view representing percent values. This attribute is restricted
// to values ranging from 10 to 400. Horizontal & Vertical scale together.
ZoomScale float64
// TopLeftCell is a SheetViewOption.
// TopLeftCell is a SheetViewOption. It specifies a location of the top left
// visible cell Location of the top left visible cell in the bottom right
// pane (when in Left-to-Right mode).
TopLeftCell string
/* TODO
// ShowWhiteSpace is a SheetViewOption.
ShowWhiteSpace bool
// ShowZeros is a SheetViewOption.
// ShowZeros is a SheetViewOption. It specifies a flag indicating
// whether to "show a zero in cells that have zero value".
// When using a formula to reference another cell which is empty, the referenced value becomes 0
// when the flag is true. (Default setting is true.)
ShowZeros bool
/* TODO
// ShowWhiteSpace is a SheetViewOption. It specifies a flag indicating
// whether page layout view shall display margins. False means do not display
// left, right, top (header), and bottom (footer) margins (even when there is
// data in the header or footer).
ShowWhiteSpace bool
// WindowProtection is a SheetViewOption.
WindowProtection bool
*/
@ -89,6 +110,14 @@ func (o *ShowGridLines) getSheetViewOption(view *xlsxSheetView) {
*o = ShowGridLines(defaultTrue(view.ShowGridLines)) // Excel default: true
}
func (o ShowZeros) setSheetViewOption(view *xlsxSheetView) {
view.ShowZeros = boolPtr(bool(o))
}
func (o *ShowZeros) getSheetViewOption(view *xlsxSheetView) {
*o = ShowZeros(defaultTrue(view.ShowZeros)) // Excel default: true
}
func (o ShowRowColHeaders) setSheetViewOption(view *xlsxSheetView) {
view.ShowRowColHeaders = boolPtr(bool(o))
}
@ -98,7 +127,7 @@ func (o *ShowRowColHeaders) getSheetViewOption(view *xlsxSheetView) {
}
func (o ZoomScale) setSheetViewOption(view *xlsxSheetView) {
//This attribute is restricted to values ranging from 10 to 400.
// This attribute is restricted to values ranging from 10 to 400.
if float64(o) >= 10 && float64(o) <= 400 {
view.ZoomScale = float64(o)
}
@ -126,17 +155,23 @@ func (f *File) getSheetView(sheetName string, viewIndex int) (*xlsxSheetView, er
return &(xlsx.SheetViews.SheetView[viewIndex]), err
}
// SetSheetViewOptions sets sheet view options.
// The viewIndex may be negative and if so is counted backward (-1 is the last view).
// SetSheetViewOptions sets sheet view options. The viewIndex may be negative
// and if so is counted backward (-1 is the last view).
//
// Available options:
//
// DefaultGridColor(bool)
// RightToLeft(bool)
// ShowFormulas(bool)
// ShowGridLines(bool)
// ShowRowColHeaders(bool)
// ZoomScale(float64)
// TopLeftCell(string)
//
// Example:
//
// err = f.SetSheetViewOptions("Sheet1", -1, ShowGridLines(false))
//
func (f *File) SetSheetViewOptions(name string, viewIndex int, opts ...SheetViewOption) error {
view, err := f.getSheetView(name, viewIndex)
if err != nil {
@ -149,18 +184,24 @@ func (f *File) SetSheetViewOptions(name string, viewIndex int, opts ...SheetView
return nil
}
// GetSheetViewOptions gets the value of sheet view options.
// The viewIndex may be negative and if so is counted backward (-1 is the last view).
// GetSheetViewOptions gets the value of sheet view options. The viewIndex may
// be negative and if so is counted backward (-1 is the last view).
//
// Available options:
//
// DefaultGridColor(bool)
// RightToLeft(bool)
// ShowFormulas(bool)
// ShowGridLines(bool)
// ShowRowColHeaders(bool)
// ZoomScale(float64)
// TopLeftCell(string)
//
// Example:
//
// var showGridLines excelize.ShowGridLines
// err = f.GetSheetViewOptions("Sheet1", -1, &showGridLines)
//
func (f *File) GetSheetViewOptions(name string, viewIndex int, opts ...SheetViewOptionPtr) error {
view, err := f.getSheetView(name, viewIndex)
if err != nil {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/360EntSecGroup-Skylar/excelize"
"github.com/360EntSecGroup-Skylar/excelize/v2"
)
var _ = []excelize.SheetViewOption{
@ -35,10 +35,10 @@ var _ = []excelize.SheetViewOptionPtr{
}
func ExampleFile_SetSheetViewOptions() {
xl := excelize.NewFile()
f := excelize.NewFile()
const sheet = "Sheet1"
if err := xl.SetSheetViewOptions(sheet, 0,
if err := f.SetSheetViewOptions(sheet, 0,
excelize.DefaultGridColor(false),
excelize.RightToLeft(false),
excelize.ShowFormulas(true),
@ -47,30 +47,30 @@ func ExampleFile_SetSheetViewOptions() {
excelize.ZoomScale(80),
excelize.TopLeftCell("C3"),
); err != nil {
panic(err)
fmt.Println(err)
}
var zoomScale excelize.ZoomScale
fmt.Println("Default:")
fmt.Println("- zoomScale: 80")
if err := xl.SetSheetViewOptions(sheet, 0, excelize.ZoomScale(500)); err != nil {
panic(err)
if err := f.SetSheetViewOptions(sheet, 0, excelize.ZoomScale(500)); err != nil {
fmt.Println(err)
}
if err := xl.GetSheetViewOptions(sheet, 0, &zoomScale); err != nil {
panic(err)
if err := f.GetSheetViewOptions(sheet, 0, &zoomScale); err != nil {
fmt.Println(err)
}
fmt.Println("Used out of range value:")
fmt.Println("- zoomScale:", zoomScale)
if err := xl.SetSheetViewOptions(sheet, 0, excelize.ZoomScale(123)); err != nil {
panic(err)
if err := f.SetSheetViewOptions(sheet, 0, excelize.ZoomScale(123)); err != nil {
fmt.Println(err)
}
if err := xl.GetSheetViewOptions(sheet, 0, &zoomScale); err != nil {
panic(err)
if err := f.GetSheetViewOptions(sheet, 0, &zoomScale); err != nil {
fmt.Println(err)
}
fmt.Println("Used correct value:")
@ -87,7 +87,7 @@ func ExampleFile_SetSheetViewOptions() {
}
func ExampleFile_GetSheetViewOptions() {
xl := excelize.NewFile()
f := excelize.NewFile()
const sheet = "Sheet1"
var (
@ -95,21 +95,23 @@ func ExampleFile_GetSheetViewOptions() {
rightToLeft excelize.RightToLeft
showFormulas excelize.ShowFormulas
showGridLines excelize.ShowGridLines
showZeros excelize.ShowZeros
showRowColHeaders excelize.ShowRowColHeaders
zoomScale excelize.ZoomScale
topLeftCell excelize.TopLeftCell
)
if err := xl.GetSheetViewOptions(sheet, 0,
if err := f.GetSheetViewOptions(sheet, 0,
&defaultGridColor,
&rightToLeft,
&showFormulas,
&showGridLines,
&showZeros,
&showRowColHeaders,
&zoomScale,
&topLeftCell,
); err != nil {
panic(err)
fmt.Println(err)
}
fmt.Println("Default:")
@ -117,28 +119,38 @@ func ExampleFile_GetSheetViewOptions() {
fmt.Println("- rightToLeft:", rightToLeft)
fmt.Println("- showFormulas:", showFormulas)
fmt.Println("- showGridLines:", showGridLines)
fmt.Println("- showZeros:", showZeros)
fmt.Println("- showRowColHeaders:", showRowColHeaders)
fmt.Println("- zoomScale:", zoomScale)
fmt.Println("- topLeftCell:", `"`+topLeftCell+`"`)
if err := xl.SetSheetViewOptions(sheet, 0, excelize.TopLeftCell("B2")); err != nil {
panic(err)
if err := f.SetSheetViewOptions(sheet, 0, excelize.TopLeftCell("B2")); err != nil {
fmt.Println(err)
}
if err := xl.GetSheetViewOptions(sheet, 0, &topLeftCell); err != nil {
panic(err)
if err := f.GetSheetViewOptions(sheet, 0, &topLeftCell); err != nil {
fmt.Println(err)
}
if err := xl.SetSheetViewOptions(sheet, 0, excelize.ShowGridLines(false)); err != nil {
panic(err)
if err := f.SetSheetViewOptions(sheet, 0, excelize.ShowGridLines(false)); err != nil {
fmt.Println(err)
}
if err := xl.GetSheetViewOptions(sheet, 0, &showGridLines); err != nil {
panic(err)
if err := f.GetSheetViewOptions(sheet, 0, &showGridLines); err != nil {
fmt.Println(err)
}
if err := f.SetSheetViewOptions(sheet, 0, excelize.ShowZeros(false)); err != nil {
fmt.Println(err)
}
if err := f.GetSheetViewOptions(sheet, 0, &showZeros); err != nil {
fmt.Println(err)
}
fmt.Println("After change:")
fmt.Println("- showGridLines:", showGridLines)
fmt.Println("- showZeros:", showZeros)
fmt.Println("- topLeftCell:", topLeftCell)
// Output:
@ -147,24 +159,26 @@ func ExampleFile_GetSheetViewOptions() {
// - rightToLeft: false
// - showFormulas: false
// - showGridLines: true
// - showZeros: true
// - showRowColHeaders: true
// - zoomScale: 0
// - topLeftCell: ""
// After change:
// - showGridLines: false
// - showZeros: false
// - topLeftCell: B2
}
func TestSheetViewOptionsErrors(t *testing.T) {
xl := excelize.NewFile()
f := excelize.NewFile()
const sheet = "Sheet1"
assert.NoError(t, xl.GetSheetViewOptions(sheet, 0))
assert.NoError(t, xl.GetSheetViewOptions(sheet, -1))
assert.Error(t, xl.GetSheetViewOptions(sheet, 1))
assert.Error(t, xl.GetSheetViewOptions(sheet, -2))
assert.NoError(t, xl.SetSheetViewOptions(sheet, 0))
assert.NoError(t, xl.SetSheetViewOptions(sheet, -1))
assert.Error(t, xl.SetSheetViewOptions(sheet, 1))
assert.Error(t, xl.SetSheetViewOptions(sheet, -2))
assert.NoError(t, f.GetSheetViewOptions(sheet, 0))
assert.NoError(t, f.GetSheetViewOptions(sheet, -1))
assert.Error(t, f.GetSheetViewOptions(sheet, 1))
assert.Error(t, f.GetSheetViewOptions(sheet, -2))
assert.NoError(t, f.SetSheetViewOptions(sheet, 0))
assert.NoError(t, f.SetSheetViewOptions(sheet, -1))
assert.Error(t, f.SetSheetViewOptions(sheet, 1))
assert.Error(t, f.SetSheetViewOptions(sheet, -2))
}

542
sparkline.go Normal file
View File

@ -0,0 +1,542 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"encoding/xml"
"errors"
"io"
"strings"
)
// addSparklineGroupByStyle provides a function to create x14:sparklineGroups
// element by given sparkline style ID.
func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup {
groups := []*xlsxX14SparklineGroup{
{
ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
ColorNegative: &xlsxTabColor{Theme: 5},
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
ColorFirst: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
ColorLast: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
ColorHigh: &xlsxTabColor{Theme: 4},
ColorLow: &xlsxTabColor{Theme: 4},
}, // 0
{
ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
ColorNegative: &xlsxTabColor{Theme: 5},
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
ColorFirst: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
ColorLast: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
ColorHigh: &xlsxTabColor{Theme: 4},
ColorLow: &xlsxTabColor{Theme: 4},
}, // 1
{
ColorSeries: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262},
ColorNegative: &xlsxTabColor{Theme: 6},
ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262},
ColorFirst: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921},
ColorLast: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921},
ColorHigh: &xlsxTabColor{Theme: 5},
ColorLow: &xlsxTabColor{Theme: 5},
}, // 2
{
ColorSeries: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262},
ColorNegative: &xlsxTabColor{Theme: 7},
ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262},
ColorFirst: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921},
ColorLast: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921},
ColorHigh: &xlsxTabColor{Theme: 6},
ColorLow: &xlsxTabColor{Theme: 6},
}, // 3
{
ColorSeries: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262},
ColorNegative: &xlsxTabColor{Theme: 8},
ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262},
ColorFirst: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921},
ColorLast: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921},
ColorHigh: &xlsxTabColor{Theme: 7},
ColorLow: &xlsxTabColor{Theme: 7},
}, // 4
{
ColorSeries: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262},
ColorNegative: &xlsxTabColor{Theme: 9},
ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262},
ColorFirst: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921},
ColorLast: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921},
ColorHigh: &xlsxTabColor{Theme: 8},
ColorLow: &xlsxTabColor{Theme: 8},
}, // 5
{
ColorSeries: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262},
ColorNegative: &xlsxTabColor{Theme: 4},
ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262},
ColorFirst: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921},
ColorLast: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921},
ColorHigh: &xlsxTabColor{Theme: 9},
ColorLow: &xlsxTabColor{Theme: 9},
}, // 6
{
ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorNegative: &xlsxTabColor{Theme: 5},
ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 5},
ColorLow: &xlsxTabColor{Theme: 5},
}, // 7
{
ColorSeries: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorNegative: &xlsxTabColor{Theme: 6},
ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
}, // 8
{
ColorSeries: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorNegative: &xlsxTabColor{Theme: 7},
ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
}, // 9
{
ColorSeries: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorNegative: &xlsxTabColor{Theme: 8},
ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
}, // 10
{
ColorSeries: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorNegative: &xlsxTabColor{Theme: 9},
ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
}, // 11
{
ColorSeries: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorNegative: &xlsxTabColor{Theme: 4},
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
}, // 12
{
ColorSeries: &xlsxTabColor{Theme: 4},
ColorNegative: &xlsxTabColor{Theme: 5},
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
}, // 13
{
ColorSeries: &xlsxTabColor{Theme: 5},
ColorNegative: &xlsxTabColor{Theme: 6},
ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
}, // 14
{
ColorSeries: &xlsxTabColor{Theme: 6},
ColorNegative: &xlsxTabColor{Theme: 7},
ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
}, // 15
{
ColorSeries: &xlsxTabColor{Theme: 7},
ColorNegative: &xlsxTabColor{Theme: 8},
ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
}, // 16
{
ColorSeries: &xlsxTabColor{Theme: 8},
ColorNegative: &xlsxTabColor{Theme: 9},
ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
}, // 17
{
ColorSeries: &xlsxTabColor{Theme: 9},
ColorNegative: &xlsxTabColor{Theme: 4},
ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
}, // 18
{
ColorSeries: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921},
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
ColorMarkers: &xlsxTabColor{Theme: 4, Tint: 0.79998168889431442},
ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262},
}, // 19
{
ColorSeries: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921},
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
ColorMarkers: &xlsxTabColor{Theme: 5, Tint: 0.79998168889431442},
ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262},
ColorLow: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262},
}, // 20
{
ColorSeries: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921},
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
ColorMarkers: &xlsxTabColor{Theme: 6, Tint: 0.79998168889431442},
ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262},
ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262},
}, // 21
{
ColorSeries: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921},
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
ColorMarkers: &xlsxTabColor{Theme: 7, Tint: 0.79998168889431442},
ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262},
ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262},
}, // 22
{
ColorSeries: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921},
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
ColorMarkers: &xlsxTabColor{Theme: 8, Tint: 0.79998168889431442},
ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262},
ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262},
}, // 23
{
ColorSeries: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921},
ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262},
ColorMarkers: &xlsxTabColor{Theme: 9, Tint: 0.79998168889431442},
ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262},
ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262},
}, // 24
{
ColorSeries: &xlsxTabColor{Theme: 1, Tint: 0.499984740745262},
ColorNegative: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
ColorMarkers: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893},
}, // 25
{
ColorSeries: &xlsxTabColor{Theme: 1, Tint: 0.34998626667073579},
ColorNegative: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
ColorMarkers: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
ColorFirst: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
ColorLast: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
ColorHigh: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
ColorLow: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893},
}, // 26
{
ColorSeries: &xlsxTabColor{RGB: "FF323232"},
ColorNegative: &xlsxTabColor{RGB: "FFD00000"},
ColorMarkers: &xlsxTabColor{RGB: "FFD00000"},
ColorFirst: &xlsxTabColor{RGB: "FFD00000"},
ColorLast: &xlsxTabColor{RGB: "FFD00000"},
ColorHigh: &xlsxTabColor{RGB: "FFD00000"},
ColorLow: &xlsxTabColor{RGB: "FFD00000"},
}, // 27
{
ColorSeries: &xlsxTabColor{RGB: "FF000000"},
ColorNegative: &xlsxTabColor{RGB: "FF0070C0"},
ColorMarkers: &xlsxTabColor{RGB: "FF0070C0"},
ColorFirst: &xlsxTabColor{RGB: "FF0070C0"},
ColorLast: &xlsxTabColor{RGB: "FF0070C0"},
ColorHigh: &xlsxTabColor{RGB: "FF0070C0"},
ColorLow: &xlsxTabColor{RGB: "FF0070C0"},
}, // 28
{
ColorSeries: &xlsxTabColor{RGB: "FF376092"},
ColorNegative: &xlsxTabColor{RGB: "FFD00000"},
ColorMarkers: &xlsxTabColor{RGB: "FFD00000"},
ColorFirst: &xlsxTabColor{RGB: "FFD00000"},
ColorLast: &xlsxTabColor{RGB: "FFD00000"},
ColorHigh: &xlsxTabColor{RGB: "FFD00000"},
ColorLow: &xlsxTabColor{RGB: "FFD00000"},
}, // 29
{
ColorSeries: &xlsxTabColor{RGB: "FF0070C0"},
ColorNegative: &xlsxTabColor{RGB: "FF000000"},
ColorMarkers: &xlsxTabColor{RGB: "FF000000"},
ColorFirst: &xlsxTabColor{RGB: "FF000000"},
ColorLast: &xlsxTabColor{RGB: "FF000000"},
ColorHigh: &xlsxTabColor{RGB: "FF000000"},
ColorLow: &xlsxTabColor{RGB: "FF000000"},
}, // 30
{
ColorSeries: &xlsxTabColor{RGB: "FF5F5F5F"},
ColorNegative: &xlsxTabColor{RGB: "FFFFB620"},
ColorMarkers: &xlsxTabColor{RGB: "FFD70077"},
ColorFirst: &xlsxTabColor{RGB: "FF5687C2"},
ColorLast: &xlsxTabColor{RGB: "FF359CEB"},
ColorHigh: &xlsxTabColor{RGB: "FF56BE79"},
ColorLow: &xlsxTabColor{RGB: "FFFF5055"},
}, // 31
{
ColorSeries: &xlsxTabColor{RGB: "FF5687C2"},
ColorNegative: &xlsxTabColor{RGB: "FFFFB620"},
ColorMarkers: &xlsxTabColor{RGB: "FFD70077"},
ColorFirst: &xlsxTabColor{RGB: "FF777777"},
ColorLast: &xlsxTabColor{RGB: "FF359CEB"},
ColorHigh: &xlsxTabColor{RGB: "FF56BE79"},
ColorLow: &xlsxTabColor{RGB: "FFFF5055"},
}, // 32
{
ColorSeries: &xlsxTabColor{RGB: "FFC6EFCE"},
ColorNegative: &xlsxTabColor{RGB: "FFFFC7CE"},
ColorMarkers: &xlsxTabColor{RGB: "FF8CADD6"},
ColorFirst: &xlsxTabColor{RGB: "FFFFDC47"},
ColorLast: &xlsxTabColor{RGB: "FFFFEB9C"},
ColorHigh: &xlsxTabColor{RGB: "FF60D276"},
ColorLow: &xlsxTabColor{RGB: "FFFF5367"},
}, // 33
{
ColorSeries: &xlsxTabColor{RGB: "FF00B050"},
ColorNegative: &xlsxTabColor{RGB: "FFFF0000"},
ColorMarkers: &xlsxTabColor{RGB: "FF0070C0"},
ColorFirst: &xlsxTabColor{RGB: "FFFFC000"},
ColorLast: &xlsxTabColor{RGB: "FFFFC000"},
ColorHigh: &xlsxTabColor{RGB: "FF00B050"},
ColorLow: &xlsxTabColor{RGB: "FFFF0000"},
}, // 34
{
ColorSeries: &xlsxTabColor{Theme: 3},
ColorNegative: &xlsxTabColor{Theme: 9},
ColorMarkers: &xlsxTabColor{Theme: 8},
ColorFirst: &xlsxTabColor{Theme: 4},
ColorLast: &xlsxTabColor{Theme: 5},
ColorHigh: &xlsxTabColor{Theme: 6},
ColorLow: &xlsxTabColor{Theme: 7},
}, // 35
{
ColorSeries: &xlsxTabColor{Theme: 1},
ColorNegative: &xlsxTabColor{Theme: 9},
ColorMarkers: &xlsxTabColor{Theme: 8},
ColorFirst: &xlsxTabColor{Theme: 4},
ColorLast: &xlsxTabColor{Theme: 5},
ColorHigh: &xlsxTabColor{Theme: 6},
ColorLow: &xlsxTabColor{Theme: 7},
}, // 36
}
return groups[ID]
}
// AddSparkline provides a function to add sparklines to the worksheet by
// given formatting options. Sparklines are small charts that fit in a single
// cell and are used to show trends in data. Sparklines are a feature of Excel
// 2010 and later only. You can write them to an XLSX file that can be read by
// Excel 2007 but they won't be displayed. For example, add a grouped
// sparkline. Changes are applied to all three:
//
// err := f.AddSparkline("Sheet1", &excelize.SparklineOption{
// Location: []string{"A1", "A2", "A3"},
// Range: []string{"Sheet2!A1:J1", "Sheet2!A2:J2", "Sheet2!A3:J3"},
// Markers: true,
// })
//
// The following shows the formatting options of sparkline supported by excelize:
//
// Parameter | Description
// -----------+--------------------------------------------
// Location | Required, must have the same number with 'Range' parameter
// Range | Required, must have the same number with 'Location' parameter
// Type | Enumeration value: line, column, win_loss
// Style | Value range: 0 - 35
// Hight | Toggle sparkline high points
// Low | Toggle sparkline low points
// First | Toggle sparkline first points
// Last | Toggle sparkline last points
// Negative | Toggle sparkline negative points
// Markers | Toggle sparkline markers
// ColorAxis | An RGB Color is specified as RRGGBB
// Axis | Show sparkline axis
//
func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) {
var (
ws *xlsxWorksheet
sparkType string
sparkTypes map[string]string
specifiedSparkTypes string
ok bool
group *xlsxX14SparklineGroup
groups *xlsxX14SparklineGroups
sparklineGroupsBytes, extBytes []byte
)
// parameter validation
if ws, err = f.parseFormatAddSparklineSet(sheet, opt); err != nil {
return
}
// Handle the sparkline type
sparkType = "line"
sparkTypes = map[string]string{"line": "line", "column": "column", "win_loss": "stacked"}
if opt.Type != "" {
if specifiedSparkTypes, ok = sparkTypes[opt.Type]; !ok {
err = errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'")
return
}
sparkType = specifiedSparkTypes
}
group = f.addSparklineGroupByStyle(opt.Style)
group.Type = sparkType
group.ColorAxis = &xlsxColor{RGB: "FF000000"}
group.DisplayEmptyCellsAs = "gap"
group.High = opt.High
group.Low = opt.Low
group.First = opt.First
group.Last = opt.Last
group.Negative = opt.Negative
group.DisplayXAxis = opt.Axis
group.Markers = opt.Markers
if opt.SeriesColor != "" {
group.ColorSeries = &xlsxTabColor{
RGB: getPaletteColor(opt.SeriesColor),
}
}
if opt.Reverse {
group.RightToLeft = opt.Reverse
}
f.addSparkline(opt, group)
if ws.ExtLst.Ext != "" { // append mode ext
if err = f.appendSparkline(ws, group, groups); err != nil {
return
}
} else {
groups = &xlsxX14SparklineGroups{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
SparklineGroups: []*xlsxX14SparklineGroup{group},
}
if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {
return
}
if extBytes, err = xml.Marshal(&xlsxWorksheetExt{
URI: ExtURISparklineGroups,
Content: string(sparklineGroupsBytes),
}); err != nil {
return
}
ws.ExtLst.Ext = string(extBytes)
}
return
}
// parseFormatAddSparklineSet provides a function to validate sparkline
// properties.
func (f *File) parseFormatAddSparklineSet(sheet string, opt *SparklineOption) (*xlsxWorksheet, error) {
ws, err := f.workSheetReader(sheet)
if err != nil {
return ws, err
}
if opt == nil {
return ws, errors.New("parameter is required")
}
if len(opt.Location) < 1 {
return ws, errors.New("parameter 'Location' is required")
}
if len(opt.Range) < 1 {
return ws, errors.New("parameter 'Range' is required")
}
// The ranges and locations must match.\
if len(opt.Location) != len(opt.Range) {
return ws, errors.New(`must have the same number of 'Location' and 'Range' parameters`)
}
if opt.Style < 0 || opt.Style > 35 {
return ws, errors.New("parameter 'Style' must betweent 0-35")
}
if ws.ExtLst == nil {
ws.ExtLst = &xlsxExtLst{}
}
return ws, err
}
// addSparkline provides a function to create a sparkline in a sparkline group
// by given properties.
func (f *File) addSparkline(opt *SparklineOption, group *xlsxX14SparklineGroup) {
for idx, location := range opt.Location {
group.Sparklines.Sparkline = append(group.Sparklines.Sparkline, &xlsxX14Sparkline{
F: opt.Range[idx],
Sqref: location,
})
}
}
// appendSparkline provides a function to append sparkline to sparkline
// groups.
func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, groups *xlsxX14SparklineGroups) (err error) {
var (
idx int
decodeExtLst *decodeWorksheetExt
decodeSparklineGroups *decodeX14SparklineGroups
ext *xlsxWorksheetExt
sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte
)
decodeExtLst = new(decodeWorksheetExt)
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return
}
for idx, ext = range decodeExtLst.Ext {
if ext.URI == ExtURISparklineGroups {
decodeSparklineGroups = new(decodeX14SparklineGroups)
if err = f.xmlNewDecoder(strings.NewReader(ext.Content)).
Decode(decodeSparklineGroups); err != nil && err != io.EOF {
return
}
if sparklineGroupBytes, err = xml.Marshal(group); err != nil {
return
}
groups = &xlsxX14SparklineGroups{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
Content: decodeSparklineGroups.Content + string(sparklineGroupBytes),
}
if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {
return
}
decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes)
}
}
if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil {
return
}
ws.ExtLst = &xlsxExtLst{
Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>"),
}
return
}

310
sparkline_test.go Normal file
View File

@ -0,0 +1,310 @@
package excelize
import (
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddSparkline(t *testing.T) {
f := prepareSparklineDataset()
// Set the columns widths to make the output clearer
style, err := f.NewStyle(`{"font":{"bold":true}}`)
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style))
assert.NoError(t, f.SetSheetViewOptions("Sheet1", 0, ZoomScale(150)))
assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 14))
assert.NoError(t, f.SetColWidth("Sheet1", "B", "B", 50))
// Headings
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "Sparkline"))
assert.NoError(t, f.SetCellValue("Sheet1", "B1", "Description"))
assert.NoError(t, f.SetCellValue("Sheet1", "B2", `A default "line" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A2"},
Range: []string{"Sheet3!A1:J1"},
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B3", `A default "column" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A3"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B4", `A default "win/loss" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A4"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B6", "Line with markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A6"},
Range: []string{"Sheet3!A1:J1"},
Markers: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B7", "Line with high and low points."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A7"},
Range: []string{"Sheet3!A1:J1"},
High: true,
Low: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B8", "Line with first and last point markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A8"},
Range: []string{"Sheet3!A1:J1"},
First: true,
Last: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B9", "Line with negative point markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A9"},
Range: []string{"Sheet3!A1:J1"},
Negative: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B10", "Line with axis."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A10"},
Range: []string{"Sheet3!A1:J1"},
Axis: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B12", "Column with default style (1)."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A12"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B13", "Column with style 2."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A13"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 2,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B14", "Column with style 3."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A14"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 3,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B15", "Column with style 4."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A15"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 4,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B16", "Column with style 5."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A16"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 5,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B17", "Column with style 6."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A17"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 6,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B18", "Column with a user defined color."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A18"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
SeriesColor: "#E965E0",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B20", "A win/loss sparkline."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A20"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B21", "A win/loss sparkline with negative points highlighted."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A21"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
Negative: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B23", "A left to right column (the default)."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A23"},
Range: []string{"Sheet3!A4:J4"},
Type: "column",
Style: 20,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B24", "A right to left column."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A24"},
Range: []string{"Sheet3!A4:J4"},
Type: "column",
Style: 20,
Reverse: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B25", "Sparkline and text in one cell."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A25"},
Range: []string{"Sheet3!A4:J4"},
Type: "column",
Style: 20,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "A25", "Growth"))
assert.NoError(t, f.SetCellValue("Sheet1", "B27", "A grouped sparkline. Changes are applied to all three."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A27", "A28", "A29"},
Range: []string{"Sheet3!A5:J5", "Sheet3!A6:J6", "Sheet3!A7:J7"},
Markers: true,
}))
// Sheet2 sections
assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOption{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Type: "win_loss",
Negative: true,
}))
assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOption{
Location: []string{"F1"},
Range: []string{"Sheet2!A1:E1"},
Markers: true,
}))
assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOption{
Location: []string{"F2"},
Range: []string{"Sheet2!A2:E2"},
Type: "column",
Style: 12,
}))
assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOption{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Type: "win_loss",
Negative: true,
}))
// Save xlsx file by the given path.
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddSparkline.xlsx")))
// Test error exceptions
assert.EqualError(t, f.AddSparkline("SheetN", &SparklineOption{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
}), "sheet SheetN is not exist")
assert.EqualError(t, f.AddSparkline("Sheet1", nil), "parameter is required")
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOption{
Range: []string{"Sheet2!A3:E3"},
}), `parameter 'Location' is required`)
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"F3"},
}), `parameter 'Range' is required`)
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"F2", "F3"},
Range: []string{"Sheet2!A3:E3"},
}), `must have the same number of 'Location' and 'Range' parameters`)
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Type: "unknown_type",
}), `parameter 'Type' must be 'line', 'column' or 'win_loss'`)
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Style: -1,
}), `parameter 'Style' must betweent 0-35`)
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Style: -1,
}), `parameter 'Style' must betweent 0-35`)
f.Sheet["xl/worksheets/sheet1.xml"].ExtLst.Ext = `<extLst>
<ext x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}">
<x14:sparklineGroups
xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main">
<x14:sparklineGroup>
</x14:sparklines>
</x14:sparklineGroup>
</x14:sparklineGroups>
</ext>
</extLst>`
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A2"},
Range: []string{"Sheet3!A1:J1"},
}), "XML syntax error on line 6: element <sparklineGroup> closed by </sparklines>")
}
func TestAppendSparkline(t *testing.T) {
// Test unsupport charset.
f := NewFile()
ws, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
ws.ExtLst = &xlsxExtLst{Ext: string(MacintoshCyrillicCharset)}
assert.EqualError(t, f.appendSparkline(ws, &xlsxX14SparklineGroup{}, &xlsxX14SparklineGroups{}), "XML syntax error on line 1: invalid UTF-8")
}
func prepareSparklineDataset() *File {
f := NewFile()
sheet2 := [][]int{
{-2, 2, 3, -1, 0},
{30, 20, 33, 20, 15},
{1, -1, -1, 1, -1},
}
sheet3 := [][]int{
{-2, 2, 3, -1, 0, -2, 3, 2, 1, 0},
{30, 20, 33, 20, 15, 5, 5, 15, 10, 15},
{1, 1, -1, -1, 1, -1, 1, 1, 1, -1},
{5, 6, 7, 10, 15, 20, 30, 50, 70, 100},
{-2, 2, 3, -1, 0, -2, 3, 2, 1, 0},
{3, -1, 0, -2, 3, 2, 1, 0, 2, 1},
{0, -2, 3, 2, 1, 0, 1, 2, 3, 1},
}
f.NewSheet("Sheet2")
f.NewSheet("Sheet3")
for row, data := range sheet2 {
if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil {
fmt.Println(err)
}
}
for row, data := range sheet3 {
if err := f.SetSheetRow("Sheet3", fmt.Sprintf("A%d", row+1), &data); err != nil {
fmt.Println(err)
}
}
return f
}

515
stream.go Normal file
View File

@ -0,0 +1,515 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"time"
)
// StreamWriter defined the type of stream writer.
type StreamWriter struct {
File *File
Sheet string
SheetID int
worksheet *xlsxWorksheet
rawData bufferedWriter
tableParts string
}
// NewStreamWriter return stream writer struct by given worksheet name for
// generate new worksheet with large amounts of data. Note that after set
// rows, you must call the 'Flush' method to end the streaming writing
// process and ensure that the order of line numbers is ascending. For
// example, set data for worksheet of size 102400 rows x 50 columns with
// numbers and style:
//
// file := excelize.NewFile()
// streamWriter, err := file.NewStreamWriter("Sheet1")
// if err != nil {
// fmt.Println(err)
// }
// styleID, err := file.NewStyle(`{"font":{"color":"#777777"}}`)
// if err != nil {
// fmt.Println(err)
// }
// if err := streamWriter.SetRow("A1", []interface{}{excelize.Cell{StyleID: styleID, Value: "Data"}}); err != nil {
// fmt.Println(err)
// }
// for rowID := 2; rowID <= 102400; rowID++ {
// row := make([]interface{}, 50)
// for colID := 0; colID < 50; colID++ {
// row[colID] = rand.Intn(640000)
// }
// cell, _ := excelize.CoordinatesToCellName(1, rowID)
// if err := streamWriter.SetRow(cell, row); err != nil {
// fmt.Println(err)
// }
// }
// if err := streamWriter.Flush(); err != nil {
// fmt.Println(err)
// }
// if err := file.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
//
func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
sheetID := f.getSheetID(sheet)
if sheetID == 0 {
return nil, fmt.Errorf("sheet %s is not exist", sheet)
}
sw := &StreamWriter{
File: f,
Sheet: sheet,
SheetID: sheetID,
}
var err error
sw.worksheet, err = f.workSheetReader(sheet)
if err != nil {
return nil, err
}
sw.rawData.WriteString(XMLHeader + `<worksheet` + templateNamespaceIDMap)
bulkAppendFields(&sw.rawData, sw.worksheet, 1, 5)
sw.rawData.WriteString(`<sheetData>`)
return sw, err
}
// AddTable creates an Excel table for the StreamWriter using the given
// coordinate area and format set. For example, create a table of A1:D5:
//
// err := sw.AddTable("A1", "D5", ``)
//
// Create a table of F2:H6 with format set:
//
// err := sw.AddTable("F2", "H6", `{"table_name":"table","table_style":"TableStyleMedium2","show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`)
//
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique.
//
// Currently only one table is allowed for a StreamWriter. AddTable must be
// called after the rows are written but before Flush.
//
// See File.AddTable for details on the table format.
func (sw *StreamWriter) AddTable(hcell, vcell, format string) error {
formatSet, err := parseFormatTableSet(format)
if err != nil {
return err
}
coordinates, err := areaRangeToCoordinates(hcell, vcell)
if err != nil {
return err
}
_ = sortCoordinates(coordinates)
// Correct the minimum number of rows, the table at least two lines.
if coordinates[1] == coordinates[3] {
coordinates[3]++
}
// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
ref, err := sw.File.coordinatesToAreaRef(coordinates)
if err != nil {
return err
}
// create table columns using the first row
tableHeaders, err := sw.getRowValues(coordinates[1], coordinates[0], coordinates[2])
if err != nil {
return err
}
tableColumn := make([]*xlsxTableColumn, len(tableHeaders))
for i, name := range tableHeaders {
tableColumn[i] = &xlsxTableColumn{
ID: i + 1,
Name: name,
}
}
tableID := sw.File.countTables() + 1
name := formatSet.TableName
if name == "" {
name = "Table" + strconv.Itoa(tableID)
}
table := xlsxTable{
XMLNS: NameSpaceSpreadSheet,
ID: tableID,
Name: name,
DisplayName: name,
Ref: ref,
AutoFilter: &xlsxAutoFilter{
Ref: ref,
},
TableColumns: &xlsxTableColumns{
Count: len(tableColumn),
TableColumn: tableColumn,
},
TableStyleInfo: &xlsxTableStyleInfo{
Name: formatSet.TableStyle,
ShowFirstColumn: formatSet.ShowFirstColumn,
ShowLastColumn: formatSet.ShowLastColumn,
ShowRowStripes: formatSet.ShowRowStripes,
ShowColumnStripes: formatSet.ShowColumnStripes,
},
}
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
// Add first table for given sheet.
sheetPath, _ := sw.File.sheetMap[trimSheetName(sw.Sheet)]
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
rID := sw.File.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
sw.tableParts = fmt.Sprintf(`<tableParts count="1"><tablePart r:id="rId%d"></tablePart></tableParts>`, rID)
sw.File.addContentTypePart(tableID, "table")
b, _ := xml.Marshal(table)
sw.File.saveFileList(tableXML, b)
return nil
}
// Extract values from a row in the StreamWriter.
func (sw *StreamWriter) getRowValues(hrow, hcol, vcol int) (res []string, err error) {
res = make([]string, vcol-hcol+1)
r, err := sw.rawData.Reader()
if err != nil {
return nil, err
}
dec := sw.File.xmlNewDecoder(r)
for {
token, err := dec.Token()
if err == io.EOF {
return res, nil
}
if err != nil {
return nil, err
}
startElement, ok := getRowElement(token, hrow)
if !ok {
continue
}
// decode cells
var row xlsxRow
if err := dec.DecodeElement(&row, &startElement); err != nil {
return nil, err
}
for _, c := range row.C {
col, _, err := CellNameToCoordinates(c.R)
if err != nil {
return nil, err
}
if col < hcol || col > vcol {
continue
}
res[col-hcol] = c.V
}
return res, nil
}
}
// Check if the token is an XLSX row with the matching row number.
func getRowElement(token xml.Token, hrow int) (startElement xml.StartElement, ok bool) {
startElement, ok = token.(xml.StartElement)
if !ok {
return
}
ok = startElement.Name.Local == "row"
if !ok {
return
}
ok = false
for _, attr := range startElement.Attr {
if attr.Name.Local != "r" {
continue
}
row, _ := strconv.Atoi(attr.Value)
if row == hrow {
ok = true
return
}
}
return
}
// Cell can be used directly in StreamWriter.SetRow to specify a style and
// a value.
type Cell struct {
StyleID int
Value interface{}
}
// SetRow writes an array to stream rows by giving a worksheet name, starting
// coordinate and a pointer to an array of values. Note that you must call the
// 'Flush' method to end the streaming writing process.
//
// As a special case, if Cell is used as a value, then the Cell.StyleID will be
// applied to that cell.
func (sw *StreamWriter) SetRow(axis string, values []interface{}) error {
col, row, err := CellNameToCoordinates(axis)
if err != nil {
return err
}
fmt.Fprintf(&sw.rawData, `<row r="%d">`, row)
for i, val := range values {
axis, err := CoordinatesToCellName(col+i, row)
if err != nil {
return err
}
c := xlsxC{R: axis}
if v, ok := val.(Cell); ok {
c.S = v.StyleID
val = v.Value
} else if v, ok := val.(*Cell); ok && v != nil {
c.S = v.StyleID
val = v.Value
}
if err = setCellValFunc(&c, val); err != nil {
sw.rawData.WriteString(`</row>`)
return err
}
writeCell(&sw.rawData, c)
}
sw.rawData.WriteString(`</row>`)
return sw.rawData.Sync()
}
// setCellValFunc provides a function to set value of a cell.
func setCellValFunc(c *xlsxC, val interface{}) (err error) {
switch val := val.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
err = setCellIntFunc(c, val)
case float32:
c.T, c.V = setCellFloat(float64(val), -1, 32)
case float64:
c.T, c.V = setCellFloat(val, -1, 64)
case string:
c.T, c.V, c.XMLSpace = setCellStr(val)
case []byte:
c.T, c.V, c.XMLSpace = setCellStr(string(val))
case time.Duration:
c.T, c.V = setCellDuration(val)
case time.Time:
c.T, c.V, _, err = setCellTime(val)
case bool:
c.T, c.V = setCellBool(val)
case nil:
c.T, c.V, c.XMLSpace = setCellStr("")
default:
c.T, c.V, c.XMLSpace = setCellStr(fmt.Sprint(val))
}
return err
}
// setCellIntFunc is a wrapper of SetCellInt.
func setCellIntFunc(c *xlsxC, val interface{}) (err error) {
switch val := val.(type) {
case int:
c.T, c.V = setCellInt(val)
case int8:
c.T, c.V = setCellInt(int(val))
case int16:
c.T, c.V = setCellInt(int(val))
case int32:
c.T, c.V = setCellInt(int(val))
case int64:
c.T, c.V = setCellInt(int(val))
case uint:
c.T, c.V = setCellInt(int(val))
case uint8:
c.T, c.V = setCellInt(int(val))
case uint16:
c.T, c.V = setCellInt(int(val))
case uint32:
c.T, c.V = setCellInt(int(val))
case uint64:
c.T, c.V = setCellInt(int(val))
default:
}
return
}
func writeCell(buf *bufferedWriter, c xlsxC) {
buf.WriteString(`<c`)
if c.XMLSpace.Value != "" {
fmt.Fprintf(buf, ` xml:%s="%s"`, c.XMLSpace.Name.Local, c.XMLSpace.Value)
}
fmt.Fprintf(buf, ` r="%s"`, c.R)
if c.S != 0 {
fmt.Fprintf(buf, ` s="%d"`, c.S)
}
if c.T != "" {
fmt.Fprintf(buf, ` t="%s"`, c.T)
}
buf.WriteString(`>`)
if c.V != "" {
buf.WriteString(`<v>`)
xml.EscapeText(buf, stringToBytes(c.V))
buf.WriteString(`</v>`)
}
buf.WriteString(`</c>`)
}
// Flush ending the streaming writing process.
func (sw *StreamWriter) Flush() error {
sw.rawData.WriteString(`</sheetData>`)
bulkAppendFields(&sw.rawData, sw.worksheet, 7, 37)
sw.rawData.WriteString(sw.tableParts)
bulkAppendFields(&sw.rawData, sw.worksheet, 39, 39)
sw.rawData.WriteString(`</worksheet>`)
if err := sw.rawData.Flush(); err != nil {
return err
}
sheetXML := fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)
delete(sw.File.Sheet, sheetXML)
delete(sw.File.checked, sheetXML)
defer sw.rawData.Close()
b, err := sw.rawData.Bytes()
if err != nil {
return err
}
sw.File.XLSX[sheetXML] = b
return nil
}
// bulkAppendFields bulk-appends fields in a worksheet by specified field
// names order range.
func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
s := reflect.ValueOf(ws).Elem()
enc := xml.NewEncoder(w)
for i := 0; i < s.NumField(); i++ {
if from <= i && i <= to {
enc.Encode(s.Field(i).Interface())
}
}
}
// bufferedWriter uses a temp file to store an extended buffer. Writes are
// always made to an in-memory buffer, which will always succeed. The buffer
// is written to the temp file with Sync, which may return an error.
// Therefore, Sync should be periodically called and the error checked.
type bufferedWriter struct {
tmp *os.File
buf bytes.Buffer
}
// Write to the in-memory buffer. The err is always nil.
func (bw *bufferedWriter) Write(p []byte) (n int, err error) {
return bw.buf.Write(p)
}
// WriteString wites to the in-memory buffer. The err is always nil.
func (bw *bufferedWriter) WriteString(p string) (n int, err error) {
return bw.buf.WriteString(p)
}
// Reader provides read-access to the underlying buffer/file.
func (bw *bufferedWriter) Reader() (io.Reader, error) {
if bw.tmp == nil {
return bytes.NewReader(bw.buf.Bytes()), nil
}
if err := bw.Flush(); err != nil {
return nil, err
}
fi, err := bw.tmp.Stat()
if err != nil {
return nil, err
}
// os.File.ReadAt does not affect the cursor position and is safe to use here
return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil
}
// Bytes returns the entire content of the bufferedWriter. If a temp file is
// used, Bytes will efficiently allocate a buffer to prevent re-allocations.
func (bw *bufferedWriter) Bytes() ([]byte, error) {
if bw.tmp == nil {
return bw.buf.Bytes(), nil
}
if err := bw.Flush(); err != nil {
return nil, err
}
var buf bytes.Buffer
if fi, err := bw.tmp.Stat(); err == nil {
if size := fi.Size() + bytes.MinRead; size > bytes.MinRead {
if int64(int(size)) == size {
buf.Grow(int(size))
} else {
return nil, bytes.ErrTooLarge
}
}
}
if _, err := bw.tmp.Seek(0, 0); err != nil {
return nil, err
}
_, err := buf.ReadFrom(bw.tmp)
return buf.Bytes(), err
}
// Sync will write the in-memory buffer to a temp file, if the in-memory
// buffer has grown large enough. Any error will be returned.
func (bw *bufferedWriter) Sync() (err error) {
// Try to use local storage
const chunk = 1 << 24
if bw.buf.Len() < chunk {
return nil
}
if bw.tmp == nil {
bw.tmp, err = ioutil.TempFile(os.TempDir(), "excelize-")
if err != nil {
// can not use local storage
return nil
}
}
return bw.Flush()
}
// Flush the entire in-memory buffer to the temp file, if a temp file is being
// used.
func (bw *bufferedWriter) Flush() error {
if bw.tmp == nil {
return nil
}
_, err := bw.buf.WriteTo(bw.tmp)
if err != nil {
return err
}
bw.buf.Reset()
return nil
}
// Close the underlying temp file and reset the in-memory buffer.
func (bw *bufferedWriter) Close() error {
bw.buf.Reset()
if bw.tmp == nil {
return nil
}
defer os.Remove(bw.tmp.Name())
return bw.tmp.Close()
}

174
stream_test.go Normal file
View File

@ -0,0 +1,174 @@
package excelize
import (
"encoding/xml"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func BenchmarkStreamWriter(b *testing.B) {
file := NewFile()
row := make([]interface{}, 10)
for colID := 0; colID < 10; colID++ {
row[colID] = colID
}
for n := 0; n < b.N; n++ {
streamWriter, _ := file.NewStreamWriter("Sheet1")
for rowID := 10; rowID <= 110; rowID++ {
cell, _ := CoordinatesToCellName(1, rowID)
streamWriter.SetRow(cell, row)
}
}
b.ReportAllocs()
}
func TestStreamWriter(t *testing.T) {
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
// Test max characters in a cell.
row := make([]interface{}, 1)
row[0] = strings.Repeat("c", 32769)
assert.NoError(t, streamWriter.SetRow("A1", row))
// Test leading and ending space(s) character characters in a cell.
row = make([]interface{}, 1)
row[0] = " characters"
assert.NoError(t, streamWriter.SetRow("A2", row))
row = make([]interface{}, 1)
row[0] = []byte("Word")
assert.NoError(t, streamWriter.SetRow("A3", row))
// Test set cell with style.
styleID, err := file.NewStyle(`{"font":{"color":"#777777"}}`)
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetRow("A4", []interface{}{Cell{StyleID: styleID}}))
assert.NoError(t, streamWriter.SetRow("A5", []interface{}{&Cell{StyleID: styleID, Value: "cell"}}))
assert.EqualError(t, streamWriter.SetRow("A6", []interface{}{time.Now()}), "only UTC time expected")
for rowID := 10; rowID <= 51200; rowID++ {
row := make([]interface{}, 50)
for colID := 0; colID < 50; colID++ {
row[colID] = rand.Intn(640000)
}
cell, _ := CoordinatesToCellName(1, rowID)
assert.NoError(t, streamWriter.SetRow(cell, row))
}
assert.NoError(t, streamWriter.Flush())
// Save xlsx file by the given path.
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))
// Test close temporary file error.
file = NewFile()
streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
for rowID := 10; rowID <= 51200; rowID++ {
row := make([]interface{}, 50)
for colID := 0; colID < 50; colID++ {
row[colID] = rand.Intn(640000)
}
cell, _ := CoordinatesToCellName(1, rowID)
assert.NoError(t, streamWriter.SetRow(cell, row))
}
assert.NoError(t, streamWriter.rawData.Close())
assert.Error(t, streamWriter.Flush())
streamWriter.rawData.tmp, err = ioutil.TempFile(os.TempDir(), "excelize-")
assert.NoError(t, err)
_, err = streamWriter.rawData.Reader()
assert.NoError(t, err)
assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
// Test unsupport charset
file = NewFile()
delete(file.Sheet, "xl/worksheets/sheet1.xml")
file.XLSX["xl/worksheets/sheet1.xml"] = MacintoshCyrillicCharset
streamWriter, err = file.NewStreamWriter("Sheet1")
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
}
func TestStreamTable(t *testing.T) {
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
// Write some rows. We want enough rows to force a temp file (>16MB).
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
row := []interface{}{1, 2, 3}
for r := 2; r < 10000; r++ {
assert.NoError(t, streamWriter.SetRow(fmt.Sprintf("A%d", r), row))
}
// Write a table.
assert.NoError(t, streamWriter.AddTable("A1", "C2", ``))
assert.NoError(t, streamWriter.Flush())
// Verify the table has names.
var table xlsxTable
assert.NoError(t, xml.Unmarshal(file.XLSX["xl/tables/table1.xml"], &table))
assert.Equal(t, "A", table.TableColumns.TableColumn[0].Name)
assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name)
assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name)
assert.NoError(t, streamWriter.AddTable("A1", "C1", ``))
// Test add table with illegal formatset.
assert.EqualError(t, streamWriter.AddTable("B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string")
// Test add table with illegal cell coordinates.
assert.EqualError(t, streamWriter.AddTable("A", "B1", `{}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), `cannot convert cell "B" to coordinates: invalid cell name "B"`)
}
func TestNewStreamWriter(t *testing.T) {
// Test error exceptions
file := NewFile()
_, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
_, err = file.NewStreamWriter("SheetN")
assert.EqualError(t, err, "sheet SheetN is not exist")
}
func TestSetRow(t *testing.T) {
// Test error exceptions
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}
func TestSetCellValFunc(t *testing.T) {
c := &xlsxC{}
assert.NoError(t, setCellValFunc(c, 128))
assert.NoError(t, setCellValFunc(c, int8(-128)))
assert.NoError(t, setCellValFunc(c, int16(-32768)))
assert.NoError(t, setCellValFunc(c, int32(-2147483648)))
assert.NoError(t, setCellValFunc(c, int64(-9223372036854775808)))
assert.NoError(t, setCellValFunc(c, uint(128)))
assert.NoError(t, setCellValFunc(c, uint8(255)))
assert.NoError(t, setCellValFunc(c, uint16(65535)))
assert.NoError(t, setCellValFunc(c, uint32(4294967295)))
assert.NoError(t, setCellValFunc(c, uint64(18446744073709551615)))
assert.NoError(t, setCellValFunc(c, float32(100.1588)))
assert.NoError(t, setCellValFunc(c, float64(100.1588)))
assert.NoError(t, setCellValFunc(c, " Hello"))
assert.NoError(t, setCellValFunc(c, []byte(" Hello")))
assert.NoError(t, setCellValFunc(c, time.Now().UTC()))
assert.NoError(t, setCellValFunc(c, time.Duration(1e13)))
assert.NoError(t, setCellValFunc(c, true))
assert.NoError(t, setCellValFunc(c, nil))
assert.NoError(t, setCellValFunc(c, complex64(5+10i)))
}

313
styles.go
View File

@ -1,18 +1,22 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"math"
"strconv"
"strings"
@ -852,7 +856,7 @@ func formatToInt(i int, v string) string {
if err != nil {
return v
}
return fmt.Sprintf("%d", int(f))
return fmt.Sprintf("%d", int64(f))
}
// formatToFloat provides a function to convert original string to float
@ -997,11 +1001,16 @@ func is12HourTime(format string) bool {
// stylesReader provides a function to get the pointer to the structure after
// deserialization of xl/styles.xml.
func (f *File) stylesReader() *xlsxStyleSheet {
var err error
if f.Styles == nil {
var styleSheet xlsxStyleSheet
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/styles.xml")), &styleSheet)
f.Styles = &styleSheet
f.Styles = new(xlsxStyleSheet)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/styles.xml")))).
Decode(f.Styles); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
}
return f.Styles
}
@ -1010,22 +1019,31 @@ func (f *File) stylesReader() *xlsxStyleSheet {
func (f *File) styleSheetWriter() {
if f.Styles != nil {
output, _ := xml.Marshal(f.Styles)
f.saveFileList("xl/styles.xml", replaceWorkSheetsRelationshipsNameSpaceBytes(output))
f.saveFileList("xl/styles.xml", replaceRelationshipsNameSpaceBytes(output))
}
}
// sharedStringsWriter provides a function to save xl/sharedStrings.xml after
// serialize structure.
func (f *File) sharedStringsWriter() {
if f.SharedStrings != nil {
output, _ := xml.Marshal(f.SharedStrings)
f.saveFileList("xl/sharedStrings.xml", replaceRelationshipsNameSpaceBytes(output))
}
}
// parseFormatStyleSet provides a function to parse the format settings of the
// cells and conditional formats.
func parseFormatStyleSet(style string) (*formatStyle, error) {
format := formatStyle{
func parseFormatStyleSet(style string) (*Style, error) {
format := Style{
DecimalPlaces: 2,
}
err := json.Unmarshal([]byte(style), &format)
return &format, err
}
// NewStyle provides a function to create style for cells by given style
// format. Note that the color field uses RGB color code.
// NewStyle provides a function to create the style for cells by given JSON or
// structure pointer. Note that the color field uses RGB color code.
//
// The following shows the border styles sorted by excelize index number:
//
@ -1880,26 +1898,33 @@ func parseFormatStyleSet(style string) (*formatStyle, error) {
//
// f := excelize.NewFile()
// f.SetCellValue("Sheet1", "A6", 42920.5)
// style, err := f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`)
// exp := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"
// style, err := f.NewStyle(&excelize.Style{CustomNumFmt: &exp})
// err = f.SetCellStyle("Sheet1", "A6", "A6", style)
//
// Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017
//
func (f *File) NewStyle(style string) (int, error) {
func (f *File) NewStyle(style interface{}) (int, error) {
var fs *Style
var err error
var cellXfsID, fontID, borderID, fillID int
s := f.stylesReader()
fs, err := parseFormatStyleSet(style)
if err != nil {
return cellXfsID, err
switch v := style.(type) {
case string:
fs, err = parseFormatStyleSet(v)
if err != nil {
return cellXfsID, err
}
case *Style:
fs = v
default:
return cellXfsID, errors.New("invalid parameter type")
}
s := f.stylesReader()
numFmtID := setNumFmt(s, fs)
if fs.Font != nil {
font, _ := xml.Marshal(setFont(fs))
s.Fonts.Count++
s.Fonts.Font = append(s.Fonts.Font, &xlsxFont{
Font: string(font[6 : len(font)-7]),
})
s.Fonts.Font = append(s.Fonts.Font, f.setFont(fs))
fontID = s.Fonts.Count - 1
}
@ -1930,12 +1955,16 @@ func (f *File) NewConditionalStyle(style string) (int, error) {
return 0, err
}
dxf := dxf{
Fill: setFills(fs, false),
Alignment: setAlignment(fs),
Border: setBorders(fs),
Fill: setFills(fs, false),
}
if fs.Alignment != nil {
dxf.Alignment = setAlignment(fs)
}
if len(fs.Border) > 0 {
dxf.Border = setBorders(fs)
}
if fs.Font != nil {
dxf.Font = setFont(fs)
dxf.Font = f.setFont(fs)
}
dxfStr, _ := xml.Marshal(dxf)
if s.Dxfs == nil {
@ -1948,67 +1977,97 @@ func (f *File) NewConditionalStyle(style string) (int, error) {
return s.Dxfs.Count - 1, nil
}
// GetDefaultFont provides the default font name currently set in the workbook
// Documents generated by excelize start with Calibri.
func (f *File) GetDefaultFont() string {
font := f.readDefaultFont()
return *font.Name.Val
}
// SetDefaultFont changes the default font in the workbook.
func (f *File) SetDefaultFont(fontName string) {
font := f.readDefaultFont()
font.Name.Val = stringPtr(fontName)
s := f.stylesReader()
s.Fonts.Font[0] = font
custom := true
s.CellStyles.CellStyle[0].CustomBuiltIn = &custom
}
// readDefaultFont provides an unmarshalled font value.
func (f *File) readDefaultFont() *xlsxFont {
s := f.stylesReader()
return s.Fonts.Font[0]
}
// setFont provides a function to add font style by given cell format
// settings.
func setFont(formatStyle *formatStyle) *font {
func (f *File) setFont(style *Style) *xlsxFont {
fontUnderlineType := map[string]string{"single": "single", "double": "double"}
if formatStyle.Font.Size < 1 {
formatStyle.Font.Size = 11
if style.Font.Size < 1 {
style.Font.Size = 11
}
if formatStyle.Font.Color == "" {
formatStyle.Font.Color = "#000000"
if style.Font.Color == "" {
style.Font.Color = "#000000"
}
f := font{
B: formatStyle.Font.Bold,
I: formatStyle.Font.Italic,
Sz: &attrValInt{Val: formatStyle.Font.Size},
Color: &xlsxColor{RGB: getPaletteColor(formatStyle.Font.Color)},
Name: &attrValString{Val: formatStyle.Font.Family},
Family: &attrValInt{Val: 2},
fnt := xlsxFont{
Sz: &attrValFloat{Val: float64Ptr(style.Font.Size)},
Color: &xlsxColor{RGB: getPaletteColor(style.Font.Color)},
Name: &attrValString{Val: stringPtr(style.Font.Family)},
Family: &attrValInt{Val: intPtr(2)},
}
if f.Name.Val == "" {
f.Name.Val = "Calibri"
f.Scheme = &attrValString{Val: "minor"}
if style.Font.Bold {
fnt.B = &style.Font.Bold
}
val, ok := fontUnderlineType[formatStyle.Font.Underline]
if style.Font.Italic {
fnt.I = &style.Font.Italic
}
if *fnt.Name.Val == "" {
*fnt.Name.Val = f.GetDefaultFont()
}
if style.Font.Strike {
strike := true
fnt.Strike = &strike
}
val, ok := fontUnderlineType[style.Font.Underline]
if ok {
f.U = &attrValString{Val: val}
fnt.U = &attrValString{Val: stringPtr(val)}
}
return &f
return &fnt
}
// setNumFmt provides a function to check if number format code in the range
// of built-in values.
func setNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int {
func setNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
dp := "0."
numFmtID := 164 // Default custom number format code from 164.
if formatStyle.DecimalPlaces < 0 || formatStyle.DecimalPlaces > 30 {
formatStyle.DecimalPlaces = 2
if style.DecimalPlaces < 0 || style.DecimalPlaces > 30 {
style.DecimalPlaces = 2
}
for i := 0; i < formatStyle.DecimalPlaces; i++ {
for i := 0; i < style.DecimalPlaces; i++ {
dp += "0"
}
if formatStyle.CustomNumFmt != nil {
return setCustomNumFmt(style, formatStyle)
if style.CustomNumFmt != nil {
return setCustomNumFmt(styleSheet, style)
}
_, ok := builtInNumFmt[formatStyle.NumFmt]
_, ok := builtInNumFmt[style.NumFmt]
if !ok {
fc, currency := currencyNumFmt[formatStyle.NumFmt]
fc, currency := currencyNumFmt[style.NumFmt]
if !currency {
return setLangNumFmt(style, formatStyle)
return setLangNumFmt(styleSheet, style)
}
fc = strings.Replace(fc, "0.00", dp, -1)
if formatStyle.NegRed {
if style.NegRed {
fc = fc + ";[Red]" + fc
}
if style.NumFmts != nil {
numFmtID = style.NumFmts.NumFmt[len(style.NumFmts.NumFmt)-1].NumFmtID + 1
if styleSheet.NumFmts != nil {
numFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1
nf := xlsxNumFmt{
FormatCode: fc,
NumFmtID: numFmtID,
}
style.NumFmts.NumFmt = append(style.NumFmts.NumFmt, &nf)
style.NumFmts.Count++
styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf)
styleSheet.NumFmts.Count++
} else {
nf := xlsxNumFmt{
FormatCode: fc,
@ -2018,61 +2077,61 @@ func setNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int {
NumFmt: []*xlsxNumFmt{&nf},
Count: 1,
}
style.NumFmts = &numFmts
styleSheet.NumFmts = &numFmts
}
return numFmtID
}
return formatStyle.NumFmt
return style.NumFmt
}
// setCustomNumFmt provides a function to set custom number format code.
func setCustomNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int {
nf := xlsxNumFmt{FormatCode: *formatStyle.CustomNumFmt}
if style.NumFmts != nil {
nf.NumFmtID = style.NumFmts.NumFmt[len(style.NumFmts.NumFmt)-1].NumFmtID + 1
style.NumFmts.NumFmt = append(style.NumFmts.NumFmt, &nf)
style.NumFmts.Count++
func setCustomNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
nf := xlsxNumFmt{FormatCode: *style.CustomNumFmt}
if styleSheet.NumFmts != nil {
nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1
styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf)
styleSheet.NumFmts.Count++
} else {
nf.NumFmtID = 164
numFmts := xlsxNumFmts{
NumFmt: []*xlsxNumFmt{&nf},
Count: 1,
}
style.NumFmts = &numFmts
styleSheet.NumFmts = &numFmts
}
return nf.NumFmtID
}
// setLangNumFmt provides a function to set number format code with language.
func setLangNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int {
numFmts, ok := langNumFmt[formatStyle.Lang]
func setLangNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
numFmts, ok := langNumFmt[style.Lang]
if !ok {
return 0
}
var fc string
fc, ok = numFmts[formatStyle.NumFmt]
fc, ok = numFmts[style.NumFmt]
if !ok {
return 0
}
nf := xlsxNumFmt{FormatCode: fc}
if style.NumFmts != nil {
nf.NumFmtID = style.NumFmts.NumFmt[len(style.NumFmts.NumFmt)-1].NumFmtID + 1
style.NumFmts.NumFmt = append(style.NumFmts.NumFmt, &nf)
style.NumFmts.Count++
if styleSheet.NumFmts != nil {
nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1
styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf)
styleSheet.NumFmts.Count++
} else {
nf.NumFmtID = formatStyle.NumFmt
nf.NumFmtID = style.NumFmt
numFmts := xlsxNumFmts{
NumFmt: []*xlsxNumFmt{&nf},
Count: 1,
}
style.NumFmts = &numFmts
styleSheet.NumFmts = &numFmts
}
return nf.NumFmtID
}
// setFills provides a function to add fill elements in the styles.xml by
// given cell format settings.
func setFills(formatStyle *formatStyle, fg bool) *xlsxFill {
func setFills(style *Style, fg bool) *xlsxFill {
var patterns = []string{
"none",
"solid",
@ -2103,15 +2162,15 @@ func setFills(formatStyle *formatStyle, fg bool) *xlsxFill {
}
var fill xlsxFill
switch formatStyle.Fill.Type {
switch style.Fill.Type {
case "gradient":
if len(formatStyle.Fill.Color) != 2 {
if len(style.Fill.Color) != 2 {
break
}
var gradient xlsxGradientFill
switch formatStyle.Fill.Shading {
switch style.Fill.Shading {
case 0, 1, 2, 3:
gradient.Degree = variants[formatStyle.Fill.Shading]
gradient.Degree = variants[style.Fill.Shading]
case 4:
gradient.Type = "path"
case 5:
@ -2124,7 +2183,7 @@ func setFills(formatStyle *formatStyle, fg bool) *xlsxFill {
break
}
var stops []*xlsxGradientFillStop
for index, color := range formatStyle.Fill.Color {
for index, color := range style.Fill.Color {
var stop xlsxGradientFillStop
stop.Position = float64(index)
stop.Color.RGB = getPaletteColor(color)
@ -2133,18 +2192,18 @@ func setFills(formatStyle *formatStyle, fg bool) *xlsxFill {
gradient.Stop = stops
fill.GradientFill = &gradient
case "pattern":
if formatStyle.Fill.Pattern > 18 || formatStyle.Fill.Pattern < 0 {
if style.Fill.Pattern > 18 || style.Fill.Pattern < 0 {
break
}
if len(formatStyle.Fill.Color) < 1 {
if len(style.Fill.Color) < 1 {
break
}
var pattern xlsxPatternFill
pattern.PatternType = patterns[formatStyle.Fill.Pattern]
pattern.PatternType = patterns[style.Fill.Pattern]
if fg {
pattern.FgColor.RGB = getPaletteColor(formatStyle.Fill.Color[0])
pattern.FgColor.RGB = getPaletteColor(style.Fill.Color[0])
} else {
pattern.BgColor.RGB = getPaletteColor(formatStyle.Fill.Color[0])
pattern.BgColor.RGB = getPaletteColor(style.Fill.Color[0])
}
fill.PatternFill = &pattern
default:
@ -2157,36 +2216,36 @@ func setFills(formatStyle *formatStyle, fg bool) *xlsxFill {
// text alignment in cells. There are a variety of choices for how text is
// aligned both horizontally and vertically, as well as indentation settings,
// and so on.
func setAlignment(formatStyle *formatStyle) *xlsxAlignment {
func setAlignment(style *Style) *xlsxAlignment {
var alignment xlsxAlignment
if formatStyle.Alignment != nil {
alignment.Horizontal = formatStyle.Alignment.Horizontal
alignment.Indent = formatStyle.Alignment.Indent
alignment.JustifyLastLine = formatStyle.Alignment.JustifyLastLine
alignment.ReadingOrder = formatStyle.Alignment.ReadingOrder
alignment.RelativeIndent = formatStyle.Alignment.RelativeIndent
alignment.ShrinkToFit = formatStyle.Alignment.ShrinkToFit
alignment.TextRotation = formatStyle.Alignment.TextRotation
alignment.Vertical = formatStyle.Alignment.Vertical
alignment.WrapText = formatStyle.Alignment.WrapText
if style.Alignment != nil {
alignment.Horizontal = style.Alignment.Horizontal
alignment.Indent = style.Alignment.Indent
alignment.JustifyLastLine = style.Alignment.JustifyLastLine
alignment.ReadingOrder = style.Alignment.ReadingOrder
alignment.RelativeIndent = style.Alignment.RelativeIndent
alignment.ShrinkToFit = style.Alignment.ShrinkToFit
alignment.TextRotation = style.Alignment.TextRotation
alignment.Vertical = style.Alignment.Vertical
alignment.WrapText = style.Alignment.WrapText
}
return &alignment
}
// setProtection provides a function to set protection properties associated
// with the cell.
func setProtection(formatStyle *formatStyle) *xlsxProtection {
func setProtection(style *Style) *xlsxProtection {
var protection xlsxProtection
if formatStyle.Protection != nil {
protection.Hidden = formatStyle.Protection.Hidden
protection.Locked = formatStyle.Protection.Locked
if style.Protection != nil {
protection.Hidden = style.Protection.Hidden
protection.Locked = style.Protection.Locked
}
return &protection
}
// setBorders provides a function to add border elements in the styles.xml by
// given borders format settings.
func setBorders(formatStyle *formatStyle) *xlsxBorder {
func setBorders(style *Style) *xlsxBorder {
var styles = []string{
"none",
"thin",
@ -2205,7 +2264,7 @@ func setBorders(formatStyle *formatStyle) *xlsxBorder {
}
var border xlsxBorder
for _, v := range formatStyle.Border {
for _, v := range style.Border {
if 0 <= v.Style && v.Style < 14 {
var color xlsxColor
color.RGB = getPaletteColor(v.Color)
@ -2240,21 +2299,21 @@ func setBorders(formatStyle *formatStyle) *xlsxBorder {
// cell.
func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) int {
var xf xlsxXf
xf.FontID = fontID
xf.FontID = intPtr(fontID)
if fontID != 0 {
xf.ApplyFont = true
xf.ApplyFont = boolPtr(true)
}
xf.NumFmtID = numFmtID
xf.NumFmtID = intPtr(numFmtID)
if numFmtID != 0 {
xf.ApplyNumberFormat = true
xf.ApplyNumberFormat = boolPtr(true)
}
xf.FillID = fillID
xf.BorderID = borderID
xf.FillID = intPtr(fillID)
xf.BorderID = intPtr(borderID)
style.CellXfs.Count++
xf.Alignment = alignment
xf.ApplyAlignment = applyAlignment
xf.ApplyAlignment = boolPtr(applyAlignment)
if applyProtection {
xf.ApplyProtection = applyProtection
xf.ApplyProtection = boolPtr(applyProtection)
xf.Protection = protection
}
xfID := 0
@ -2328,7 +2387,7 @@ func (f *File) GetCellStyle(sheet, axis string) (int, error) {
//
// Set font style for cell H9 on Sheet1:
//
// style, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Berlin Sans FB Demi","size":36,"color":"#777777"}}`)
// style, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`)
// if err != nil {
// fmt.Println(err)
// }
@ -2641,6 +2700,22 @@ func (f *File) SetConditionalFormat(sheet, area, formatSet string) error {
return err
}
// UnsetConditionalFormat provides a function to unset the conditional format
// by given worksheet name and range.
func (f *File) UnsetConditionalFormat(sheet, area string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
for i, cf := range ws.ConditionalFormatting {
if cf.SQRef == area {
ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i], ws.ConditionalFormatting[i+1:]...)
return nil
}
}
return nil
}
// drawCondFmtCellIs provides a function to create conditional formatting rule
// for cell value (include between, not between, equal, not equal, greater
// than and less than) by given priority, criteria type and format settings.
@ -2657,7 +2732,7 @@ func drawCondFmtCellIs(p int, ct string, format *formatConditional) *xlsxCfRule
c.Formula = append(c.Formula, format.Minimum)
c.Formula = append(c.Formula, format.Maximum)
}
_, ok = map[string]bool{"equal": true, "notEqual": true, "greaterThan": true, "lessThan": true}[ct]
_, ok = map[string]bool{"equal": true, "notEqual": true, "greaterThan": true, "lessThan": true, "greaterThanOrEqual": true, "lessThanOrEqual": true, "containsText": true, "notContains": true, "beginsWith": true, "endsWith": true}[ct]
if ok {
c.Formula = append(c.Formula, format.Value)
}
@ -2776,8 +2851,16 @@ func getPaletteColor(color string) string {
// themeReader provides a function to get the pointer to the xl/theme/theme1.xml
// structure after deserialization.
func (f *File) themeReader() *xlsxTheme {
var theme xlsxTheme
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")), &theme)
var (
err error
theme xlsxTheme
)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")))).
Decode(&theme); err != nil && err != io.EOF {
log.Printf("xml decoder error: %s", err)
}
return &theme
}

View File

@ -1,6 +1,8 @@
package excelize
import (
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -25,15 +27,15 @@ func TestStyleFill(t *testing.T) {
xl := NewFile()
styleID, err := xl.NewStyle(testCase.format)
if err != nil {
t.Fatalf("%v", err)
t.Fatal(err)
}
styles := xl.stylesReader()
style := styles.CellXfs.Xf[styleID]
if testCase.expectFill {
assert.NotEqual(t, style.FillID, 0, testCase.label)
assert.NotEqual(t, *style.FillID, 0, testCase.label)
} else {
assert.Equal(t, style.FillID, 0, testCase.label)
assert.Equal(t, *style.FillID, 0, testCase.label)
}
}
}
@ -165,3 +167,68 @@ func TestSetConditionalFormat(t *testing.T) {
assert.EqualValues(t, testCase.rules, cf[0].CfRule, testCase.label)
}
}
func TestUnsetConditionalFormat(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
format, err := f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
assert.NoError(t, err)
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format)))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
// Test unset conditional format on not exists worksheet.
assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN is not exist")
// Save xlsx file by the given path.
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnsetConditionalFormat.xlsx")))
}
func TestNewStyle(t *testing.T) {
f := NewFile()
styleID, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`)
assert.NoError(t, err)
styles := f.stylesReader()
fontID := styles.CellXfs.Xf[styleID].FontID
font := styles.Fonts.Font[*fontID]
assert.Contains(t, *font.Name.Val, "Times New Roman", "Stored font should contain font name")
assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles")
_, err = f.NewStyle(&Style{})
assert.NoError(t, err)
_, err = f.NewStyle(Style{})
assert.EqualError(t, err, "invalid parameter type")
}
func TestGetDefaultFont(t *testing.T) {
f := NewFile()
s := f.GetDefaultFont()
assert.Equal(t, s, "Calibri", "Default font should be Calibri")
}
func TestSetDefaultFont(t *testing.T) {
f := NewFile()
f.SetDefaultFont("Ariel")
styles := f.stylesReader()
s := f.GetDefaultFont()
assert.Equal(t, s, "Ariel", "Default font should change to Ariel")
assert.Equal(t, *styles.CellStyles.CellStyle[0].CustomBuiltIn, true)
}
func TestStylesReader(t *testing.T) {
f := NewFile()
// Test read styles with unsupport charset.
f.Styles = nil
f.XLSX["xl/styles.xml"] = MacintoshCyrillicCharset
assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader())
}
func TestThemeReader(t *testing.T) {
f := NewFile()
// Test read theme with unsupport charset.
f.XLSX["xl/theme/theme1.xml"] = MacintoshCyrillicCharset
assert.EqualValues(t, new(xlsxTheme), f.themeReader())
}
func TestSetCellStyle(t *testing.T) {
f := NewFile()
// Test set cell style on not exists worksheet.
assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN is not exist")
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -39,8 +39,10 @@ func parseFormatTableSet(formatSet string) (*formatTable, error) {
//
// err := f.AddTable("Sheet2", "F2", "H6", `{"table_name":"table","table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`)
//
// Note that the table at least two lines include string type header. Multiple
// tables coordinate areas can't have an intersection.
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique, and must set the
// header row data of the table before calling the AddTable function. Multiple
// tables coordinate areas that can't have an intersection.
//
// table_name: The name of the table, in the same worksheet name of the table should be unique
//
@ -77,7 +79,8 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error {
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
// Add first table for given sheet.
rID := f.addSheetRelationships(sheet, SourceRelationshipTable, sheetRelationshipsTableXML, "")
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
f.addSheetTable(sheet, rID)
err = f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet)
if err != nil {
@ -115,35 +118,30 @@ func (f *File) addSheetTable(sheet string, rID int) {
// addTable provides a function to add table by given worksheet name,
// coordinate area and format set.
func (f *File) addTable(sheet, tableXML string, hcol, hrow, vcol, vrow, i int, formatSet *formatTable) error {
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet *formatTable) error {
// Correct the minimum number of rows, the table at least two lines.
if hrow == vrow {
vrow++
if y1 == y2 {
y2++
}
// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
hcell, err := CoordinatesToCellName(hcol, hrow)
ref, err := f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
if err != nil {
return err
}
vcell, err := CoordinatesToCellName(vcol, vrow)
if err != nil {
return err
}
ref := hcell + ":" + vcell
var tableColumn []*xlsxTableColumn
idx := 0
for i := hcol; i <= vcol; i++ {
for i := x1; i <= x2; i++ {
idx++
cell, err := CoordinatesToCellName(i, hrow)
cell, err := CoordinatesToCellName(i, y1)
if err != nil {
return err
}
name, _ := f.GetCellValue(sheet, cell)
if _, err := strconv.Atoi(name); err == nil {
f.SetCellStr(sheet, cell, name)
_ = f.SetCellStr(sheet, cell, name)
}
if name == "" {
name = "Column" + strconv.Itoa(idx)
@ -283,15 +281,39 @@ func (f *File) AutoFilter(sheet, hcell, vcell, format string) error {
formatSet, _ := parseAutoFilterSet(format)
var cellStart, cellEnd string
cellStart, err = CoordinatesToCellName(hcol, hrow)
if err != nil {
if cellStart, err = CoordinatesToCellName(hcol, hrow); err != nil {
return err
}
cellEnd, err = CoordinatesToCellName(vcol, vrow)
if err != nil {
if cellEnd, err = CoordinatesToCellName(vcol, vrow); err != nil {
return err
}
ref := cellStart + ":" + cellEnd
ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase"
wb := f.workbookReader()
sheetID := f.GetSheetIndex(sheet)
filterRange := fmt.Sprintf("%s!%s", sheet, ref)
d := xlsxDefinedName{
Name: filterDB,
Hidden: true,
LocalSheetID: intPtr(sheetID),
Data: filterRange,
}
if wb.DefinedNames == nil {
wb.DefinedNames = &xlsxDefinedNames{
DefinedName: []xlsxDefinedName{d},
}
} else {
var definedNameExists bool
for idx := range wb.DefinedNames.DefinedName {
definedName := wb.DefinedNames.DefinedName[idx]
if definedName.Name == filterDB && *definedName.LocalSheetID == sheetID && definedName.Hidden {
wb.DefinedNames.DefinedName[idx].Data = filterRange
definedNameExists = true
}
}
if !definedNameExists {
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
}
}
refRange := vcol - hcol
return f.autoFilter(sheet, ref, refRange, hcol, formatSet)
}

125
table_test.go Normal file
View File

@ -0,0 +1,125 @@
package excelize
import (
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddTable(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
err = f.AddTable("Sheet1", "B26", "A21", `{}`)
if !assert.NoError(t, err) {
t.FailNow()
}
err = f.AddTable("Sheet2", "A2", "B5", `{"table_name":"table","table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`)
if !assert.NoError(t, err) {
t.FailNow()
}
err = f.AddTable("Sheet2", "F1", "F1", `{"table_style":"TableStyleMedium8"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
// Test add table with illegal formatset.
assert.EqualError(t, f.AddTable("Sheet1", "B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string")
// Test add table with illegal cell coordinates.
assert.EqualError(t, f.AddTable("Sheet1", "A", "B1", `{}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.AddTable("Sheet1", "A1", "B", `{}`), `cannot convert cell "B" to coordinates: invalid cell name "B"`)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx")))
// Test addTable with illegal cell coordinates.
f = NewFile()
assert.EqualError(t, f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil), "invalid cell coordinates [0, 0]")
assert.EqualError(t, f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil), "invalid cell coordinates [0, 0]")
}
func TestAutoFilter(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
formats := []string{
``,
`{"column":"B","expression":"x != blanks"}`,
`{"column":"B","expression":"x == blanks"}`,
`{"column":"B","expression":"x != nonblanks"}`,
`{"column":"B","expression":"x == nonblanks"}`,
`{"column":"B","expression":"x <= 1 and x >= 2"}`,
`{"column":"B","expression":"x == 1 or x == 2"}`,
`{"column":"B","expression":"x == 1 or x == 2*"}`,
}
for i, format := range formats {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
err = f.AutoFilter("Sheet1", "D4", "B1", format)
assert.NoError(t, err)
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
})
}
// testing AutoFilter with illegal cell coordinates.
assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), `cannot convert cell "B" to coordinates: invalid cell name "B"`)
}
func TestAutoFilterError(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx")
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
formats := []string{
`{"column":"B","expression":"x <= 1 and x >= blanks"}`,
`{"column":"B","expression":"x -- y or x == *2*"}`,
`{"column":"B","expression":"x != y or x ? *2"}`,
`{"column":"B","expression":"x -- y o r x == *2"}`,
`{"column":"B","expression":"x -- y"}`,
`{"column":"A","expression":"x -- y"}`,
}
for i, format := range formats {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
err = f.AutoFilter("Sheet3", "D4", "B1", format)
if assert.Error(t, err) {
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
}
})
}
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &formatAutoFilter{
Column: "-",
Expression: "-",
}), `invalid column name "-"`)
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &formatAutoFilter{
Column: "A",
Expression: "-",
}), `incorrect index of column 'A'`)
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &formatAutoFilter{
Column: "A",
Expression: "-",
}), `incorrect number of tokens in criteria '-'`)
}
func TestParseFilterTokens(t *testing.T) {
f := NewFile()
// Test with unknown operator.
_, _, err := f.parseFilterTokens("", []string{"", "!"})
assert.EqualError(t, err, "unknown operator: !")
// Test invalid operator in context.
_, _, err = f.parseFilterTokens("", []string{"", "<", "x != blanks"})
assert.EqualError(t, err, "the operator '<' in expression '' is not valid in relation to Blanks/NonBlanks'")
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 134 KiB

BIN
test/images/excel.tif Normal file

Binary file not shown.

BIN
test/vbaProject.bin Executable file

Binary file not shown.

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize

61
xmlApp.go Normal file
View File

@ -0,0 +1,61 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// xlsxProperties specifies to an OOXML document properties such as the
// template used, the number of pages and words, and the application name and
// version.
type xlsxProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties Properties"`
Template string
Manager string
Company string
Pages int
Words int
Characters int
PresentationFormat string
Lines int
Paragraphs int
Slides int
Notes int
TotalTime int
HiddenSlides int
MMClips int
ScaleCrop bool
HeadingPairs *xlsxVectorVariant
TitlesOfParts *xlsxVectorLpstr
LinksUpToDate bool
CharactersWithSpaces int
SharedDoc bool
HyperlinkBase string
HLinks *xlsxVectorVariant
HyperlinksChanged bool
DigSig *xlsxDigSig
Application string
AppVersion string
DocSecurity int
}
// xlsxVectorVariant specifies the set of hyperlinks that were in this
// document when last saved.
type xlsxVectorVariant struct {
Content string `xml:",innerxml"`
}
type xlsxVectorLpstr struct {
Content string `xml:",innerxml"`
}
// xlsxDigSig contains the signature of a digitally signed document.
type xlsxDigSig struct {
Content string `xml:",innerxml"`
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -18,6 +18,61 @@ type xlsxCalcChain struct {
}
// xlsxCalcChainC directly maps the c element.
//
// Attributes | Attributes
// --------------------------+----------------------------------------------------------
// a (Array) | A Boolean flag indicating whether the cell's formula
// | is an array formula. True if this cell's formula is
// | an array formula, false otherwise. If there is a
// | conflict between this attribute and the t attribute
// | of the f element (§18.3.1.40), the t attribute takes
// | precedence. The possible values for this attribute
// | are defined by the W3C XML Schema boolean datatype.
// |
// i (Sheet Id) | A sheet Id of a sheet the cell belongs to. If this is
// | omitted, it is assumed to be the same as the i value
// | of the previous cell.The possible values for this
// | attribute are defined by the W3C XML Schema int datatype.
// |
// l (New Dependency Level) | A Boolean flag indicating that the cell's formula
// | starts a new dependency level. True if the formula
// | starts a new dependency level, false otherwise.
// | Starting a new dependency level means that all
// | concurrent calculations, and child calculations, shall
// | be completed - and the cells have new values - before
// | the calc chain can continue. In other words, this
// | dependency level might depend on levels that came before
// | it, and any later dependency levels might depend on
// | this level; but not later dependency levels can have
// | any calculations started until this dependency level
// | completes.The possible values for this attribute are
// | defined by the W3C XML Schema boolean datatype.
// |
// r (Cell Reference) | An A-1 style reference to a cell.The possible values
// | for this attribute are defined by the ST_CellRef
// | simple type (§18.18.7).
// |
// s (Child Chain) | A Boolean flag indicating whether the cell's formula
// | is on a child chain. True if this cell is part of a
// | child chain, false otherwise. If this is omitted, it
// | is assumed to be the same as the s value of the
// | previous cell .A child chain is a list of calculations
// | that occur which depend on the parent to the chain.
// | There shall not be cross dependencies between child
// | chains. Child chains are not the same as dependency
// | levels - a child chain and its parent are all on the
// | same dependency level. Child chains are series of
// | calculations that can be independently farmed out to
// | other threads or processors.The possible values for
// | this attribute are defined by the W3C XML Schema
// | boolean datatype.
// |
// t (New Thread) | A Boolean flag indicating whether the cell's formula
// | starts a new thread. True if the cell's formula starts
// | a new thread, false otherwise.The possible values for
// | this attribute are defined by the W3C XML Schema
// | boolean datatype.
//
type xlsxCalcChainC struct {
R string `xml:"r,attr"`
I int `xml:"i,attr"`

View File

@ -1,76 +1,76 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// xlsxChartSpace directly maps the c:chartSpace element. The chart namespace in
// xlsxChartSpace directly maps the chartSpace element. The chart namespace in
// DrawingML is for representing visualizations of numeric data with column
// charts, pie charts, scatter charts, or other types of charts.
type xlsxChartSpace struct {
XMLName xml.Name `xml:"c:chartSpace"`
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/chart chartSpace"`
XMLNSc string `xml:"xmlns:c,attr"`
XMLNSa string `xml:"xmlns:a,attr"`
XMLNSr string `xml:"xmlns:r,attr"`
XMLNSc16r2 string `xml:"xmlns:c16r2,attr"`
Date1904 *attrValBool `xml:"c:date1904"`
Lang *attrValString `xml:"c:lang"`
RoundedCorners *attrValBool `xml:"c:roundedCorners"`
Chart cChart `xml:"c:chart"`
SpPr *cSpPr `xml:"c:spPr"`
TxPr *cTxPr `xml:"c:txPr"`
PrintSettings *cPrintSettings `xml:"c:printSettings"`
Date1904 *attrValBool `xml:"date1904"`
Lang *attrValString `xml:"lang"`
RoundedCorners *attrValBool `xml:"roundedCorners"`
Chart cChart `xml:"chart"`
SpPr *cSpPr `xml:"spPr"`
TxPr *cTxPr `xml:"txPr"`
PrintSettings *cPrintSettings `xml:"printSettings"`
}
// cThicknessSpPr directly maps the element that specifies the thickness of the
// walls or floor as a percentage of the largest dimension of the plot volume
// and SpPr element.
// cThicknessSpPr directly maps the element that specifies the thickness of
// the walls or floor as a percentage of the largest dimension of the plot
// volume and SpPr element.
type cThicknessSpPr struct {
Thickness *attrValInt `xml:"c:thickness"`
SpPr *cSpPr `xml:"c:spPr"`
Thickness *attrValInt `xml:"thickness"`
SpPr *cSpPr `xml:"spPr"`
}
// cChart (Chart) directly maps the c:chart element. This element specifies a
// cChart (Chart) directly maps the chart element. This element specifies a
// title.
type cChart struct {
Title *cTitle `xml:"c:title"`
AutoTitleDeleted *cAutoTitleDeleted `xml:"c:autoTitleDeleted"`
View3D *cView3D `xml:"c:view3D"`
Floor *cThicknessSpPr `xml:"c:floor"`
SideWall *cThicknessSpPr `xml:"c:sideWall"`
BackWall *cThicknessSpPr `xml:"c:backWall"`
PlotArea *cPlotArea `xml:"c:plotArea"`
Legend *cLegend `xml:"c:legend"`
PlotVisOnly *attrValBool `xml:"c:plotVisOnly"`
DispBlanksAs *attrValString `xml:"c:dispBlanksAs"`
ShowDLblsOverMax *attrValBool `xml:"c:showDLblsOverMax"`
Title *cTitle `xml:"title"`
AutoTitleDeleted *cAutoTitleDeleted `xml:"autoTitleDeleted"`
View3D *cView3D `xml:"view3D"`
Floor *cThicknessSpPr `xml:"floor"`
SideWall *cThicknessSpPr `xml:"sideWall"`
BackWall *cThicknessSpPr `xml:"backWall"`
PlotArea *cPlotArea `xml:"plotArea"`
Legend *cLegend `xml:"legend"`
PlotVisOnly *attrValBool `xml:"plotVisOnly"`
DispBlanksAs *attrValString `xml:"dispBlanksAs"`
ShowDLblsOverMax *attrValBool `xml:"showDLblsOverMax"`
}
// cTitle (Title) directly maps the c:title element. This element specifies a
// cTitle (Title) directly maps the title element. This element specifies a
// title.
type cTitle struct {
Tx cTx `xml:"c:tx,omitempty"`
Layout string `xml:"c:layout,omitempty"`
Overlay attrValBool `xml:"c:overlay,omitempty"`
SpPr cSpPr `xml:"c:spPr,omitempty"`
TxPr cTxPr `xml:"c:txPr,omitempty"`
Tx cTx `xml:"tx,omitempty"`
Layout string `xml:"layout,omitempty"`
Overlay *attrValBool `xml:"overlay"`
SpPr cSpPr `xml:"spPr,omitempty"`
TxPr cTxPr `xml:"txPr,omitempty"`
}
// cTx (Chart Text) directly maps the c:tx element. This element specifies text
// cTx (Chart Text) directly maps the tx element. This element specifies text
// to use on a chart, including rich text formatting.
type cTx struct {
StrRef *cStrRef `xml:"c:strRef"`
Rich *cRich `xml:"c:rich,omitempty"`
StrRef *cStrRef `xml:"strRef"`
Rich *cRich `xml:"rich,omitempty"`
}
// cRich (Rich Text) directly maps the c:rich element. This element contains a
// cRich (Rich Text) directly maps the rich element. This element contains a
// string with rich text formatting.
type cRich struct {
BodyPr aBodyPr `xml:"a:bodyPr,omitempty"`
@ -141,25 +141,25 @@ type aSchemeClr struct {
// attrValInt directly maps the val element with integer data type as an
// attribute。
type attrValInt struct {
Val int `xml:"val,attr"`
Val *int `xml:"val,attr"`
}
// attrValFloat directly maps the val element with float64 data type as an
// attribute。
type attrValFloat struct {
Val float64 `xml:"val,attr"`
Val *float64 `xml:"val,attr"`
}
// attrValBool directly maps the val element with boolean data type as an
// attribute。
type attrValBool struct {
Val bool `xml:"val,attr"`
Val *bool `xml:"val,attr"`
}
// attrValString directly maps the val element with string data type as an
// attribute。
type attrValString struct {
Val string `xml:"val,attr"`
Val *string `xml:"val,attr"`
}
// aCs directly maps the a:cs element.
@ -186,7 +186,7 @@ type aR struct {
T string `xml:"a:t,omitempty"`
}
// aRPr (Run Properties) directly maps the c:rPr element. This element
// aRPr (Run Properties) directly maps the rPr element. This element
// specifies a set of run properties which shall be applied to the contents of
// the parent run after all style formatting has been applied to the text. These
// properties are defined as direct formatting, since they are directly applied
@ -209,7 +209,7 @@ type aRPr struct {
SmtID uint64 `xml:"smtId,attr,omitempty"`
Spc int `xml:"spc,attr"`
Strike string `xml:"strike,attr,omitempty"`
Sz int `xml:"sz,attr,omitempty"`
Sz float64 `xml:"sz,attr,omitempty"`
U string `xml:"u,attr,omitempty"`
SolidFill *aSolidFill `xml:"a:solidFill"`
Latin *aLatin `xml:"a:latin"`
@ -217,7 +217,7 @@ type aRPr struct {
Cs *aCs `xml:"a:cs"`
}
// cSpPr (Shape Properties) directly maps the c:spPr element. This element
// cSpPr (Shape Properties) directly maps the spPr element. This element
// specifies the visual shape properties that can be applied to a shape. These
// properties include the shape fill, outline, geometry, effects, and 3D
// orientation.
@ -259,7 +259,7 @@ type aLn struct {
SolidFill *aSolidFill `xml:"a:solidFill"`
}
// cTxPr (Text Properties) directly maps the c:txPr element. This element
// cTxPr (Text Properties) directly maps the txPr element. This element
// specifies text formatting. The lstStyle element is not supported.
type cTxPr struct {
BodyPr aBodyPr `xml:"a:bodyPr,omitempty"`
@ -282,207 +282,232 @@ type aEndParaRPr struct {
}
// cAutoTitleDeleted (Auto Title Is Deleted) directly maps the
// c:autoTitleDeleted element. This element specifies the title shall not be
// autoTitleDeleted element. This element specifies the title shall not be
// shown for this chart.
type cAutoTitleDeleted struct {
Val bool `xml:"val,attr"`
}
// cView3D (View In 3D) directly maps the c:view3D element. This element
// cView3D (View In 3D) directly maps the view3D element. This element
// specifies the 3-D view of the chart.
type cView3D struct {
RotX *attrValInt `xml:"c:rotX"`
RotY *attrValInt `xml:"c:rotY"`
DepthPercent *attrValInt `xml:"c:depthPercent"`
RAngAx *attrValInt `xml:"c:rAngAx"`
RotX *attrValInt `xml:"rotX"`
RotY *attrValInt `xml:"rotY"`
RAngAx *attrValInt `xml:"rAngAx"`
DepthPercent *attrValInt `xml:"depthPercent"`
Perspective *attrValInt `xml:"perspective"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// cPlotArea directly maps the c:plotArea element. This element specifies the
// cPlotArea directly maps the plotArea element. This element specifies the
// plot area of the chart.
type cPlotArea struct {
Layout *string `xml:"c:layout"`
AreaChart *cCharts `xml:"c:areaChart"`
Area3DChart *cCharts `xml:"c:area3DChart"`
BarChart *cCharts `xml:"c:barChart"`
Bar3DChart *cCharts `xml:"c:bar3DChart"`
DoughnutChart *cCharts `xml:"c:doughnutChart"`
LineChart *cCharts `xml:"c:lineChart"`
PieChart *cCharts `xml:"c:pieChart"`
Pie3DChart *cCharts `xml:"c:pie3DChart"`
RadarChart *cCharts `xml:"c:radarChart"`
ScatterChart *cCharts `xml:"c:scatterChart"`
CatAx []*cAxs `xml:"c:catAx"`
ValAx []*cAxs `xml:"c:valAx"`
SpPr *cSpPr `xml:"c:spPr"`
Layout *string `xml:"layout"`
AreaChart *cCharts `xml:"areaChart"`
Area3DChart *cCharts `xml:"area3DChart"`
BarChart *cCharts `xml:"barChart"`
Bar3DChart *cCharts `xml:"bar3DChart"`
BubbleChart *cCharts `xml:"bubbleChart"`
DoughnutChart *cCharts `xml:"doughnutChart"`
LineChart *cCharts `xml:"lineChart"`
PieChart *cCharts `xml:"pieChart"`
Pie3DChart *cCharts `xml:"pie3DChart"`
OfPieChart *cCharts `xml:"ofPieChart"`
RadarChart *cCharts `xml:"radarChart"`
ScatterChart *cCharts `xml:"scatterChart"`
Surface3DChart *cCharts `xml:"surface3DChart"`
SurfaceChart *cCharts `xml:"surfaceChart"`
CatAx []*cAxs `xml:"catAx"`
ValAx []*cAxs `xml:"valAx"`
SerAx []*cAxs `xml:"serAx"`
SpPr *cSpPr `xml:"spPr"`
}
// cCharts specifies the common element of the chart.
type cCharts struct {
BarDir *attrValString `xml:"c:barDir"`
Grouping *attrValString `xml:"c:grouping"`
RadarStyle *attrValString `xml:"c:radarStyle"`
ScatterStyle *attrValString `xml:"c:scatterStyle"`
VaryColors *attrValBool `xml:"c:varyColors"`
Ser *[]cSer `xml:"c:ser"`
DLbls *cDLbls `xml:"c:dLbls"`
HoleSize *attrValInt `xml:"c:holeSize"`
Smooth *attrValBool `xml:"c:smooth"`
Overlap *attrValInt `xml:"c:overlap"`
AxID []*attrValInt `xml:"c:axId"`
BarDir *attrValString `xml:"barDir"`
BubbleScale *attrValFloat `xml:"bubbleScale"`
Grouping *attrValString `xml:"grouping"`
RadarStyle *attrValString `xml:"radarStyle"`
ScatterStyle *attrValString `xml:"scatterStyle"`
OfPieType *attrValString `xml:"ofPieType"`
VaryColors *attrValBool `xml:"varyColors"`
Wireframe *attrValBool `xml:"wireframe"`
Ser *[]cSer `xml:"ser"`
SerLines *attrValString `xml:"serLines"`
DLbls *cDLbls `xml:"dLbls"`
Shape *attrValString `xml:"shape"`
HoleSize *attrValInt `xml:"holeSize"`
Smooth *attrValBool `xml:"smooth"`
Overlap *attrValInt `xml:"overlap"`
AxID []*attrValInt `xml:"axId"`
}
// cAxs directly maps the c:catAx and c:valAx element.
// cAxs directly maps the catAx and valAx element.
type cAxs struct {
AxID *attrValInt `xml:"c:axId"`
Scaling *cScaling `xml:"c:scaling"`
Delete *attrValBool `xml:"c:delete"`
AxPos *attrValString `xml:"c:axPos"`
NumFmt *cNumFmt `xml:"c:numFmt"`
MajorTickMark *attrValString `xml:"c:majorTickMark"`
MinorTickMark *attrValString `xml:"c:minorTickMark"`
TickLblPos *attrValString `xml:"c:tickLblPos"`
SpPr *cSpPr `xml:"c:spPr"`
TxPr *cTxPr `xml:"c:txPr"`
CrossAx *attrValInt `xml:"c:crossAx"`
Crosses *attrValString `xml:"c:crosses"`
CrossBetween *attrValString `xml:"c:crossBetween"`
Auto *attrValBool `xml:"c:auto"`
LblAlgn *attrValString `xml:"c:lblAlgn"`
LblOffset *attrValInt `xml:"c:lblOffset"`
NoMultiLvlLbl *attrValBool `xml:"c:noMultiLvlLbl"`
AxID *attrValInt `xml:"axId"`
Scaling *cScaling `xml:"scaling"`
Delete *attrValBool `xml:"delete"`
AxPos *attrValString `xml:"axPos"`
MajorGridlines *cChartLines `xml:"majorGridlines"`
MinorGridlines *cChartLines `xml:"minorGridlines"`
NumFmt *cNumFmt `xml:"numFmt"`
MajorTickMark *attrValString `xml:"majorTickMark"`
MinorTickMark *attrValString `xml:"minorTickMark"`
TickLblPos *attrValString `xml:"tickLblPos"`
SpPr *cSpPr `xml:"spPr"`
TxPr *cTxPr `xml:"txPr"`
CrossAx *attrValInt `xml:"crossAx"`
Crosses *attrValString `xml:"crosses"`
CrossBetween *attrValString `xml:"crossBetween"`
MajorUnit *attrValFloat `xml:"majorUnit"`
MinorUnit *attrValFloat `xml:"minorUnit"`
Auto *attrValBool `xml:"auto"`
LblAlgn *attrValString `xml:"lblAlgn"`
LblOffset *attrValInt `xml:"lblOffset"`
TickLblSkip *attrValInt `xml:"tickLblSkip"`
TickMarkSkip *attrValInt `xml:"tickMarkSkip"`
NoMultiLvlLbl *attrValBool `xml:"noMultiLvlLbl"`
}
// cScaling directly maps the c:scaling element. This element contains
// cChartLines directly maps the chart lines content model.
type cChartLines struct {
SpPr *cSpPr `xml:"spPr"`
}
// cScaling directly maps the scaling element. This element contains
// additional axis settings.
type cScaling struct {
Orientation *attrValString `xml:"c:orientation"`
Max *attrValFloat `xml:"c:max"`
Min *attrValFloat `xml:"c:min"`
Orientation *attrValString `xml:"orientation"`
Max *attrValFloat `xml:"max"`
Min *attrValFloat `xml:"min"`
}
// cNumFmt (Numbering Format) directly maps the c:numFmt element. This element
// cNumFmt (Numbering Format) directly maps the numFmt element. This element
// specifies number formatting for the parent element.
type cNumFmt struct {
FormatCode string `xml:"formatCode,attr"`
SourceLinked bool `xml:"sourceLinked,attr"`
}
// cSer directly maps the c:ser element. This element specifies a series on a
// cSer directly maps the ser element. This element specifies a series on a
// chart.
type cSer struct {
IDx *attrValInt `xml:"c:idx"`
Order *attrValInt `xml:"c:order"`
Tx *cTx `xml:"c:tx"`
SpPr *cSpPr `xml:"c:spPr"`
DPt []*cDPt `xml:"c:dPt"`
DLbls *cDLbls `xml:"c:dLbls"`
Marker *cMarker `xml:"c:marker"`
InvertIfNegative *attrValBool `xml:"c:invertIfNegative"`
Cat *cCat `xml:"c:cat"`
Val *cVal `xml:"c:val"`
XVal *cCat `xml:"c:xVal"`
YVal *cVal `xml:"c:yVal"`
Smooth *attrValBool `xml:"c:smooth"`
IDx *attrValInt `xml:"idx"`
Order *attrValInt `xml:"order"`
Tx *cTx `xml:"tx"`
SpPr *cSpPr `xml:"spPr"`
DPt []*cDPt `xml:"dPt"`
DLbls *cDLbls `xml:"dLbls"`
Marker *cMarker `xml:"marker"`
InvertIfNegative *attrValBool `xml:"invertIfNegative"`
Cat *cCat `xml:"cat"`
Val *cVal `xml:"val"`
XVal *cCat `xml:"xVal"`
YVal *cVal `xml:"yVal"`
Smooth *attrValBool `xml:"smooth"`
BubbleSize *cVal `xml:"bubbleSize"`
Bubble3D *attrValBool `xml:"bubble3D"`
}
// cMarker (Marker) directly maps the c:marker element. This element specifies a
// cMarker (Marker) directly maps the marker element. This element specifies a
// data marker.
type cMarker struct {
Symbol *attrValString `xml:"c:symbol"`
Size *attrValInt `xml:"c:size"`
SpPr *cSpPr `xml:"c:spPr"`
Symbol *attrValString `xml:"symbol"`
Size *attrValInt `xml:"size"`
SpPr *cSpPr `xml:"spPr"`
}
// cDPt (Data Point) directly maps the c:dPt element. This element specifies a
// cDPt (Data Point) directly maps the dPt element. This element specifies a
// single data point.
type cDPt struct {
IDx *attrValInt `xml:"c:idx"`
Bubble3D *attrValBool `xml:"c:bubble3D"`
SpPr *cSpPr `xml:"c:spPr"`
IDx *attrValInt `xml:"idx"`
Bubble3D *attrValBool `xml:"bubble3D"`
SpPr *cSpPr `xml:"spPr"`
}
// cCat (Category Axis Data) directly maps the c:cat element. This element
// cCat (Category Axis Data) directly maps the cat element. This element
// specifies the data used for the category axis.
type cCat struct {
StrRef *cStrRef `xml:"c:strRef"`
StrRef *cStrRef `xml:"strRef"`
}
// cStrRef (String Reference) directly maps the c:strRef element. This element
// cStrRef (String Reference) directly maps the strRef element. This element
// specifies a reference to data for a single data label or title with a cache
// of the last values used.
type cStrRef struct {
F string `xml:"c:f"`
StrCache *cStrCache `xml:"c:strCache"`
F string `xml:"f"`
StrCache *cStrCache `xml:"strCache"`
}
// cStrCache (String Cache) directly maps the c:strCache element. This element
// cStrCache (String Cache) directly maps the strCache element. This element
// specifies the last string data used for a chart.
type cStrCache struct {
Pt []*cPt `xml:"c:pt"`
PtCount *attrValInt `xml:"c:ptCount"`
Pt []*cPt `xml:"pt"`
PtCount *attrValInt `xml:"ptCount"`
}
// cPt directly maps the c:pt element. This element specifies data for a
// cPt directly maps the pt element. This element specifies data for a
// particular data point.
type cPt struct {
IDx int `xml:"idx,attr"`
V *string `xml:"c:v"`
V *string `xml:"v"`
}
// cVal directly maps the c:val element. This element specifies the data values
// cVal directly maps the val element. This element specifies the data values
// which shall be used to define the location of data markers on a chart.
type cVal struct {
NumRef *cNumRef `xml:"c:numRef"`
NumRef *cNumRef `xml:"numRef"`
}
// cNumRef directly maps the c:numRef element. This element specifies a
// cNumRef directly maps the numRef element. This element specifies a
// reference to numeric data with a cache of the last values used.
type cNumRef struct {
F string `xml:"c:f"`
NumCache *cNumCache `xml:"c:numCache"`
F string `xml:"f"`
NumCache *cNumCache `xml:"numCache"`
}
// cNumCache directly maps the c:numCache element. This element specifies the
// cNumCache directly maps the numCache element. This element specifies the
// last data shown on the chart for a series.
type cNumCache struct {
FormatCode string `xml:"c:formatCode"`
Pt []*cPt `xml:"c:pt"`
PtCount *attrValInt `xml:"c:ptCount"`
FormatCode string `xml:"formatCode"`
Pt []*cPt `xml:"pt"`
PtCount *attrValInt `xml:"ptCount"`
}
// cDLbls (Data Lables) directly maps the c:dLbls element. This element serves
// cDLbls (Data Lables) directly maps the dLbls element. This element serves
// as a root element that specifies the settings for the data labels for an
// entire series or the entire chart. It contains child elements that specify
// the specific formatting and positioning settings.
type cDLbls struct {
ShowLegendKey *attrValBool `xml:"c:showLegendKey"`
ShowVal *attrValBool `xml:"c:showVal"`
ShowCatName *attrValBool `xml:"c:showCatName"`
ShowSerName *attrValBool `xml:"c:showSerName"`
ShowPercent *attrValBool `xml:"c:showPercent"`
ShowBubbleSize *attrValBool `xml:"c:showBubbleSize"`
ShowLeaderLines *attrValBool `xml:"c:showLeaderLines"`
ShowLegendKey *attrValBool `xml:"showLegendKey"`
ShowVal *attrValBool `xml:"showVal"`
ShowCatName *attrValBool `xml:"showCatName"`
ShowSerName *attrValBool `xml:"showSerName"`
ShowPercent *attrValBool `xml:"showPercent"`
ShowBubbleSize *attrValBool `xml:"showBubbleSize"`
ShowLeaderLines *attrValBool `xml:"showLeaderLines"`
}
// cLegend (Legend) directly maps the c:legend element. This element specifies
// cLegend (Legend) directly maps the legend element. This element specifies
// the legend.
type cLegend struct {
Layout *string `xml:"c:layout"`
LegendPos *attrValString `xml:"c:legendPos"`
Overlay *attrValBool `xml:"c:overlay"`
SpPr *cSpPr `xml:"c:spPr"`
TxPr *cTxPr `xml:"c:txPr"`
Layout *string `xml:"layout"`
LegendPos *attrValString `xml:"legendPos"`
Overlay *attrValBool `xml:"overlay"`
SpPr *cSpPr `xml:"spPr"`
TxPr *cTxPr `xml:"txPr"`
}
// cPrintSettings directly maps the c:printSettings element. This element
// cPrintSettings directly maps the printSettings element. This element
// specifies the print settings for the chart.
type cPrintSettings struct {
HeaderFooter *string `xml:"c:headerFooter"`
PageMargins *cPageMargins `xml:"c:pageMargins"`
PageSetup *string `xml:"c:pageSetup"`
HeaderFooter *string `xml:"headerFooter"`
PageMargins *cPageMargins `xml:"pageMargins"`
PageSetup *string `xml:"pageSetup"`
}
// cPageMargins directly maps the c:pageMargins element. This element specifies
// cPageMargins directly maps the pageMargins element. This element specifies
// the page margins for a chart.
type cPageMargins struct {
B float64 `xml:"b,attr"`
@ -496,11 +521,14 @@ type cPageMargins struct {
// formatChartAxis directly maps the format settings of the chart axis.
type formatChartAxis struct {
Crossing string `json:"crossing"`
MajorGridlines bool `json:"major_grid_lines"`
MinorGridlines bool `json:"minor_grid_lines"`
MajorTickMark string `json:"major_tick_mark"`
MinorTickMark string `json:"minor_tick_mark"`
MinorUnitType string `json:"minor_unit_type"`
MajorUnit int `json:"major_unit"`
MajorUnit float64 `json:"major_unit"`
MajorUnitType string `json:"major_unit_type"`
TickLabelSkip int `json:"tick_label_skip"`
DisplayUnits string `json:"display_units"`
DisplayUnitsVisible bool `json:"display_units_visible"`
DateAxis bool `json:"date_axis"`
@ -569,13 +597,14 @@ type formatChart struct {
ShowHiddenData bool `json:"show_hidden_data"`
SetRotation int `json:"set_rotation"`
SetHoleSize int `json:"set_hole_size"`
order int
}
// formatChartLegend directly maps the format settings of the chart legend.
type formatChartLegend struct {
None bool `json:"none"`
DeleteSeries []int `json:"delete_series"`
Font formatFont `json:"font"`
Font Font `json:"font"`
Layout formatLayout `json:"layout"`
Position string `json:"position"`
ShowLegendEntry bool `json:"show_legend_entry"`
@ -588,8 +617,9 @@ type formatChartSeries struct {
Categories string `json:"categories"`
Values string `json:"values"`
Line struct {
None bool `json:"none"`
Color string `json:"color"`
None bool `json:"none"`
Color string `json:"color"`
Width float64 `json:"width"`
} `json:"line"`
Marker struct {
Type string `json:"type"`

88
xmlChartSheet.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// struct code generated by github.com/xuri/xgen
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// xlsxChartsheet directly maps the chartsheet element of Chartsheet Parts in
// a SpreadsheetML document.
type xlsxChartsheet struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main chartsheet"`
SheetPr []*xlsxChartsheetPr `xml:"sheetPr"`
SheetViews []*xlsxChartsheetViews `xml:"sheetViews"`
SheetProtection []*xlsxChartsheetProtection `xml:"sheetProtection"`
CustomSheetViews []*xlsxCustomChartsheetViews `xml:"customSheetViews"`
PageMargins *xlsxPageMargins `xml:"pageMargins"`
PageSetup []*xlsxPageSetUp `xml:"pageSetup"`
HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"`
Drawing *xlsxDrawing `xml:"drawing"`
DrawingHF []*xlsxDrawingHF `xml:"drawingHF"`
Picture []*xlsxPicture `xml:"picture"`
WebPublishItems []*xlsxInnerXML `xml:"webPublishItems"`
ExtLst []*xlsxExtLst `xml:"extLst"`
}
// xlsxChartsheetPr specifies chart sheet properties.
type xlsxChartsheetPr struct {
XMLName xml.Name `xml:"sheetPr"`
PublishedAttr bool `xml:"published,attr,omitempty"`
CodeNameAttr string `xml:"codeName,attr,omitempty"`
TabColor []*xlsxTabColor `xml:"tabColor"`
}
// xlsxChartsheetViews specifies chart sheet views.
type xlsxChartsheetViews struct {
XMLName xml.Name `xml:"sheetViews"`
SheetView []*xlsxChartsheetView `xml:"sheetView"`
ExtLst []*xlsxExtLst `xml:"extLst"`
}
// xlsxChartsheetView defines custom view properties for chart sheets.
type xlsxChartsheetView struct {
XMLName xml.Name `xml:"sheetView"`
TabSelectedAttr bool `xml:"tabSelected,attr,omitempty"`
ZoomScaleAttr uint32 `xml:"zoomScale,attr,omitempty"`
WorkbookViewIDAttr uint32 `xml:"workbookViewId,attr"`
ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"`
ExtLst []*xlsxExtLst `xml:"extLst"`
}
// xlsxChartsheetProtection collection expresses the chart sheet protection
// options to enforce when the chart sheet is protected.
type xlsxChartsheetProtection struct {
XMLName xml.Name `xml:"sheetProtection"`
AlgorithmNameAttr string `xml:"algorithmName,attr,omitempty"`
HashValueAttr []byte `xml:"hashValue,attr,omitempty"`
SaltValueAttr []byte `xml:"saltValue,attr,omitempty"`
SpinCountAttr uint32 `xml:"spinCount,attr,omitempty"`
ContentAttr bool `xml:"content,attr,omitempty"`
ObjectsAttr bool `xml:"objects,attr,omitempty"`
}
// xlsxCustomChartsheetViews collection of custom Chart Sheet View
// information.
type xlsxCustomChartsheetViews struct {
XMLName xml.Name `xml:"customChartsheetViews"`
CustomSheetView []*xlsxCustomChartsheetView `xml:"customSheetView"`
}
// xlsxCustomChartsheetView defines custom view properties for chart sheets.
type xlsxCustomChartsheetView struct {
XMLName xml.Name `xml:"customChartsheetView"`
GUIDAttr string `xml:"guid,attr"`
ScaleAttr uint32 `xml:"scale,attr,omitempty"`
StateAttr string `xml:"state,attr,omitempty"`
ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"`
PageMargins []*xlsxPageMargins `xml:"pageMargins"`
PageSetup []*xlsxPageSetUp `xml:"pageSetup"`
HeaderFooter []*xlsxHeaderFooter `xml:"headerFooter"`
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -54,7 +54,20 @@ type xlsxComment struct {
// spreadsheet application implementation detail. A recommended guideline is
// 32767 chars.
type xlsxText struct {
R []xlsxR `xml:"r"`
T *string `xml:"t"`
R []xlsxR `xml:"r"`
RPh *xlsxPhoneticRun `xml:"rPh"`
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
}
// xlsxPhoneticRun element represents a run of text which displays a phonetic
// hint for this String Item (si). Phonetic hints are used to give information
// about the pronunciation of an East Asian language. The hints are displayed
// as text within the spreadsheet cells across the top portion of the cell.
type xlsxPhoneticRun struct {
Sb uint32 `xml:"sb,attr"`
Eb uint32 `xml:"eb,attr"`
T string `xml:"t,attr"`
}
// formatComment directly maps the format settings of the comment.

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize

89
xmlCore.go Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// DocProperties directly maps the document core properties.
type DocProperties struct {
Category string
ContentStatus string
Created string
Creator string
Description string
Identifier string
Keywords string
LastModifiedBy string
Modified string
Revision string
Subject string
Title string
Language string
Version string
}
// decodeCoreProperties directly maps the root element for a part of this
// content type shall coreProperties. In order to solve the problem that the
// label structure is changed after serialization and deserialization, two
// different structures are defined. decodeCoreProperties just for
// deserialization.
type decodeCoreProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
Title string `xml:"http://purl.org/dc/elements/1.1/ title,omitempty"`
Subject string `xml:"http://purl.org/dc/elements/1.1/ subject,omitempty"`
Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
Keywords string `xml:"keywords,omitempty"`
Description string `xml:"http://purl.org/dc/elements/1.1/ description,omitempty"`
LastModifiedBy string `xml:"lastModifiedBy"`
Language string `xml:"http://purl.org/dc/elements/1.1/ language,omitempty"`
Identifier string `xml:"http://purl.org/dc/elements/1.1/ identifier,omitempty"`
Revision string `xml:"revision,omitempty"`
Created struct {
Text string `xml:",chardata"`
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
} `xml:"http://purl.org/dc/terms/ created"`
Modified struct {
Text string `xml:",chardata"`
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
} `xml:"http://purl.org/dc/terms/ modified"`
ContentStatus string `xml:"contentStatus,omitempty"`
Category string `xml:"category,omitempty"`
Version string `xml:"version,omitempty"`
}
// xlsxCoreProperties directly maps the root element for a part of this
// content type shall coreProperties.
type xlsxCoreProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
Dc string `xml:"xmlns:dc,attr"`
Dcterms string `xml:"xmlns:dcterms,attr"`
Dcmitype string `xml:"xmlns:dcmitype,attr"`
XSI string `xml:"xmlns:xsi,attr"`
Title string `xml:"dc:title,omitempty"`
Subject string `xml:"dc:subject,omitempty"`
Creator string `xml:"dc:creator"`
Keywords string `xml:"keywords,omitempty"`
Description string `xml:"dc:description,omitempty"`
LastModifiedBy string `xml:"lastModifiedBy"`
Language string `xml:"dc:language,omitempty"`
Identifier string `xml:"dc:identifier,omitempty"`
Revision string `xml:"revision,omitempty"`
Created struct {
Text string `xml:",chardata"`
Type string `xml:"xsi:type,attr"`
} `xml:"dcterms:created"`
Modified struct {
Text string `xml:",chardata"`
Type string `xml:"xsi:type,attr"`
} `xml:"dcterms:modified"`
ContentStatus string `xml:"contentStatus,omitempty"`
Category string `xml:"category,omitempty"`
Version string `xml:"version,omitempty"`
}

View File

@ -1,29 +1,65 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// decodeCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape Size)
// and twoCellAnchor (Two Cell Anchor Shape Size). This element specifies a two
// cell anchor placeholder for a group, a shape, or a drawing element. It moves
// with cells and its extents are in EMU units.
// decodeCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape
// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element
// specifies a two cell anchor placeholder for a group, a shape, or a drawing
// element. It moves with cells and its extents are in EMU units.
type decodeCellAnchor struct {
EditAs string `xml:"editAs,attr,omitempty"`
Content string `xml:",innerxml"`
EditAs string `xml:"editAs,attr,omitempty"`
From *decodeFrom `xml:"from"`
To *decodeTo `xml:"to"`
Sp *decodeSp `xml:"sp"`
ClientData *decodeClientData `xml:"clientData"`
Content string `xml:",innerxml"`
}
// xdrSp (Shape) directly maps the sp element. This element specifies the
// existence of a single shape. A shape can either be a preset or a custom
// geometry, defined using the SpreadsheetDrawingML framework. In addition to
// a geometry each shape can have both visual and non-visual properties
// attached. Text and corresponding styling information can also be attached
// to a shape. This shape is specified along with all other shapes within
// either the shape tree or group shape elements.
type decodeSp struct {
NvSpPr *decodeNvSpPr `xml:"nvSpPr"`
SpPr *decodeSpPr `xml:"spPr"`
}
// decodeSp (Non-Visual Properties for a Shape) directly maps the nvSpPr
// element. This element specifies all non-visual properties for a shape. This
// element is a container for the non-visual identification properties, shape
// properties and application properties that are to be associated with a
// shape. This allows for additional information that does not affect the
// appearance of the shape to be stored.
type decodeNvSpPr struct {
CNvPr *decodeCNvPr `xml:"cNvPr"`
ExtLst *decodeExt `xml:"extLst"`
CNvSpPr *decodeCNvSpPr `xml:"cNvSpPr"`
}
// decodeCNvSpPr (Connection Non-Visual Shape Properties) directly maps the
// cNvSpPr element. This element specifies the set of non-visual properties
// for a connection shape. These properties specify all data about the
// connection shape which do not affect its display within a spreadsheet.
type decodeCNvSpPr struct {
TxBox bool `xml:"txBox,attr"`
}
// decodeWsDr directly maps the root element for a part of this content type
// shall wsDr. In order to solve the problem that the label structure is changed
// after serialization and deserialization, two different structures are
// defined. decodeWsDr just for deserialization.
// shall wsDr. In order to solve the problem that the label structure is
// changed after serialization and deserialization, two different structures
// are defined. decodeWsDr just for deserialization.
type decodeWsDr struct {
A string `xml:"xmlns a,attr"`
Xdr string `xml:"xmlns xdr,attr"`
@ -34,9 +70,9 @@ type decodeWsDr struct {
}
// decodeTwoCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape
// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element specifies
// a two cell anchor placeholder for a group, a shape, or a drawing element. It
// moves with cells and its extents are in EMU units.
// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element
// specifies a two cell anchor placeholder for a group, a shape, or a drawing
// element. It moves with cells and its extents are in EMU units.
type decodeTwoCellAnchor struct {
From *decodeFrom `xml:"from"`
To *decodeTo `xml:"to"`
@ -46,7 +82,8 @@ type decodeTwoCellAnchor struct {
// decodeCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
// element specifies non-visual canvas properties. This allows for additional
// information that does not affect the appearance of the picture to be stored.
// information that does not affect the appearance of the picture to be
// stored.
type decodeCNvPr struct {
ID int `xml:"id,attr"`
Name string `xml:"name,attr"`
@ -55,8 +92,8 @@ type decodeCNvPr struct {
}
// decodePicLocks directly maps the picLocks (Picture Locks). This element
// specifies all locking properties for a graphic frame. These properties inform
// the generating application about specific properties that have been
// specifies all locking properties for a graphic frame. These properties
// inform the generating application about specific properties that have been
// previously locked and thus should not be changed.
type decodePicLocks struct {
NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"`
@ -82,9 +119,9 @@ type decodeBlip struct {
R string `xml:"r,attr"`
}
// decodeStretch directly maps the stretch element. This element specifies that
// a BLIP should be stretched to fill the target rectangle. The other option is
// a tile where a BLIP is tiled to fill the available area.
// decodeStretch directly maps the stretch element. This element specifies
// that a BLIP should be stretched to fill the target rectangle. The other
// option is a tile where a BLIP is tiled to fill the available area.
type decodeStretch struct {
FillRect string `xml:"fillRect"`
}
@ -128,12 +165,12 @@ type decodeCNvPicPr struct {
PicLocks decodePicLocks `xml:"picLocks"`
}
// directly maps the nvPicPr (Non-Visual Properties for a Picture). This element
// specifies all non-visual properties for a picture. This element is a
// container for the non-visual identification properties, shape properties and
// application properties that are to be associated with a picture. This allows
// for additional information that does not affect the appearance of the picture
// to be stored.
// directly maps the nvPicPr (Non-Visual Properties for a Picture). This
// element specifies all non-visual properties for a picture. This element is
// a container for the non-visual identification properties, shape properties
// and application properties that are to be associated with a picture. This
// allows for additional information that does not affect the appearance of
// the picture to be stored.
type decodeNvPicPr struct {
CNvPr decodeCNvPr `xml:"cNvPr"`
CNvPicPr decodeCNvPicPr `xml:"cNvPicPr"`
@ -148,20 +185,20 @@ type decodeBlipFill struct {
Stretch decodeStretch `xml:"stretch"`
}
// decodeSpPr directly maps the spPr (Shape Properties). This element specifies
// the visual shape properties that can be applied to a picture. These are the
// same properties that are allowed to describe the visual properties of a shape
// but are used here to describe the visual appearance of a picture within a
// document.
// decodeSpPr directly maps the spPr (Shape Properties). This element
// specifies the visual shape properties that can be applied to a picture.
// These are the same properties that are allowed to describe the visual
// properties of a shape but are used here to describe the visual appearance
// of a picture within a document.
type decodeSpPr struct {
Xfrm decodeXfrm `xml:"a:xfrm"`
PrstGeom decodePrstGeom `xml:"a:prstGeom"`
Xfrm decodeXfrm `xml:"xfrm"`
PrstGeom decodePrstGeom `xml:"prstGeom"`
}
// decodePic elements encompass the definition of pictures within the DrawingML
// framework. While pictures are in many ways very similar to shapes they have
// specific properties that are unique in order to optimize for picture-
// specific scenarios.
// decodePic elements encompass the definition of pictures within the
// DrawingML framework. While pictures are in many ways very similar to shapes
// they have specific properties that are unique in order to optimize for
// picture- specific scenarios.
type decodePic struct {
NvPicPr decodeNvPicPr `xml:"nvPicPr"`
BlipFill decodeBlipFill `xml:"blipFill"`
@ -184,8 +221,8 @@ type decodeTo struct {
RowOff int `xml:"rowOff"`
}
// decodeClientData directly maps the clientData element. An empty element which
// specifies (via attributes) certain properties related to printing and
// decodeClientData directly maps the clientData element. An empty element
// which specifies (via attributes) certain properties related to printing and
// selection of the drawing object. The fLocksWithSheet attribute (either true
// or false) determines whether to disable selection when the sheet is
// protected, and fPrintsWithSheet attribute (either true or false) determines

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -13,32 +13,74 @@ import "encoding/xml"
// Source relationship and namespace.
const (
SourceRelationship = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
SourceRelationshipChart201506 = "http://schemas.microsoft.com/office/drawing/2015/06/chart"
SourceRelationshipChart20070802 = "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"
SourceRelationshipChart2014 = "http://schemas.microsoft.com/office/drawing/2014/chart"
SourceRelationshipCompatibility = "http://schemas.openxmlformats.org/markup-compatibility/2006"
NameSpaceDrawingML = "http://schemas.openxmlformats.org/drawingml/2006/main"
NameSpaceDrawingMLChart = "http://schemas.openxmlformats.org/drawingml/2006/chart"
NameSpaceDrawingMLSpreadSheet = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
NameSpaceSpreadSheet = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
NameSpaceXML = "http://www.w3.org/XML/1998/namespace"
StrictSourceRelationship = "http://purl.oclc.org/ooxml/officeDocument/relationships"
StrictSourceRelationshipChart = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart"
StrictSourceRelationshipComments = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments"
StrictSourceRelationshipImage = "http://purl.oclc.org/ooxml/officeDocument/relationships/image"
StrictNameSpaceSpreadSheet = "http://purl.oclc.org/ooxml/spreadsheetml/main"
SourceRelationship = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
SourceRelationshipChartsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet"
SourceRelationshipDialogsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet"
SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
SourceRelationshipSharedStrings = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
SourceRelationshipChart201506 = "http://schemas.microsoft.com/office/drawing/2015/06/chart"
SourceRelationshipChart20070802 = "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"
SourceRelationshipChart2014 = "http://schemas.microsoft.com/office/drawing/2014/chart"
SourceRelationshipCompatibility = "http://schemas.openxmlformats.org/markup-compatibility/2006"
NameSpaceDrawingML = "http://schemas.openxmlformats.org/drawingml/2006/main"
NameSpaceDrawingMLChart = "http://schemas.openxmlformats.org/drawingml/2006/chart"
NameSpaceDrawingMLSpreadSheet = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
NameSpaceSpreadSheet = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
NameSpaceSpreadSheetX14 = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
NameSpaceSpreadSheetX15 = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"
NameSpaceSpreadSheetExcel2006Main = "http://schemas.microsoft.com/office/excel/2006/main"
NameSpaceMacExcel2008Main = "http://schemas.microsoft.com/office/mac/excel/2008/main"
NameSpaceXML = "http://www.w3.org/XML/1998/namespace"
NameSpaceXMLSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance"
StrictSourceRelationship = "http://purl.oclc.org/ooxml/officeDocument/relationships"
StrictSourceRelationshipChart = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart"
StrictSourceRelationshipComments = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments"
StrictSourceRelationshipImage = "http://purl.oclc.org/ooxml/officeDocument/relationships/image"
StrictNameSpaceSpreadSheet = "http://purl.oclc.org/ooxml/spreadsheetml/main"
NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/"
NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/"
NameSpaceDublinCoreMetadataIntiative = "http://purl.org/dc/dcmitype/"
ContentTypeDrawing = "application/vnd.openxmlformats-officedocument.drawing+xml"
ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
ContentTypeSpreadSheetMLPivotTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
ContentTypeSpreadSheetMLSharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
ContentTypeSpreadSheetMLTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
ContentTypeSpreadSheetMLWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
ContentTypeVBA = "application/vnd.ms-office.vbaProject"
ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing"
// ExtURIConditionalFormattings is the extLst child element
// ([ISO/IEC29500-1:2016] section 18.2.10) of the worksheet element
// ([ISO/IEC29500-1:2016] section 18.3.1.99) is extended by the addition of
// new child ext elements ([ISO/IEC29500-1:2016] section 18.2.7)
ExtURIConditionalFormattings = "{78C0D931-6437-407D-A8EE-F0AAD7539E65}"
ExtURIDataValidations = "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}"
ExtURISparklineGroups = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"
ExtURISlicerListX14 = "{A8765BA9-456A-4DAB-B4F3-ACF838C121DE}"
ExtURISlicerCachesListX14 = "{BBE1A952-AA13-448e-AADC-164F8A28A991}"
ExtURISlicerListX15 = "{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}"
ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}"
ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}"
ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}"
ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}"
ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}"
ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}"
)
var supportImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png"}
var supportImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff"}
// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
// element specifies non-visual canvas properties. This allows for additional
@ -213,6 +255,7 @@ type xdrClientData struct {
// with cells and its extents are in EMU units.
type xdrCellAnchor struct {
EditAs string `xml:"editAs,attr,omitempty"`
Pos *xlsxPoint2D `xml:"xdr:pos"`
From *xlsxFrom `xml:"xdr:from"`
To *xlsxTo `xml:"xdr:to"`
Ext *xlsxExt `xml:"xdr:ext"`
@ -222,15 +265,23 @@ type xdrCellAnchor struct {
ClientData *xdrClientData `xml:"xdr:clientData"`
}
// xlsxPoint2D describes the position of a drawing element within a spreadsheet.
type xlsxPoint2D struct {
XMLName xml.Name `xml:"xdr:pos"`
X int `xml:"x,attr"`
Y int `xml:"y,attr"`
}
// xlsxWsDr directly maps the root element for a part of this content type shall
// wsDr.
type xlsxWsDr struct {
XMLName xml.Name `xml:"xdr:wsDr"`
OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"`
TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"`
A string `xml:"xmlns:a,attr,omitempty"`
Xdr string `xml:"xmlns:xdr,attr,omitempty"`
R string `xml:"xmlns:r,attr,omitempty"`
XMLName xml.Name `xml:"xdr:wsDr"`
AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"`
OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"`
TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"`
A string `xml:"xmlns:a,attr,omitempty"`
Xdr string `xml:"xmlns:xdr,attr,omitempty"`
R string `xml:"xmlns:r,attr,omitempty"`
}
// xlsxGraphicFrame (Graphic Frame) directly maps the xdr:graphicFrame element.
@ -368,6 +419,7 @@ type formatPicture struct {
FPrintsWithSheet bool `json:"print_obj"`
FLocksWithSheet bool `json:"locked"`
NoChangeAspect bool `json:"lock_aspect_ratio"`
Autofit bool `json:"autofit"`
OffsetX int `json:"x_offset"`
OffsetY int `json:"y_offset"`
XScale float64 `json:"x_scale"`
@ -390,8 +442,8 @@ type formatShape struct {
// formatShapeParagraph directly maps the format settings of the paragraph in
// the shape.
type formatShapeParagraph struct {
Font formatFont `json:"font"`
Text string `json:"text"`
Font Font `json:"font"`
Text string `json:"text"`
}
// formatShapeColor directly maps the color settings of the shape.

229
xmlPivotCache.go Normal file
View File

@ -0,0 +1,229 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
// spreadsheet documents generated by Microsoft Exce™ 2007 and later. Supports
// complex components by high compatibility, and provided streaming API for
// generating or reading data from a worksheet with huge amounts of data. This
// library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// xlsxPivotCacheDefinition represents the pivotCacheDefinition part. This part
// defines each field in the source data, including the name, the string
// resources of the instance data (for shared items), and information about
// the type of data that appears in the field.
type xlsxPivotCacheDefinition struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotCacheDefinition"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
Invalid bool `xml:"invalid,attr,omitempty"`
SaveData bool `xml:"saveData,attr"`
RefreshOnLoad bool `xml:"refreshOnLoad,attr,omitempty"`
OptimizeMemory bool `xml:"optimizeMemory,attr,omitempty"`
EnableRefresh bool `xml:"enableRefresh,attr,omitempty"`
RefreshedBy string `xml:"refreshedBy,attr,omitempty"`
RefreshedDate float64 `xml:"refreshedDate,attr,omitempty"`
RefreshedDateIso float64 `xml:"refreshedDateIso,attr,omitempty"`
BackgroundQuery bool `xml:"backgroundQuery,attr"`
MissingItemsLimit int `xml:"missingItemsLimit,attr,omitempty"`
CreatedVersion int `xml:"createdVersion,attr,omitempty"`
RefreshedVersion int `xml:"refreshedVersion,attr,omitempty"`
MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"`
RecordCount int `xml:"recordCount,attr,omitempty"`
UpgradeOnRefresh bool `xml:"upgradeOnRefresh,attr,omitempty"`
TupleCacheAttr bool `xml:"tupleCache,attr,omitempty"`
SupportSubquery bool `xml:"supportSubquery,attr,omitempty"`
SupportAdvancedDrill bool `xml:"supportAdvancedDrill,attr,omitempty"`
CacheSource *xlsxCacheSource `xml:"cacheSource"`
CacheFields *xlsxCacheFields `xml:"cacheFields"`
CacheHierarchies *xlsxCacheHierarchies `xml:"cacheHierarchies"`
Kpis *xlsxKpis `xml:"kpis"`
TupleCache *xlsxTupleCache `xml:"tupleCache"`
CalculatedItems *xlsxCalculatedItems `xml:"calculatedItems"`
CalculatedMembers *xlsxCalculatedMembers `xml:"calculatedMembers"`
Dimensions *xlsxDimensions `xml:"dimensions"`
MeasureGroups *xlsxMeasureGroups `xml:"measureGroups"`
Maps *xlsxMaps `xml:"maps"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxCacheSource represents the description of data source whose data is
// stored in the pivot cache. The data source refers to the underlying rows or
// database records that provide the data for a PivotTable. You can create a
// PivotTable report from a SpreadsheetML table, an external database
// (including OLAP cubes), multiple SpreadsheetML worksheets, or another
// PivotTable.
type xlsxCacheSource struct {
Type string `xml:"type,attr"`
ConnectionID int `xml:"connectionId,attr,omitempty"`
WorksheetSource *xlsxWorksheetSource `xml:"worksheetSource"`
Consolidation *xlsxConsolidation `xml:"consolidation"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxWorksheetSource represents the location of the source of the data that
// is stored in the cache.
type xlsxWorksheetSource struct {
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
Ref string `xml:"ref,attr,omitempty"`
Name string `xml:"name,attr,omitempty"`
Sheet string `xml:"sheet,attr,omitempty"`
}
// xlsxConsolidation represents the description of the PivotCache source using
// multiple consolidation ranges. This element is used when the source of the
// PivotTable is a collection of ranges in the workbook. The ranges are
// specified in the rangeSets collection. The logic for how the application
// consolidates the data in the ranges is application- defined.
type xlsxConsolidation struct {
}
// xlsxCacheFields represents the collection of field definitions in the
// source data.
type xlsxCacheFields struct {
Count int `xml:"count,attr"`
CacheField []*xlsxCacheField `xml:"cacheField"`
}
// xlsxCacheField represent a single field in the PivotCache. This definition
// contains information about the field, such as its source, data type, and
// location within a level or hierarchy. The sharedItems element stores
// additional information about the data in this field. If there are no shared
// items, then values are stored directly in the pivotCacheRecords part.
type xlsxCacheField struct {
Name string `xml:"name,attr"`
Caption string `xml:"caption,attr,omitempty"`
PropertyName string `xml:"propertyName,attr,omitempty"`
ServerField bool `xml:"serverField,attr,omitempty"`
UniqueList bool `xml:"uniqueList,attr,omitempty"`
NumFmtID int `xml:"numFmtId,attr"`
Formula string `xml:"formula,attr,omitempty"`
SQLType int `xml:"sqlType,attr,omitempty"`
Hierarchy int `xml:"hierarchy,attr,omitempty"`
Level int `xml:"level,attr,omitempty"`
DatabaseField bool `xml:"databaseField,attr,omitempty"`
MappingCount int `xml:"mappingCount,attr,omitempty"`
MemberPropertyField bool `xml:"memberPropertyField,attr,omitempty"`
SharedItems *xlsxSharedItems `xml:"sharedItems"`
FieldGroup *xlsxFieldGroup `xml:"fieldGroup"`
MpMap *xlsxX `xml:"mpMap"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxSharedItems represents the collection of unique items for a field in
// the PivotCacheDefinition. The sharedItems complex type stores data type and
// formatting information about the data in a field. Items in the
// PivotCacheDefinition can be shared in order to reduce the redundancy of
// those values that are referenced in multiple places across all the
// PivotTable parts.
type xlsxSharedItems struct {
ContainsSemiMixedTypes bool `xml:"containsSemiMixedTypes,attr,omitempty"`
ContainsNonDate bool `xml:"containsNonDate,attr,omitempty"`
ContainsDate bool `xml:"containsDate,attr,omitempty"`
ContainsString bool `xml:"containsString,attr,omitempty"`
ContainsBlank bool `xml:"containsBlank,attr,omitempty"`
ContainsMixedTypes bool `xml:"containsMixedTypes,attr,omitempty"`
ContainsNumber bool `xml:"containsNumber,attr,omitempty"`
ContainsInteger bool `xml:"containsInteger,attr,omitempty"`
MinValue float64 `xml:"minValue,attr,omitempty"`
MaxValue float64 `xml:"maxValue,attr,omitempty"`
MinDate string `xml:"minDate,attr,omitempty"`
MaxDate string `xml:"maxDate,attr,omitempty"`
Count int `xml:"count,attr"`
LongText bool `xml:"longText,attr,omitempty"`
M *xlsxMissing `xml:"m"`
N *xlsxNumber `xml:"n"`
B *xlsxBoolean `xml:"b"`
E *xlsxError `xml:"e"`
S *xlsxString `xml:"s"`
D *xlsxDateTime `xml:"d"`
}
// xlsxMissing represents a value that was not specified.
type xlsxMissing struct {
}
// xlsxNumber represents a numeric value in the PivotTable.
type xlsxNumber struct {
V float64 `xml:"v,attr"`
U bool `xml:"u,attr,omitempty"`
F bool `xml:"f,attr,omitempty"`
C string `xml:"c,attr,omitempty"`
Cp int `xml:"cp,attr,omitempty"`
In int `xml:"in,attr,omitempty"`
Bc string `xml:"bc,attr,omitempty"`
Fc string `xml:"fc,attr,omitempty"`
I bool `xml:"i,attr,omitempty"`
Un bool `xml:"un,attr,omitempty"`
St bool `xml:"st,attr,omitempty"`
B bool `xml:"b,attr,omitempty"`
Tpls *xlsxTuples `xml:"tpls"`
X *attrValInt `xml:"x"`
}
// xlsxTuples represents members for the OLAP sheet data entry, also known as
// a tuple.
type xlsxTuples struct {
}
// xlsxBoolean represents a boolean value for an item in the PivotTable.
type xlsxBoolean struct {
}
// xlsxError represents an error value. The use of this item indicates that an
// error value is present in the PivotTable source. The error is recorded in
// the value attribute.
type xlsxError struct {
}
// xlsxString represents a character value in a PivotTable.
type xlsxString struct {
}
// xlsxDateTime represents a date-time value in the PivotTable.
type xlsxDateTime struct {
}
// xlsxFieldGroup represents the collection of properties for a field group.
type xlsxFieldGroup struct {
}
// xlsxCacheHierarchies represents the collection of OLAP hierarchies in the
// PivotCache.
type xlsxCacheHierarchies struct {
}
// xlsxKpis represents the collection of Key Performance Indicators (KPIs)
// defined on the OLAP server and stored in the PivotCache.
type xlsxKpis struct {
}
// xlsxTupleCache represents the cache of OLAP sheet data members, or tuples.
type xlsxTupleCache struct {
}
// xlsxCalculatedItems represents the collection of calculated items.
type xlsxCalculatedItems struct {
}
// xlsxCalculatedMembers represents the collection of calculated members in an
// OLAP PivotTable.
type xlsxCalculatedMembers struct {
}
// xlsxDimensions represents the collection of PivotTable OLAP dimensions.
type xlsxDimensions struct {
}
// xlsxMeasureGroups represents the collection of PivotTable OLAP measure
// groups.
type xlsxMeasureGroups struct {
}
// xlsxMaps represents the PivotTable OLAP measure group - Dimension maps.
type xlsxMaps struct {
}

294
xmlPivotTable.go Normal file
View File

@ -0,0 +1,294 @@
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// xlsxPivotTableDefinition represents the PivotTable root element for
// non-null PivotTables. There exists one pivotTableDefinition for each
// PivotTableDefinition part
type xlsxPivotTableDefinition struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotTableDefinition"`
Name string `xml:"name,attr"`
CacheID int `xml:"cacheId,attr"`
ApplyNumberFormats bool `xml:"applyNumberFormats,attr,omitempty"`
ApplyBorderFormats bool `xml:"applyBorderFormats,attr,omitempty"`
ApplyFontFormats bool `xml:"applyFontFormats,attr,omitempty"`
ApplyPatternFormats bool `xml:"applyPatternFormats,attr,omitempty"`
ApplyAlignmentFormats bool `xml:"applyAlignmentFormats,attr,omitempty"`
ApplyWidthHeightFormats bool `xml:"applyWidthHeightFormats,attr,omitempty"`
DataOnRows bool `xml:"dataOnRows,attr,omitempty"`
DataPosition int `xml:"dataPosition,attr,omitempty"`
DataCaption string `xml:"dataCaption,attr"`
GrandTotalCaption string `xml:"grandTotalCaption,attr,omitempty"`
ErrorCaption string `xml:"errorCaption,attr,omitempty"`
ShowError bool `xml:"showError,attr,omitempty"`
MissingCaption string `xml:"missingCaption,attr,omitempty"`
ShowMissing bool `xml:"showMissing,attr,omitempty"`
PageStyle string `xml:"pageStyle,attr,omitempty"`
PivotTableStyle string `xml:"pivotTableStyle,attr,omitempty"`
VacatedStyle string `xml:"vacatedStyle,attr,omitempty"`
Tag string `xml:"tag,attr,omitempty"`
UpdatedVersion int `xml:"updatedVersion,attr,omitempty"`
MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"`
AsteriskTotals bool `xml:"asteriskTotals,attr,omitempty"`
ShowItems bool `xml:"showItems,attr,omitempty"`
EditData bool `xml:"editData,attr,omitempty"`
DisableFieldList bool `xml:"disableFieldList,attr,omitempty"`
ShowCalcMbrs bool `xml:"showCalcMbrs,attr,omitempty"`
VisualTotals bool `xml:"visualTotals,attr,omitempty"`
ShowMultipleLabel bool `xml:"showMultipleLabel,attr,omitempty"`
ShowDataDropDown bool `xml:"showDataDropDown,attr,omitempty"`
ShowDrill bool `xml:"showDrill,attr,omitempty"`
PrintDrill bool `xml:"printDrill,attr,omitempty"`
ShowMemberPropertyTips bool `xml:"showMemberPropertyTips,attr,omitempty"`
ShowDataTips bool `xml:"showDataTips,attr,omitempty"`
EnableWizard bool `xml:"enableWizard,attr,omitempty"`
EnableDrill bool `xml:"enableDrill,attr,omitempty"`
EnableFieldProperties bool `xml:"enableFieldProperties,attr,omitempty"`
PreserveFormatting bool `xml:"preserveFormatting,attr,omitempty"`
UseAutoFormatting bool `xml:"useAutoFormatting,attr,omitempty"`
PageWrap int `xml:"pageWrap,attr,omitempty"`
PageOverThenDown bool `xml:"pageOverThenDown,attr,omitempty"`
SubtotalHiddenItems bool `xml:"subtotalHiddenItems,attr,omitempty"`
RowGrandTotals bool `xml:"rowGrandTotals,attr,omitempty"`
ColGrandTotals bool `xml:"colGrandTotals,attr,omitempty"`
FieldPrintTitles bool `xml:"fieldPrintTitles,attr,omitempty"`
ItemPrintTitles bool `xml:"itemPrintTitles,attr,omitempty"`
MergeItem bool `xml:"mergeItem,attr,omitempty"`
ShowDropZones bool `xml:"showDropZones,attr,omitempty"`
CreatedVersion int `xml:"createdVersion,attr,omitempty"`
Indent int `xml:"indent,attr,omitempty"`
ShowEmptyRow bool `xml:"showEmptyRow,attr,omitempty"`
ShowEmptyCol bool `xml:"showEmptyCol,attr,omitempty"`
ShowHeaders bool `xml:"showHeaders,attr,omitempty"`
Compact bool `xml:"compact,attr"`
Outline bool `xml:"outline,attr"`
OutlineData bool `xml:"outlineData,attr,omitempty"`
CompactData bool `xml:"compactData,attr,omitempty"`
Published bool `xml:"published,attr,omitempty"`
GridDropZones bool `xml:"gridDropZones,attr,omitempty"`
Immersive bool `xml:"immersive,attr,omitempty"`
MultipleFieldFilters bool `xml:"multipleFieldFilters,attr,omitempty"`
ChartFormat int `xml:"chartFormat,attr,omitempty"`
RowHeaderCaption string `xml:"rowHeaderCaption,attr,omitempty"`
ColHeaderCaption string `xml:"colHeaderCaption,attr,omitempty"`
FieldListSortAscending bool `xml:"fieldListSortAscending,attr,omitempty"`
MdxSubqueries bool `xml:"mdxSubqueries,attr,omitempty"`
CustomListSort bool `xml:"customListSort,attr,omitempty"`
Location *xlsxLocation `xml:"location"`
PivotFields *xlsxPivotFields `xml:"pivotFields"`
RowFields *xlsxRowFields `xml:"rowFields"`
RowItems *xlsxRowItems `xml:"rowItems"`
ColFields *xlsxColFields `xml:"colFields"`
ColItems *xlsxColItems `xml:"colItems"`
PageFields *xlsxPageFields `xml:"pageFields"`
DataFields *xlsxDataFields `xml:"dataFields"`
ConditionalFormats *xlsxConditionalFormats `xml:"conditionalFormats"`
PivotTableStyleInfo *xlsxPivotTableStyleInfo `xml:"pivotTableStyleInfo"`
}
// xlsxLocation represents location information for the PivotTable.
type xlsxLocation struct {
Ref string `xml:"ref,attr"`
FirstHeaderRow int `xml:"firstHeaderRow,attr"`
FirstDataRow int `xml:"firstDataRow,attr"`
FirstDataCol int `xml:"firstDataCol,attr"`
RowPageCount int `xml:"rowPageCount,attr,omitempty"`
ColPageCount int `xml:"colPageCount,attr,omitempty"`
}
// xlsxPivotFields represents the collection of fields that appear on the
// PivotTable.
type xlsxPivotFields struct {
Count int `xml:"count,attr"`
PivotField []*xlsxPivotField `xml:"pivotField"`
}
// xlsxPivotField represents a single field in the PivotTable. This element
// contains information about the field, including the collection of items in
// the field.
type xlsxPivotField struct {
Name string `xml:"name,attr,omitempty"`
Axis string `xml:"axis,attr,omitempty"`
DataField bool `xml:"dataField,attr,omitempty"`
SubtotalCaption string `xml:"subtotalCaption,attr,omitempty"`
ShowDropDowns bool `xml:"showDropDowns,attr,omitempty"`
HiddenLevel bool `xml:"hiddenLevel,attr,omitempty"`
UniqueMemberProperty string `xml:"uniqueMemberProperty,attr,omitempty"`
Compact bool `xml:"compact,attr"`
AllDrilled bool `xml:"allDrilled,attr,omitempty"`
NumFmtID string `xml:"numFmtId,attr,omitempty"`
Outline bool `xml:"outline,attr"`
SubtotalTop bool `xml:"subtotalTop,attr,omitempty"`
DragToRow bool `xml:"dragToRow,attr,omitempty"`
DragToCol bool `xml:"dragToCol,attr,omitempty"`
MultipleItemSelectionAllowed bool `xml:"multipleItemSelectionAllowed,attr,omitempty"`
DragToPage bool `xml:"dragToPage,attr,omitempty"`
DragToData bool `xml:"dragToData,attr,omitempty"`
DragOff bool `xml:"dragOff,attr,omitempty"`
ShowAll bool `xml:"showAll,attr"`
InsertBlankRow bool `xml:"insertBlankRow,attr,omitempty"`
ServerField bool `xml:"serverField,attr,omitempty"`
InsertPageBreak bool `xml:"insertPageBreak,attr,omitempty"`
AutoShow bool `xml:"autoShow,attr,omitempty"`
TopAutoShow bool `xml:"topAutoShow,attr,omitempty"`
HideNewItems bool `xml:"hideNewItems,attr,omitempty"`
MeasureFilter bool `xml:"measureFilter,attr,omitempty"`
IncludeNewItemsInFilter bool `xml:"includeNewItemsInFilter,attr,omitempty"`
ItemPageCount int `xml:"itemPageCount,attr,omitempty"`
SortType string `xml:"sortType,attr,omitempty"`
DataSourceSort bool `xml:"dataSourceSort,attr,omitempty"`
NonAutoSortDefault bool `xml:"nonAutoSortDefault,attr,omitempty"`
RankBy int `xml:"rankBy,attr,omitempty"`
DefaultSubtotal bool `xml:"defaultSubtotal,attr,omitempty"`
SumSubtotal bool `xml:"sumSubtotal,attr,omitempty"`
CountASubtotal bool `xml:"countASubtotal,attr,omitempty"`
AvgSubtotal bool `xml:"avgSubtotal,attr,omitempty"`
MaxSubtotal bool `xml:"maxSubtotal,attr,omitempty"`
MinSubtotal bool `xml:"minSubtotal,attr,omitempty"`
ProductSubtotal bool `xml:"productSubtotal,attr,omitempty"`
CountSubtotal bool `xml:"countSubtotal,attr,omitempty"`
StdDevSubtotal bool `xml:"stdDevSubtotal,attr,omitempty"`
StdDevPSubtotal bool `xml:"stdDevPSubtotal,attr,omitempty"`
VarSubtotal bool `xml:"varSubtotal,attr,omitempty"`
VarPSubtotal bool `xml:"varPSubtotal,attr,omitempty"`
ShowPropCell bool `xml:"showPropCell,attr,omitempty"`
ShowPropTip bool `xml:"showPropTip,attr,omitempty"`
ShowPropAsCaption bool `xml:"showPropAsCaption,attr,omitempty"`
DefaultAttributeDrillState bool `xml:"defaultAttributeDrillState,attr,omitempty"`
Items *xlsxItems `xml:"items"`
AutoSortScope *xlsxAutoSortScope `xml:"autoSortScope"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxItems represents the collection of items in a PivotTable field. The
// items in the collection are ordered by index. Items represent the unique
// entries from the field in the source data.
type xlsxItems struct {
Count int `xml:"count,attr"`
Item []*xlsxItem `xml:"item"`
}
// xlsxItem represents a single item in PivotTable field.
type xlsxItem struct {
N string `xml:"n,attr,omitempty"`
T string `xml:"t,attr,omitempty"`
H bool `xml:"h,attr,omitempty"`
S bool `xml:"s,attr,omitempty"`
SD bool `xml:"sd,attr,omitempty"`
F bool `xml:"f,attr,omitempty"`
M bool `xml:"m,attr,omitempty"`
C bool `xml:"c,attr,omitempty"`
X int `xml:"x,attr,omitempty"`
D bool `xml:"d,attr,omitempty"`
E bool `xml:"e,attr,omitempty"`
}
// xlsxAutoSortScope represents the sorting scope for the PivotTable.
type xlsxAutoSortScope struct {
}
// xlsxRowFields represents the collection of row fields for the PivotTable.
type xlsxRowFields struct {
Count int `xml:"count,attr"`
Field []*xlsxField `xml:"field"`
}
// xlsxField represents a generic field that can appear either on the column
// or the row region of the PivotTable. There areas many <x> elements as there
// are item values in any particular column or row.
type xlsxField struct {
X int `xml:"x,attr"`
}
// xlsxRowItems represents the collection of items in row axis of the
// PivotTable.
type xlsxRowItems struct {
Count int `xml:"count,attr"`
I []*xlsxI `xml:"i"`
}
// xlsxI represents the collection of items in the row region of the
// PivotTable.
type xlsxI struct {
X []*xlsxX `xml:"x"`
}
// xlsxX represents an array of indexes to cached shared item values.
type xlsxX struct {
}
// xlsxColFields represents the collection of fields that are on the column
// axis of the PivotTable.
type xlsxColFields struct {
Count int `xml:"count,attr"`
Field []*xlsxField `xml:"field"`
}
// xlsxColItems represents the collection of column items of the PivotTable.
type xlsxColItems struct {
Count int `xml:"count,attr"`
I []*xlsxI `xml:"i"`
}
// xlsxPageFields represents the collection of items in the page or report
// filter region of the PivotTable.
type xlsxPageFields struct {
Count int `xml:"count,attr"`
PageField []*xlsxPageField `xml:"pageField"`
}
// xlsxPageField represents a field on the page or report filter of the
// PivotTable.
type xlsxPageField struct {
Fld int `xml:"fld,attr"`
Item int `xml:"item,attr,omitempty"`
Hier int `xml:"hier,attr,omitempty"`
Name string `xml:"name,attr,omitempty"`
Cap string `xml:"cap,attr,omitempty"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxDataFields represents the collection of items in the data region of the
// PivotTable.
type xlsxDataFields struct {
Count int `xml:"count,attr"`
DataField []*xlsxDataField `xml:"dataField"`
}
// xlsxDataField represents a field from a source list, table, or database
// that contains data that is summarized in a PivotTable.
type xlsxDataField struct {
Name string `xml:"name,attr,omitempty"`
Fld int `xml:"fld,attr"`
Subtotal string `xml:"subtotal,attr,omitempty"`
ShowDataAs string `xml:"showDataAs,attr,omitempty"`
BaseField int `xml:"baseField,attr,omitempty"`
BaseItem int64 `xml:"baseItem,attr,omitempty"`
NumFmtID string `xml:"numFmtId,attr,omitempty"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxConditionalFormats represents the collection of conditional formats
// applied to a PivotTable.
type xlsxConditionalFormats struct {
}
// xlsxPivotTableStyleInfo represent information on style applied to the
// PivotTable.
type xlsxPivotTableStyleInfo struct {
Name string `xml:"name,attr"`
ShowRowHeaders bool `xml:"showRowHeaders,attr"`
ShowColHeaders bool `xml:"showColHeaders,attr"`
ShowRowStripes bool `xml:"showRowStripes,attr,omitempty"`
ShowColStripes bool `xml:"showColStripes,attr,omitempty"`
ShowLastColumn bool `xml:"showLastColumn,attr,omitempty"`
}

View File

@ -1,15 +1,18 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
import (
"encoding/xml"
"strings"
)
// xlsxSST directly maps the sst element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may
@ -25,20 +28,46 @@ type xlsxSST struct {
SI []xlsxSI `xml:"si"`
}
// xlsxSI directly maps the si element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
// not checked this for completeness - it does as much as I need.
// xlsxSI (String Item) is the representation of an individual string in the
// Shared String table. If the string is just a simple string with formatting
// applied at the cell level, then the String Item (si) should contain a
// single text element used to express the string. However, if the string in
// the cell is more complex - i.e., has formatting applied at the character
// level - then the string item shall consist of multiple rich text runs which
// collectively are used to express the string.
type xlsxSI struct {
T string `xml:"t"`
T string `xml:"t,omitempty"`
R []xlsxR `xml:"r"`
}
// xlsxR directly maps the r element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
// not checked this for completeness - it does as much as I need.
// String extracts characters from a string item.
func (x xlsxSI) String() string {
if len(x.R) > 0 {
var rows strings.Builder
for _, s := range x.R {
if s.T != nil {
rows.WriteString(s.T.Val)
}
}
return rows.String()
}
return x.T
}
// xlsxR represents a run of rich text. A rich text run is a region of text
// that share a common set of properties, such as formatting properties. The
// properties are defined in the rPr element, and the text displayed to the
// user is defined in the Text (t) element.
type xlsxR struct {
RPr *xlsxRPr `xml:"rPr"`
T string `xml:"t"`
T *xlsxT `xml:"t"`
}
// xlsxT directly maps the t element in the run properties.
type xlsxT struct {
XMLName xml.Name `xml:"t"`
Space string `xml:"xml:space,attr,omitempty"`
Val string `xml:",innerxml"`
}
// xlsxRPr (Run Properties) specifies a set of run properties which shall be
@ -47,9 +76,25 @@ type xlsxR struct {
// they are directly applied to the run and supersede any formatting from
// styles.
type xlsxRPr struct {
B string `xml:"b,omitempty"`
Sz *attrValFloat `xml:"sz"`
Color *xlsxColor `xml:"color"`
RFont *attrValString `xml:"rFont"`
Family *attrValInt `xml:"family"`
RFont *attrValString `xml:"rFont"`
Charset *attrValInt `xml:"charset"`
Family *attrValInt `xml:"family"`
B string `xml:"b,omitempty"`
I string `xml:"i,omitempty"`
Strike string `xml:"strike,omitempty"`
Outline string `xml:"outline,omitempty"`
Shadow string `xml:"shadow,omitempty"`
Condense string `xml:"condense,omitempty"`
Extend string `xml:"extend,omitempty"`
Color *xlsxColor `xml:"color"`
Sz *attrValFloat `xml:"sz"`
U *attrValString `xml:"u"`
VertAlign *attrValString `xml:"vertAlign"`
Scheme *attrValString `xml:"scheme"`
}
// RichTextRun directly maps the settings of the rich text run.
type RichTextRun struct {
Font *Font
Text string
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -82,28 +82,23 @@ type xlsxFonts struct {
Font []*xlsxFont `xml:"font"`
}
// font directly maps the font element.
type font struct {
Name *attrValString `xml:"name"`
Charset *attrValInt `xml:"charset"`
Family *attrValInt `xml:"family"`
B bool `xml:"b,omitempty"`
I bool `xml:"i,omitempty"`
Strike bool `xml:"strike,omitempty"`
Outline bool `xml:"outline,omitempty"`
Shadow bool `xml:"shadow,omitempty"`
Condense bool `xml:"condense,omitempty"`
Extend bool `xml:"extend,omitempty"`
Color *xlsxColor `xml:"color"`
Sz *attrValInt `xml:"sz"`
U *attrValString `xml:"u"`
Scheme *attrValString `xml:"scheme"`
}
// xlsxFont directly maps the font element. This element defines the properties
// for one of the fonts used in this workbook.
// xlsxFont directly maps the font element. This element defines the
// properties for one of the fonts used in this workbook.
type xlsxFont struct {
Font string `xml:",innerxml"`
B *bool `xml:"b,omitempty"`
I *bool `xml:"i,omitempty"`
Strike *bool `xml:"strike,omitempty"`
Outline *bool `xml:"outline,omitempty"`
Shadow *bool `xml:"shadow,omitempty"`
Condense *bool `xml:"condense,omitempty"`
Extend *bool `xml:"extend,omitempty"`
U *attrValString `xml:"u"`
Sz *attrValFloat `xml:"sz"`
Color *xlsxColor `xml:"color"`
Name *attrValString `xml:"name"`
Family *attrValInt `xml:"family"`
Charset *attrValInt `xml:"charset"`
Scheme *attrValString `xml:"scheme"`
}
// xlsxFills directly maps the fills element. This element defines the cell
@ -191,12 +186,12 @@ type xlsxCellStyles struct {
// workbook.
type xlsxCellStyle struct {
XMLName xml.Name `xml:"cellStyle"`
BuiltInID *int `xml:"builtinId,attr,omitempty"`
CustomBuiltIn *bool `xml:"customBuiltin,attr,omitempty"`
Hidden *bool `xml:"hidden,attr,omitempty"`
ILevel *bool `xml:"iLevel,attr,omitempty"`
Name string `xml:"name,attr"`
XfID int `xml:"xfId,attr"`
BuiltInID *int `xml:"builtinId,attr,omitempty"`
ILevel *int `xml:"iLevel,attr,omitempty"`
Hidden *bool `xml:"hidden,attr,omitempty"`
CustomBuiltIn *bool `xml:"customBuiltin,attr,omitempty"`
}
// xlsxCellStyleXfs directly maps the cellStyleXfs element. This element
@ -214,19 +209,19 @@ type xlsxCellStyleXfs struct {
// xlsxXf directly maps the xf element. A single xf element describes all of the
// formatting for a cell.
type xlsxXf struct {
ApplyAlignment bool `xml:"applyAlignment,attr"`
ApplyBorder bool `xml:"applyBorder,attr"`
ApplyFill bool `xml:"applyFill,attr"`
ApplyFont bool `xml:"applyFont,attr"`
ApplyNumberFormat bool `xml:"applyNumberFormat,attr"`
ApplyProtection bool `xml:"applyProtection,attr"`
BorderID int `xml:"borderId,attr"`
FillID int `xml:"fillId,attr"`
FontID int `xml:"fontId,attr"`
NumFmtID int `xml:"numFmtId,attr"`
PivotButton bool `xml:"pivotButton,attr,omitempty"`
QuotePrefix bool `xml:"quotePrefix,attr,omitempty"`
NumFmtID *int `xml:"numFmtId,attr"`
FontID *int `xml:"fontId,attr"`
FillID *int `xml:"fillId,attr"`
BorderID *int `xml:"borderId,attr"`
XfID *int `xml:"xfId,attr"`
QuotePrefix *bool `xml:"quotePrefix,attr"`
PivotButton *bool `xml:"pivotButton,attr"`
ApplyNumberFormat *bool `xml:"applyNumberFormat,attr"`
ApplyFont *bool `xml:"applyFont,attr"`
ApplyFill *bool `xml:"applyFill,attr"`
ApplyBorder *bool `xml:"applyBorder,attr"`
ApplyAlignment *bool `xml:"applyAlignment,attr"`
ApplyProtection *bool `xml:"applyProtection,attr"`
Alignment *xlsxAlignment `xml:"alignment"`
Protection *xlsxProtection `xml:"protection"`
}
@ -262,7 +257,7 @@ type xlsxDxf struct {
// dxf directly maps the dxf element.
type dxf struct {
Font *font `xml:"font"`
Font *xlsxFont `xml:"font"`
NumFmt *xlsxNumFmt `xml:"numFmt"`
Fill *xlsxFill `xml:"fill"`
Alignment *xlsxAlignment `xml:"alignment"`
@ -318,48 +313,61 @@ type xlsxStyleColors struct {
Color string `xml:",innerxml"`
}
// formatFont directly maps the styles settings of the fonts.
type formatFont struct {
Bold bool `json:"bold"`
Italic bool `json:"italic"`
Underline string `json:"underline"`
Family string `json:"family"`
Size int `json:"size"`
Color string `json:"color"`
// Alignment directly maps the alignment settings of the cells.
type Alignment struct {
Horizontal string `json:"horizontal"`
Indent int `json:"indent"`
JustifyLastLine bool `json:"justify_last_line"`
ReadingOrder uint64 `json:"reading_order"`
RelativeIndent int `json:"relative_indent"`
ShrinkToFit bool `json:"shrink_to_fit"`
TextRotation int `json:"text_rotation"`
Vertical string `json:"vertical"`
WrapText bool `json:"wrap_text"`
}
// formatStyle directly maps the styles settings of the cells.
type formatStyle struct {
Border []struct {
Type string `json:"type"`
Color string `json:"color"`
Style int `json:"style"`
} `json:"border"`
Fill struct {
Type string `json:"type"`
Pattern int `json:"pattern"`
Color []string `json:"color"`
Shading int `json:"shading"`
} `json:"fill"`
Font *formatFont `json:"font"`
Alignment *struct {
Horizontal string `json:"horizontal"`
Indent int `json:"indent"`
JustifyLastLine bool `json:"justify_last_line"`
ReadingOrder uint64 `json:"reading_order"`
RelativeIndent int `json:"relative_indent"`
ShrinkToFit bool `json:"shrink_to_fit"`
TextRotation int `json:"text_rotation"`
Vertical string `json:"vertical"`
WrapText bool `json:"wrap_text"`
} `json:"alignment"`
Protection *struct {
Hidden bool `json:"hidden"`
Locked bool `json:"locked"`
} `json:"protection"`
NumFmt int `json:"number_format"`
DecimalPlaces int `json:"decimal_places"`
CustomNumFmt *string `json:"custom_number_format"`
Lang string `json:"lang"`
NegRed bool `json:"negred"`
// Border directly maps the border settings of the cells.
type Border struct {
Type string `json:"type"`
Color string `json:"color"`
Style int `json:"style"`
}
// Font directly maps the font settings of the fonts.
type Font struct {
Bold bool `json:"bold"`
Italic bool `json:"italic"`
Underline string `json:"underline"`
Family string `json:"family"`
Size float64 `json:"size"`
Strike bool `json:"strike"`
Color string `json:"color"`
}
// Fill directly maps the fill settings of the cells.
type Fill struct {
Type string `json:"type"`
Pattern int `json:"pattern"`
Color []string `json:"color"`
Shading int `json:"shading"`
}
// Protection directly maps the protection settings of the cells.
type Protection struct {
Hidden bool `json:"hidden"`
Locked bool `json:"locked"`
}
// Style directly maps the style settings of the cells.
type Style struct {
Border []Border `json:"border"`
Fill Fill `json:"fill"`
Font *Font `json:"font"`
Alignment *Alignment `json:"alignment"`
Protection *Protection `json:"protection"`
NumFmt int `json:"number_format"`
DecimalPlaces int `json:"decimal_places"`
CustomNumFmt *string `json:"custom_number_format"`
Lang string `json:"lang"`
NegRed bool `json:"negred"`
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
@ -44,6 +44,7 @@ type xlsxTable struct {
// applied column by column to a table of data in the worksheet. This collection
// expresses AutoFilter settings.
type xlsxAutoFilter struct {
XMLName xml.Name `xml:"autoFilter"`
Ref string `xml:"ref,attr"`
FilterColumn *xlsxFilterColumn `xml:"filterColumn"`
}

View File

@ -1,11 +1,11 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize

View File

@ -1,24 +1,24 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// xmlxWorkbookRels contains xmlxWorkbookRelations which maps sheet id and sheet XML.
type xlsxWorkbookRels struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"`
Relationships []xlsxWorkbookRelation `xml:"Relationship"`
// xlsxRelationships describe references from parts to other internal resources in the package or to external resources.
type xlsxRelationships struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"`
Relationships []xlsxRelationship `xml:"Relationship"`
}
// xmlxWorkbookRelation maps sheet id and xl/worksheets/_rels/sheet%d.xml.rels
type xlsxWorkbookRelation struct {
// xlsxRelationship contains relations which maps id and XML.
type xlsxRelationship struct {
ID string `xml:"Id,attr"`
Target string `xml:",attr"`
Type string `xml:",attr"`
@ -33,7 +33,7 @@ type xlsxWorkbook struct {
FileVersion *xlsxFileVersion `xml:"fileVersion"`
WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"`
WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"`
BookViews xlsxBookViews `xml:"bookViews"`
BookViews *xlsxBookViews `xml:"bookViews"`
Sheets xlsxSheets `xml:"sheets"`
ExternalReferences *xlsxExternalReferences `xml:"externalReferences"`
DefinedNames *xlsxDefinedNames `xml:"definedNames"`
@ -146,9 +146,8 @@ type xlsxSheets struct {
Sheet []xlsxSheet `xml:"sheet"`
}
// xlsxSheet directly maps the sheet element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
// not checked it for completeness - it does as much as I need.
// xlsxSheet defines a sheet in this workbook. Sheet data is stored in a
// separate part.
type xlsxSheet struct {
Name string `xml:"name,attr,omitempty"`
SheetID int `xml:"sheetId,attr,omitempty"`
@ -176,7 +175,7 @@ type xlsxPivotCaches struct {
// xlsxPivotCache directly maps the pivotCache element.
type xlsxPivotCache struct {
CacheID int `xml:"cacheId,attr,omitempty"`
CacheID int `xml:"cacheId,attr"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
@ -204,7 +203,7 @@ type xlsxDefinedNames struct {
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
// defines a defined name within this workbook. A defined name is descriptive
// text that is used to represents a cell, range of cells, formula, or constant
// value. For a descriptions of the attributes see https://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.definedname.aspx
// value. For a descriptions of the attributes see https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname
type xlsxDefinedName struct {
Comment string `xml:"comment,attr,omitempty"`
CustomMenu string `xml:"customMenu,attr,omitempty"`
@ -289,3 +288,12 @@ type xlsxCustomWorkbookView struct {
XWindow *int `xml:"xWindow,attr"`
YWindow *int `xml:"yWindow,attr"`
}
// DefinedName directly maps the name for a cell or cell range on a
// worksheet.
type DefinedName struct {
Name string
Comment string
RefersTo string
Scope string
}

View File

@ -1,48 +1,65 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
// charts of XLSX. This library needs Go version 1.10 or later.
package excelize
import "encoding/xml"
// xlsxWorksheet directly maps the worksheet element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
// not checked it for completeness - it does as much as I need.
// http://schemas.openxmlformats.org/spreadsheetml/2006/main.
type xlsxWorksheet struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
SheetPr *xlsxSheetPr `xml:"sheetPr"`
Dimension xlsxDimension `xml:"dimension"`
SheetViews xlsxSheetViews `xml:"sheetViews,omitempty"`
Dimension *xlsxDimension `xml:"dimension"`
SheetViews *xlsxSheetViews `xml:"sheetViews"`
SheetFormatPr *xlsxSheetFormatPr `xml:"sheetFormatPr"`
Cols *xlsxCols `xml:"cols,omitempty"`
Cols *xlsxCols `xml:"cols"`
SheetData xlsxSheetData `xml:"sheetData"`
SheetCalcPr *xlsxInnerXML `xml:"sheetCalcPr"`
SheetProtection *xlsxSheetProtection `xml:"sheetProtection"`
ProtectedRanges *xlsxInnerXML `xml:"protectedRanges"`
Scenarios *xlsxInnerXML `xml:"scenarios"`
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
SortState *xlsxSortState `xml:"sortState"`
DataConsolidate *xlsxInnerXML `xml:"dataConsolidate"`
CustomSheetViews *xlsxCustomSheetViews `xml:"customSheetViews"`
MergeCells *xlsxMergeCells `xml:"mergeCells"`
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
ConditionalFormatting []*xlsxConditionalFormatting `xml:"conditionalFormatting"`
DataValidations *xlsxDataValidations `xml:"dataValidations,omitempty"`
DataValidations *xlsxDataValidations `xml:"dataValidations"`
Hyperlinks *xlsxHyperlinks `xml:"hyperlinks"`
PrintOptions *xlsxPrintOptions `xml:"printOptions"`
PageMargins *xlsxPageMargins `xml:"pageMargins"`
PageSetUp *xlsxPageSetUp `xml:"pageSetup"`
HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"`
RowBreaks *xlsxBreaks `xml:"rowBreaks"`
ColBreaks *xlsxBreaks `xml:"colBreaks"`
CustomProperties *xlsxInnerXML `xml:"customProperties"`
CellWatches *xlsxInnerXML `xml:"cellWatches"`
IgnoredErrors *xlsxInnerXML `xml:"ignoredErrors"`
SmartTags *xlsxInnerXML `xml:"smartTags"`
Drawing *xlsxDrawing `xml:"drawing"`
LegacyDrawing *xlsxLegacyDrawing `xml:"legacyDrawing"`
LegacyDrawingHF *xlsxLegacyDrawingHF `xml:"legacyDrawingHF"`
DrawingHF *xlsxDrawingHF `xml:"drawingHF"`
Picture *xlsxPicture `xml:"picture"`
OleObjects *xlsxInnerXML `xml:"oleObjects"`
Controls *xlsxInnerXML `xml:"controls"`
WebPublishItems *xlsxInnerXML `xml:"webPublishItems"`
TableParts *xlsxTableParts `xml:"tableParts"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxDrawing change r:id to rid in the namespace.
type xlsxDrawing struct {
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
XMLName xml.Name `xml:"drawing"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxHeaderFooter directly maps the headerFooter element in the namespace
@ -53,49 +70,55 @@ type xlsxDrawing struct {
// footers on the first page can differ from those on odd- and even-numbered
// pages. In the latter case, the first page is not considered an odd page.
type xlsxHeaderFooter struct {
DifferentFirst bool `xml:"differentFirst,attr,omitempty"`
DifferentOddEven bool `xml:"differentOddEven,attr,omitempty"`
OddHeader []*xlsxOddHeader `xml:"oddHeader"`
OddFooter []*xlsxOddFooter `xml:"oddFooter"`
XMLName xml.Name `xml:"headerFooter"`
AlignWithMargins bool `xml:"alignWithMargins,attr,omitempty"`
DifferentFirst bool `xml:"differentFirst,attr,omitempty"`
DifferentOddEven bool `xml:"differentOddEven,attr,omitempty"`
ScaleWithDoc bool `xml:"scaleWithDoc,attr,omitempty"`
OddHeader string `xml:"oddHeader,omitempty"`
OddFooter string `xml:"oddFooter,omitempty"`
EvenHeader string `xml:"evenHeader,omitempty"`
EvenFooter string `xml:"evenFooter,omitempty"`
FirstFooter string `xml:"firstFooter,omitempty"`
FirstHeader string `xml:"firstHeader,omitempty"`
DrawingHF *xlsxDrawingHF `xml:"drawingHF"`
}
// xlsxOddHeader directly maps the oddHeader element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
// not checked it for completeness - it does as much as I need.
type xlsxOddHeader struct {
Content string `xml:",chardata"`
}
// xlsxOddFooter directly maps the oddFooter element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
// not checked it for completeness - it does as much as I need.
type xlsxOddFooter struct {
Content string `xml:",chardata"`
// xlsxDrawingHF (Drawing Reference in Header Footer) specifies the usage of
// drawing objects to be rendered in the headers and footers of the sheet. It
// specifies an explicit relationship to the part containing the DrawingML
// shapes used in the headers and footers. It also indicates where in the
// headers and footers each shape belongs. One drawing object can appear in
// each of the left section, center section and right section of a header and
// a footer.
type xlsxDrawingHF struct {
Content string `xml:",innerxml"`
}
// xlsxPageSetUp directly maps the pageSetup element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page setup
// settings for the worksheet.
type xlsxPageSetUp struct {
BlackAndWhite bool `xml:"blackAndWhite,attr,omitempty"`
CellComments string `xml:"cellComments,attr,omitempty"`
Copies int `xml:"copies,attr,omitempty"`
Draft bool `xml:"draft,attr,omitempty"`
Errors string `xml:"errors,attr,omitempty"`
FirstPageNumber int `xml:"firstPageNumber,attr,omitempty"`
FitToHeight *int `xml:"fitToHeight,attr"`
FitToWidth int `xml:"fitToWidth,attr,omitempty"`
HorizontalDPI float32 `xml:"horizontalDpi,attr,omitempty"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
Orientation string `xml:"orientation,attr,omitempty"`
PageOrder string `xml:"pageOrder,attr,omitempty"`
PaperHeight string `xml:"paperHeight,attr,omitempty"`
PaperSize int `xml:"paperSize,attr,omitempty"`
PaperWidth string `xml:"paperWidth,attr,omitempty"`
Scale int `xml:"scale,attr,omitempty"`
UseFirstPageNumber bool `xml:"useFirstPageNumber,attr,omitempty"`
UsePrinterDefaults bool `xml:"usePrinterDefaults,attr,omitempty"`
VerticalDPI float32 `xml:"verticalDpi,attr,omitempty"`
XMLName xml.Name `xml:"pageSetup"`
BlackAndWhite bool `xml:"blackAndWhite,attr,omitempty"`
CellComments string `xml:"cellComments,attr,omitempty"`
Copies int `xml:"copies,attr,omitempty"`
Draft bool `xml:"draft,attr,omitempty"`
Errors string `xml:"errors,attr,omitempty"`
FirstPageNumber int `xml:"firstPageNumber,attr,omitempty"`
FitToHeight int `xml:"fitToHeight,attr,omitempty"`
FitToWidth int `xml:"fitToWidth,attr,omitempty"`
HorizontalDPI int `xml:"horizontalDpi,attr,omitempty"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
Orientation string `xml:"orientation,attr,omitempty"`
PageOrder string `xml:"pageOrder,attr,omitempty"`
PaperHeight string `xml:"paperHeight,attr,omitempty"`
PaperSize int `xml:"paperSize,attr,omitempty"`
PaperWidth string `xml:"paperWidth,attr,omitempty"`
Scale int `xml:"scale,attr,omitempty"`
UseFirstPageNumber bool `xml:"useFirstPageNumber,attr,omitempty"`
UsePrinterDefaults bool `xml:"usePrinterDefaults,attr,omitempty"`
VerticalDPI int `xml:"verticalDpi,attr,omitempty"`
}
// xlsxPrintOptions directly maps the printOptions element in the namespace
@ -103,44 +126,48 @@ type xlsxPageSetUp struct {
// the sheet. Printer-specific settings are stored separately in the Printer
// Settings part.
type xlsxPrintOptions struct {
GridLines bool `xml:"gridLines,attr,omitempty"`
GridLinesSet bool `xml:"gridLinesSet,attr,omitempty"`
Headings bool `xml:"headings,attr,omitempty"`
HorizontalCentered bool `xml:"horizontalCentered,attr,omitempty"`
VerticalCentered bool `xml:"verticalCentered,attr,omitempty"`
XMLName xml.Name `xml:"printOptions"`
GridLines bool `xml:"gridLines,attr,omitempty"`
GridLinesSet bool `xml:"gridLinesSet,attr,omitempty"`
Headings bool `xml:"headings,attr,omitempty"`
HorizontalCentered bool `xml:"horizontalCentered,attr,omitempty"`
VerticalCentered bool `xml:"verticalCentered,attr,omitempty"`
}
// xlsxPageMargins directly maps the pageMargins element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page margins for
// a sheet or a custom sheet view.
type xlsxPageMargins struct {
Bottom float64 `xml:"bottom,attr"`
Footer float64 `xml:"footer,attr"`
Header float64 `xml:"header,attr"`
Left float64 `xml:"left,attr"`
Right float64 `xml:"right,attr"`
Top float64 `xml:"top,attr"`
XMLName xml.Name `xml:"pageMargins"`
Bottom float64 `xml:"bottom,attr"`
Footer float64 `xml:"footer,attr"`
Header float64 `xml:"header,attr"`
Left float64 `xml:"left,attr"`
Right float64 `xml:"right,attr"`
Top float64 `xml:"top,attr"`
}
// xlsxSheetFormatPr directly maps the sheetFormatPr element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. This element
// specifies the sheet formatting properties.
type xlsxSheetFormatPr struct {
BaseColWidth uint8 `xml:"baseColWidth,attr,omitempty"`
DefaultColWidth float64 `xml:"defaultColWidth,attr,omitempty"`
DefaultRowHeight float64 `xml:"defaultRowHeight,attr"`
CustomHeight bool `xml:"customHeight,attr,omitempty"`
ZeroHeight bool `xml:"zeroHeight,attr,omitempty"`
ThickTop bool `xml:"thickTop,attr,omitempty"`
ThickBottom bool `xml:"thickBottom,attr,omitempty"`
OutlineLevelRow uint8 `xml:"outlineLevelRow,attr,omitempty"`
OutlineLevelCol uint8 `xml:"outlineLevelCol,attr,omitempty"`
XMLName xml.Name `xml:"sheetFormatPr"`
BaseColWidth uint8 `xml:"baseColWidth,attr,omitempty"`
DefaultColWidth float64 `xml:"defaultColWidth,attr,omitempty"`
DefaultRowHeight float64 `xml:"defaultRowHeight,attr"`
CustomHeight bool `xml:"customHeight,attr,omitempty"`
ZeroHeight bool `xml:"zeroHeight,attr,omitempty"`
ThickTop bool `xml:"thickTop,attr,omitempty"`
ThickBottom bool `xml:"thickBottom,attr,omitempty"`
OutlineLevelRow uint8 `xml:"outlineLevelRow,attr,omitempty"`
OutlineLevelCol uint8 `xml:"outlineLevelCol,attr,omitempty"`
}
// xlsxSheetViews directly maps the sheetViews element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Worksheet views
// collection.
type xlsxSheetViews struct {
XMLName xml.Name `xml:"sheetViews"`
SheetView []xlsxSheetView `xml:"sheetView"`
}
@ -154,13 +181,13 @@ type xlsxSheetViews struct {
// last sheetView definition is loaded, and the others are discarded. When
// multiple windows are viewing the same sheet, multiple sheetView elements
// (with corresponding workbookView entries) are saved.
// See https://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.sheetview.aspx
// See https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetview
type xlsxSheetView struct {
WindowProtection bool `xml:"windowProtection,attr,omitempty"`
ShowFormulas bool `xml:"showFormulas,attr,omitempty"`
ShowGridLines *bool `xml:"showGridLines,attr"`
ShowRowColHeaders *bool `xml:"showRowColHeaders,attr"`
ShowZeros bool `xml:"showZeros,attr,omitempty"`
ShowZeros *bool `xml:"showZeros,attr,omitempty"`
RightToLeft bool `xml:"rightToLeft,attr,omitempty"`
TabSelected bool `xml:"tabSelected,attr,omitempty"`
ShowWhiteSpace *bool `xml:"showWhiteSpace,attr"`
@ -202,16 +229,18 @@ type xlsxPane struct {
// properties.
type xlsxSheetPr struct {
XMLName xml.Name `xml:"sheetPr"`
CodeName string `xml:"codeName,attr,omitempty"`
EnableFormatConditionsCalculation *bool `xml:"enableFormatConditionsCalculation,attr"`
FilterMode bool `xml:"filterMode,attr,omitempty"`
Published *bool `xml:"published,attr"`
SyncHorizontal bool `xml:"syncHorizontal,attr,omitempty"`
SyncVertical bool `xml:"syncVertical,attr,omitempty"`
SyncRef string `xml:"syncRef,attr,omitempty"`
TransitionEvaluation bool `xml:"transitionEvaluation,attr,omitempty"`
Published *bool `xml:"published,attr"`
CodeName string `xml:"codeName,attr,omitempty"`
FilterMode bool `xml:"filterMode,attr,omitempty"`
EnableFormatConditionsCalculation *bool `xml:"enableFormatConditionsCalculation,attr"`
TransitionEntry bool `xml:"transitionEntry,attr,omitempty"`
TabColor *xlsxTabColor `xml:"tabColor,omitempty"`
PageSetUpPr *xlsxPageSetUpPr `xml:"pageSetUpPr,omitempty"`
OutlinePr *xlsxOutlinePr `xml:"outlinePr,omitempty"`
PageSetUpPr *xlsxPageSetUpPr `xml:"pageSetUpPr,omitempty"`
}
// xlsxOutlinePr maps to the outlinePr element
@ -231,6 +260,7 @@ type xlsxPageSetUpPr struct {
// xlsxTabColor directly maps the tabColor element in the namespace currently I
// have not checked it for completeness - it does as much as I need.
type xlsxTabColor struct {
RGB string `xml:"rgb,attr,omitempty"`
Theme int `xml:"theme,attr,omitempty"`
Tint float64 `xml:"tint,attr,omitempty"`
}
@ -239,22 +269,23 @@ type xlsxTabColor struct {
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
// not checked it for completeness - it does as much as I need.
type xlsxCols struct {
Col []xlsxCol `xml:"col"`
XMLName xml.Name `xml:"cols"`
Col []xlsxCol `xml:"col"`
}
// xlsxCol directly maps the col (Column Width & Formatting). Defines column
// width and column formatting for one or more columns of the worksheet.
type xlsxCol struct {
BestFit bool `xml:"bestFit,attr,omitempty"`
Collapsed bool `xml:"collapsed,attr"`
Collapsed bool `xml:"collapsed,attr,omitempty"`
CustomWidth bool `xml:"customWidth,attr,omitempty"`
Hidden bool `xml:"hidden,attr"`
Hidden bool `xml:"hidden,attr,omitempty"`
Max int `xml:"max,attr"`
Min int `xml:"min,attr"`
OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"`
Phonetic bool `xml:"phonetic,attr,omitempty"`
Style int `xml:"style,attr"`
Width float64 `xml:"width,attr"`
Style int `xml:"style,attr,omitempty"`
Width float64 `xml:"width,attr,omitempty"`
}
// xlsxDimension directly maps the dimension element in the namespace
@ -265,7 +296,8 @@ type xlsxCol struct {
// When an entire column is formatted, only the first cell in that column is
// considered used.
type xlsxDimension struct {
Ref string `xml:"ref,attr"`
XMLName xml.Name `xml:"dimension"`
Ref string `xml:"ref,attr"`
}
// xlsxSheetData directly maps the sheetData element in the namespace
@ -295,6 +327,74 @@ type xlsxRow struct {
C []xlsxC `xml:"c"`
}
// xlsxSortState directly maps the sortState element. This collection
// preserves the AutoFilter sort state.
type xlsxSortState struct {
ColumnSort bool `xml:"columnSort,attr,omitempty"`
CaseSensitive bool `xml:"caseSensitive,attr,omitempty"`
SortMethod string `xml:"sortMethod,attr,omitempty"`
Ref string `xml:"ref,attr"`
Content string `xml:",innerxml"`
}
// xlsxCustomSheetViews directly maps the customSheetViews element. This is a
// collection of custom sheet views.
type xlsxCustomSheetViews struct {
XMLName xml.Name `xml:"customSheetViews"`
CustomSheetView []*xlsxCustomSheetView `xml:"customSheetView"`
}
// xlsxBrk directly maps the row or column break to use when paginating a
// worksheet.
type xlsxBrk struct {
ID int `xml:"id,attr,omitempty"`
Min int `xml:"min,attr,omitempty"`
Max int `xml:"max,attr,omitempty"`
Man bool `xml:"man,attr,omitempty"`
Pt bool `xml:"pt,attr,omitempty"`
}
// xlsxBreaks directly maps a collection of the row or column breaks.
type xlsxBreaks struct {
Brk []*xlsxBrk `xml:"brk"`
Count int `xml:"count,attr,omitempty"`
ManualBreakCount int `xml:"manualBreakCount,attr,omitempty"`
}
// xlsxCustomSheetView directly maps the customSheetView element.
type xlsxCustomSheetView struct {
Pane *xlsxPane `xml:"pane"`
Selection *xlsxSelection `xml:"selection"`
RowBreaks *xlsxBreaks `xml:"rowBreaks"`
ColBreaks *xlsxBreaks `xml:"colBreaks"`
PageMargins *xlsxPageMargins `xml:"pageMargins"`
PrintOptions *xlsxPrintOptions `xml:"printOptions"`
PageSetup *xlsxPageSetUp `xml:"pageSetup"`
HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"`
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
ExtLst *xlsxExtLst `xml:"extLst"`
GUID string `xml:"guid,attr"`
Scale int `xml:"scale,attr,omitempty"`
ColorID int `xml:"colorId,attr,omitempty"`
ShowPageBreaks bool `xml:"showPageBreaks,attr,omitempty"`
ShowFormulas bool `xml:"showFormulas,attr,omitempty"`
ShowGridLines bool `xml:"showGridLines,attr,omitempty"`
ShowRowCol bool `xml:"showRowCol,attr,omitempty"`
OutlineSymbols bool `xml:"outlineSymbols,attr,omitempty"`
ZeroValues bool `xml:"zeroValues,attr,omitempty"`
FitToPage bool `xml:"fitToPage,attr,omitempty"`
PrintArea bool `xml:"printArea,attr,omitempty"`
Filter bool `xml:"filter,attr,omitempty"`
ShowAutoFilter bool `xml:"showAutoFilter,attr,omitempty"`
HiddenRows bool `xml:"hiddenRows,attr,omitempty"`
HiddenColumns bool `xml:"hiddenColumns,attr,omitempty"`
State string `xml:"state,attr,omitempty"`
FilterUnique bool `xml:"filterUnique,attr,omitempty"`
View string `xml:"view,attr,omitempty"`
ShowRuler bool `xml:"showRuler,attr,omitempty"`
TopLeftCell string `xml:"topLeftCell,attr,omitempty"`
}
// xlsxMergeCell directly maps the mergeCell element. A single merged cell.
type xlsxMergeCell struct {
Ref string `xml:"ref,attr,omitempty"`
@ -303,13 +403,15 @@ type xlsxMergeCell struct {
// xlsxMergeCells directly maps the mergeCells element. This collection
// expresses all the merged cells in the sheet.
type xlsxMergeCells struct {
Count int `xml:"count,attr,omitempty"`
Cells []*xlsxMergeCell `xml:"mergeCell,omitempty"`
XMLName xml.Name `xml:"mergeCells"`
Count int `xml:"count,attr,omitempty"`
Cells []*xlsxMergeCell `xml:"mergeCell,omitempty"`
}
// xlsxDataValidations expresses all data validation information for cells in a
// sheet which have data validation features applied.
type xlsxDataValidations struct {
XMLName xml.Name `xml:"dataValidations"`
Count int `xml:"count,attr,omitempty"`
DisablePrompts bool `xml:"disablePrompts,attr,omitempty"`
XWindow int `xml:"xWindow,attr,omitempty"`
@ -324,16 +426,16 @@ type DataValidation struct {
Error *string `xml:"error,attr"`
ErrorStyle *string `xml:"errorStyle,attr"`
ErrorTitle *string `xml:"errorTitle,attr"`
Operator string `xml:"operator,attr"`
Operator string `xml:"operator,attr,omitempty"`
Prompt *string `xml:"prompt,attr"`
PromptTitle *string `xml:"promptTitle"`
ShowDropDown bool `xml:"showDropDown,attr"`
ShowErrorMessage bool `xml:"showErrorMessage,attr"`
ShowInputMessage bool `xml:"showInputMessage,attr"`
PromptTitle *string `xml:"promptTitle,attr"`
ShowDropDown bool `xml:"showDropDown,attr,omitempty"`
ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"`
ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"`
Sqref string `xml:"sqref,attr"`
Type string `xml:"type,attr"`
Formula1 string `xml:"formula1"`
Formula2 string `xml:"formula2"`
Type string `xml:"type,attr,omitempty"`
Formula1 string `xml:",innerxml"`
Formula2 string `xml:",innerxml"`
}
// xlsxC directly maps the c element in the namespace
@ -353,22 +455,19 @@ type DataValidation struct {
// str (String) | Cell containing a formula string.
//
type xlsxC struct {
R string `xml:"r,attr"` // Cell ID, e.g. A1
S int `xml:"s,attr,omitempty"` // Style reference.
// Str string `xml:"str,attr,omitempty"` // Style reference.
T string `xml:"t,attr,omitempty"` // Type.
F *xlsxF `xml:"f,omitempty"` // Formula
V string `xml:"v,omitempty"` // Value
IS *xlsxIS `xml:"is"`
XMLName xml.Name `xml:"c"`
XMLSpace xml.Attr `xml:"space,attr,omitempty"`
R string `xml:"r,attr,omitempty"` // Cell ID, e.g. A1
S int `xml:"s,attr,omitempty"` // Style reference.
// Str string `xml:"str,attr,omitempty"` // Style reference.
T string `xml:"t,attr,omitempty"` // Type.
F *xlsxF `xml:"f,omitempty"` // Formula
V string `xml:"v,omitempty"` // Value
IS *xlsxSI `xml:"is"`
}
// xlsxIS directly maps the t element. Cell containing an (inline) rich
// string, i.e., one not in the shared string table. If this cell type is
// used, then the cell value is in the is element rather than the v element in
// the cell (c element).
type xlsxIS struct {
T string `xml:"t"`
func (c *xlsxC) hasValue() bool {
return c.S != 0 || c.V != "" || c.F != nil || c.T != ""
}
// xlsxF directly maps the f element in the namespace
@ -384,27 +483,28 @@ type xlsxF struct {
// xlsxSheetProtection collection expresses the sheet protection options to
// enforce when the sheet is protected.
type xlsxSheetProtection struct {
AlgorithmName string `xml:"algorithmName,attr,omitempty"`
AutoFilter bool `xml:"autoFilter,attr,omitempty"`
DeleteColumns bool `xml:"deleteColumns,attr,omitempty"`
DeleteRows bool `xml:"deleteRows,attr,omitempty"`
FormatCells bool `xml:"formatCells,attr,omitempty"`
FormatColumns bool `xml:"formatColumns,attr,omitempty"`
FormatRows bool `xml:"formatRows,attr,omitempty"`
HashValue string `xml:"hashValue,attr,omitempty"`
InsertColumns bool `xml:"insertColumns,attr,omitempty"`
InsertHyperlinks bool `xml:"insertHyperlinks,attr,omitempty"`
InsertRows bool `xml:"insertRows,attr,omitempty"`
Objects bool `xml:"objects,attr,omitempty"`
Password string `xml:"password,attr,omitempty"`
PivotTables bool `xml:"pivotTables,attr,omitempty"`
SaltValue string `xml:"saltValue,attr,omitempty"`
Scenarios bool `xml:"scenarios,attr,omitempty"`
SelectLockedCells bool `xml:"selectLockedCells,attr,omitempty"`
SelectUnlockedCells bool `xml:"selectUnlockedCells,attr,omitempty"`
Sheet bool `xml:"sheet,attr,omitempty"`
Sort bool `xml:"sort,attr,omitempty"`
SpinCount int `xml:"spinCount,attr,omitempty"`
XMLName xml.Name `xml:"sheetProtection"`
AlgorithmName string `xml:"algorithmName,attr,omitempty"`
Password string `xml:"password,attr,omitempty"`
HashValue string `xml:"hashValue,attr,omitempty"`
SaltValue string `xml:"saltValue,attr,omitempty"`
SpinCount int `xml:"spinCount,attr,omitempty"`
Sheet bool `xml:"sheet,attr"`
Objects bool `xml:"objects,attr"`
Scenarios bool `xml:"scenarios,attr"`
FormatCells bool `xml:"formatCells,attr"`
FormatColumns bool `xml:"formatColumns,attr"`
FormatRows bool `xml:"formatRows,attr"`
InsertColumns bool `xml:"insertColumns,attr"`
InsertRows bool `xml:"insertRows,attr"`
InsertHyperlinks bool `xml:"insertHyperlinks,attr"`
DeleteColumns bool `xml:"deleteColumns,attr"`
DeleteRows bool `xml:"deleteRows,attr"`
SelectLockedCells bool `xml:"selectLockedCells,attr"`
Sort bool `xml:"sort,attr"`
AutoFilter bool `xml:"autoFilter,attr"`
PivotTables bool `xml:"pivotTables,attr"`
SelectUnlockedCells bool `xml:"selectUnlockedCells,attr"`
}
// xlsxPhoneticPr (Phonetic Properties) represents a collection of phonetic
@ -415,9 +515,10 @@ type xlsxSheetProtection struct {
// every phonetic hint is expressed as a phonetic run (rPh), and these
// properties specify how to display that phonetic run.
type xlsxPhoneticPr struct {
Alignment string `xml:"alignment,attr,omitempty"`
FontID *int `xml:"fontId,attr"`
Type string `xml:"type,attr,omitempty"`
XMLName xml.Name `xml:"phoneticPr"`
Alignment string `xml:"alignment,attr,omitempty"`
FontID *int `xml:"fontId,attr"`
Type string `xml:"type,attr,omitempty"`
}
// A Conditional Format is a format, such as cell shading or font color, that a
@ -425,8 +526,9 @@ type xlsxPhoneticPr struct {
// condition is true. This collection expresses conditional formatting rules
// applied to a particular cell or range.
type xlsxConditionalFormatting struct {
SQRef string `xml:"sqref,attr,omitempty"`
CfRule []*xlsxCfRule `xml:"cfRule"`
XMLName xml.Name `xml:"conditionalFormatting"`
SQRef string `xml:"sqref,attr,omitempty"`
CfRule []*xlsxCfRule `xml:"cfRule"`
}
// xlsxCfRule (Conditional Formatting Rule) represents a description of a
@ -482,7 +584,7 @@ type xlsxIconSet struct {
type xlsxCfvo struct {
Gte bool `xml:"gte,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
Val string `xml:"val,attr"`
Val string `xml:"val,attr,omitempty"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
@ -491,6 +593,7 @@ type xlsxCfvo struct {
// be stored in a package as a relationship. Hyperlinks shall be identified by
// containing a target which specifies the destination of the given hyperlink.
type xlsxHyperlinks struct {
XMLName xml.Name `xml:"hyperlinks"`
Hyperlink []xlsxHyperlink `xml:"hyperlink"`
}
@ -535,6 +638,7 @@ type xlsxHyperlink struct {
// </worksheet>
//
type xlsxTableParts struct {
XMLName xml.Name `xml:"tableParts"`
Count int `xml:"count,attr,omitempty"`
TableParts []*xlsxTablePart `xml:"tablePart"`
}
@ -552,7 +656,8 @@ type xlsxTablePart struct {
// <picture r:id="rId1"/>
//
type xlsxPicture struct {
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
XMLName xml.Name `xml:"picture"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxLegacyDrawing directly maps the legacyDrawing element in the namespace
@ -565,7 +670,121 @@ type xlsxPicture struct {
// can also be used to explain assumptions made in a formula or to call out
// something special about the cell.
type xlsxLegacyDrawing struct {
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
XMLName xml.Name `xml:"legacyDrawing"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxLegacyDrawingHF specifies the explicit relationship to the part
// containing the VML defining pictures rendered in the header / footer of the
// sheet.
type xlsxLegacyDrawingHF struct {
XMLName xml.Name `xml:"legacyDrawingHF"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
type xlsxInnerXML struct {
Content string `xml:",innerxml"`
}
// xlsxWorksheetExt directly maps the ext element in the worksheet.
type xlsxWorksheetExt struct {
XMLName xml.Name `xml:"ext"`
URI string `xml:"uri,attr"`
Content string `xml:",innerxml"`
}
// decodeWorksheetExt directly maps the ext element.
type decodeWorksheetExt struct {
XMLName xml.Name `xml:"extLst"`
Ext []*xlsxWorksheetExt `xml:"ext"`
}
// decodeX14SparklineGroups directly maps the sparklineGroups element.
type decodeX14SparklineGroups struct {
XMLName xml.Name `xml:"sparklineGroups"`
XMLNSXM string `xml:"xmlns:xm,attr"`
Content string `xml:",innerxml"`
}
// xlsxX14SparklineGroups directly maps the sparklineGroups element.
type xlsxX14SparklineGroups struct {
XMLName xml.Name `xml:"x14:sparklineGroups"`
XMLNSXM string `xml:"xmlns:xm,attr"`
SparklineGroups []*xlsxX14SparklineGroup `xml:"x14:sparklineGroup"`
Content string `xml:",innerxml"`
}
// xlsxX14SparklineGroup directly maps the sparklineGroup element.
type xlsxX14SparklineGroup struct {
XMLName xml.Name `xml:"x14:sparklineGroup"`
ManualMax int `xml:"manualMax,attr,omitempty"`
ManualMin int `xml:"manualMin,attr,omitempty"`
LineWeight float64 `xml:"lineWeight,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
DateAxis bool `xml:"dateAxis,attr,omitempty"`
DisplayEmptyCellsAs string `xml:"displayEmptyCellsAs,attr,omitempty"`
Markers bool `xml:"markers,attr,omitempty"`
High bool `xml:"high,attr,omitempty"`
Low bool `xml:"low,attr,omitempty"`
First bool `xml:"first,attr,omitempty"`
Last bool `xml:"last,attr,omitempty"`
Negative bool `xml:"negative,attr,omitempty"`
DisplayXAxis bool `xml:"displayXAxis,attr,omitempty"`
DisplayHidden bool `xml:"displayHidden,attr,omitempty"`
MinAxisType string `xml:"minAxisType,attr,omitempty"`
MaxAxisType string `xml:"maxAxisType,attr,omitempty"`
RightToLeft bool `xml:"rightToLeft,attr,omitempty"`
ColorSeries *xlsxTabColor `xml:"x14:colorSeries"`
ColorNegative *xlsxTabColor `xml:"x14:colorNegative"`
ColorAxis *xlsxColor `xml:"x14:colorAxis"`
ColorMarkers *xlsxTabColor `xml:"x14:colorMarkers"`
ColorFirst *xlsxTabColor `xml:"x14:colorFirst"`
ColorLast *xlsxTabColor `xml:"x14:colorLast"`
ColorHigh *xlsxTabColor `xml:"x14:colorHigh"`
ColorLow *xlsxTabColor `xml:"x14:colorLow"`
Sparklines xlsxX14Sparklines `xml:"x14:sparklines"`
}
// xlsxX14Sparklines directly maps the sparklines element.
type xlsxX14Sparklines struct {
Sparkline []*xlsxX14Sparkline `xml:"x14:sparkline"`
}
// xlsxX14Sparkline directly maps the sparkline element.
type xlsxX14Sparkline struct {
F string `xml:"xm:f"`
Sqref string `xml:"xm:sqref"`
}
// SparklineOption directly maps the settings of the sparkline.
type SparklineOption struct {
Location []string
Range []string
Max int
CustMax int
Min int
CustMin int
Type string
Weight float64
DateAxis bool
Markers bool
High bool
Low bool
First bool
Last bool
Negative bool
Axis bool
Hidden bool
Reverse bool
Style int
SeriesColor string
NegativeColor string
MarkersColor string
FirstColor string
LastColor string
HightColor string
LowColor string
EmptyCells string
}
// formatPanes directly maps the settings of the panes.
@ -627,3 +846,27 @@ type FormatSheetProtection struct {
SelectUnlockedCells bool
Sort bool
}
// FormatHeaderFooter directly maps the settings of header and footer.
type FormatHeaderFooter struct {
AlignWithMargins bool
DifferentFirst bool
DifferentOddEven bool
ScaleWithDoc bool
OddHeader string
OddFooter string
EvenHeader string
EvenFooter string
FirstFooter string
FirstHeader string
}
// FormatPageMargins directly maps the settings of page margins
type FormatPageMargins struct {
Bottom string
Footer string
Header string
Left string
Right string
Top string
}