Merge branch 'master' into fix/cell_lock
This commit is contained in:
commit
ec14de32f0
|
@ -1,4 +1,5 @@
|
|||
~$*.xlsx
|
||||
test/Test*.xlsx
|
||||
*.out
|
||||
*.test
|
||||
*.test
|
||||
.idea
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
3
LICENSE
3
LICENSE
|
@ -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
|
||||
|
|
40
README.md
40
README.md
|
@ -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™ 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™ 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/).
|
||||
|
|
40
README_zh.md
40
README_zh.md
|
@ -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™ 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-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 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/) 创作共用授权条款。
|
||||
|
|
|
@ -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
233
adjust.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
24
calcchain.go
24
calcchain.go
|
@ -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)
|
||||
|
|
|
@ -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
385
cell.go
|
@ -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,
|
||||
|
|
149
cell_test.go
149
cell_test.go
|
@ -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"`)
|
||||
}
|
||||
|
|
182
chart_test.go
182
chart_test.go
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
tenets:
|
||||
- import: codelingo/effective-go
|
||||
- import: codelingo/code-review-comments
|
206
col.go
206
col.go
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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")))
|
||||
}
|
85
comment.go
85
comment.go
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
12
date.go
|
@ -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
|
||||
}
|
||||
|
|
32
date_test.go
32
date_test.go
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
296
excelize.go
296
excelize.go
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
BIN
excelize.png
BIN
excelize.png
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 24 KiB |
932
excelize_test.go
932
excelize_test.go
File diff suppressed because it is too large
Load Diff
24
file.go
24
file.go
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
14
go.mod
|
@ -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
33
go.sum
|
@ -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
120
lib.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
|
@ -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"`)
|
||||
|
||||
}
|
345
picture.go
345
picture.go
|
@ -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
|
||||
}
|
||||
|
|
132
picture_test.go
132
picture_test.go
|
@ -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"`)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
346
rows.go
|
@ -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
|
||||
|
|
293
rows_test.go
293
rows_test.go
|
@ -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] == "" {
|
||||
|
|
30
shape.go
30
shape.go
|
@ -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)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")))
|
||||
}
|
246
sheet_test.go
246
sheet_test.go
|
@ -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)
|
||||
}
|
||||
|
|
363
sheetpr.go
363
sheetpr.go
|
@ -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
|
||||
}
|
||||
|
|
355
sheetpr_test.go
355
sheetpr_test.go
|
@ -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")
|
||||
}
|
||||
|
|
81
sheetview.go
81
sheetview.go
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
313
styles.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
66
table.go
66
table.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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'")
|
||||
}
|
10
templates.go
10
templates.go
File diff suppressed because one or more lines are too long
Binary file not shown.
BIN
test/Book1.xlsx
BIN
test/Book1.xlsx
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 |
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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"`
|
||||
|
|
360
xmlChart.go
360
xmlChart.go
|
@ -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"`
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
|
|
120
xmlDrawing.go
120
xmlDrawing.go
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
172
xmlStyles.go
172
xmlStyles.go
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
501
xmlWorksheet.go
501
xmlWorksheet.go
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue